From d33490cdc03df510aa2757686faa355e1d25fd81 Mon Sep 17 00:00:00 2001 From: Mickael Remond Date: Tue, 16 Jan 2018 22:33:21 +0100 Subject: [PATCH] Work-in-progress on dynamic IQ parsing --- auth.go | 4 +- client_test.go | 2 +- iq.go | 133 +++++++++++++++++++++++++++++++++++++------------ iq_test.go | 40 ++++++++++++++- session.go | 2 +- stream.go | 2 +- 6 files changed, 144 insertions(+), 39 deletions(-) diff --git a/auth.go b/auth.go index 3b4b754..9474817 100644 --- a/auth.go +++ b/auth.go @@ -104,13 +104,13 @@ type auth struct { Value string `xml:",innerxml"` } -type bindBind struct { +type BindBind struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` Resource string `xml:"resource,omitempty"` Jid string `xml:"jid,omitempty"` } -func (*bindBind) IsIQPayload() { +func (*BindBind) IsIQPayload() { } // Session is obsolete in RFC 6121. diff --git a/client_test.go b/client_test.go index 381d5af..5ed938b 100644 --- a/client_test.go +++ b/client_test.go @@ -172,7 +172,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) { // TODO Check all elements switch iq.Payload[0].(type) { - case *bindBind: + case *BindBind: result := ` %s diff --git a/iq.go b/iq.go index 1b174f6..695df48 100644 --- a/iq.go +++ b/iq.go @@ -4,29 +4,59 @@ import ( "encoding/xml" "fmt" + "reflect" + "fluux.io/xmpp/iot" ) /* TODO I would like to be able to write - newIQ(Id, From, To, Type, Lang).AddPayload(IQPayload) + NewIQ(Id, From, To, Type, Lang).AddPayload(IQPayload) + Payload would be: - xmpp.IQ{ - XMLName: xml.Name{ - Space: "", - Local: "", - }, - PacketAttrs: xmpp.PacketAttrs{ - Id: "", - From: "", - To: "", - Type: "", - Lang: "", - }, - Payload: nil, - RawXML: "", - } + payload := Node{ + Ns: "http://jabber.org/protocol/disco#info", + Tag: "identity", + Attrs: map[string]string{ + "category":"gateway", + "type": "skype", + "name": "Test Gateway", + }, + Nodes: []Node{}, + } + + AddPayload(Ns, Tag, Attrs) + +ex: + +NewIQ("get", "test@localhost", "admin@localhost", "en") + .AddPayload("http://jabber.org/protocol/disco#info", + "identity", + map[string]string{ + "category":"gateway", + "type": "skype", + "name": "Test Gateway", + }) + + NewNode(Ns, Tag, Attrs) + NewNodeWithChildren(Ns, Tag, Attrs, Nodes) + +Attr { + K string + V string +} + +xmpp.Elt.DiscoInfo("identity", "gateway", "skype", "Test Gateway") +xmppElt.DiscoInfo.identity(" +import xmpp/node/discoinfo + +discoinfo.Identity("gateway", "skype", "Test Gateway") + + +[]Attr{{"category", "gateway"} + +TODO support ability to put Raw payload */ @@ -75,6 +105,7 @@ func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) { // UnmarshalXML implements custom parsing for IQs func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { iq.XMLName = start.Name + fmt.Println("IQ Name", iq.XMLName) // Extract IQ attributes for _, attr := range start.Attr { if attr.Name.Local == "id" { @@ -95,34 +126,38 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { } // decode inner elements + level := 0 for { t, err := d.Token() if err != nil { return err } - var p IQPayload switch tt := t.(type) { case xml.StartElement: - switch tt.Name.Space + " " + tt.Name.Local { - case "urn:ietf:params:xml:ns:xmpp-bind bind": - p = new(bindBind) - case "urn:xmpp:iot:control set": - p = new(iot.ControlSet) - default: - p = new(Node) - } - if p != nil { - err = d.DecodeElement(p, &tt) - if err != nil { - return err + level++ + if level <= 1 { + var elt interface{} + payloadType := tt.Name.Space + " " + tt.Name.Local + if payloadType := typeRegistry[payloadType]; payloadType != nil { + val := reflect.New(payloadType) + elt = val.Interface() + } else { + elt = new(Node) + } + + if iqPl, ok := elt.(IQPayload); ok { + err = d.DecodeElement(elt, &tt) + if err != nil { + return err + } + iq.Payload = append(iq.Payload, iqPl) // []IQPayload{iqPl} } - iq.Payload = []IQPayload{p} - p = nil } case xml.EndElement: + level-- if tt == start.End() { return nil } @@ -165,10 +200,15 @@ type Node struct { Nodes []Node `xml:",any"` } +type Attr struct { + K string + V string +} + func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - // Assign "n.Attrs = start.Attr", without repeating xmlns in attributes + // Assign "n.Attrs = start.Attr", without repeating xmlns in attributes: for _, attr := range start.Attr { - // Do not repeat xmlns + // Do not repeat xmlns, it is already in XMLName if attr.Name.Local != "xmlns" { n.Attrs = append(n.Attrs, attr) } @@ -187,3 +227,30 @@ func (n *Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { } func (*Node) IsIQPayload() {} + +// ============================================================================ +// Disco + +type DiscoInfo struct { + XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"` + Identity Identity +} + +func (*DiscoInfo) IsIQPayload() {} + +type Identity struct { + XMLName xml.Name `xml:"identity"` + Name string `xml:"name,attr"` + Category string `xml:"category,attr"` + Type string `xml:"type,attr"` +} + +// ============================================================================ + +var typeRegistry = make(map[string]reflect.Type) + +func init() { + typeRegistry["http://jabber.org/protocol/disco#info query"] = reflect.TypeOf(DiscoInfo{}) + typeRegistry["urn:ietf:params:xml:ns:xmpp-bind bind"] = reflect.TypeOf(BindBind{}) + typeRegistry["urn:xmpp:iot:control set"] = reflect.TypeOf(iot.ControlSet{}) +} diff --git a/iq_test.go b/iq_test.go index ab85370..1a88d72 100644 --- a/iq_test.go +++ b/iq_test.go @@ -2,6 +2,7 @@ package xmpp // import "fluux.io/xmpp" import ( "encoding/xml" + "fmt" "reflect" "testing" ) @@ -42,7 +43,7 @@ func TestGenerateIq(t *testing.T) { }, Attrs: []xml.Attr{ {Name: xml.Name{Local: "category"}, Value: "gateway"}, - {Name: xml.Name{Local: "type"}, Value: "skype"}, + {Name: xml.Name{Local: "type"}, Value: "mqtt"}, {Name: xml.Name{Local: "name"}, Value: "Test Gateway"}, }, Nodes: nil, @@ -54,6 +55,43 @@ func TestGenerateIq(t *testing.T) { t.Errorf("cannot marshal xml structure") } + fmt.Printf("XML Struct: %s\n", data) + + var parsedIQ = new(IQ) + if err = xml.Unmarshal(data, parsedIQ); err != nil { + t.Errorf("Unmarshal(%s) returned error", data) + } + + if !reflect.DeepEqual(parsedIQ.Payload[0], iq.Payload[0]) { + t.Errorf("expecting result %+v = %+v", parsedIQ.Payload[0], iq.Payload[0]) + } + + fmt.Println("ParsedIQ", parsedIQ) +} + +func TestGenerateIqNew(t *testing.T) { + iq := NewIQ("get", "admin@localhost", "test@localhost", "1", "en") + payload := DiscoInfo{ + XMLName: xml.Name{ + Space: "http://jabber.org/protocol/disco#info", + Local: "query", + }, + Identity: Identity{ + XMLName: xml.Name{ + Space: "http://jabber.org/protocol/disco#info", + Local: "identity", + }, + Name: "Test Gateway", + Category: "gateway", + Type: "mqtt", + }, + } + iq.AddPayload(&payload) + data, err := xml.Marshal(iq) + if err != nil { + t.Errorf("cannot marshal xml structure") + } + var parsedIQ = new(IQ) if err = xml.Unmarshal(data, parsedIQ); err != nil { t.Errorf("Unmarshal(%s) returned error", data) diff --git a/session.go b/session.go index c4afa58..1b33e14 100644 --- a/session.go +++ b/session.go @@ -165,7 +165,7 @@ func (s *Session) bind(o Options) { // TODO Check all elements switch payload := iq.Payload[0].(type) { - case *bindBind: + case *BindBind: s.BindJid = payload.Jid // our local id (with possibly randomly generated resource default: s.err = errors.New("iq bind result missing") diff --git a/stream.go b/stream.go index 3c6afd6..0e29bda 100644 --- a/stream.go +++ b/stream.go @@ -12,7 +12,7 @@ type streamFeatures struct { StartTLS tlsStartTLS Caps Caps Mechanisms saslMechanisms - Bind bindBind + Bind BindBind Session sessionSession Any []xml.Name `xml:",any"` }