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"`
}
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.

View file

@ -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 := `<iq id='%s' type='result'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<jid>%s</jid>

133
iq.go
View file

@ -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{})
}

View file

@ -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)

View file

@ -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")

View file

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