Merge pull request #6 "Error parsing / generation"
This commit is contained in:
commit
adb14260f0
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.
|
||||||
|
|
|
@ -127,6 +127,7 @@ func (c *Client) Recv() <-chan interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sends message text.
|
// Send sends message text.
|
||||||
|
// TODO Move to Go XML Marshaller
|
||||||
func (c *Client) Send(packet string) error {
|
func (c *Client) Send(packet string) error {
|
||||||
fmt.Fprintf(c.Session.socketProxy, packet) // TODO handle errors
|
fmt.Fprintf(c.Session.socketProxy, packet) // TODO handle errors
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"fluux.io/xmpp"
|
"fluux.io/xmpp"
|
||||||
|
@ -22,28 +21,28 @@ func main() {
|
||||||
switch p := packet.(type) {
|
switch p := packet.(type) {
|
||||||
case xmpp.IQ:
|
case xmpp.IQ:
|
||||||
switch inner := p.Payload[0].(type) {
|
switch inner := p.Payload[0].(type) {
|
||||||
case *xmpp.Node:
|
case *xmpp.DiscoInfo:
|
||||||
fmt.Printf("%q\n", inner)
|
fmt.Println("Disco Info")
|
||||||
|
if p.Type == "get" {
|
||||||
data, err := xml.Marshal(inner)
|
DiscoResult(component, p.From, p.To, p.Id)
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot marshall payload")
|
|
||||||
}
|
}
|
||||||
fmt.Println("data=", string(data))
|
|
||||||
component.processIQ(p.Type, p.Id, p.From, inner)
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("default")
|
fmt.Println("ignoring iq packet", inner)
|
||||||
|
xerror := xmpp.Err{
|
||||||
|
Code: 501,
|
||||||
|
Reason: "feature-not-implemented",
|
||||||
|
Type: "cancel",
|
||||||
|
}
|
||||||
|
reply := p.MakeError(xerror)
|
||||||
|
component.xmpp.Send(&reply)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("Packet unhandled packet:", packet)
|
fmt.Println("ignoring packet:", packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
NSDiscoInfo = "http://jabber.org/protocol/disco#info"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MyComponent struct {
|
type MyComponent struct {
|
||||||
Name string
|
Name string
|
||||||
// Typical categories and types: https://xmpp.org/registrar/disco-categories.html
|
// Typical categories and types: https://xmpp.org/registrar/disco-categories.html
|
||||||
|
@ -53,34 +52,19 @@ type MyComponent struct {
|
||||||
xmpp *xmpp.Component
|
xmpp *xmpp.Component
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MyComponent) processIQ(iqType, id, from string, inner *xmpp.Node) {
|
func DiscoResult(c MyComponent, from, to, id string) {
|
||||||
fmt.Println("Node:", inner.XMLName.Space, inner.XMLName.Local)
|
iq := xmpp.NewIQ("result", to, from, id, "en")
|
||||||
switch inner.XMLName.Space + " " + iqType {
|
payload := xmpp.DiscoInfo{
|
||||||
case NSDiscoInfo + " get":
|
Identity: xmpp.Identity{
|
||||||
fmt.Println("Send Disco Info")
|
Name: c.Name,
|
||||||
result := fmt.Sprintf(`<iq type='result'
|
Category: c.Category,
|
||||||
from='%s'
|
Type: c.Type,
|
||||||
to='%s'
|
},
|
||||||
id='%s'>
|
Features: []xmpp.Feature{
|
||||||
<query xmlns='http://jabber.org/protocol/disco#info'>
|
{Var: "http://jabber.org/protocol/disco#info"},
|
||||||
<identity
|
{Var: "http://jabber.org/protocol/disco#item"},
|
||||||
category='%s'
|
},
|
||||||
type='%s'
|
|
||||||
name='%s'/>
|
|
||||||
<feature var='http://jabber.org/protocol/disco#info'/>
|
|
||||||
<feature var='http://jabber.org/protocol/disco#items'/>
|
|
||||||
</query>
|
|
||||||
</iq>`, c.xmpp.Host, from, id, c.Category, c.Type, c.Name)
|
|
||||||
c.xmpp.Send(result)
|
|
||||||
default:
|
|
||||||
iqErr := fmt.Sprintf(`<iq type='error'
|
|
||||||
from='%s'
|
|
||||||
to='%s'
|
|
||||||
id='%s'>
|
|
||||||
<error type="cancel" code="501">
|
|
||||||
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
|
|
||||||
</error>
|
|
||||||
</iq>`, c.xmpp.Host, from, id)
|
|
||||||
c.xmpp.Send(iqErr)
|
|
||||||
}
|
}
|
||||||
|
iq.AddPayload(&payload)
|
||||||
|
c.xmpp.Send(iq)
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,8 +94,13 @@ func (c *Component) ReadPacket() (Packet, error) {
|
||||||
return next(c.decoder)
|
return next(c.decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Component) Send(packet string) error {
|
func (c *Component) Send(packet Packet) error {
|
||||||
if _, err := fmt.Fprintf(c.conn, packet); err != nil {
|
data, err := xml.Marshal(packet)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("cannot marshal packet " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(c.conn, string(data)); err != nil {
|
||||||
return errors.New("cannot send packet " + err.Error())
|
return errors.New("cannot send packet " + err.Error())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
257
iq.go
257
iq.go
|
@ -4,32 +4,156 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"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
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// XMPP Errors
|
||||||
|
|
||||||
|
type Err struct {
|
||||||
|
XMLName xml.Name `xml:"error"`
|
||||||
|
Code int `xml:"code,attr,omitempty"`
|
||||||
|
Type string `xml:"type,attr,omitempty"`
|
||||||
|
Reason string
|
||||||
|
Text string `xml:"urn:ietf:params:xml:ns:xmpp-stanzas text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Err) IsIQPayload() {}
|
||||||
|
|
||||||
|
// UnmarshalXML implements custom parsing for IQs
|
||||||
|
func (x *Err) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
x.XMLName = start.Name
|
||||||
|
|
||||||
|
// Extract attributes
|
||||||
|
for _, attr := range start.Attr {
|
||||||
|
if attr.Name.Local == "type" {
|
||||||
|
x.Type = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Name.Local == "code" {
|
||||||
|
if code, err := strconv.Atoi(attr.Value); err == nil {
|
||||||
|
x.Code = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := t.(type) {
|
||||||
|
|
||||||
|
case xml.StartElement:
|
||||||
|
elt := new(Node)
|
||||||
|
|
||||||
|
err = d.DecodeElement(elt, &tt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
textName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
||||||
|
if elt.XMLName == textName {
|
||||||
|
x.Text = string(elt.Content)
|
||||||
|
} else if elt.XMLName.Space == "urn:ietf:params:xml:ns:xmpp-stanzas" {
|
||||||
|
x.Reason = elt.XMLName.Local
|
||||||
|
}
|
||||||
|
|
||||||
|
case xml.EndElement:
|
||||||
|
if tt == start.End() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x Err) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
||||||
|
code := xml.Attr{
|
||||||
|
Name: xml.Name{Local: "code"},
|
||||||
|
Value: strconv.Itoa(x.Code),
|
||||||
|
}
|
||||||
|
typ := xml.Attr{
|
||||||
|
Name: xml.Name{Local: "type"},
|
||||||
|
Value: x.Type,
|
||||||
|
}
|
||||||
|
start.Name = xml.Name{Local: "error"}
|
||||||
|
start.Attr = append(start.Attr, code, typ)
|
||||||
|
err = e.EncodeToken(start)
|
||||||
|
|
||||||
|
// Subtags
|
||||||
|
// Reason
|
||||||
|
if x.Reason != "" {
|
||||||
|
reason := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: x.Reason}
|
||||||
|
e.EncodeToken(xml.StartElement{Name: reason})
|
||||||
|
e.EncodeToken(xml.EndElement{Name: reason})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text
|
||||||
|
if x.Text != "" {
|
||||||
|
text := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
||||||
|
e.EncodeToken(xml.StartElement{Name: text})
|
||||||
|
e.EncodeToken(xml.CharData(x.Text))
|
||||||
|
e.EncodeToken(xml.EndElement{Name: text})
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// IQ Packet
|
// IQ Packet
|
||||||
|
|
||||||
|
@ -38,7 +162,7 @@ type IQ struct { // Info/Query
|
||||||
PacketAttrs
|
PacketAttrs
|
||||||
Payload []IQPayload `xml:",omitempty"`
|
Payload []IQPayload `xml:",omitempty"`
|
||||||
RawXML string `xml:",innerxml"`
|
RawXML string `xml:",innerxml"`
|
||||||
// Error clientError
|
Error Err `xml:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIQ(iqtype, from, to, id, lang string) IQ {
|
func NewIQ(iqtype, from, to, id, lang string) IQ {
|
||||||
|
@ -58,6 +182,18 @@ func (iq *IQ) AddPayload(payload IQPayload) {
|
||||||
iq.Payload = append(iq.Payload, payload)
|
iq.Payload = append(iq.Payload, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (iq IQ) MakeError(xerror Err) IQ {
|
||||||
|
from := iq.From
|
||||||
|
to := iq.To
|
||||||
|
|
||||||
|
iq.Type = "error"
|
||||||
|
iq.From = to
|
||||||
|
iq.To = from
|
||||||
|
iq.Error = xerror
|
||||||
|
|
||||||
|
return iq
|
||||||
|
}
|
||||||
|
|
||||||
func (IQ) Name() string {
|
func (IQ) Name() string {
|
||||||
return "iq"
|
return "iq"
|
||||||
}
|
}
|
||||||
|
@ -75,6 +211,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
|
||||||
|
|
||||||
// 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 +232,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)
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -161,14 +302,19 @@ type IQPayload interface {
|
||||||
type Node struct {
|
type Node struct {
|
||||||
XMLName xml.Name
|
XMLName xml.Name
|
||||||
Attrs []xml.Attr `xml:"-"`
|
Attrs []xml.Attr `xml:"-"`
|
||||||
// Content []byte `xml:",innerxml"`
|
Content string `xml:",innerxml"`
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -177,7 +323,7 @@ func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
return d.DecodeElement((*node)(n), &start)
|
return d.DecodeElement((*node)(n), &start)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
||||||
start.Attr = n.Attrs
|
start.Attr = n.Attrs
|
||||||
start.Name = n.XMLName
|
start.Name = n.XMLName
|
||||||
|
|
||||||
|
@ -187,3 +333,40 @@ func (n *Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Node) IsIQPayload() {}
|
func (*Node) IsIQPayload() {}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Disco
|
||||||
|
|
||||||
|
const (
|
||||||
|
NSDiscoInfo = "http://jabber.org/protocol/disco#info"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DiscoInfo struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
||||||
|
Identity Identity `xml:"identity"`
|
||||||
|
Features []Feature `xml:"feature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DiscoInfo) IsIQPayload() {}
|
||||||
|
|
||||||
|
type Identity struct {
|
||||||
|
XMLName xml.Name `xml:"identity,omitempty"`
|
||||||
|
Name string `xml:"name,attr,omitempty"`
|
||||||
|
Category string `xml:"category,attr,omitempty"`
|
||||||
|
Type string `xml:"type,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Feature struct {
|
||||||
|
XMLName xml.Name `xml:"feature"`
|
||||||
|
Var string `xml:"var,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{})
|
||||||
|
}
|
||||||
|
|
95
iq_test.go
95
iq_test.go
|
@ -2,8 +2,9 @@ package xmpp // import "fluux.io/xmpp"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUnmarshalIqs(t *testing.T) {
|
func TestUnmarshalIqs(t *testing.T) {
|
||||||
|
@ -17,49 +18,91 @@ func TestUnmarshalIqs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
var parsedIQ = new(IQ)
|
parsedIQ := IQ{}
|
||||||
err := xml.Unmarshal([]byte(test.iqString), parsedIQ)
|
err := xml.Unmarshal([]byte(test.iqString), &parsedIQ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unmarshal(%s) returned error", test.iqString)
|
t.Errorf("Unmarshal(%s) returned error", test.iqString)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(parsedIQ, &test.parsedIQ) {
|
|
||||||
t.Errorf("Unmarshal(%s) expecting result %+v = %+v", test.iqString, parsedIQ, &test.parsedIQ)
|
if !xmlEqual(parsedIQ, test.parsedIQ) {
|
||||||
|
t.Errorf("non matching items\n%s", cmp.Diff(parsedIQ, test.parsedIQ))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateIq(t *testing.T) {
|
func TestGenerateIq(t *testing.T) {
|
||||||
iq := NewIQ("get", "admin@localhost", "test@localhost", "1", "en")
|
iq := NewIQ("result", "admin@localhost", "test@localhost", "1", "en")
|
||||||
payload := Node{
|
payload := DiscoInfo{
|
||||||
XMLName: xml.Name{
|
Identity: Identity{
|
||||||
Space: "http://jabber.org/protocol/disco#info",
|
Name: "Test Gateway",
|
||||||
Local: "query",
|
Category: "gateway",
|
||||||
|
Type: "mqtt",
|
||||||
|
},
|
||||||
|
Features: []Feature{
|
||||||
|
{Var: "http://jabber.org/protocol/disco#info"},
|
||||||
|
{Var: "http://jabber.org/protocol/disco#item"},
|
||||||
},
|
},
|
||||||
Nodes: []Node{
|
|
||||||
{XMLName: xml.Name{
|
|
||||||
Space: "http://jabber.org/protocol/disco#info",
|
|
||||||
Local: "identity",
|
|
||||||
},
|
|
||||||
Attrs: []xml.Attr{
|
|
||||||
{Name: xml.Name{Local: "category"}, Value: "gateway"},
|
|
||||||
{Name: xml.Name{Local: "type"}, Value: "skype"},
|
|
||||||
{Name: xml.Name{Local: "name"}, Value: "Test Gateway"},
|
|
||||||
},
|
|
||||||
Nodes: nil,
|
|
||||||
}},
|
|
||||||
}
|
}
|
||||||
iq.AddPayload(&payload)
|
iq.AddPayload(&payload)
|
||||||
|
|
||||||
data, err := xml.Marshal(iq)
|
data, err := xml.Marshal(iq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("cannot marshal xml structure")
|
t.Errorf("cannot marshal xml structure")
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedIQ = new(IQ)
|
parsedIQ := 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(parsedIQ.Payload[0], iq.Payload[0]) {
|
if !xmlEqual(parsedIQ.Payload, iq.Payload) {
|
||||||
t.Errorf("expecting result %+v = %+v", parsedIQ.Payload[0], iq.Payload[0])
|
t.Errorf("non matching items\n%s", cmp.Diff(parsedIQ.Payload, iq.Payload))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestErrorTag(t *testing.T) {
|
||||||
|
xError := Err{
|
||||||
|
XMLName: xml.Name{Local: "error"},
|
||||||
|
Code: 503,
|
||||||
|
Type: "cancel",
|
||||||
|
Reason: "service-unavailable",
|
||||||
|
Text: "User session not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(xError)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("cannot marshal xml structure: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedError := Err{}
|
||||||
|
if err = xml.Unmarshal(data, &parsedError); err != nil {
|
||||||
|
t.Errorf("Unmarshal(%s) returned error", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !xmlEqual(parsedError, xError) {
|
||||||
|
t.Errorf("non matching items\n%s", cmp.Diff(parsedError, xError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare iq structure but ignore empty namespace as they are set properly on
|
||||||
|
// marshal / unmarshal. There is no need to manage them on the manually
|
||||||
|
// crafted structure.
|
||||||
|
func xmlEqual(x, y interface{}) bool {
|
||||||
|
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
|
||||||
|
opts := cmp.Options{
|
||||||
|
cmp.FilterValues(func(x, y interface{}) bool {
|
||||||
|
xx, xok := x.(xml.Name)
|
||||||
|
yy, yok := y.(xml.Name)
|
||||||
|
if xok && yok {
|
||||||
|
zero := xml.Name{}
|
||||||
|
if xx == zero || yy == zero {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}, alwaysEqual),
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmp.Equal(x, y, opts)
|
||||||
|
}
|
||||||
|
|
|
@ -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