Work-in-progress on dynamic IQ parsing

This commit is contained in:
Mickael Remond 2018-01-16 22:33:21 +01:00
parent 2e47f1659d
commit d33490cdc0
No known key found for this signature in database
GPG key ID: E6F6045D79965AA3
6 changed files with 144 additions and 39 deletions

View file

@ -104,13 +104,13 @@ type auth struct {
Value string `xml:",innerxml"` Value string `xml:",innerxml"`
} }
type bindBind struct { type BindBind struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
Resource string `xml:"resource,omitempty"` Resource string `xml:"resource,omitempty"`
Jid string `xml:"jid,omitempty"` Jid string `xml:"jid,omitempty"`
} }
func (*bindBind) IsIQPayload() { func (*BindBind) IsIQPayload() {
} }
// Session is obsolete in RFC 6121. // Session is obsolete in RFC 6121.

View file

@ -172,7 +172,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
// TODO Check all elements // TODO Check all elements
switch iq.Payload[0].(type) { switch iq.Payload[0].(type) {
case *bindBind: case *BindBind:
result := `<iq id='%s' type='result'> result := `<iq id='%s' type='result'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<jid>%s</jid> <jid>%s</jid>

133
iq.go
View file

@ -4,29 +4,59 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"reflect"
"fluux.io/xmpp/iot" "fluux.io/xmpp/iot"
) )
/* /*
TODO I would like to be able to write 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{ payload := Node{
XMLName: xml.Name{ Ns: "http://jabber.org/protocol/disco#info",
Space: "", Tag: "identity",
Local: "", Attrs: map[string]string{
}, "category":"gateway",
PacketAttrs: xmpp.PacketAttrs{ "type": "skype",
Id: "", "name": "Test Gateway",
From: "", },
To: "", Nodes: []Node{},
Type: "", }
Lang: "",
}, AddPayload(Ns, Tag, Attrs)
Payload: nil,
RawXML: "", 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 // UnmarshalXML implements custom parsing for IQs
func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
iq.XMLName = start.Name iq.XMLName = start.Name
fmt.Println("IQ Name", iq.XMLName)
// Extract IQ attributes // Extract IQ attributes
for _, attr := range start.Attr { for _, attr := range start.Attr {
if attr.Name.Local == "id" { if attr.Name.Local == "id" {
@ -95,34 +126,38 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
} }
// decode inner elements // decode inner elements
level := 0
for { for {
t, err := d.Token() t, err := d.Token()
if err != nil { if err != nil {
return err return err
} }
var p IQPayload
switch tt := t.(type) { switch tt := t.(type) {
case xml.StartElement: case xml.StartElement:
switch tt.Name.Space + " " + tt.Name.Local { level++
case "urn:ietf:params:xml:ns:xmpp-bind bind": if level <= 1 {
p = new(bindBind) var elt interface{}
case "urn:xmpp:iot:control set": payloadType := tt.Name.Space + " " + tt.Name.Local
p = new(iot.ControlSet) if payloadType := typeRegistry[payloadType]; payloadType != nil {
default: val := reflect.New(payloadType)
p = new(Node) elt = val.Interface()
} } else {
if p != nil { elt = new(Node)
err = d.DecodeElement(p, &tt) }
if err != nil {
return err 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: case xml.EndElement:
level--
if tt == start.End() { if tt == start.End() {
return nil return nil
} }
@ -165,10 +200,15 @@ type Node struct {
Nodes []Node `xml:",any"` Nodes []Node `xml:",any"`
} }
type Attr struct {
K string
V string
}
func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 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 { for _, attr := range start.Attr {
// Do not repeat xmlns // Do not repeat xmlns, it is already in XMLName
if attr.Name.Local != "xmlns" { if attr.Name.Local != "xmlns" {
n.Attrs = append(n.Attrs, attr) 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() {} 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{})
}

View file

@ -2,6 +2,7 @@ package xmpp // import "fluux.io/xmpp"
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"reflect" "reflect"
"testing" "testing"
) )
@ -42,7 +43,7 @@ func TestGenerateIq(t *testing.T) {
}, },
Attrs: []xml.Attr{ Attrs: []xml.Attr{
{Name: xml.Name{Local: "category"}, Value: "gateway"}, {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"}, {Name: xml.Name{Local: "name"}, Value: "Test Gateway"},
}, },
Nodes: nil, Nodes: nil,
@ -54,6 +55,43 @@ func TestGenerateIq(t *testing.T) {
t.Errorf("cannot marshal xml structure") 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) var parsedIQ = new(IQ)
if err = xml.Unmarshal(data, parsedIQ); err != nil { if err = xml.Unmarshal(data, parsedIQ); err != nil {
t.Errorf("Unmarshal(%s) returned error", data) t.Errorf("Unmarshal(%s) returned error", data)

View file

@ -165,7 +165,7 @@ func (s *Session) bind(o Options) {
// TODO Check all elements // TODO Check all elements
switch payload := iq.Payload[0].(type) { switch payload := iq.Payload[0].(type) {
case *bindBind: case *BindBind:
s.BindJid = payload.Jid // our local id (with possibly randomly generated resource s.BindJid = payload.Jid // our local id (with possibly randomly generated resource
default: default:
s.err = errors.New("iq bind result missing") s.err = errors.New("iq bind result missing")

View file

@ -12,7 +12,7 @@ type streamFeatures struct {
StartTLS tlsStartTLS StartTLS tlsStartTLS
Caps Caps Caps Caps
Mechanisms saslMechanisms Mechanisms saslMechanisms
Bind bindBind Bind BindBind
Session sessionSession Session sessionSession
Any []xml.Name `xml:",any"` Any []xml.Name `xml:",any"`
} }