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"`
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
@ -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>
|
||||
|
|
123
iq.go
123
iq.go
|
@ -4,30 +4,60 @@ 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: "",
|
||||
payload := Node{
|
||||
Ns: "http://jabber.org/protocol/disco#info",
|
||||
Tag: "identity",
|
||||
Attrs: map[string]string{
|
||||
"category":"gateway",
|
||||
"type": "skype",
|
||||
"name": "Test Gateway",
|
||||
},
|
||||
PacketAttrs: xmpp.PacketAttrs{
|
||||
Id: "",
|
||||
From: "",
|
||||
To: "",
|
||||
Type: "",
|
||||
Lang: "",
|
||||
},
|
||||
Payload: nil,
|
||||
RawXML: "",
|
||||
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)
|
||||
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 p != nil {
|
||||
err = d.DecodeElement(p, &tt)
|
||||
|
||||
if iqPl, ok := elt.(IQPayload); ok {
|
||||
err = d.DecodeElement(elt, &tt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iq.Payload = []IQPayload{p}
|
||||
p = nil
|
||||
iq.Payload = append(iq.Payload, iqPl) // []IQPayload{iqPl}
|
||||
}
|
||||
}
|
||||
|
||||
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{})
|
||||
}
|
||||
|
|
40
iq_test.go
40
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)
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue