Work-in-progress on dynamic IQ parsing
This commit is contained in:
parent
2e47f1659d
commit
d33490cdc0
4
auth.go
4
auth.go
|
@ -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.
|
||||||
|
|
|
@ -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
133
iq.go
|
@ -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{})
|
||||||
|
}
|
||||||
|
|
40
iq_test.go
40
iq_test.go
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue