Refactor and move parsing and stanza to a separate package

This commit is contained in:
Mickael Remond 2019-06-26 17:14:52 +02:00
parent 0acf824217
commit 428787d7ab
No known key found for this signature in database
GPG key ID: E6F6045D79965AA3
51 changed files with 614 additions and 580 deletions

View file

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

View file

@ -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},
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,
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",

View file

@ -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},
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

View file

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

View file

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

81
auth.go
View file

@ -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, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc)
fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", 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
}

View file

@ -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, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
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

View file

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

View file

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

View file

@ -9,6 +9,8 @@ import (
"io"
"net"
"time"
"gosrc.io/xmpp/stanza"
)
const componentStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s'>"
@ -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

View file

@ -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 := `<message to='service.localhost' from='localhost'>
<delegation xmlns='urn:xmpp:delegation:1'>
<delegated namespace='http://jabber.org/protocol/pubsub'/>
</delegation>
</message>`
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 := `<iq to='service.localhost' from='localhost' type='set' id='1'>
<delegation xmlns='urn:xmpp:delegation:1'>
<forwarded xmlns='urn:xmpp:forward:0'>
<iq xml:lang='en' to='test1@localhost' from='test1@localhost/mremond-mbp' type='set' id='aaf3a' xmlns='jabber:client'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='http://jabber.org/protocol/mood'>
<item id='current'>
<mood xmlns='http://jabber.org/protocol/mood'>
<excited/>
</mood>
</item>
</publish>
</pubsub>
</iq>
</forwarded>
</delegation>
</iq>`
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)
}
}

View file

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

View file

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