diff --git a/README.md b/README.md
index b9ba409..3e7829b 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ import (
"os"
"gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func main() {
@@ -57,15 +58,15 @@ func main() {
log.Fatal(cm.Run())
}
-func handleMessage(s xmpp.Sender, p xmpp.Packet) {
- msg, ok := p.(xmpp.Message)
+func handleMessage(s xmpp.Sender, p stanza.Packet) {
+ msg, ok := p.(stanza.Message)
if !ok {
_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
return
}
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
- reply := xmpp.Message{Attrs: xmpp.Attrs{To: msg.From}, Body: msg.Body}
+ reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
_ = s.Send(reply)
}
```
diff --git a/_examples/delegation/delegation.go b/_examples/delegation/delegation.go
index 473fefa..31c18d3 100644
--- a/_examples/delegation/delegation.go
+++ b/_examples/delegation/delegation.go
@@ -6,6 +6,7 @@ import (
"log"
"gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func main() {
@@ -23,8 +24,8 @@ func main() {
router := xmpp.NewRouter()
router.HandleFunc("message", handleMessage)
router.NewRoute().
- IQNamespaces(xmpp.NSDiscoInfo).
- HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+ IQNamespaces(stanza.NSDiscoInfo).
+ HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
discoInfo(s, p, opts)
})
router.NewRoute().
@@ -43,14 +44,14 @@ func main() {
log.Fatal(cm.Run())
}
-func handleMessage(_ xmpp.Sender, p xmpp.Packet) {
- msg, ok := p.(xmpp.Message)
+func handleMessage(_ xmpp.Sender, p stanza.Packet) {
+ msg, ok := p.(stanza.Message)
if !ok {
return
}
var msgProcessed bool
for _, ext := range msg.Extensions {
- delegation, ok := ext.(*xmpp.Delegation)
+ delegation, ok := ext.(*stanza.Delegation)
if ok {
msgProcessed = true
fmt.Printf("Delegation confirmed for namespace %s\n", delegation.Delegated.Namespace)
@@ -72,18 +73,18 @@ const (
// TODO: replace xmpp.Sender by ctx xmpp.Context ?
// ctx.Stream.Send / SendRaw
// ctx.Opts
-func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
+func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
// Type conversion & sanity checks
- iq, ok := p.(xmpp.IQ)
+ iq, ok := p.(stanza.IQ)
if !ok {
return
}
- info, ok := iq.Payload.(*xmpp.DiscoInfo)
+ info, ok := iq.Payload.(*stanza.DiscoInfo)
if !ok {
return
}
- iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
+ iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
switch info.Node {
case "":
@@ -97,22 +98,22 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
_ = c.Send(iqResp)
}
-func discoInfoRoot(iqResp *xmpp.IQ, opts xmpp.ComponentOptions) {
+func discoInfoRoot(iqResp *stanza.IQ, opts xmpp.ComponentOptions) {
// Higher level discovery
- identity := xmpp.Identity{
+ identity := stanza.Identity{
Name: opts.Name,
Category: opts.Category,
Type: opts.Type,
}
- payload := xmpp.DiscoInfo{
+ payload := stanza.DiscoInfo{
XMLName: xml.Name{
- Space: xmpp.NSDiscoInfo,
+ Space: stanza.NSDiscoInfo,
Local: "query",
},
- Identity: identity,
- Features: []xmpp.Feature{
- {Var: xmpp.NSDiscoInfo},
- {Var: xmpp.NSDiscoItems},
+ Identity: []stanza.Identity{identity},
+ Features: []stanza.Feature{
+ {Var: stanza.NSDiscoInfo},
+ {Var: stanza.NSDiscoItems},
{Var: "jabber:iq:version"},
{Var: "urn:xmpp:delegation:1"},
},
@@ -120,14 +121,14 @@ func discoInfoRoot(iqResp *xmpp.IQ, opts xmpp.ComponentOptions) {
iqResp.Payload = &payload
}
-func discoInfoPubSub(iqResp *xmpp.IQ) {
- payload := xmpp.DiscoInfo{
+func discoInfoPubSub(iqResp *stanza.IQ) {
+ payload := stanza.DiscoInfo{
XMLName: xml.Name{
- Space: xmpp.NSDiscoInfo,
+ Space: stanza.NSDiscoInfo,
Local: "query",
},
Node: pubsubNode,
- Features: []xmpp.Feature{
+ Features: []stanza.Feature{
{Var: "http://jabber.org/protocol/pubsub"},
{Var: "http://jabber.org/protocol/pubsub#publish"},
{Var: "http://jabber.org/protocol/pubsub#subscribe"},
@@ -137,19 +138,19 @@ func discoInfoPubSub(iqResp *xmpp.IQ) {
iqResp.Payload = &payload
}
-func discoInfoPEP(iqResp *xmpp.IQ) {
- identity := xmpp.Identity{
+func discoInfoPEP(iqResp *stanza.IQ) {
+ identity := stanza.Identity{
Category: "pubsub",
Type: "pep",
}
- payload := xmpp.DiscoInfo{
+ payload := stanza.DiscoInfo{
XMLName: xml.Name{
- Space: xmpp.NSDiscoInfo,
+ Space: stanza.NSDiscoInfo,
Local: "query",
},
- Identity: identity,
+ Identity: []stanza.Identity{identity},
Node: pepNode,
- Features: []xmpp.Feature{
+ Features: []stanza.Feature{
{Var: "http://jabber.org/protocol/pubsub#access-presence"},
{Var: "http://jabber.org/protocol/pubsub#auto-create"},
{Var: "http://jabber.org/protocol/pubsub#auto-subscribe"},
@@ -166,25 +167,25 @@ func discoInfoPEP(iqResp *xmpp.IQ) {
iqResp.Payload = &payload
}
-func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
+func handleDelegation(s xmpp.Sender, p stanza.Packet) {
// Type conversion & sanity checks
- iq, ok := p.(xmpp.IQ)
+ iq, ok := p.(stanza.IQ)
if !ok {
return
}
- delegation, ok := iq.Payload.(*xmpp.Delegation)
+ delegation, ok := iq.Payload.(*stanza.Delegation)
if !ok {
return
}
forwardedPacket := delegation.Forwarded.Stanza
fmt.Println(forwardedPacket)
- forwardedIQ, ok := forwardedPacket.(xmpp.IQ)
+ forwardedIQ, ok := forwardedPacket.(stanza.IQ)
if !ok {
return
}
- pubsub, ok := forwardedIQ.Payload.(*xmpp.PubSub)
+ pubsub, ok := forwardedIQ.Payload.(*stanza.PubSub)
if !ok {
// We only support pubsub delegation
return
@@ -192,8 +193,8 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
if pubsub.Publish.XMLName.Local == "publish" {
// Prepare pubsub IQ reply
- iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
- payload := xmpp.PubSub{
+ iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
+ payload := stanza.PubSub{
XMLName: xml.Name{
Space: "http://jabber.org/protocol/pubsub",
Local: "pubsub",
@@ -201,13 +202,13 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
}
iqResp.Payload = &payload
// Wrap the reply in delegation 'forward'
- iqForward := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
- delegPayload := xmpp.Delegation{
+ iqForward := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
+ delegPayload := stanza.Delegation{
XMLName: xml.Name{
Space: "urn:xmpp:delegation:1",
Local: "delegation",
},
- Forwarded: &xmpp.Forwarded{
+ Forwarded: &stanza.Forwarded{
XMLName: xml.Name{
Space: "urn:xmpp:forward:0",
Local: "forward",
diff --git a/_examples/xmpp_component/xmpp_component.go b/_examples/xmpp_component/xmpp_component.go
index 17cfc1a..4bc906b 100644
--- a/_examples/xmpp_component/xmpp_component.go
+++ b/_examples/xmpp_component/xmpp_component.go
@@ -6,6 +6,7 @@ import (
"log"
"gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func main() {
@@ -21,12 +22,12 @@ func main() {
router := xmpp.NewRouter()
router.HandleFunc("message", handleMessage)
router.NewRoute().
- IQNamespaces(xmpp.NSDiscoInfo).
- HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+ IQNamespaces(stanza.NSDiscoInfo).
+ HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
discoInfo(s, p, opts)
})
router.NewRoute().
- IQNamespaces(xmpp.NSDiscoItems).
+ IQNamespaces(stanza.NSDiscoItems).
HandlerFunc(discoItems)
router.NewRoute().
IQNamespaces("jabber:iq:version").
@@ -44,36 +45,36 @@ func main() {
log.Fatal(cm.Run())
}
-func handleMessage(_ xmpp.Sender, p xmpp.Packet) {
- msg, ok := p.(xmpp.Message)
+func handleMessage(_ xmpp.Sender, p stanza.Packet) {
+ msg, ok := p.(stanza.Message)
if !ok {
return
}
fmt.Println("Received message:", msg.Body)
}
-func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
+func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
// Type conversion & sanity checks
- iq, ok := p.(xmpp.IQ)
+ iq, ok := p.(stanza.IQ)
if !ok || iq.Type != "get" {
return
}
- iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
- identity := xmpp.Identity{
+ iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+ identity := stanza.Identity{
Name: opts.Name,
Category: opts.Category,
Type: opts.Type,
}
- payload := xmpp.DiscoInfo{
+ payload := stanza.DiscoInfo{
XMLName: xml.Name{
- Space: xmpp.NSDiscoInfo,
+ Space: stanza.NSDiscoInfo,
Local: "query",
},
- Identity: identity,
- Features: []xmpp.Feature{
- {Var: xmpp.NSDiscoInfo},
- {Var: xmpp.NSDiscoItems},
+ Identity: []stanza.Identity{identity},
+ Features: []stanza.Feature{
+ {Var: stanza.NSDiscoInfo},
+ {Var: stanza.NSDiscoItems},
{Var: "jabber:iq:version"},
{Var: "urn:xmpp:delegation:1"},
},
@@ -83,24 +84,24 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
}
// TODO: Handle iq error responses
-func discoItems(c xmpp.Sender, p xmpp.Packet) {
+func discoItems(c xmpp.Sender, p stanza.Packet) {
// Type conversion & sanity checks
- iq, ok := p.(xmpp.IQ)
+ iq, ok := p.(stanza.IQ)
if !ok || iq.Type != "get" {
return
}
- discoItems, ok := iq.Payload.(*xmpp.DiscoItems)
+ discoItems, ok := iq.Payload.(*stanza.DiscoItems)
if !ok {
return
}
- iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+ iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
- var payload xmpp.DiscoItems
+ var payload stanza.DiscoItems
if discoItems.Node == "" {
- payload = xmpp.DiscoItems{
- Items: []xmpp.DiscoItem{
+ payload = stanza.DiscoItems{
+ Items: []stanza.DiscoItem{
{Name: "test node", JID: "service.localhost", Node: "node1"},
},
}
@@ -109,15 +110,15 @@ func discoItems(c xmpp.Sender, p xmpp.Packet) {
_ = c.Send(iqResp)
}
-func handleVersion(c xmpp.Sender, p xmpp.Packet) {
+func handleVersion(c xmpp.Sender, p stanza.Packet) {
// Type conversion & sanity checks
- iq, ok := p.(xmpp.IQ)
+ iq, ok := p.(stanza.IQ)
if !ok {
return
}
- iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
- var payload xmpp.Version
+ iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+ var payload stanza.Version
payload.Name = "Fluux XMPP Component"
payload.Version = "0.0.1"
iq.Payload = &payload
diff --git a/_examples/xmpp_echo/xmpp_echo.go b/_examples/xmpp_echo/xmpp_echo.go
index 3725f08..b4da207 100644
--- a/_examples/xmpp_echo/xmpp_echo.go
+++ b/_examples/xmpp_echo/xmpp_echo.go
@@ -10,6 +10,7 @@ import (
"os"
"gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func main() {
@@ -35,15 +36,15 @@ func main() {
log.Fatal(cm.Run())
}
-func handleMessage(s xmpp.Sender, p xmpp.Packet) {
- msg, ok := p.(xmpp.Message)
+func handleMessage(s xmpp.Sender, p stanza.Packet) {
+ msg, ok := p.(stanza.Message)
if !ok {
_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
return
}
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
- reply := xmpp.Message{Attrs: xmpp.Attrs{To: msg.From}, Body: msg.Body}
+ reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
_ = s.Send(reply)
}
diff --git a/_examples/xmpp_jukebox/xmpp_jukebox.go b/_examples/xmpp_jukebox/xmpp_jukebox.go
index 2218279..02e2c76 100644
--- a/_examples/xmpp_jukebox/xmpp_jukebox.go
+++ b/_examples/xmpp_jukebox/xmpp_jukebox.go
@@ -12,6 +12,7 @@ import (
"github.com/processone/mpg123"
"github.com/processone/soundcloud"
"gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
// Get the actual song Stream URL from SoundCloud website song URL and play it with mpg123 player.
@@ -41,12 +42,12 @@ func main() {
router := xmpp.NewRouter()
router.NewRoute().
Packet("message").
- HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+ HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
handleMessage(s, p, player)
})
router.NewRoute().
Packet("message").
- HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+ HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
handleIQ(s, p, player)
})
@@ -59,8 +60,8 @@ func main() {
log.Fatal(cm.Run())
}
-func handleMessage(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
- msg, ok := p.(xmpp.Message)
+func handleMessage(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
+ msg, ok := p.(stanza.Message)
if !ok {
return
}
@@ -73,14 +74,14 @@ func handleMessage(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
}
}
-func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
- iq, ok := p.(xmpp.IQ)
+func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
+ iq, ok := p.(stanza.IQ)
if !ok {
return
}
switch payload := iq.Payload.(type) {
// We support IOT Control IQ
- case *xmpp.ControlSet:
+ case *stanza.ControlSet:
var url string
for _, element := range payload.Fields {
if element.XMLName.Local == "string" && element.Name == "url" {
@@ -90,9 +91,9 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
}
playSCURL(player, url)
- setResponse := new(xmpp.ControlSetResponse)
+ setResponse := new(stanza.ControlSetResponse)
// FIXME: Broken
- reply := xmpp.IQ{Attrs: xmpp.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
+ reply := stanza.IQ{Attrs: stanza.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
_ = s.Send(reply)
// TODO add Soundclound artist / title retrieval
sendUserTune(s, "Radiohead", "Spectre")
@@ -102,9 +103,9 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
}
func sendUserTune(s xmpp.Sender, artist string, title string) {
- tune := xmpp.Tune{Artist: artist, Title: title}
- iq := xmpp.NewIQ(xmpp.Attrs{Type: "set", Id: "usertune-1", Lang: "en"})
- payload := xmpp.PubSub{Publish: &xmpp.Publish{Node: "http://jabber.org/protocol/tune", Item: xmpp.Item{Tune: &tune}}}
+ tune := stanza.Tune{Artist: artist, Title: title}
+ iq := stanza.NewIQ(stanza.Attrs{Type: "set", Id: "usertune-1", Lang: "en"})
+ payload := stanza.PubSub{Publish: &stanza.Publish{Node: "http://jabber.org/protocol/tune", Item: stanza.Item{Tune: &tune}}}
iq.Payload = &payload
_ = s.Send(iq)
}
diff --git a/auth.go b/auth.go
index 497258d..926426d 100644
--- a/auth.go
+++ b/auth.go
@@ -6,9 +6,11 @@ import (
"errors"
"fmt"
"io"
+
+ "gosrc.io/xmpp/stanza"
)
-func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f StreamFeatures, user string, password string) (err error) {
+func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f stanza.StreamFeatures, user string, password string) (err error) {
// TODO: Implement other type of SASL Authentication
havePlain := false
for _, m := range f.Mechanisms.Mechanism {
@@ -30,17 +32,17 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
raw := "\x00" + user + "\x00" + password
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
- fmt.Fprintf(socket, "%s", nsSASL, enc)
+ fmt.Fprintf(socket, "%s", stanza.NSSASL, enc)
// Next message should be either success or failure.
- val, err := nextPacket(decoder)
+ val, err := stanza.NextPacket(decoder)
if err != nil {
return err
}
switch v := val.(type) {
- case SASLSuccess:
- case SASLFailure:
+ case stanza.SASLSuccess:
+ case stanza.SASLFailure:
// v.Any is type of sub-element in failure, which gives a description of what failed.
err := errors.New("auth failure: " + v.Any.Local)
return NewConnError(err, true)
@@ -49,72 +51,3 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
}
return err
}
-
-// ============================================================================
-// SASLSuccess
-
-type SASLSuccess struct {
- XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
-}
-
-func (SASLSuccess) Name() string {
- return "sasl:success"
-}
-
-type saslSuccessDecoder struct{}
-
-var saslSuccess saslSuccessDecoder
-
-func (saslSuccessDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLSuccess, error) {
- var packet SASLSuccess
- err := p.DecodeElement(&packet, &se)
- return packet, err
-}
-
-// ============================================================================
-// SASLFailure
-
-type SASLFailure struct {
- XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
- Any xml.Name // error reason is a subelement
-}
-
-func (SASLFailure) Name() string {
- return "sasl:failure"
-}
-
-type saslFailureDecoder struct{}
-
-var saslFailure saslFailureDecoder
-
-func (saslFailureDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLFailure, error) {
- var packet SASLFailure
- err := p.DecodeElement(&packet, &se)
- return packet, err
-}
-
-// ============================================================================
-
-type auth struct {
- XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
- Mechanism string `xml:"mecanism,attr"`
- Value string `xml:",innerxml"`
-}
-
-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 (b *BindBind) Namespace() string {
- return b.XMLName.Space
-}
-
-// Session is obsolete in RFC 6121.
-// Added for compliance with RFC 3121.
-// Remove when ejabberd purely conforms to RFC 6121.
-type sessionSession struct {
- XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
- optional xml.Name // If it does exist, it mean we are not required to open session
-}
diff --git a/check_cert.go b/check_cert.go
index 5addd87..e33d9f9 100644
--- a/check_cert.go
+++ b/check_cert.go
@@ -8,6 +8,8 @@ import (
"net"
"strings"
"time"
+
+ "gosrc.io/xmpp/stanza"
)
// TODO: Should I move this as an extension of the client?
@@ -49,28 +51,28 @@ func (c *ServerCheck) Check() error {
decoder := xml.NewDecoder(tcpconn)
// Send stream open tag
- if _, err = fmt.Fprintf(tcpconn, xmppStreamOpen, c.domain, NSClient, NSStream); err != nil {
+ if _, err = fmt.Fprintf(tcpconn, xmppStreamOpen, c.domain, stanza.NSClient, stanza.NSStream); err != nil {
return err
}
// Set xml decoder and extract streamID from reply (not used for now)
- _, err = initStream(decoder)
+ _, err = stanza.InitStream(decoder)
if err != nil {
return err
}
// extract stream features
- var f StreamFeatures
- packet, err := nextPacket(decoder)
+ var f stanza.StreamFeatures
+ packet, err := stanza.NextPacket(decoder)
if err != nil {
err = fmt.Errorf("stream open decode features: %s", err)
return err
}
switch p := packet.(type) {
- case StreamFeatures:
+ case stanza.StreamFeatures:
f = p
- case StreamError:
+ case stanza.StreamError:
return errors.New("open stream error: " + p.Error.Local)
default:
return errors.New("expected packet received while expecting features, got " + p.Name())
@@ -79,13 +81,13 @@ func (c *ServerCheck) Check() error {
if _, ok := f.DoesStartTLS(); ok {
fmt.Fprintf(tcpconn, "")
- var k tlsProceed
+ var k stanza.TLSProceed
if err = decoder.DecodeElement(&k, nil); err != nil {
return fmt.Errorf("expecting starttls proceed: %s", err)
}
- DefaultTlsConfig.ServerName = c.domain
- tlsConn := tls.Client(tcpconn, &DefaultTlsConfig)
+ stanza.DefaultTlsConfig.ServerName = c.domain
+ tlsConn := tls.Client(tcpconn, &stanza.DefaultTlsConfig)
// We convert existing connection to TLS
if err = tlsConn.Handshake(); err != nil {
return err
diff --git a/client.go b/client.go
index 1a63041..2bd8224 100644
--- a/client.go
+++ b/client.go
@@ -6,6 +6,8 @@ import (
"fmt"
"net"
"time"
+
+ "gosrc.io/xmpp/stanza"
)
//=============================================================================
@@ -153,7 +155,7 @@ func (c *Client) SetHandler(handler EventHandler) {
}
// Send marshals XMPP stanza and sends it to the server.
-func (c *Client) Send(packet Packet) error {
+func (c *Client) Send(packet stanza.Packet) error {
conn := c.conn
if conn == nil {
return errors.New("client is not connected")
@@ -191,7 +193,7 @@ func (c *Client) SendRaw(packet string) error {
// Loop: Receive data from server
func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
for {
- val, err := nextPacket(c.Session.decoder)
+ val, err := stanza.NextPacket(c.Session.decoder)
if err != nil {
close(keepaliveQuit)
c.updateState(StateDisconnected)
@@ -200,7 +202,7 @@ func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
// Handle stream errors
switch packet := val.(type) {
- case StreamError:
+ case stanza.StreamError:
c.router.route(c, val)
close(keepaliveQuit)
c.streamError(packet.Error.Local, packet.Text)
diff --git a/client_test.go b/client_test.go
index 813f2be..a599d5b 100644
--- a/client_test.go
+++ b/client_test.go
@@ -7,6 +7,8 @@ import (
"net"
"testing"
"time"
+
+ "gosrc.io/xmpp/stanza"
)
const (
@@ -126,11 +128,11 @@ func checkOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
switch elem := token.(type) {
// Wait for first startElement
case xml.StartElement:
- if elem.Name.Space != NSStream || elem.Name.Local != "stream" {
+ if elem.Name.Space != stanza.NSStream || elem.Name.Local != "stream" {
err = errors.New("xmpp: expected but got <" + elem.Name.Local + "> in " + elem.Name.Space)
return
}
- if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", NSClient, NSStream); err != nil {
+ if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", stanza.NSClient, stanza.NSStream); err != nil {
t.Errorf("cannot write server stream open: %s", err)
}
return
@@ -152,14 +154,14 @@ func sendStreamFeatures(t *testing.T, c net.Conn, _ *xml.Decoder) {
// TODO return err in case of error reading the auth params
func readAuth(t *testing.T, decoder *xml.Decoder) string {
- se, err := nextStart(decoder)
+ se, err := stanza.NextStart(decoder)
if err != nil {
t.Errorf("cannot read auth: %s", err)
return ""
}
var nv interface{}
- nv = &auth{}
+ nv = &stanza.Auth{}
// Decode element into pointer storage
if err = decoder.DecodeElement(nv, &se); err != nil {
t.Errorf("cannot decode auth: %s", err)
@@ -167,7 +169,7 @@ func readAuth(t *testing.T, decoder *xml.Decoder) string {
}
switch v := nv.(type) {
- case *auth:
+ case *stanza.Auth:
return v.Value
}
return ""
@@ -184,13 +186,13 @@ func sendBindFeature(t *testing.T, c net.Conn, _ *xml.Decoder) {
}
func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
- se, err := nextStart(decoder)
+ se, err := stanza.NextStart(decoder)
if err != nil {
t.Errorf("cannot read bind: %s", err)
return
}
- iq := &IQ{}
+ iq := &stanza.IQ{}
// Decode element into pointer storage
if err = decoder.DecodeElement(&iq, &se); err != nil {
t.Errorf("cannot decode bind iq: %s", err)
@@ -199,7 +201,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
// TODO Check all elements
switch iq.Payload.(type) {
- case *BindBind:
+ case *stanza.BindBind:
result := `
%s
diff --git a/component.go b/component.go
index d13822b..0176371 100644
--- a/component.go
+++ b/component.go
@@ -9,6 +9,8 @@ import (
"io"
"net"
"time"
+
+ "gosrc.io/xmpp/stanza"
)
const componentStreamOpen = ""
@@ -72,13 +74,13 @@ func (c *Component) Connect() error {
c.conn = conn
// 1. Send stream open tag
- if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, NSComponent, NSStream); err != nil {
+ if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, stanza.NSComponent, stanza.NSStream); err != nil {
return errors.New("cannot send stream open " + err.Error())
}
c.decoder = xml.NewDecoder(conn)
// 2. Initialize xml decoder and extract streamID from reply
- streamId, err := initStream(c.decoder)
+ streamId, err := stanza.InitStream(c.decoder)
if err != nil {
return errors.New("cannot init decoder " + err.Error())
}
@@ -89,15 +91,15 @@ func (c *Component) Connect() error {
}
// 4. Check server response for authentication
- val, err := nextPacket(c.decoder)
+ val, err := stanza.NextPacket(c.decoder)
if err != nil {
return err
}
switch v := val.(type) {
- case StreamError:
+ case stanza.StreamError:
return errors.New("handshake failed " + v.Error.Local)
- case Handshake:
+ case stanza.Handshake:
// Start the receiver go routine
go c.recv()
return nil
@@ -119,7 +121,7 @@ func (c *Component) SetHandler(handler EventHandler) {
// Receiver Go routine receiver
func (c *Component) recv() (err error) {
for {
- val, err := nextPacket(c.decoder)
+ val, err := stanza.NextPacket(c.decoder)
if err != nil {
c.updateState(StateDisconnected)
return err
@@ -127,7 +129,7 @@ func (c *Component) recv() (err error) {
// Handle stream errors
switch p := val.(type) {
- case StreamError:
+ case stanza.StreamError:
c.router.route(c, val)
c.streamError(p.Error.Local, p.Text)
return errors.New("stream error: " + p.Error.Local)
@@ -137,7 +139,7 @@ func (c *Component) recv() (err error) {
}
// Send marshalls XMPP stanza and sends it to the server.
-func (c *Component) Send(packet Packet) error {
+func (c *Component) Send(packet stanza.Packet) error {
conn := c.conn
if conn == nil {
return errors.New("component is not connected")
@@ -186,90 +188,6 @@ func (c *Component) handshake(streamId string) string {
return encodedStr
}
-// ============================================================================
-// Handshake Stanza
-
-// Handshake is a stanza used by XMPP components to authenticate on XMPP
-// component port.
-type Handshake struct {
- XMLName xml.Name `xml:"jabber:component:accept handshake"`
- // TODO Add handshake value with test for proper serialization
- // Value string `xml:",innerxml"`
-}
-
-func (Handshake) Name() string {
- return "component:handshake"
-}
-
-// Handshake decoding wrapper
-
-type handshakeDecoder struct{}
-
-var handshake handshakeDecoder
-
-func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
- var packet Handshake
- err := p.DecodeElement(&packet, &se)
- return packet, err
-}
-
-// ============================================================================
-// Component delegation
-// XEP-0355
-
-// Delegation can be used both on message (for delegated) and IQ (for Forwarded),
-// depending on the context.
-type Delegation struct {
- MsgExtension
- XMLName xml.Name `xml:"urn:xmpp:delegation:1 delegation"`
- Forwarded *Forwarded // This is used in iq to wrap delegated iqs
- Delegated *Delegated // This is used in a message to confirm delegated namespace
-}
-
-func (d *Delegation) Namespace() string {
- return d.XMLName.Space
-}
-
-type Forwarded struct {
- XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
- Stanza Packet
-}
-
-// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
-// transform generic XML content into hierarchical Node structure.
-func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
- // Check subelements to extract required field as boolean
- for {
- t, err := d.Token()
- if err != nil {
- return err
- }
-
- switch tt := t.(type) {
-
- case xml.StartElement:
- if packet, err := decodeClient(d, tt); err == nil {
- f.Stanza = packet
- }
-
- case xml.EndElement:
- if tt == start.End() {
- return nil
- }
- }
- }
-}
-
-type Delegated struct {
- XMLName xml.Name `xml:"delegated"`
- Namespace string `xml:"namespace,attr,omitempty"`
-}
-
-func init() {
- TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
- TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
-}
-
/*
TODO: Add support for discovery management directly in component
TODO: Support multiple identities on disco info
diff --git a/component_test.go b/component_test.go
index fd7ae3c..222b471 100644
--- a/component_test.go
+++ b/component_test.go
@@ -1,7 +1,6 @@
package xmpp
import (
- "encoding/xml"
"testing"
)
@@ -24,76 +23,3 @@ func TestHandshake(t *testing.T) {
func TestGenerateHandshake(t *testing.T) {
// TODO
}
-
-// We should be able to properly parse delegation confirmation messages
-func TestParsingDelegationMessage(t *testing.T) {
- packetStr := `
-
-
-
-`
- var msg Message
- data := []byte(packetStr)
- if err := xml.Unmarshal(data, &msg); err != nil {
- t.Errorf("Unmarshal(%s) returned error", data)
- }
-
- // Check that we have extracted the delegation info as MsgExtension
- var nsDelegated string
- for _, ext := range msg.Extensions {
- if delegation, ok := ext.(*Delegation); ok {
- nsDelegated = delegation.Delegated.Namespace
- }
- }
- if nsDelegated != "http://jabber.org/protocol/pubsub" {
- t.Errorf("Could not find delegated namespace in delegation: %#v\n", msg)
- }
-}
-
-// Check that we can parse a delegation IQ.
-// The most important thing is to be able to
-func TestParsingDelegationIQ(t *testing.T) {
- packetStr := `
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-`
- var iq IQ
- data := []byte(packetStr)
- if err := xml.Unmarshal(data, &iq); err != nil {
- t.Errorf("Unmarshal(%s) returned error", data)
- }
-
- // Check that we have extracted the delegation info as IQPayload
- var node string
- if iq.Payload != nil {
- if delegation, ok := iq.Payload.(*Delegation); ok {
- packet := delegation.Forwarded.Stanza
- forwardedIQ, ok := packet.(IQ)
- if !ok {
- t.Errorf("Could not extract packet IQ")
- return
- }
- if forwardedIQ.Payload != nil {
- if pubsub, ok := forwardedIQ.Payload.(*PubSub); ok {
- node = pubsub.Publish.Node
- }
- }
- }
- }
- if node != "http://jabber.org/protocol/mood" {
- t.Errorf("Could not find mood node name on delegated publish: %#v\n", iq)
- }
-}
diff --git a/iq.go b/iq.go
deleted file mode 100644
index d04ab36..0000000
--- a/iq.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package xmpp
-
-import (
- "encoding/xml"
- "fmt"
-)
-
-/*
-TODO support ability to put Raw payload inside IQ
-*/
-
-// ============================================================================
-// IQ Packet
-
-// IQ implements RFC 6120 - A.5 Client Namespace (a part)
-type IQ struct { // Info/Query
- XMLName xml.Name `xml:"iq"`
- // MUST have a ID
- Attrs
- // We can only have one payload on IQ:
- // "An IQ stanza of type "get" or "set" MUST contain exactly one
- // child element, which specifies the semantics of the particular
- // request."
- Payload IQPayload `xml:",omitempty"`
- Error Err `xml:"error,omitempty"`
- // Any is used to decode unknown payload as a generique structure
- Any *Node `xml:",any"`
-}
-
-type IQPayload interface {
- Namespace() string
-}
-
-func NewIQ(a Attrs) IQ {
- // TODO generate IQ ID if not set
- // TODO ensure that type is set, as it is required
- return IQ{
- XMLName: xml.Name{Local: "iq"},
- Attrs: a,
- }
-}
-
-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 {
- return "iq"
-}
-
-type iqDecoder struct{}
-
-var iq iqDecoder
-
-func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
- var packet IQ
- err := p.DecodeElement(&packet, &se)
- return packet, err
-}
-
-// UnmarshalXML implements custom parsing for IQs
-func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
- iq.XMLName = start.Name
-
- // Extract IQ attributes
- for _, attr := range start.Attr {
- if attr.Name.Local == "id" {
- iq.Id = attr.Value
- }
- if attr.Name.Local == "type" {
- iq.Type = StanzaType(attr.Value)
- }
- if attr.Name.Local == "to" {
- iq.To = attr.Value
- }
- if attr.Name.Local == "from" {
- iq.From = attr.Value
- }
- }
-
- // decode inner elements
- for {
- t, err := d.Token()
- if err != nil {
- return err
- }
-
- switch tt := t.(type) {
- case xml.StartElement:
- if tt.Name.Local == "error" {
- var xmppError Err
- err = d.DecodeElement(&xmppError, &tt)
- if err != nil {
- fmt.Println(err)
- return err
- }
- iq.Error = xmppError
- continue
- }
- if iqExt := TypeRegistry.GetIQExtension(tt.Name); iqExt != nil {
- // Decode payload extension
- err = d.DecodeElement(iqExt, &tt)
- if err != nil {
- return err
- }
- iq.Payload = iqExt
- continue
- }
- // TODO: If unknown decode as generic node
- node := new(Node)
- err = d.DecodeElement(node, &tt)
- if err != nil {
- return err
- }
- iq.Any = node
- case xml.EndElement:
- if tt == start.End() {
- return nil
- }
- }
- }
-}
-
-// ============================================================================
-// Generic / unknown content
-
-// Node is a generic structure to represent XML data. It is used to parse
-// unreferenced or custom stanza payload.
-type Node struct {
- XMLName xml.Name
- Attrs []xml.Attr `xml:"-"`
- Content string `xml:",innerxml"`
- Nodes []Node `xml:",any"`
-}
-
-func (n *Node) Namespace() string {
- return n.XMLName.Space
-}
-
-// Attr represents generic XML attributes, as used on the generic XML Node
-// representation.
-type Attr struct {
- K string
- V string
-}
-
-// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
-// transform generic XML content into hierarchical Node structure.
-func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
- // Assign "n.Attrs = start.Attr", without repeating xmlns in attributes:
- for _, attr := range start.Attr {
- // Do not repeat xmlns, it is already in XMLName
- if attr.Name.Local != "xmlns" {
- n.Attrs = append(n.Attrs, attr)
- }
- }
- type node Node
- return d.DecodeElement((*node)(n), &start)
-}
-
-// MarshalXML is a custom XML serializer used by xml.Marshal to serialize a
-// Node structure to XML.
-func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
- start.Attr = n.Attrs
- start.Name = n.XMLName
-
- err = e.EncodeToken(start)
- e.EncodeElement(n.Nodes, xml.StartElement{Name: n.XMLName})
- return e.EncodeToken(xml.EndElement{Name: start.Name})
-}
-
-// ============================================================================
-// Disco
-
-const (
- NSDiscoInfo = "http://jabber.org/protocol/disco#info"
- NSDiscoItems = "http://jabber.org/protocol/disco#items"
-)
-
-// Disco Info
-type DiscoInfo struct {
- XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
- Node string `xml:"node,attr,omitempty"`
- Identity Identity `xml:"identity"`
- Features []Feature `xml:"feature"`
-}
-
-func (d *DiscoInfo) Namespace() string {
- return d.XMLName.Space
-}
-
-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"`
-}
-
-// Disco Items
-type DiscoItems struct {
- XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
- Node string `xml:"node,attr,omitempty"`
- Items []DiscoItem `xml:"item"`
-}
-
-func (d *DiscoItems) Namespace() string {
- return d.XMLName.Space
-}
-
-type DiscoItem struct {
- XMLName xml.Name `xml:"item"`
- Name string `xml:"name,attr,omitempty"`
- JID string `xml:"jid,attr,omitempty"`
- Node string `xml:"node,attr,omitempty"`
-}
-
-// ============================================================================
-// Software Version (XEP-0092)
-
-// Version
-type Version struct {
- XMLName xml.Name `xml:"jabber:iq:version query"`
- Name string `xml:"name,omitempty"`
- Version string `xml:"version,omitempty"`
- OS string `xml:"os,omitempty"`
-}
-
-func (v *Version) Namespace() string {
- return v.XMLName.Space
-}
-
-// ============================================================================
-// Registry init
-
-func init() {
- TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoInfo, "query"}, DiscoInfo{})
- TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoItems, "query"}, DiscoItems{})
- TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:ietf:params:xml:ns:xmpp-bind", "bind"}, BindBind{})
- TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:iot:control", "set"}, ControlSet{})
- TypeRegistry.MapExtension(PKTIQ, xml.Name{"jabber:iq:version", "query"}, Version{})
-}
diff --git a/router.go b/router.go
index b252db6..e8bf4b5 100644
--- a/router.go
+++ b/router.go
@@ -3,6 +3,8 @@ package xmpp
import (
"encoding/xml"
"strings"
+
+ "gosrc.io/xmpp/stanza"
)
/*
@@ -32,7 +34,7 @@ func NewRouter() *Router {
// route is called by the XMPP client to dispatch stanza received using the set up routes.
// It is also used by test, but is not supposed to be used directly by users of the library.
-func (r *Router) route(s Sender, p Packet) {
+func (r *Router) route(s Sender, p stanza.Packet) {
var match RouteMatch
if r.Match(p, &match) {
@@ -41,15 +43,15 @@ func (r *Router) route(s Sender, p Packet) {
return
}
// If there is no match and we receive an iq set or get, we need to send a reply
- if iq, ok := p.(IQ); ok {
- if iq.Type == IQTypeGet || iq.Type == IQTypeSet {
+ if iq, ok := p.(stanza.IQ); ok {
+ if iq.Type == stanza.IQTypeGet || iq.Type == stanza.IQTypeSet {
iqNotImplemented(s, iq)
}
}
}
-func iqNotImplemented(s Sender, iq IQ) {
- err := Err{
+func iqNotImplemented(s Sender, iq stanza.IQ) {
+ err := stanza.Err{
XMLName: xml.Name{Local: "error"},
Code: 501,
Type: "cancel",
@@ -66,7 +68,7 @@ func (r *Router) NewRoute() *Route {
return route
}
-func (r *Router) Match(p Packet, match *RouteMatch) bool {
+func (r *Router) Match(p stanza.Packet, match *RouteMatch) bool {
for _, route := range r.routes {
if route.Match(p, match) {
return true
@@ -83,14 +85,14 @@ func (r *Router) Handle(name string, handler Handler) *Route {
// HandleFunc registers a new route with a matcher for for a given packet name (iq, message, presence)
// See Route.Path() and Route.HandlerFunc().
-func (r *Router) HandleFunc(name string, f func(s Sender, p Packet)) *Route {
+func (r *Router) HandleFunc(name string, f func(s Sender, p stanza.Packet)) *Route {
return r.NewRoute().Packet(name).HandlerFunc(f)
}
// ============================================================================
// Route
type Handler interface {
- HandlePacket(s Sender, p Packet)
+ HandlePacket(s Sender, p stanza.Packet)
}
type Route struct {
@@ -108,10 +110,10 @@ func (r *Route) Handler(handler Handler) *Route {
// ordinary functions as XMPP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
-type HandlerFunc func(s Sender, p Packet)
+type HandlerFunc func(s Sender, p stanza.Packet)
// HandlePacket calls f(s, p)
-func (f HandlerFunc) HandlePacket(s Sender, p Packet) {
+func (f HandlerFunc) HandlePacket(s Sender, p stanza.Packet) {
f(s, p)
}
@@ -126,7 +128,7 @@ func (r *Route) addMatcher(m matcher) *Route {
return r
}
-func (r *Route) Match(p Packet, match *RouteMatch) bool {
+func (r *Route) Match(p stanza.Packet, match *RouteMatch) bool {
for _, m := range r.matchers {
if matched := m.Match(p, match); !matched {
return false
@@ -144,18 +146,18 @@ func (r *Route) Match(p Packet, match *RouteMatch) bool {
type nameMatcher string
-func (n nameMatcher) Match(p Packet, match *RouteMatch) bool {
+func (n nameMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
var name string
// TODO: To avoid type switch everywhere in matching, I think we will need to have
// to move to a concrete type for packets, to make matching and comparison more natural.
// Current code structure is probably too rigid.
// Maybe packet types should even be from an enum.
switch p.(type) {
- case Message:
+ case stanza.Message:
name = "message"
- case IQ:
+ case stanza.IQ:
name = "iq"
- case Presence:
+ case stanza.Presence:
name = "presence"
}
if name == string(n) {
@@ -177,14 +179,14 @@ func (r *Route) Packet(name string) *Route {
// nsTypeMather matches on a list of IQ payload namespaces
type nsTypeMatcher []string
-func (m nsTypeMatcher) Match(p Packet, match *RouteMatch) bool {
- var stanzaType StanzaType
+func (m nsTypeMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
+ var stanzaType stanza.StanzaType
switch packet := p.(type) {
- case IQ:
+ case stanza.IQ:
stanzaType = packet.Type
- case Presence:
+ case stanza.Presence:
stanzaType = packet.Type
- case Message:
+ case stanza.Message:
if packet.Type == "" {
// optional on message, normal is the default type
stanzaType = "normal"
@@ -211,8 +213,8 @@ func (r *Route) StanzaType(types ...string) *Route {
// nsIqMather matches on a list of IQ payload namespaces
type nsIQMatcher []string
-func (m nsIQMatcher) Match(p Packet, match *RouteMatch) bool {
- iq, ok := p.(IQ)
+func (m nsIQMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
+ iq, ok := p.(stanza.IQ)
if !ok {
return false
}
@@ -235,7 +237,7 @@ func (r *Route) IQNamespaces(namespaces ...string) *Route {
// Matchers are used to "specialize" routes and focus on specific packet features
type matcher interface {
- Match(Packet, *RouteMatch) bool
+ Match(stanza.Packet, *RouteMatch) bool
}
// RouteMatch extracts and gather match information
diff --git a/router_test.go b/router_test.go
index c1049bf..98a4697 100644
--- a/router_test.go
+++ b/router_test.go
@@ -4,6 +4,8 @@ import (
"bytes"
"encoding/xml"
"testing"
+
+ "gosrc.io/xmpp/stanza"
)
// ============================================================================
@@ -11,13 +13,13 @@ import (
func TestNameMatcher(t *testing.T) {
router := NewRouter()
- router.HandleFunc("message", func(s Sender, p Packet) {
+ router.HandleFunc("message", func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that a message packet is properly matched
conn := NewSenderMock()
- msg := NewMessage(Attrs{Type: MessageTypeChat, To: "test@localhost", Id: "1"})
+ msg := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, To: "test@localhost", Id: "1"})
msg.Body = "Hello"
router.route(conn, msg)
if conn.String() != successFlag {
@@ -26,8 +28,8 @@ func TestNameMatcher(t *testing.T) {
// Check that an IQ packet is not matched
conn = NewSenderMock()
- iq := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
- iq.Payload = &DiscoInfo{}
+ iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
+ iq.Payload = &stanza.DiscoInfo{}
router.route(conn, iq)
if conn.String() == successFlag {
t.Error("IQ should not have been matched and routed")
@@ -37,18 +39,18 @@ func TestNameMatcher(t *testing.T) {
func TestIQNSMatcher(t *testing.T) {
router := NewRouter()
router.NewRoute().
- IQNamespaces(NSDiscoInfo, NSDiscoItems).
- HandlerFunc(func(s Sender, p Packet) {
+ IQNamespaces(stanza.NSDiscoInfo, stanza.NSDiscoItems).
+ HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that an IQ with proper namespace does match
conn := NewSenderMock()
- iqDisco := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
+ iqDisco := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
// TODO: Add a function to generate payload with proper namespace initialisation
- iqDisco.Payload = &DiscoInfo{
+ iqDisco.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
- Space: NSDiscoInfo,
+ Space: stanza.NSDiscoInfo,
Local: "query",
}}
router.route(conn, iqDisco)
@@ -58,9 +60,9 @@ func TestIQNSMatcher(t *testing.T) {
// Check that another namespace is not matched
conn = NewSenderMock()
- iqVersion := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
+ iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
// TODO: Add a function to generate payload with proper namespace initialisation
- iqVersion.Payload = &DiscoInfo{
+ iqVersion.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
@@ -75,13 +77,13 @@ func TestTypeMatcher(t *testing.T) {
router := NewRouter()
router.NewRoute().
StanzaType("normal").
- HandlerFunc(func(s Sender, p Packet) {
+ HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that a packet with the proper type matches
conn := NewSenderMock()
- message := NewMessage(Attrs{Type: "normal", To: "test@localhost", Id: "1"})
+ message := stanza.NewMessage(stanza.Attrs{Type: "normal", To: "test@localhost", Id: "1"})
message.Body = "hello"
router.route(conn, message)
@@ -91,7 +93,7 @@ func TestTypeMatcher(t *testing.T) {
// We should match on default type 'normal' for message without a type
conn = NewSenderMock()
- message = NewMessage(Attrs{To: "test@localhost", Id: "1"})
+ message = stanza.NewMessage(stanza.Attrs{To: "test@localhost", Id: "1"})
message.Body = "hello"
router.route(conn, message)
@@ -101,8 +103,8 @@ func TestTypeMatcher(t *testing.T) {
// We do not match on other types
conn = NewSenderMock()
- iqVersion := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
- iqVersion.Payload = &DiscoInfo{
+ iqVersion := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+ iqVersion.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
@@ -119,38 +121,38 @@ func TestCompositeMatcher(t *testing.T) {
router.NewRoute().
IQNamespaces("jabber:iq:version").
StanzaType("get").
- HandlerFunc(func(s Sender, p Packet) {
+ HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Data set
- getVersionIq := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
- getVersionIq.Payload = &Version{
+ getVersionIq := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+ getVersionIq.Payload = &stanza.Version{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
- setVersionIq := NewIQ(Attrs{Type: "set", From: "service.localhost", To: "test@localhost", Id: "1"})
- setVersionIq.Payload = &Version{
+ setVersionIq := stanza.NewIQ(stanza.Attrs{Type: "set", From: "service.localhost", To: "test@localhost", Id: "1"})
+ setVersionIq.Payload = &stanza.Version{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
- GetDiscoIq := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
- GetDiscoIq.Payload = &DiscoInfo{
+ GetDiscoIq := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+ GetDiscoIq.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "http://jabber.org/protocol/disco#info",
Local: "query",
}}
- message := NewMessage(Attrs{Type: "normal", To: "test@localhost", Id: "1"})
+ message := stanza.NewMessage(stanza.Attrs{Type: "normal", To: "test@localhost", Id: "1"})
message.Body = "hello"
tests := []struct {
name string
- input Packet
+ input stanza.Packet
want bool
}{
{name: "match get version iq", input: getVersionIq, want: true},
@@ -178,13 +180,13 @@ func TestCompositeMatcher(t *testing.T) {
func TestCatchallMatcher(t *testing.T) {
router := NewRouter()
router.NewRoute().
- HandlerFunc(func(s Sender, p Packet) {
+ HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that we match on several packets
conn := NewSenderMock()
- message := NewMessage(Attrs{Type: "chat", To: "test@localhost", Id: "1"})
+ message := stanza.NewMessage(stanza.Attrs{Type: "chat", To: "test@localhost", Id: "1"})
message.Body = "hello"
router.route(conn, message)
@@ -193,8 +195,8 @@ func TestCatchallMatcher(t *testing.T) {
}
conn = NewSenderMock()
- iqVersion := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
- iqVersion.Payload = &DiscoInfo{
+ iqVersion := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+ iqVersion.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
@@ -219,7 +221,7 @@ func NewSenderMock() SenderMock {
return SenderMock{buffer: new(bytes.Buffer)}
}
-func (s SenderMock) Send(packet Packet) error {
+func (s SenderMock) Send(packet stanza.Packet) error {
out, err := xml.Marshal(packet)
if err != nil {
return err
@@ -239,7 +241,7 @@ func (s SenderMock) String() string {
func TestSenderMock(t *testing.T) {
conn := NewSenderMock()
- msg := NewMessage(Attrs{To: "test@localhost", Id: "1"})
+ msg := stanza.NewMessage(stanza.Attrs{To: "test@localhost", Id: "1"})
msg.Body = "Hello"
if err := conn.Send(msg); err != nil {
t.Error("Could not send message")
diff --git a/session.go b/session.go
index 88486c6..2077e7a 100644
--- a/session.go
+++ b/session.go
@@ -7,6 +7,8 @@ import (
"fmt"
"io"
"net"
+
+ "gosrc.io/xmpp/stanza"
)
const xmppStreamOpen = ""
@@ -15,7 +17,7 @@ type Session struct {
// Session info
BindJid string // Jabber ID as provided by XMPP server
StreamId string
- Features StreamFeatures
+ Features stanza.StreamFeatures
TlsEnabled bool
lastPacketId int
@@ -85,14 +87,14 @@ func (s *Session) setProxy(conn net.Conn, newConn net.Conn, o Config) {
s.decoder.CharsetReader = o.CharsetReader
}
-func (s *Session) open(domain string) (f StreamFeatures) {
+func (s *Session) open(domain string) (f stanza.StreamFeatures) {
// Send stream open tag
- if _, s.err = fmt.Fprintf(s.socketProxy, xmppStreamOpen, domain, NSClient, NSStream); s.err != nil {
+ if _, s.err = fmt.Fprintf(s.socketProxy, xmppStreamOpen, domain, stanza.NSClient, stanza.NSStream); s.err != nil {
return
}
// Set xml decoder and extract streamID from reply
- s.StreamId, s.err = initStream(s.decoder) // TODO refactor / rename
+ s.StreamId, s.err = stanza.InitStream(s.decoder) // TODO refactor / rename
if s.err != nil {
return
}
@@ -112,7 +114,7 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn {
if _, ok := s.Features.DoesStartTLS(); ok {
fmt.Fprintf(s.socketProxy, "")
- var k tlsProceed
+ var k stanza.TLSProceed
if s.err = s.decoder.DecodeElement(&k, nil); s.err != nil {
s.err = errors.New("expecting starttls proceed: " + s.err.Error())
return conn
@@ -120,8 +122,8 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn {
s.TlsEnabled = true
// TODO: add option to accept all TLS certificates: insecureSkipTlsVerify (DefaultTlsConfig.InsecureSkipVerify)
- DefaultTlsConfig.ServerName = domain
- tlsConn := tls.Client(conn, &DefaultTlsConfig)
+ stanza.DefaultTlsConfig.ServerName = domain
+ tlsConn := tls.Client(conn, &stanza.DefaultTlsConfig)
// We convert existing connection to TLS
if s.err = tlsConn.Handshake(); s.err != nil {
return tlsConn
@@ -153,12 +155,12 @@ func (s *Session) bind(o Config) {
var resource = o.parsedJid.Resource
if resource != "" {
fmt.Fprintf(s.socketProxy, "%s",
- s.PacketId(), nsBind, resource)
+ s.PacketId(), stanza.NSBind, resource)
} else {
- fmt.Fprintf(s.socketProxy, "", s.PacketId(), nsBind)
+ fmt.Fprintf(s.socketProxy, "", s.PacketId(), stanza.NSBind)
}
- var iq IQ
+ var iq stanza.IQ
if s.err = s.decoder.Decode(&iq); s.err != nil {
s.err = errors.New("error decoding iq bind result: " + s.err.Error())
return
@@ -166,7 +168,7 @@ func (s *Session) bind(o Config) {
// TODO Check all elements
switch payload := iq.Payload.(type) {
- case *BindBind:
+ case *stanza.BindBind:
s.BindJid = payload.Jid // our local id (with possibly randomly generated resource
default:
s.err = errors.New("iq bind result missing")
@@ -182,9 +184,9 @@ func (s *Session) rfc3921Session(o Config) {
return
}
- var iq IQ
- if s.Features.Session.optional.Local != "" {
- fmt.Fprintf(s.socketProxy, "", s.PacketId(), nsSession)
+ var iq stanza.IQ
+ if s.Features.Session.Optional.Local != "" {
+ fmt.Fprintf(s.socketProxy, "", s.PacketId(), stanza.NSSession)
if s.err = s.decoder.Decode(&iq); s.err != nil {
s.err = errors.New("expecting iq result after session open: " + s.err.Error())
return
diff --git a/stanza/README.md b/stanza/README.md
new file mode 100644
index 0000000..466f594
--- /dev/null
+++ b/stanza/README.md
@@ -0,0 +1,76 @@
+# XMPP Stanza
+
+XMPP `stanza` package is use to parse, marshall and and unmarshal XMPP stanzas and nonzas.
+
+## Stanza creation
+
+When creating stanzas, you can use two approaches:
+
+1. You can create IQ, Presence or Message structs, set the fields and manually prepare extensions struct to add to the
+stanza.
+2. You can use `stanza` build helper to be guided when creating the stanza, and have more controls performed on the
+final stanza.
+
+The methods are equivalent and you can use whatever suits you best. The helpers will finally generate the same type of
+struct that you can build by hand.
+
+### Composing stanzas manually with structs
+
+Here is for example how you would generate an IQ discovery result:
+
+ iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+ identity := stanza.Identity{
+ Name: opts.Name,
+ Category: opts.Category,
+ Type: opts.Type,
+ }
+ payload := stanza.DiscoInfo{
+ XMLName: xml.Name{
+ Space: stanza.NSDiscoInfo,
+ Local: "query",
+ },
+ Identity: []stanza.Identity{identity},
+ Features: []stanza.Feature{
+ {Var: stanza.NSDiscoInfo},
+ {Var: stanza.NSDiscoItems},
+ {Var: "jabber:iq:version"},
+ {Var: "urn:xmpp:delegation:1"},
+ },
+ }
+ iqResp.Payload = &payload
+
+### Using helpers
+
+Here is for example how you would generate an IQ discovery result using Builder:
+
+ iq := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
+ disco := iq.DiscoInfo()
+ disco.AddIdentity("Test Component", "gateway", "service")
+ disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
+
+## Payload and extensions
+
+### Message
+
+### Presence
+
+### IQ
+
+IQ (Information Queries) contain a payload associated with the request and possibly an error. The main difference with
+Message and Presence extension is that you can only have one payload per IQ. The XMPP specification does not support
+having multiple payloads.
+
+Here is the list of structs implementing IQPayloads:
+
+- BindBind
+- Pubsub
+
+Finally, when the payload of the parsed stanza is unknown, the parser will provide the unknown payload as a generic
+`Node` element. You can also use the Node struct to add custom information on stanza generation. However, in both cases,
+you may also consider [adding your own custom extensions on stanzas]().
+
+
+## Adding your own custom extensions on stanzas
+
+Extensions are registered on launch using the `Registry`. It can be used to register you own custom payload. You may
+want to do so to support extensions we did not yet implement, or to add your own custom extensions to your XMPP stanzas.
diff --git a/stanza/auth_sasl.go b/stanza/auth_sasl.go
new file mode 100644
index 0000000..5961d9f
--- /dev/null
+++ b/stanza/auth_sasl.go
@@ -0,0 +1,79 @@
+package stanza
+
+import "encoding/xml"
+
+// ============================================================================
+// SASLSuccess
+
+type SASLSuccess struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
+}
+
+func (SASLSuccess) Name() string {
+ return "sasl:success"
+}
+
+type saslSuccessDecoder struct{}
+
+var saslSuccess saslSuccessDecoder
+
+func (saslSuccessDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLSuccess, error) {
+ var packet SASLSuccess
+ err := p.DecodeElement(&packet, &se)
+ return packet, err
+}
+
+// ============================================================================
+// SASLFailure
+
+type SASLFailure struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
+ Any xml.Name // error reason is a subelement
+}
+
+func (SASLFailure) Name() string {
+ return "sasl:failure"
+}
+
+type saslFailureDecoder struct{}
+
+var saslFailure saslFailureDecoder
+
+func (saslFailureDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLFailure, error) {
+ var packet SASLFailure
+ err := p.DecodeElement(&packet, &se)
+ return packet, err
+}
+
+// ============================================================================
+
+type Auth struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
+ Mechanism string `xml:"mecanism,attr"`
+ Value string `xml:",innerxml"`
+}
+
+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 (b *BindBind) Namespace() string {
+ return b.XMLName.Space
+}
+
+// Session is obsolete in RFC 6121.
+// Added for compliance with RFC 3121.
+// Remove when ejabberd purely conforms to RFC 6121.
+type sessionSession struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
+ Optional xml.Name // If it does exist, it mean we are not required to open session
+}
+
+// ============================================================================
+// Registry init
+
+func init() {
+ TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:ietf:params:xml:ns:xmpp-bind", "bind"}, BindBind{})
+}
diff --git a/stanza/component.go b/stanza/component.go
new file mode 100644
index 0000000..196faea
--- /dev/null
+++ b/stanza/component.go
@@ -0,0 +1,89 @@
+package stanza
+
+import (
+ "encoding/xml"
+)
+
+// ============================================================================
+// Handshake Stanza
+
+// Handshake is a stanza used by XMPP components to authenticate on XMPP
+// component port.
+type Handshake struct {
+ XMLName xml.Name `xml:"jabber:component:accept handshake"`
+ // TODO Add handshake value with test for proper serialization
+ // Value string `xml:",innerxml"`
+}
+
+func (Handshake) Name() string {
+ return "component:handshake"
+}
+
+// Handshake decoding wrapper
+
+type handshakeDecoder struct{}
+
+var handshake handshakeDecoder
+
+func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
+ var packet Handshake
+ err := p.DecodeElement(&packet, &se)
+ return packet, err
+}
+
+// ============================================================================
+// Component delegation
+// XEP-0355
+
+// Delegation can be used both on message (for delegated) and IQ (for Forwarded),
+// depending on the context.
+type Delegation struct {
+ MsgExtension
+ XMLName xml.Name `xml:"urn:xmpp:delegation:1 delegation"`
+ Forwarded *Forwarded // This is used in iq to wrap delegated iqs
+ Delegated *Delegated // This is used in a message to confirm delegated namespace
+}
+
+func (d *Delegation) Namespace() string {
+ return d.XMLName.Space
+}
+
+type Forwarded struct {
+ XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
+ Stanza Packet
+}
+
+// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
+// transform generic XML content into hierarchical Node structure.
+func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ // Check subelements to extract required field as boolean
+ for {
+ t, err := d.Token()
+ if err != nil {
+ return err
+ }
+
+ switch tt := t.(type) {
+
+ case xml.StartElement:
+ if packet, err := decodeClient(d, tt); err == nil {
+ f.Stanza = packet
+ }
+
+ case xml.EndElement:
+ if tt == start.End() {
+ return nil
+ }
+ }
+ }
+}
+
+type Delegated struct {
+ XMLName xml.Name `xml:"delegated"`
+ Namespace string `xml:"namespace,attr,omitempty"`
+}
+
+func init() {
+ TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
+ TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
+}
diff --git a/stanza/component_test.go b/stanza/component_test.go
new file mode 100644
index 0000000..d02531e
--- /dev/null
+++ b/stanza/component_test.go
@@ -0,0 +1,79 @@
+package stanza
+
+import (
+ "encoding/xml"
+ "testing"
+)
+
+// We should be able to properly parse delegation confirmation messages
+func TestParsingDelegationMessage(t *testing.T) {
+ packetStr := `
+
+
+
+`
+ var msg Message
+ data := []byte(packetStr)
+ if err := xml.Unmarshal(data, &msg); err != nil {
+ t.Errorf("Unmarshal(%s) returned error", data)
+ }
+
+ // Check that we have extracted the delegation info as MsgExtension
+ var nsDelegated string
+ for _, ext := range msg.Extensions {
+ if delegation, ok := ext.(*Delegation); ok {
+ nsDelegated = delegation.Delegated.Namespace
+ }
+ }
+ if nsDelegated != "http://jabber.org/protocol/pubsub" {
+ t.Errorf("Could not find delegated namespace in delegation: %#v\n", msg)
+ }
+}
+
+// Check that we can parse a delegation IQ.
+// The most important thing is to be able to
+func TestParsingDelegationIQ(t *testing.T) {
+ packetStr := `
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+`
+ var iq IQ
+ data := []byte(packetStr)
+ if err := xml.Unmarshal(data, &iq); err != nil {
+ t.Errorf("Unmarshal(%s) returned error", data)
+ }
+
+ // Check that we have extracted the delegation info as IQPayload
+ var node string
+ if iq.Payload != nil {
+ if delegation, ok := iq.Payload.(*Delegation); ok {
+ packet := delegation.Forwarded.Stanza
+ forwardedIQ, ok := packet.(IQ)
+ if !ok {
+ t.Errorf("Could not extract packet IQ")
+ return
+ }
+ if forwardedIQ.Payload != nil {
+ if pubsub, ok := forwardedIQ.Payload.(*PubSub); ok {
+ node = pubsub.Publish.Node
+ }
+ }
+ }
+ }
+ if node != "http://jabber.org/protocol/mood" {
+ t.Errorf("Could not find mood node name on delegated publish: %#v\n", iq)
+ }
+}
diff --git a/error.go b/stanza/error.go
similarity index 97%
rename from error.go
rename to stanza/error.go
index a4b4f17..0ac857f 100644
--- a/error.go
+++ b/stanza/error.go
@@ -1,14 +1,10 @@
-package xmpp
+package stanza
import (
"encoding/xml"
"strconv"
)
-/*
-TODO support ability to put Raw payload inside IQ
-*/
-
// ============================================================================
// XMPP Errors
diff --git a/error_enum.go b/stanza/error_enum.go
similarity index 95%
rename from error_enum.go
rename to stanza/error_enum.go
index 314df4c..710de0e 100644
--- a/error_enum.go
+++ b/stanza/error_enum.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
// ErrorType is a Enum of error attribute type
type ErrorType string
diff --git a/iot_control.go b/stanza/iot.go
similarity index 73%
rename from iot_control.go
rename to stanza/iot.go
index 67a54a1..5e15056 100644
--- a/iot_control.go
+++ b/stanza/iot.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
@@ -24,10 +24,16 @@ type ControlField struct {
}
type ControlSetResponse struct {
- IQPayload
XMLName xml.Name `xml:"urn:xmpp:iot:control setResponse"`
}
func (c *ControlSetResponse) Namespace() string {
return c.XMLName.Space
}
+
+// ============================================================================
+// Registry init
+
+func init() {
+ TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:iot:control", "set"}, ControlSet{})
+}
diff --git a/iot_control_test.go b/stanza/iot_test.go
similarity index 97%
rename from iot_control_test.go
rename to stanza/iot_test.go
index aaf26c9..43a60ec 100644
--- a/iot_control_test.go
+++ b/stanza/iot_test.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/stanza/iq.go b/stanza/iq.go
new file mode 100644
index 0000000..34c74bb
--- /dev/null
+++ b/stanza/iq.go
@@ -0,0 +1,130 @@
+package stanza
+
+import (
+ "encoding/xml"
+ "fmt"
+)
+
+/*
+TODO support ability to put Raw payload inside IQ
+*/
+
+// ============================================================================
+// IQ Packet
+
+// IQ implements RFC 6120 - A.5 Client Namespace (a part)
+type IQ struct { // Info/Query
+ XMLName xml.Name `xml:"iq"`
+ // MUST have a ID
+ Attrs
+ // We can only have one payload on IQ:
+ // "An IQ stanza of type "get" or "set" MUST contain exactly one
+ // child element, which specifies the semantics of the particular
+ // request."
+ Payload IQPayload `xml:",omitempty"`
+ Error Err `xml:"error,omitempty"`
+ // Any is used to decode unknown payload as a generique structure
+ Any *Node `xml:",any"`
+}
+
+type IQPayload interface {
+ Namespace() string
+}
+
+func NewIQ(a Attrs) IQ {
+ // TODO generate IQ ID if not set
+ // TODO ensure that type is set, as it is required
+ return IQ{
+ XMLName: xml.Name{Local: "iq"},
+ Attrs: a,
+ }
+}
+
+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 {
+ return "iq"
+}
+
+type iqDecoder struct{}
+
+var iq iqDecoder
+
+func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
+ var packet IQ
+ err := p.DecodeElement(&packet, &se)
+ return packet, err
+}
+
+// UnmarshalXML implements custom parsing for IQs
+func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ iq.XMLName = start.Name
+
+ // Extract IQ attributes
+ for _, attr := range start.Attr {
+ if attr.Name.Local == "id" {
+ iq.Id = attr.Value
+ }
+ if attr.Name.Local == "type" {
+ iq.Type = StanzaType(attr.Value)
+ }
+ if attr.Name.Local == "to" {
+ iq.To = attr.Value
+ }
+ if attr.Name.Local == "from" {
+ iq.From = attr.Value
+ }
+ }
+
+ // decode inner elements
+ for {
+ t, err := d.Token()
+ if err != nil {
+ return err
+ }
+
+ switch tt := t.(type) {
+ case xml.StartElement:
+ if tt.Name.Local == "error" {
+ var xmppError Err
+ err = d.DecodeElement(&xmppError, &tt)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ iq.Error = xmppError
+ continue
+ }
+ if iqExt := TypeRegistry.GetIQExtension(tt.Name); iqExt != nil {
+ // Decode payload extension
+ err = d.DecodeElement(iqExt, &tt)
+ if err != nil {
+ return err
+ }
+ iq.Payload = iqExt
+ continue
+ }
+ // TODO: If unknown decode as generic node
+ node := new(Node)
+ err = d.DecodeElement(node, &tt)
+ if err != nil {
+ return err
+ }
+ iq.Any = node
+ case xml.EndElement:
+ if tt == start.End() {
+ return nil
+ }
+ }
+ }
+}
diff --git a/stanza/iq_disco.go b/stanza/iq_disco.go
new file mode 100644
index 0000000..0097c05
--- /dev/null
+++ b/stanza/iq_disco.go
@@ -0,0 +1,120 @@
+package stanza
+
+import (
+ "encoding/xml"
+)
+
+// ============================================================================
+// Disco Info
+
+const (
+ NSDiscoInfo = "http://jabber.org/protocol/disco#info"
+)
+
+// ----------
+// Namespaces
+
+type DiscoInfo struct {
+ XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
+ Node string `xml:"node,attr,omitempty"`
+ Identity []Identity `xml:"identity"`
+ Features []Feature `xml:"feature"`
+}
+
+func (d *DiscoInfo) Namespace() string {
+ return d.XMLName.Space
+}
+
+// ---------------
+// Builder helpers
+
+// DiscoInfo builds a default DiscoInfo payload
+func (iq *IQ) DiscoInfo() *DiscoInfo {
+ d := DiscoInfo{
+ XMLName: xml.Name{
+ Space: NSDiscoInfo,
+ Local: "query",
+ },
+ }
+ iq.Payload = &d
+ return &d
+}
+
+func (d *DiscoInfo) AddIdentity(name, category, typ string) {
+ identity := Identity{
+ XMLName: xml.Name{Local: "identity"},
+ Name: name,
+ Category: category,
+ Type: typ,
+ }
+ d.Identity = append(d.Identity, identity)
+}
+
+func (d *DiscoInfo) AddFeatures(namespace ...string) {
+ for _, ns := range namespace {
+ d.Features = append(d.Features, Feature{Var: ns})
+ }
+}
+
+func (d *DiscoInfo) SetNode(node string) {
+ d.Node = node
+}
+
+func (d *DiscoInfo) SetIdentities(ident ...Identity) *DiscoInfo {
+ d.Identity = ident
+ return d
+}
+
+func (d *DiscoInfo) SetFeatures(namespace ...string) *DiscoInfo {
+ for _, ns := range namespace {
+ d.Features = append(d.Features, Feature{Var: ns})
+ }
+ return d
+}
+
+// -----------
+// SubElements
+
+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"`
+}
+
+// ============================================================================
+// Disco Info
+
+const (
+ NSDiscoItems = "http://jabber.org/protocol/disco#items"
+)
+
+type DiscoItems struct {
+ XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
+ Node string `xml:"node,attr,omitempty"`
+ Items []DiscoItem `xml:"item"`
+}
+
+func (d *DiscoItems) Namespace() string {
+ return d.XMLName.Space
+}
+
+type DiscoItem struct {
+ XMLName xml.Name `xml:"item"`
+ Name string `xml:"name,attr,omitempty"`
+ JID string `xml:"jid,attr,omitempty"`
+ Node string `xml:"node,attr,omitempty"`
+}
+
+// ============================================================================
+// Registry init
+
+func init() {
+ TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoInfo, "query"}, DiscoInfo{})
+ TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoItems, "query"}, DiscoItems{})
+}
diff --git a/stanza/iq_disco_test.go b/stanza/iq_disco_test.go
new file mode 100644
index 0000000..8b76767
--- /dev/null
+++ b/stanza/iq_disco_test.go
@@ -0,0 +1,55 @@
+package stanza_test
+
+import (
+ "encoding/xml"
+ "testing"
+
+ "gosrc.io/xmpp/stanza"
+)
+
+func TestDiscoInfoBuilder(t *testing.T) {
+ iq := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
+ disco := iq.DiscoInfo()
+ disco.AddIdentity("Test Component", "gateway", "service")
+ disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
+
+ // Marshall
+ data, err := xml.Marshal(iq)
+ if err != nil {
+ t.Errorf("cannot marshal xml structure: %s", err)
+ return
+ }
+
+ // Unmarshall
+ var parsedIQ stanza.IQ
+ if err = xml.Unmarshal(data, &parsedIQ); err != nil {
+ t.Errorf("Unmarshal(%s) returned error: %s", data, err)
+ }
+
+ // Check result
+ pp, ok := parsedIQ.Payload.(*stanza.DiscoInfo)
+ if !ok {
+ t.Errorf("Parsed stanza does not contain an IQ payload")
+ }
+
+ // Check features
+ features := []string{stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1"}
+ if len(pp.Features) != len(features) {
+ t.Errorf("Features length mismatch: %#v", pp.Features)
+ } else {
+ for i, f := range pp.Features {
+ if f.Var != features[i] {
+ t.Errorf("Missing feature: %s", features[i])
+ }
+ }
+ }
+
+ // Check identity
+ if len(pp.Identity) != 1 {
+ t.Errorf("Identity length mismatch: %#v", pp.Identity)
+ } else {
+ if pp.Identity[0].Name != "Test Component" {
+ t.Errorf("Incorrect identity name: %#v", pp.Identity[0].Name)
+ }
+ }
+}
diff --git a/iq_test.go b/stanza/iq_test.go
similarity index 77%
rename from iq_test.go
rename to stanza/iq_test.go
index df9b360..04a868a 100644
--- a/iq_test.go
+++ b/stanza/iq_test.go
@@ -1,4 +1,4 @@
-package xmpp_test
+package stanza_test
import (
"encoding/xml"
@@ -6,22 +6,22 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
- "gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func TestUnmarshalIqs(t *testing.T) {
//var cs1 = new(iot.ControlSet)
var tests = []struct {
iqString string
- parsedIQ xmpp.IQ
+ parsedIQ stanza.IQ
}{
{"",
- xmpp.IQ{XMLName: xml.Name{Local: "iq"}, Attrs: xmpp.Attrs{Type: xmpp.IQTypeSet, To: "test@localhost", Id: "1"}}},
+ stanza.IQ{XMLName: xml.Name{Local: "iq"}, Attrs: stanza.Attrs{Type: stanza.IQTypeSet, To: "test@localhost", Id: "1"}}},
//{"", IQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}},
}
for _, test := range tests {
- parsedIQ := xmpp.IQ{}
+ parsedIQ := stanza.IQ{}
err := xml.Unmarshal([]byte(test.iqString), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal(%s) returned error", test.iqString)
@@ -35,16 +35,16 @@ func TestUnmarshalIqs(t *testing.T) {
}
func TestGenerateIq(t *testing.T) {
- iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
- payload := xmpp.DiscoInfo{
- Identity: xmpp.Identity{
- Name: "Test Gateway",
- Category: "gateway",
- Type: "mqtt",
- },
- Features: []xmpp.Feature{
- {Var: xmpp.NSDiscoInfo},
- {Var: xmpp.NSDiscoItems},
+ iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
+ payload := stanza.DiscoInfo{
+ Identity: []stanza.Identity{
+ {Name: "Test Gateway",
+ Category: "gateway",
+ Type: "mqtt",
+ }},
+ Features: []stanza.Feature{
+ {Var: stanza.NSDiscoInfo},
+ {Var: stanza.NSDiscoItems},
},
}
iq.Payload = &payload
@@ -58,18 +58,18 @@ func TestGenerateIq(t *testing.T) {
t.Error("empty error should not be serialized")
}
- parsedIQ := xmpp.IQ{}
+ parsedIQ := stanza.IQ{}
if err = xml.Unmarshal(data, &parsedIQ); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
- if !xmlEqual(parsedIQ.Payload, iq.Payload) {
- t.Errorf("non matching items\n%s", cmp.Diff(parsedIQ.Payload, iq.Payload))
+ if !xmlEqual(iq.Payload, parsedIQ.Payload) {
+ t.Errorf("non matching items\n%s", xmlDiff(iq.Payload, parsedIQ.Payload))
}
}
func TestErrorTag(t *testing.T) {
- xError := xmpp.Err{
+ xError := stanza.Err{
XMLName: xml.Name{Local: "error"},
Code: 503,
Type: "cancel",
@@ -82,7 +82,7 @@ func TestErrorTag(t *testing.T) {
t.Errorf("cannot marshal xml structure: %s", err)
}
- parsedError := xmpp.Err{}
+ parsedError := stanza.Err{}
if err = xml.Unmarshal(data, &parsedError); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@@ -93,8 +93,8 @@ func TestErrorTag(t *testing.T) {
}
func TestDiscoItems(t *testing.T) {
- iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
- payload := xmpp.DiscoItems{
+ iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
+ payload := stanza.DiscoItems{
Node: "music",
}
iq.Payload = &payload
@@ -104,7 +104,7 @@ func TestDiscoItems(t *testing.T) {
t.Errorf("cannot marshal xml structure")
}
- parsedIQ := xmpp.IQ{}
+ parsedIQ := stanza.IQ{}
if err = xml.Unmarshal(data, &parsedIQ); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@@ -117,7 +117,7 @@ func TestDiscoItems(t *testing.T) {
func TestUnmarshalPayload(t *testing.T) {
query := ""
- parsedIQ := xmpp.IQ{}
+ parsedIQ := stanza.IQ{}
err := xml.Unmarshal([]byte(query), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal(%s) returned error", query)
@@ -142,7 +142,7 @@ func TestPayloadWithError(t *testing.T) {
`
- parsedIQ := xmpp.IQ{}
+ parsedIQ := stanza.IQ{}
err := xml.Unmarshal([]byte(iq), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal error: %s", iq)
@@ -158,7 +158,7 @@ func TestUnknownPayload(t *testing.T) {
iq := `
`
- parsedIQ := xmpp.IQ{}
+ parsedIQ := stanza.IQ{}
err := xml.Unmarshal([]byte(iq), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal error: %#v (%s)", err, iq)
diff --git a/stanza/iq_version.go b/stanza/iq_version.go
new file mode 100644
index 0000000..ebf4452
--- /dev/null
+++ b/stanza/iq_version.go
@@ -0,0 +1,25 @@
+package stanza
+
+import "encoding/xml"
+
+// ============================================================================
+// Software Version (XEP-0092)
+
+// Version
+type Version struct {
+ XMLName xml.Name `xml:"jabber:iq:version query"`
+ Name string `xml:"name,omitempty"`
+ Version string `xml:"version,omitempty"`
+ OS string `xml:"os,omitempty"`
+}
+
+func (v *Version) Namespace() string {
+ return v.XMLName.Space
+}
+
+// ============================================================================
+// Registry init
+
+func init() {
+ TypeRegistry.MapExtension(PKTIQ, xml.Name{"jabber:iq:version", "query"}, Version{})
+}
diff --git a/message.go b/stanza/message.go
similarity index 99%
rename from message.go
rename to stanza/message.go
index 6b57f65..35e44c1 100644
--- a/message.go
+++ b/stanza/message.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/message_test.go b/stanza/message_test.go
similarity index 80%
rename from message_test.go
rename to stanza/message_test.go
index 8f18d39..4681694 100644
--- a/message_test.go
+++ b/stanza/message_test.go
@@ -1,15 +1,15 @@
-package xmpp_test
+package stanza_test
import (
"encoding/xml"
"testing"
"github.com/google/go-cmp/cmp"
- "gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func TestGenerateMessage(t *testing.T) {
- message := xmpp.NewMessage(xmpp.Attrs{Type: xmpp.MessageTypeChat, From: "admin@localhost", To: "test@localhost", Id: "1"})
+ message := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, From: "admin@localhost", To: "test@localhost", Id: "1"})
message.Body = "Hi"
message.Subject = "Msg Subject"
@@ -18,7 +18,7 @@ func TestGenerateMessage(t *testing.T) {
t.Errorf("cannot marshal xml structure")
}
- parsedMessage := xmpp.Message{}
+ parsedMessage := stanza.Message{}
if err = xml.Unmarshal(data, &parsedMessage); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@@ -38,7 +38,7 @@ func TestDecodeError(t *testing.T) {
`
- parsedMessage := xmpp.Message{}
+ parsedMessage := stanza.Message{}
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
t.Errorf("message error stanza unmarshall error: %v", err)
return
@@ -50,15 +50,15 @@ func TestDecodeError(t *testing.T) {
func TestGetOOB(t *testing.T) {
image := "https://localhost/image.png"
- msg := xmpp.NewMessage(xmpp.Attrs{To: "test@localhost"})
- ext := xmpp.OOB{
+ msg := stanza.NewMessage(stanza.Attrs{To: "test@localhost"})
+ ext := stanza.OOB{
XMLName: xml.Name{Space: "jabber:x:oob", Local: "x"},
URL: image,
}
msg.Extensions = append(msg.Extensions, &ext)
// OOB can properly be found
- var oob xmpp.OOB
+ var oob stanza.OOB
// Try to find and
if ok := msg.Get(&oob); !ok {
t.Error("could not find oob extension")
@@ -69,7 +69,7 @@ func TestGetOOB(t *testing.T) {
}
// Markable is not found
- var m xmpp.Markable
+ var m stanza.Markable
if ok := msg.Get(&m); ok {
t.Error("we should not have found markable extension")
}
diff --git a/msg_chat_markers.go b/stanza/msg_chat_markers.go
similarity index 96%
rename from msg_chat_markers.go
rename to stanza/msg_chat_markers.go
index b33aa3c..f226379 100644
--- a/msg_chat_markers.go
+++ b/stanza/msg_chat_markers.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
-import "encoding/xml"
+import (
+ "encoding/xml"
+)
/*
Support for:
diff --git a/msg_chat_state.go b/stanza/msg_chat_state.go
similarity index 96%
rename from msg_chat_state.go
rename to stanza/msg_chat_state.go
index 7f2f89e..728a52e 100644
--- a/msg_chat_state.go
+++ b/stanza/msg_chat_state.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
-import "encoding/xml"
+import (
+ "encoding/xml"
+)
/*
Support for:
diff --git a/msg_html.go b/stanza/msg_html.go
similarity index 92%
rename from msg_html.go
rename to stanza/msg_html.go
index fbb4aae..1b4016c 100644
--- a/msg_html.go
+++ b/stanza/msg_html.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
-import "encoding/xml"
+import (
+ "encoding/xml"
+)
type HTML struct {
MsgExtension
diff --git a/msg_html_test.go b/stanza/msg_html_test.go
similarity index 81%
rename from msg_html_test.go
rename to stanza/msg_html_test.go
index b88104a..b37e9d6 100644
--- a/msg_html_test.go
+++ b/stanza/msg_html_test.go
@@ -1,20 +1,20 @@
-package xmpp_test
+package stanza_test
import (
"encoding/xml"
"testing"
- "gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func TestHTMLGen(t *testing.T) {
htmlBody := "Hello World
"
- msg := xmpp.NewMessage(xmpp.Attrs{To: "test@localhost"})
+ msg := stanza.NewMessage(stanza.Attrs{To: "test@localhost"})
msg.Body = "Hello World"
- body := xmpp.HTMLBody{
+ body := stanza.HTMLBody{
InnerXML: htmlBody,
}
- html := xmpp.HTML{Body: body}
+ html := stanza.HTML{Body: body}
msg.Extensions = append(msg.Extensions, html)
result := msg.XMPPFormat()
@@ -23,7 +23,7 @@ func TestHTMLGen(t *testing.T) {
t.Errorf("incorrect serialize message:\n%s", result)
}
- parsedMessage := xmpp.Message{}
+ parsedMessage := stanza.Message{}
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
t.Errorf("message HTML unmarshall error: %v", err)
return
@@ -33,7 +33,7 @@ func TestHTMLGen(t *testing.T) {
t.Errorf("incorrect parsed body: '%s'", parsedMessage.Body)
}
- var h xmpp.HTML
+ var h stanza.HTML
if ok := parsedMessage.Get(&h); !ok {
t.Error("could not extract HTML body")
}
diff --git a/msg_oob.go b/stanza/msg_oob.go
similarity index 88%
rename from msg_oob.go
rename to stanza/msg_oob.go
index 38b6aeb..039fac1 100644
--- a/msg_oob.go
+++ b/stanza/msg_oob.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
-import "encoding/xml"
+import (
+ "encoding/xml"
+)
/*
Support for:
diff --git a/msg_receipts.go b/stanza/msg_receipts.go
similarity index 94%
rename from msg_receipts.go
rename to stanza/msg_receipts.go
index 87a1f73..85c2783 100644
--- a/msg_receipts.go
+++ b/stanza/msg_receipts.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
-import "encoding/xml"
+import (
+ "encoding/xml"
+)
/*
Support for:
diff --git a/msg_receipts_test.go b/stanza/msg_receipts_test.go
similarity index 89%
rename from msg_receipts_test.go
rename to stanza/msg_receipts_test.go
index 0db0c64..bf379a1 100644
--- a/msg_receipts_test.go
+++ b/stanza/msg_receipts_test.go
@@ -1,10 +1,10 @@
-package xmpp_test
+package stanza_test
import (
"encoding/xml"
"testing"
- "gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func TestDecodeRequest(t *testing.T) {
@@ -15,7 +15,7 @@ func TestDecodeRequest(t *testing.T) {
My lord, dispatch; read o'er these articles.
`
- parsedMessage := xmpp.Message{}
+ parsedMessage := stanza.Message{}
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
t.Errorf("message receipt unmarshall error: %v", err)
return
@@ -31,7 +31,7 @@ func TestDecodeRequest(t *testing.T) {
}
switch ext := parsedMessage.Extensions[0].(type) {
- case *xmpp.ReceiptRequest:
+ case *stanza.ReceiptRequest:
if ext.XMLName.Local != "request" {
t.Errorf("unexpected extension: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
}
diff --git a/stanza/node.go b/stanza/node.go
new file mode 100644
index 0000000..26a3e03
--- /dev/null
+++ b/stanza/node.go
@@ -0,0 +1,51 @@
+package stanza
+
+import "encoding/xml"
+
+// ============================================================================
+// Generic / unknown content
+
+// Node is a generic structure to represent XML data. It is used to parse
+// unreferenced or custom stanza payload.
+type Node struct {
+ XMLName xml.Name
+ Attrs []xml.Attr `xml:"-"`
+ Content string `xml:",innerxml"`
+ Nodes []Node `xml:",any"`
+}
+
+func (n *Node) Namespace() string {
+ return n.XMLName.Space
+}
+
+// Attr represents generic XML attributes, as used on the generic XML Node
+// representation.
+type Attr struct {
+ K string
+ V string
+}
+
+// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
+// transform generic XML content into hierarchical Node structure.
+func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ // Assign "n.Attrs = start.Attr", without repeating xmlns in attributes:
+ for _, attr := range start.Attr {
+ // Do not repeat xmlns, it is already in XMLName
+ if attr.Name.Local != "xmlns" {
+ n.Attrs = append(n.Attrs, attr)
+ }
+ }
+ type node Node
+ return d.DecodeElement((*node)(n), &start)
+}
+
+// MarshalXML is a custom XML serializer used by xml.Marshal to serialize a
+// Node structure to XML.
+func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
+ start.Attr = n.Attrs
+ start.Name = n.XMLName
+
+ err = e.EncodeToken(start)
+ e.EncodeElement(n.Nodes, xml.StartElement{Name: n.XMLName})
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
diff --git a/ns.go b/stanza/ns.go
similarity index 52%
rename from ns.go
rename to stanza/ns.go
index d36e3db..e955a35 100644
--- a/ns.go
+++ b/stanza/ns.go
@@ -1,11 +1,11 @@
-package xmpp
+package stanza
const (
NSStream = "http://etherx.jabber.org/streams"
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
- nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
- nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
- nsSession = "urn:ietf:params:xml:ns:xmpp-session"
+ NSSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
+ NSBind = "urn:ietf:params:xml:ns:xmpp-bind"
+ NSSession = "urn:ietf:params:xml:ns:xmpp-session"
NSClient = "jabber:client"
NSComponent = "jabber:component:accept"
)
diff --git a/packet.go b/stanza/packet.go
similarity index 96%
rename from packet.go
rename to stanza/packet.go
index ce42236..567796d 100644
--- a/packet.go
+++ b/stanza/packet.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
type Packet interface {
Name() string
diff --git a/packet_enum.go b/stanza/packet_enum.go
similarity index 98%
rename from packet_enum.go
rename to stanza/packet_enum.go
index 9bc30e4..103966a 100644
--- a/packet_enum.go
+++ b/stanza/packet_enum.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
type StanzaType string
diff --git a/parser.go b/stanza/parser.go
similarity index 90%
rename from parser.go
rename to stanza/parser.go
index 465db5d..c83e17e 100644
--- a/parser.go
+++ b/stanza/parser.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
@@ -14,7 +14,7 @@ import (
// reattach features (allowing to resume an existing stream at the point the connection was interrupted, without
// getting through the authentication process.
// TODO We should handle stream error from XEP-0114 ( or )
-func initStream(p *xml.Decoder) (sessionID string, err error) {
+func InitStream(p *xml.Decoder) (sessionID string, err error) {
for {
var t xml.Token
t, err = p.Token()
@@ -41,31 +41,14 @@ func initStream(p *xml.Decoder) (sessionID string, err error) {
}
}
-// Scan XML token stream to find next StartElement.
-func nextStart(p *xml.Decoder) (xml.StartElement, error) {
- for {
- t, err := p.Token()
- if err == io.EOF {
- return xml.StartElement{}, errors.New("connection closed")
- }
- if err != nil {
- return xml.StartElement{}, fmt.Errorf("nextStart %s", err)
- }
- switch t := t.(type) {
- case xml.StartElement:
- return t, nil
- }
- }
-}
-
-// nextPacket scans XML token stream for next complete XMPP stanza.
+// NextPacket scans XML token stream for next complete XMPP stanza.
// Once the type of stanza has been identified, a structure is created to decode
// that stanza and returned.
// TODO Use an interface to return packets interface xmppDecoder
-// TODO make auth and bind use nextPacket instead of directly nextStart
-func nextPacket(p *xml.Decoder) (Packet, error) {
+// TODO make auth and bind use NextPacket instead of directly NextStart
+func NextPacket(p *xml.Decoder) (Packet, error) {
// Read start element to find out how we want to parse the XMPP packet
- se, err := nextStart(p)
+ se, err := NextStart(p)
if err != nil {
return nil, err
}
@@ -74,7 +57,7 @@ func nextPacket(p *xml.Decoder) (Packet, error) {
switch se.Name.Space {
case NSStream:
return decodeStream(p, se)
- case nsSASL:
+ case NSSASL:
return decodeSASL(p, se)
case NSClient:
return decodeClient(p, se)
@@ -86,6 +69,23 @@ func nextPacket(p *xml.Decoder) (Packet, error) {
}
}
+// Scan XML token stream to find next StartElement.
+func NextStart(p *xml.Decoder) (xml.StartElement, error) {
+ for {
+ t, err := p.Token()
+ if err == io.EOF {
+ return xml.StartElement{}, errors.New("connection closed")
+ }
+ if err != nil {
+ return xml.StartElement{}, fmt.Errorf("NextStart %s", err)
+ }
+ switch t := t.(type) {
+ case xml.StartElement:
+ return t, nil
+ }
+ }
+}
+
/*
TODO: From all the decoder, we can return a pointer to the actual concrete type, instead of directly that
type.
diff --git a/pep.go b/stanza/pep.go
similarity index 98%
rename from pep.go
rename to stanza/pep.go
index 7223dc4..7de57ea 100644
--- a/pep.go
+++ b/stanza/pep.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/pres_muc.go b/stanza/pres_muc.go
similarity index 99%
rename from pres_muc.go
rename to stanza/pres_muc.go
index f7ae599..bc0e75e 100644
--- a/pres_muc.go
+++ b/stanza/pres_muc.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/pres_muc_test.go b/stanza/pres_muc_test.go
similarity index 85%
rename from pres_muc_test.go
rename to stanza/pres_muc_test.go
index 5b8db83..209dc93 100644
--- a/pres_muc_test.go
+++ b/stanza/pres_muc_test.go
@@ -1,10 +1,10 @@
-package xmpp_test
+package stanza_test
import (
"encoding/xml"
"testing"
- "gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
// https://xmpp.org/extensions/xep-0045.html#example-27
@@ -18,12 +18,12 @@ func TestMucPassword(t *testing.T) {
`
- var parsedPresence xmpp.Presence
+ var parsedPresence stanza.Presence
if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
t.Errorf("Unmarshal(%s) returned error", str)
}
- var muc xmpp.MucPresence
+ var muc stanza.MucPresence
if ok := parsedPresence.Get(&muc); !ok {
t.Error("muc presence extension was not found")
}
@@ -44,13 +44,13 @@ func TestMucHistory(t *testing.T) {
`
- var parsedPresence xmpp.Presence
+ var parsedPresence stanza.Presence
if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
t.Errorf("Unmarshal(%s) returned error: %s", str, err)
return
}
- var muc xmpp.MucPresence
+ var muc stanza.MucPresence
if ok := parsedPresence.Get(&muc); !ok {
t.Error("muc presence extension was not found")
return
@@ -74,14 +74,14 @@ func TestMucNoHistory(t *testing.T) {
maxstanzas := 0
- pres := xmpp.Presence{Attrs: xmpp.Attrs{
+ pres := stanza.Presence{Attrs: stanza.Attrs{
From: "hag66@shakespeare.lit/pda",
Id: "n13mt3l",
To: "coven@chat.shakespeare.lit/thirdwitch",
},
- Extensions: []xmpp.PresExtension{
- xmpp.MucPresence{
- History: xmpp.History{MaxStanzas: xmpp.NewNullableInt(maxstanzas)},
+ Extensions: []stanza.PresExtension{
+ stanza.MucPresence{
+ History: stanza.History{MaxStanzas: stanza.NewNullableInt(maxstanzas)},
},
},
}
diff --git a/presence.go b/stanza/presence.go
similarity index 99%
rename from presence.go
rename to stanza/presence.go
index 466b9e1..d3a8d1a 100644
--- a/presence.go
+++ b/stanza/presence.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/presence_enum.go b/stanza/presence_enum.go
similarity index 95%
rename from presence_enum.go
rename to stanza/presence_enum.go
index c0723bd..c92691f 100644
--- a/presence_enum.go
+++ b/stanza/presence_enum.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
// PresenceShow is a Enum of presence element show
type PresenceShow string
diff --git a/presence_test.go b/stanza/presence_test.go
similarity index 72%
rename from presence_test.go
rename to stanza/presence_test.go
index fb8e09a..94431d8 100644
--- a/presence_test.go
+++ b/stanza/presence_test.go
@@ -1,24 +1,23 @@
-package xmpp_test
+package stanza_test
import (
"encoding/xml"
"testing"
- "gosrc.io/xmpp"
-
"github.com/google/go-cmp/cmp"
+ "gosrc.io/xmpp/stanza"
)
func TestGeneratePresence(t *testing.T) {
- presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
- presence.Show = xmpp.PresenceShowChat
+ presence := stanza.NewPresence(stanza.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
+ presence.Show = stanza.PresenceShowChat
data, err := xml.Marshal(presence)
if err != nil {
t.Errorf("cannot marshal xml structure")
}
- var parsedPresence xmpp.Presence
+ var parsedPresence stanza.Presence
if err = xml.Unmarshal(data, &parsedPresence); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@@ -32,13 +31,13 @@ func TestPresenceSubElt(t *testing.T) {
// Test structure to ensure that show, status and priority are correctly defined as presence
// package sub-elements
type pres struct {
- Show xmpp.PresenceShow `xml:"show"`
- Status string `xml:"status"`
- Priority int8 `xml:"priority"`
+ Show stanza.PresenceShow `xml:"show"`
+ Status string `xml:"status"`
+ Priority int8 `xml:"priority"`
}
- presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
- presence.Show = xmpp.PresenceShowXA
+ presence := stanza.NewPresence(stanza.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
+ presence.Show = stanza.PresenceShowXA
presence.Status = "Coding"
presence.Priority = 10
diff --git a/pubsub.go b/stanza/pubsub.go
similarity index 98%
rename from pubsub.go
rename to stanza/pubsub.go
index 42b28e0..010f8b0 100644
--- a/pubsub.go
+++ b/stanza/pubsub.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/registry.go b/stanza/registry.go
similarity index 99%
rename from registry.go
rename to stanza/registry.go
index 8edacb4..beb35df 100644
--- a/registry.go
+++ b/stanza/registry.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/registry_test.go b/stanza/registry_test.go
similarity index 98%
rename from registry_test.go
rename to stanza/registry_test.go
index 63feea3..02d321b 100644
--- a/registry_test.go
+++ b/stanza/registry_test.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/starttls.go b/stanza/starttls.go
similarity index 88%
rename from starttls.go
rename to stanza/starttls.go
index edb20ec..e0a187c 100644
--- a/starttls.go
+++ b/stanza/starttls.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"crypto/tls"
@@ -8,7 +8,7 @@ import (
var DefaultTlsConfig tls.Config
// Used during stream initiation / session establishment
-type tlsProceed struct {
+type TLSProceed struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
}
diff --git a/stream.go b/stanza/stream.go
similarity index 99%
rename from stream.go
rename to stanza/stream.go
index 0173254..221e7fb 100644
--- a/stream.go
+++ b/stanza/stream.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
import (
"encoding/xml"
diff --git a/xmpp_test.go b/stanza/xmpp_test.go
similarity index 66%
rename from xmpp_test.go
rename to stanza/xmpp_test.go
index 7f7e6de..611948a 100644
--- a/xmpp_test.go
+++ b/stanza/xmpp_test.go
@@ -1,4 +1,4 @@
-package xmpp_test
+package stanza_test
import (
"encoding/xml"
@@ -10,6 +10,15 @@ import (
// marshal / unmarshal. There is no need to manage them on the manually
// crafted structure.
func xmlEqual(x, y interface{}) bool {
+ return cmp.Equal(x, y, xmlOpts())
+}
+
+// xmlDiff compares xml structures ignoring namespace preferences
+func xmlDiff(x, y interface{}) string {
+ return cmp.Diff(x, y, xmlOpts())
+}
+
+func xmlOpts() cmp.Options {
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
opts := cmp.Options{
cmp.FilterValues(func(x, y interface{}) bool {
@@ -20,10 +29,12 @@ func xmlEqual(x, y interface{}) bool {
if xx == zero || yy == zero {
return true
}
+ if xx.Space == "" || yy.Space == "" {
+ return true
+ }
}
return false
}, alwaysEqual),
}
-
- return cmp.Equal(x, y, opts)
+ return opts
}
diff --git a/stream_manager.go b/stream_manager.go
index 9c7e020..c2dd78b 100644
--- a/stream_manager.go
+++ b/stream_manager.go
@@ -6,6 +6,7 @@ import (
"time"
"golang.org/x/xerrors"
+ "gosrc.io/xmpp/stanza"
)
// The Fluux XMPP lib can manage client or component XMPP streams.
@@ -29,7 +30,7 @@ type StreamClient interface {
// Sender is an interface provided by Stream clients to allow sending XMPP data.
type Sender interface {
- Send(packet Packet) error
+ Send(packet stanza.Packet) error
SendRaw(packet string) error
}
diff --git a/stream_test.go b/stream_test.go
index fac1446..663cad9 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -4,14 +4,14 @@ import (
"encoding/xml"
"testing"
- "gosrc.io/xmpp"
+ "gosrc.io/xmpp/stanza"
)
func TestNoStartTLS(t *testing.T) {
streamFeatures := `
`
- var parsedSF xmpp.StreamFeatures
+ var parsedSF stanza.StreamFeatures
if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
}
@@ -32,7 +32,7 @@ func TestStartTLS(t *testing.T) {
`
- var parsedSF xmpp.StreamFeatures
+ var parsedSF stanza.StreamFeatures
if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
}
@@ -52,7 +52,7 @@ func TestStreamManagement(t *testing.T) {
`
- var parsedSF xmpp.StreamFeatures
+ var parsedSF stanza.StreamFeatures
if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
}