Add constants (enumlike) for stanza types and simplify packet creation (#62)
* Add constants (enumlike) for stanza types * NewIQ, NewMessage and NewPresence are now initialized with the Attrs struct * Update examples * Do not export backoff code. For now, we do not need to expose backoff in the documentation * Make presence priority an int8
This commit is contained in:
parent
145fce6b3f
commit
d9fdff0839
|
@ -83,7 +83,7 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := xmpp.NewIQ("result", iq.To, iq.From, iq.Id, "en")
|
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||||
|
|
||||||
switch info.Node {
|
switch info.Node {
|
||||||
case "":
|
case "":
|
||||||
|
@ -192,7 +192,7 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
|
||||||
|
|
||||||
if pubsub.Publish.XMLName.Local == "publish" {
|
if pubsub.Publish.XMLName.Local == "publish" {
|
||||||
// Prepare pubsub IQ reply
|
// Prepare pubsub IQ reply
|
||||||
iqResp := xmpp.NewIQ("result", forwardedIQ.To, forwardedIQ.From, forwardedIQ.Id, "en")
|
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
|
||||||
payload := xmpp.PubSub{
|
payload := xmpp.PubSub{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "http://jabber.org/protocol/pubsub",
|
Space: "http://jabber.org/protocol/pubsub",
|
||||||
|
@ -201,7 +201,7 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
|
||||||
}
|
}
|
||||||
iqResp.Payload = &payload
|
iqResp.Payload = &payload
|
||||||
// Wrap the reply in delegation 'forward'
|
// Wrap the reply in delegation 'forward'
|
||||||
iqForward := xmpp.NewIQ("result", iq.To, iq.From, iq.Id, "en")
|
iqForward := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||||
delegPayload := xmpp.Delegation{
|
delegPayload := xmpp.Delegation{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "urn:xmpp:delegation:1",
|
Space: "urn:xmpp:delegation:1",
|
||||||
|
|
|
@ -8,3 +8,5 @@ require (
|
||||||
github.com/processone/soundcloud v1.0.0
|
github.com/processone/soundcloud v1.0.0
|
||||||
gosrc.io/xmpp v0.1.1-0.20190619120342-a6cbc0c08f52
|
gosrc.io/xmpp v0.1.1-0.20190619120342-a6cbc0c08f52
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace gosrc.io/xmpp => gosrc.io/xmpp v0.1.1-0.20190619153249-b1dde2330764
|
||||||
|
|
|
@ -8,4 +8,7 @@ golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gosrc.io/xmpp v0.1.1-0.20190619120342-a6cbc0c08f52 h1:H5BezaFYvDL9r72ng90ICneftomo1iXx6+BxxZ9jBtg=
|
||||||
gosrc.io/xmpp v0.1.1-0.20190619120342-a6cbc0c08f52/go.mod h1:WvSgrZF7lMvjd1SH8nVGi7ZGr6gNU7oUuBdwpFTs9nY=
|
gosrc.io/xmpp v0.1.1-0.20190619120342-a6cbc0c08f52/go.mod h1:WvSgrZF7lMvjd1SH8nVGi7ZGr6gNU7oUuBdwpFTs9nY=
|
||||||
|
gosrc.io/xmpp v0.1.1-0.20190619153249-b1dde2330764 h1:jlYtpqdRoBC3Gke7MacXsVpSZL0g5nIBG/b9JVxpAVY=
|
||||||
|
gosrc.io/xmpp v0.1.1-0.20190619153249-b1dde2330764/go.mod h1:WvSgrZF7lMvjd1SH8nVGi7ZGr6gNU7oUuBdwpFTs9nY=
|
||||||
|
|
|
@ -59,7 +59,7 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := xmpp.NewIQ("result", iq.To, iq.From, iq.Id, "en")
|
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||||
identity := xmpp.Identity{
|
identity := xmpp.Identity{
|
||||||
Name: opts.Name,
|
Name: opts.Name,
|
||||||
Category: opts.Category,
|
Category: opts.Category,
|
||||||
|
@ -95,7 +95,7 @@ func discoItems(c xmpp.Sender, p xmpp.Packet) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := xmpp.NewIQ("result", iq.To, iq.From, iq.Id, "en")
|
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||||
|
|
||||||
var payload xmpp.DiscoItems
|
var payload xmpp.DiscoItems
|
||||||
if discoItems.Node == "" {
|
if discoItems.Node == "" {
|
||||||
|
@ -116,7 +116,7 @@ func handleVersion(c xmpp.Sender, p xmpp.Packet) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := xmpp.NewIQ("result", iq.To, iq.From, iq.Id, "en")
|
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||||
var payload xmpp.Version
|
var payload xmpp.Version
|
||||||
payload.Name = "Fluux XMPP Component"
|
payload.Name = "Fluux XMPP Component"
|
||||||
payload.Version = "0.0.1"
|
payload.Version = "0.0.1"
|
||||||
|
|
|
@ -43,7 +43,7 @@ func handleMessage(s xmpp.Sender, p xmpp.Packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
|
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
|
||||||
reply := xmpp.Message{PacketAttrs: xmpp.PacketAttrs{To: msg.From}, Body: msg.Body}
|
reply := xmpp.Message{Attrs: xmpp.Attrs{To: msg.From}, Body: msg.Body}
|
||||||
_ = s.Send(reply)
|
_ = s.Send(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ func main() {
|
||||||
Address: *address,
|
Address: *address,
|
||||||
Jid: *jid,
|
Jid: *jid,
|
||||||
Password: *password,
|
Password: *password,
|
||||||
|
// PacketLogger: os.Stdout,
|
||||||
Insecure: true,
|
Insecure: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
|
||||||
playSCURL(player, url)
|
playSCURL(player, url)
|
||||||
setResponse := new(xmpp.ControlSetResponse)
|
setResponse := new(xmpp.ControlSetResponse)
|
||||||
// FIXME: Broken
|
// FIXME: Broken
|
||||||
reply := xmpp.IQ{PacketAttrs: xmpp.PacketAttrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
|
reply := xmpp.IQ{Attrs: xmpp.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
|
||||||
_ = s.Send(reply)
|
_ = s.Send(reply)
|
||||||
// TODO add Soundclound artist / title retrieval
|
// TODO add Soundclound artist / title retrieval
|
||||||
sendUserTune(s, "Radiohead", "Spectre")
|
sendUserTune(s, "Radiohead", "Spectre")
|
||||||
|
@ -102,7 +103,7 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
|
||||||
|
|
||||||
func sendUserTune(s xmpp.Sender, artist string, title string) {
|
func sendUserTune(s xmpp.Sender, artist string, title string) {
|
||||||
tune := xmpp.Tune{Artist: artist, Title: title}
|
tune := xmpp.Tune{Artist: artist, Title: title}
|
||||||
iq := xmpp.NewIQ("set", "", "", "usertune-1", "en")
|
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}}}
|
payload := xmpp.PubSub{Publish: &xmpp.Publish{Node: "http://jabber.org/protocol/tune", Item: xmpp.Item{Tune: &tune}}}
|
||||||
iq.Payload = &payload
|
iq.Payload = &payload
|
||||||
_ = s.Send(iq)
|
_ = s.Send(iq)
|
||||||
|
|
2
auth.go
2
auth.go
|
@ -33,7 +33,7 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
|
||||||
fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc)
|
fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc)
|
||||||
|
|
||||||
// Next message should be either success or failure.
|
// Next message should be either success or failure.
|
||||||
val, err := next(decoder)
|
val, err := nextPacket(decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
28
backoff.go
28
backoff.go
|
@ -13,7 +13,7 @@ It can be used in several ways:
|
||||||
- Using ticker channel to trigger callback function on tick
|
- Using ticker channel to trigger callback function on tick
|
||||||
|
|
||||||
The functions for Backoff are not threadsafe, but you can:
|
The functions for Backoff are not threadsafe, but you can:
|
||||||
- Keep the attempt counter on your end and use DurationForAttempt(int)
|
- Keep the attempt counter on your end and use durationForAttempt(int)
|
||||||
- Use lock in your own code to protect the Backoff structure.
|
- Use lock in your own code to protect the Backoff structure.
|
||||||
|
|
||||||
TODO: Implement Backoff Ticker channel
|
TODO: Implement Backoff Ticker channel
|
||||||
|
@ -34,11 +34,11 @@ const (
|
||||||
defaultCap int = 180000 // 3 minutes
|
defaultCap int = 180000 // 3 minutes
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backoff can provide increasing duration with the number of attempt
|
// backoff provides increasing duration with the number of attempt
|
||||||
// performed. The structure is used to support exponential backoff on
|
// performed. The structure is used to support exponential backoff on
|
||||||
// connection attempts to avoid hammering the server we are connecting
|
// connection attempts to avoid hammering the server we are connecting
|
||||||
// to.
|
// to.
|
||||||
type Backoff struct {
|
type backoff struct {
|
||||||
NoJitter bool
|
NoJitter bool
|
||||||
Base int
|
Base int
|
||||||
Factor int
|
Factor int
|
||||||
|
@ -47,20 +47,20 @@ type Backoff struct {
|
||||||
attempt int
|
attempt int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration returns the duration to apply to the current attempt.
|
// duration returns the duration to apply to the current attempt.
|
||||||
func (b *Backoff) Duration() time.Duration {
|
func (b *backoff) duration() time.Duration {
|
||||||
d := b.DurationForAttempt(b.attempt)
|
d := b.durationForAttempt(b.attempt)
|
||||||
b.attempt++
|
b.attempt++
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait sleeps for backoff duration for current attempt.
|
// wait sleeps for backoff duration for current attempt.
|
||||||
func (b *Backoff) Wait() {
|
func (b *backoff) wait() {
|
||||||
time.Sleep(b.Duration())
|
time.Sleep(b.duration())
|
||||||
}
|
}
|
||||||
|
|
||||||
// DurationForAttempt returns a duration for an attempt number, in a stateless way.
|
// durationForAttempt returns a duration for an attempt number, in a stateless way.
|
||||||
func (b *Backoff) DurationForAttempt(attempt int) time.Duration {
|
func (b *backoff) durationForAttempt(attempt int) time.Duration {
|
||||||
b.setDefault()
|
b.setDefault()
|
||||||
expBackoff := math.Min(float64(b.Cap), float64(b.Base)*math.Pow(float64(b.Factor), float64(b.attempt)))
|
expBackoff := math.Min(float64(b.Cap), float64(b.Base)*math.Pow(float64(b.Factor), float64(b.attempt)))
|
||||||
d := int(math.Trunc(expBackoff))
|
d := int(math.Trunc(expBackoff))
|
||||||
|
@ -70,13 +70,13 @@ func (b *Backoff) DurationForAttempt(attempt int) time.Duration {
|
||||||
return time.Duration(d) * time.Millisecond
|
return time.Duration(d) * time.Millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset sets back the number of attempts to 0. This is to be called after a successfull operation has been performed,
|
// reset sets back the number of attempts to 0. This is to be called after a successful operation has been performed,
|
||||||
// to reset the exponential backoff interval.
|
// to reset the exponential backoff interval.
|
||||||
func (b *Backoff) Reset() {
|
func (b *backoff) reset() {
|
||||||
b.attempt = 0
|
b.attempt = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backoff) setDefault() {
|
func (b *backoff) setDefault() {
|
||||||
if b.Base == 0 {
|
if b.Base == 0 {
|
||||||
b.Base = defaultBase
|
b.Base = defaultBase
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
package xmpp_test
|
package xmpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gosrc.io/xmpp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDurationForAttempt_NoJitter(t *testing.T) {
|
func TestDurationForAttempt_NoJitter(t *testing.T) {
|
||||||
b := xmpp.Backoff{Base: 25, NoJitter: true}
|
b := backoff{Base: 25, NoJitter: true}
|
||||||
bInMS := time.Duration(b.Base) * time.Millisecond
|
bInMS := time.Duration(b.Base) * time.Millisecond
|
||||||
if b.DurationForAttempt(0) != bInMS {
|
if b.durationForAttempt(0) != bInMS {
|
||||||
t.Errorf("incorrect default duration for attempt #0 (%d) = %d", b.DurationForAttempt(0)/time.Millisecond, bInMS/time.Millisecond)
|
t.Errorf("incorrect default duration for attempt #0 (%d) = %d", b.durationForAttempt(0)/time.Millisecond, bInMS/time.Millisecond)
|
||||||
}
|
}
|
||||||
var prevDuration, d time.Duration
|
var prevDuration, d time.Duration
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
d = b.DurationForAttempt(i)
|
d = b.durationForAttempt(i)
|
||||||
if !(d >= prevDuration) {
|
if !(d >= prevDuration) {
|
||||||
t.Errorf("duration should be increasing between attempts. #%d (%d) > %d", i, d, prevDuration)
|
t.Errorf("duration should be increasing between attempts. #%d (%d) > %d", i, d, prevDuration)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,14 +54,14 @@ func (c *ServerCheck) Check() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set xml decoder and extract streamID from reply (not used for now)
|
// Set xml decoder and extract streamID from reply (not used for now)
|
||||||
_, err = initDecoder(decoder)
|
_, err = initStream(decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract stream features
|
// extract stream features
|
||||||
var f StreamFeatures
|
var f StreamFeatures
|
||||||
packet, err := next(decoder)
|
packet, err := nextPacket(decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("stream open decode features: %s", err)
|
err = fmt.Errorf("stream open decode features: %s", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -200,7 +200,7 @@ func (c *Client) SendRaw(packet string) error {
|
||||||
// Loop: Receive data from server
|
// Loop: Receive data from server
|
||||||
func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
|
func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
|
||||||
for {
|
for {
|
||||||
val, err := next(c.Session.decoder)
|
val, err := nextPacket(c.Session.decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close(keepaliveQuit)
|
close(keepaliveQuit)
|
||||||
c.updateState(StateDisconnected)
|
c.updateState(StateDisconnected)
|
||||||
|
|
|
@ -78,7 +78,7 @@ func (c *Component) Connect() error {
|
||||||
c.decoder = xml.NewDecoder(conn)
|
c.decoder = xml.NewDecoder(conn)
|
||||||
|
|
||||||
// 2. Initialize xml decoder and extract streamID from reply
|
// 2. Initialize xml decoder and extract streamID from reply
|
||||||
streamId, err := initDecoder(c.decoder)
|
streamId, err := initStream(c.decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("cannot init decoder " + err.Error())
|
return errors.New("cannot init decoder " + err.Error())
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func (c *Component) Connect() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Check server response for authentication
|
// 4. Check server response for authentication
|
||||||
val, err := next(c.decoder)
|
val, err := nextPacket(c.decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ func (c *Component) SetHandler(handler EventHandler) {
|
||||||
// Receiver Go routine receiver
|
// Receiver Go routine receiver
|
||||||
func (c *Component) recv() (err error) {
|
func (c *Component) recv() (err error) {
|
||||||
for {
|
for {
|
||||||
val, err := next(c.decoder)
|
val, err := nextPacket(c.decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.updateState(StateDisconnected)
|
c.updateState(StateDisconnected)
|
||||||
return err
|
return err
|
||||||
|
|
16
doc.go
16
doc.go
|
@ -1,13 +1,23 @@
|
||||||
/*
|
/*
|
||||||
Fluux XMPP is a Go XMPP library, focusing on simplicity, simple automation, and IoT.
|
Fluux XMPP is an modern and full-featured XMPP library that can be used to build clients or
|
||||||
|
server components.
|
||||||
|
|
||||||
The goal is to make simple to write simple adhoc XMPP clients:
|
The goal is to make simple to write modern compliant XMPP software:
|
||||||
|
|
||||||
- For automation (like for example monitoring of an XMPP service),
|
- For automation (like for example monitoring of an XMPP service),
|
||||||
- For building connected "things" by plugging them on an XMPP server,
|
- For building connected "things" by plugging them on an XMPP server,
|
||||||
- For writing simple chatbots to control a service or a thing.
|
- For writing simple chatbots to control a service or a thing.
|
||||||
|
- For writing XMPP servers components. Fluux XMPP supports:
|
||||||
|
- XEP-0114: Jabber Component Protocol
|
||||||
|
- XEP-0355: Namespace Delegation
|
||||||
|
- XEP-0356: Privileged Entity
|
||||||
|
|
||||||
Fluux XMPP can be used to build XMPP clients or XMPP components.
|
The library is designed to have minimal dependencies. For now, the library does not depend on any other library.
|
||||||
|
|
||||||
|
The library includes a StreamManager that provides features like autoreconnect exponential back-off.
|
||||||
|
|
||||||
|
The library is implementing latest versions of the XMPP specifications (RFC 6120 and RFC 6121), and includes
|
||||||
|
support for many extensions.
|
||||||
|
|
||||||
Clients
|
Clients
|
||||||
|
|
||||||
|
|
118
error.go
Normal file
118
error.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO support ability to put Raw payload inside IQ
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// XMPP Errors
|
||||||
|
|
||||||
|
// Err is an XMPP stanza payload that is used to report error on message,
|
||||||
|
// presence or iq stanza.
|
||||||
|
// It is intended to be added in the payload of the erroneous stanza.
|
||||||
|
type Err struct {
|
||||||
|
XMLName xml.Name `xml:"error"`
|
||||||
|
Code int `xml:"code,attr,omitempty"`
|
||||||
|
Type ErrorType `xml:"type,attr"` // required
|
||||||
|
Reason string
|
||||||
|
Text string `xml:"urn:ietf:params:xml:ns:xmpp-stanzas text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Err) Namespace() string {
|
||||||
|
return x.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML implements custom parsing for IQs
|
||||||
|
func (x *Err) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
x.XMLName = start.Name
|
||||||
|
|
||||||
|
// Extract attributes
|
||||||
|
for _, attr := range start.Attr {
|
||||||
|
if attr.Name.Local == "type" {
|
||||||
|
x.Type = ErrorType(attr.Value)
|
||||||
|
}
|
||||||
|
if attr.Name.Local == "code" {
|
||||||
|
if code, err := strconv.Atoi(attr.Value); err == nil {
|
||||||
|
x.Code = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check subelements to extract error text and reason (from local namespace).
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := t.(type) {
|
||||||
|
|
||||||
|
case xml.StartElement:
|
||||||
|
elt := new(Node)
|
||||||
|
|
||||||
|
err = d.DecodeElement(elt, &tt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
textName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
||||||
|
if elt.XMLName == textName {
|
||||||
|
x.Text = string(elt.Content)
|
||||||
|
} else if elt.XMLName.Space == "urn:ietf:params:xml:ns:xmpp-stanzas" {
|
||||||
|
x.Reason = elt.XMLName.Local
|
||||||
|
}
|
||||||
|
|
||||||
|
case xml.EndElement:
|
||||||
|
if tt == start.End() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x Err) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
||||||
|
if x.Code == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode start element and attributes
|
||||||
|
start.Name = xml.Name{Local: "error"}
|
||||||
|
|
||||||
|
code := xml.Attr{
|
||||||
|
Name: xml.Name{Local: "code"},
|
||||||
|
Value: strconv.Itoa(x.Code),
|
||||||
|
}
|
||||||
|
start.Attr = append(start.Attr, code)
|
||||||
|
|
||||||
|
if len(x.Type) > 0 {
|
||||||
|
typ := xml.Attr{
|
||||||
|
Name: xml.Name{Local: "type"},
|
||||||
|
Value: string(x.Type),
|
||||||
|
}
|
||||||
|
start.Attr = append(start.Attr, typ)
|
||||||
|
}
|
||||||
|
err = e.EncodeToken(start)
|
||||||
|
|
||||||
|
// SubTags
|
||||||
|
// Reason
|
||||||
|
if x.Reason != "" {
|
||||||
|
reason := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: x.Reason}
|
||||||
|
e.EncodeToken(xml.StartElement{Name: reason})
|
||||||
|
e.EncodeToken(xml.EndElement{Name: reason})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text
|
||||||
|
if x.Text != "" {
|
||||||
|
text := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
||||||
|
e.EncodeToken(xml.StartElement{Name: text})
|
||||||
|
e.EncodeToken(xml.CharData(x.Text))
|
||||||
|
e.EncodeToken(xml.EndElement{Name: text})
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||||
|
}
|
13
error_enum.go
Normal file
13
error_enum.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
// ErrorType is a Enum of error attribute type
|
||||||
|
type ErrorType string
|
||||||
|
|
||||||
|
// RFC 6120: part of A.5 Client Namespace and A.6 Server Namespace
|
||||||
|
const (
|
||||||
|
ErrorTypeAuth ErrorType = "auth"
|
||||||
|
ErrorTypeCancel ErrorType = "cancel"
|
||||||
|
ErrorTypeContinue ErrorType = "continue"
|
||||||
|
ErrorTypeModify ErrorType = "motify"
|
||||||
|
ErrorTypeWait ErrorType = "wait"
|
||||||
|
)
|
141
iq.go
141
iq.go
|
@ -3,127 +3,20 @@ package xmpp
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO support ability to put Raw payload inside IQ
|
TODO support ability to put Raw payload inside IQ
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// XMPP Errors
|
|
||||||
|
|
||||||
// Err is an XMPP stanza payload that is used to report error on message,
|
|
||||||
// presence or iq stanza.
|
|
||||||
// It is intended to be added in the payload of the erroneous stanza.
|
|
||||||
type Err struct {
|
|
||||||
XMLName xml.Name `xml:"error"`
|
|
||||||
Code int `xml:"code,attr,omitempty"`
|
|
||||||
Type string `xml:"type,attr,omitempty"`
|
|
||||||
Reason string
|
|
||||||
Text string `xml:"urn:ietf:params:xml:ns:xmpp-stanzas text,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Err) Namespace() string {
|
|
||||||
return x.XMLName.Space
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalXML implements custom parsing for IQs
|
|
||||||
func (x *Err) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
||||||
x.XMLName = start.Name
|
|
||||||
|
|
||||||
// Extract attributes
|
|
||||||
for _, attr := range start.Attr {
|
|
||||||
if attr.Name.Local == "type" {
|
|
||||||
x.Type = attr.Value
|
|
||||||
}
|
|
||||||
if attr.Name.Local == "code" {
|
|
||||||
if code, err := strconv.Atoi(attr.Value); err == nil {
|
|
||||||
x.Code = code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check subelements to extract error text and reason (from local namespace).
|
|
||||||
for {
|
|
||||||
t, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tt := t.(type) {
|
|
||||||
|
|
||||||
case xml.StartElement:
|
|
||||||
elt := new(Node)
|
|
||||||
|
|
||||||
err = d.DecodeElement(elt, &tt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
textName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
|
||||||
if elt.XMLName == textName {
|
|
||||||
x.Text = string(elt.Content)
|
|
||||||
} else if elt.XMLName.Space == "urn:ietf:params:xml:ns:xmpp-stanzas" {
|
|
||||||
x.Reason = elt.XMLName.Local
|
|
||||||
}
|
|
||||||
|
|
||||||
case xml.EndElement:
|
|
||||||
if tt == start.End() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x Err) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
|
||||||
if x.Code == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode start element and attributes
|
|
||||||
start.Name = xml.Name{Local: "error"}
|
|
||||||
|
|
||||||
code := xml.Attr{
|
|
||||||
Name: xml.Name{Local: "code"},
|
|
||||||
Value: strconv.Itoa(x.Code),
|
|
||||||
}
|
|
||||||
start.Attr = append(start.Attr, code)
|
|
||||||
|
|
||||||
if len(x.Type) > 0 {
|
|
||||||
typ := xml.Attr{
|
|
||||||
Name: xml.Name{Local: "type"},
|
|
||||||
Value: x.Type,
|
|
||||||
}
|
|
||||||
start.Attr = append(start.Attr, typ)
|
|
||||||
}
|
|
||||||
err = e.EncodeToken(start)
|
|
||||||
|
|
||||||
// SubTags
|
|
||||||
// Reason
|
|
||||||
if x.Reason != "" {
|
|
||||||
reason := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: x.Reason}
|
|
||||||
e.EncodeToken(xml.StartElement{Name: reason})
|
|
||||||
e.EncodeToken(xml.EndElement{Name: reason})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text
|
|
||||||
if x.Text != "" {
|
|
||||||
text := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
|
||||||
e.EncodeToken(xml.StartElement{Name: text})
|
|
||||||
e.EncodeToken(xml.CharData(x.Text))
|
|
||||||
e.EncodeToken(xml.EndElement{Name: text})
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// IQ Packet
|
// IQ Packet
|
||||||
|
|
||||||
|
// IQ implements RFC 6120 - A.5 Client Namespace (a part)
|
||||||
type IQ struct { // Info/Query
|
type IQ struct { // Info/Query
|
||||||
XMLName xml.Name `xml:"iq"`
|
XMLName xml.Name `xml:"iq"`
|
||||||
PacketAttrs
|
// MUST have a ID
|
||||||
|
Attrs
|
||||||
// We can only have one payload on IQ:
|
// We can only have one payload on IQ:
|
||||||
// "An IQ stanza of type "get" or "set" MUST contain exactly one
|
// "An IQ stanza of type "get" or "set" MUST contain exactly one
|
||||||
// child element, which specifies the semantics of the particular
|
// child element, which specifies the semantics of the particular
|
||||||
|
@ -133,16 +26,16 @@ type IQ struct { // Info/Query
|
||||||
RawXML string `xml:",innerxml"`
|
RawXML string `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIQ(iqtype, from, to, id, lang string) IQ {
|
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{
|
return IQ{
|
||||||
XMLName: xml.Name{Local: "iq"},
|
XMLName: xml.Name{Local: "iq"},
|
||||||
PacketAttrs: PacketAttrs{
|
Attrs: a,
|
||||||
Id: id,
|
|
||||||
From: from,
|
|
||||||
To: to,
|
|
||||||
Type: iqtype,
|
|
||||||
Lang: lang,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +75,7 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
iq.Id = attr.Value
|
iq.Id = attr.Value
|
||||||
}
|
}
|
||||||
if attr.Name.Local == "type" {
|
if attr.Name.Local == "type" {
|
||||||
iq.Type = attr.Value
|
iq.Type = StanzaType(attr.Value)
|
||||||
}
|
}
|
||||||
if attr.Name.Local == "to" {
|
if attr.Name.Local == "to" {
|
||||||
iq.To = attr.Value
|
iq.To = attr.Value
|
||||||
|
@ -190,9 +83,6 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
if attr.Name.Local == "from" {
|
if attr.Name.Local == "from" {
|
||||||
iq.From = attr.Value
|
iq.From = attr.Value
|
||||||
}
|
}
|
||||||
if attr.Name.Local == "lang" {
|
|
||||||
iq.Lang = attr.Value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode inner elements
|
// decode inner elements
|
||||||
|
@ -223,6 +113,7 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
iq.Payload = iqExt
|
iq.Payload = iqExt
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// TODO: If unknown decode as generic node
|
||||||
return fmt.Errorf("unexpected element in iq: %s %s", tt.Name.Space, tt.Name.Local)
|
return fmt.Errorf("unexpected element in iq: %s %s", tt.Name.Space, tt.Name.Local)
|
||||||
case xml.EndElement:
|
case xml.EndElement:
|
||||||
if tt == start.End() {
|
if tt == start.End() {
|
||||||
|
@ -233,11 +124,7 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Generic IQ Payload
|
// Generic / unknown content
|
||||||
|
|
||||||
type IQPayload interface {
|
|
||||||
Namespace() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is a generic structure to represent XML data. It is used to parse
|
// Node is a generic structure to represent XML data. It is used to parse
|
||||||
// unreferenced or custom stanza payload.
|
// unreferenced or custom stanza payload.
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestUnmarshalIqs(t *testing.T) {
|
||||||
parsedIQ xmpp.IQ
|
parsedIQ xmpp.IQ
|
||||||
}{
|
}{
|
||||||
{"<iq id=\"1\" type=\"set\" to=\"test@localhost\"/>",
|
{"<iq id=\"1\" type=\"set\" to=\"test@localhost\"/>",
|
||||||
xmpp.IQ{XMLName: xml.Name{Space: "", Local: "iq"}, PacketAttrs: xmpp.PacketAttrs{To: "test@localhost", Type: "set", Id: "1"}}},
|
xmpp.IQ{XMLName: xml.Name{Local: "iq"}, Attrs: xmpp.Attrs{Type: xmpp.IQTypeSet, To: "test@localhost", Id: "1"}}},
|
||||||
//{"<iq xmlns=\"jabber:client\" id=\"2\" type=\"set\" to=\"test@localhost\" from=\"server\"><set xmlns=\"urn:xmpp:iot:control\"/></iq>", IQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}},
|
//{"<iq xmlns=\"jabber:client\" id=\"2\" type=\"set\" to=\"test@localhost\" from=\"server\"><set xmlns=\"urn:xmpp:iot:control\"/></iq>", IQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func TestUnmarshalIqs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateIq(t *testing.T) {
|
func TestGenerateIq(t *testing.T) {
|
||||||
iq := xmpp.NewIQ("result", "admin@localhost", "test@localhost", "1", "en")
|
iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
|
||||||
payload := xmpp.DiscoInfo{
|
payload := xmpp.DiscoInfo{
|
||||||
Identity: xmpp.Identity{
|
Identity: xmpp.Identity{
|
||||||
Name: "Test Gateway",
|
Name: "Test Gateway",
|
||||||
|
@ -93,7 +93,7 @@ func TestErrorTag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoItems(t *testing.T) {
|
func TestDiscoItems(t *testing.T) {
|
||||||
iq := xmpp.NewIQ("get", "romeo@montague.net/orchard", "catalog.shakespeare.lit", "items3", "en")
|
iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
|
||||||
payload := xmpp.DiscoItems{
|
payload := xmpp.DiscoItems{
|
||||||
Node: "music",
|
Node: "music",
|
||||||
}
|
}
|
||||||
|
|
16
message.go
16
message.go
|
@ -7,9 +7,11 @@ import (
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Message Packet
|
// Message Packet
|
||||||
|
|
||||||
|
// Message implements RFC 6120 - A.5 Client Namespace (a part)
|
||||||
type Message struct {
|
type Message struct {
|
||||||
XMLName xml.Name `xml:"message"`
|
XMLName xml.Name `xml:"message"`
|
||||||
PacketAttrs
|
Attrs
|
||||||
|
|
||||||
Subject string `xml:"subject,omitempty"`
|
Subject string `xml:"subject,omitempty"`
|
||||||
Body string `xml:"body,omitempty"`
|
Body string `xml:"body,omitempty"`
|
||||||
Thread string `xml:"thread,omitempty"`
|
Thread string `xml:"thread,omitempty"`
|
||||||
|
@ -21,16 +23,10 @@ func (Message) Name() string {
|
||||||
return "message"
|
return "message"
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessage(msgtype, from, to, id, lang string) Message {
|
func NewMessage(a Attrs) Message {
|
||||||
return Message{
|
return Message{
|
||||||
XMLName: xml.Name{Local: "message"},
|
XMLName: xml.Name{Local: "message"},
|
||||||
PacketAttrs: PacketAttrs{
|
Attrs: a,
|
||||||
Id: id,
|
|
||||||
From: from,
|
|
||||||
To: to,
|
|
||||||
Type: msgtype,
|
|
||||||
Lang: lang,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +59,7 @@ func (msg *Message) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
msg.Id = attr.Value
|
msg.Id = attr.Value
|
||||||
}
|
}
|
||||||
if attr.Name.Local == "type" {
|
if attr.Name.Local == "type" {
|
||||||
msg.Type = attr.Value
|
msg.Type = StanzaType(attr.Value)
|
||||||
}
|
}
|
||||||
if attr.Name.Local == "to" {
|
if attr.Name.Local == "to" {
|
||||||
msg.To = attr.Value
|
msg.To = attr.Value
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateMessage(t *testing.T) {
|
func TestGenerateMessage(t *testing.T) {
|
||||||
message := xmpp.NewMessage("chat", "admin@localhost", "test@localhost", "1", "en")
|
message := xmpp.NewMessage(xmpp.Attrs{Type: xmpp.MessageTypeChat, From: "admin@localhost", To: "test@localhost", Id: "1"})
|
||||||
message.Body = "Hi"
|
message.Body = "Hi"
|
||||||
message.Subject = "Msg Subject"
|
message.Subject = "Msg Subject"
|
||||||
|
|
||||||
|
|
14
packet.go
14
packet.go
|
@ -4,13 +4,13 @@ type Packet interface {
|
||||||
Name() string
|
Name() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PacketAttrs represents the common structure for base XMPP packets.
|
// Attrs represents the common structure for base XMPP packets.
|
||||||
type PacketAttrs struct {
|
type Attrs struct {
|
||||||
Id string `xml:"id,attr,omitempty"`
|
Type StanzaType `xml:"type,attr,omitempty"`
|
||||||
From string `xml:"from,attr,omitempty"`
|
Id string `xml:"id,attr,omitempty"`
|
||||||
To string `xml:"to,attr,omitempty"`
|
From string `xml:"from,attr,omitempty"`
|
||||||
Type string `xml:"type,attr,omitempty"`
|
To string `xml:"to,attr,omitempty"`
|
||||||
Lang string `xml:"lang,attr,omitempty"`
|
Lang string `xml:"lang,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type packetFormatter interface {
|
type packetFormatter interface {
|
||||||
|
|
25
packet_enum.go
Normal file
25
packet_enum.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
type StanzaType string
|
||||||
|
|
||||||
|
// RFC 6120: part of A.5 Client Namespace and A.6 Server Namespace
|
||||||
|
const (
|
||||||
|
IQTypeError StanzaType = "error"
|
||||||
|
IQTypeGet StanzaType = "get"
|
||||||
|
IQTypeResult StanzaType = "result"
|
||||||
|
IQTypeSet StanzaType = "set"
|
||||||
|
|
||||||
|
MessageTypeChat StanzaType = "chat"
|
||||||
|
MessageTypeError StanzaType = "error"
|
||||||
|
MessageTypeGroupchat StanzaType = "groupchat"
|
||||||
|
MessageTypeHeadline StanzaType = "headline"
|
||||||
|
MessageTypeNormal StanzaType = "normal" // Default
|
||||||
|
|
||||||
|
PresenceTypeError StanzaType = "error"
|
||||||
|
PresenceTypeProbe StanzaType = "probe"
|
||||||
|
PresenceTypeSubscribe StanzaType = "subscribe"
|
||||||
|
PresenceTypeSubscribed StanzaType = "subscribed"
|
||||||
|
PresenceTypeUnavailable StanzaType = "unavailable"
|
||||||
|
PresenceTypeUnsubscribe StanzaType = "unsubscribe"
|
||||||
|
PresenceTypeUnsubscribed StanzaType = "unsubscribed"
|
||||||
|
)
|
30
parser.go
30
parser.go
|
@ -14,29 +14,29 @@ import (
|
||||||
// reattach features (allowing to resume an existing stream at the point the connection was interrupted, without
|
// reattach features (allowing to resume an existing stream at the point the connection was interrupted, without
|
||||||
// getting through the authentication process.
|
// getting through the authentication process.
|
||||||
// TODO We should handle stream error from XEP-0114 ( <conflict/> or <host-unknown/> )
|
// TODO We should handle stream error from XEP-0114 ( <conflict/> or <host-unknown/> )
|
||||||
func initDecoder(p *xml.Decoder) (sessionID string, err error) {
|
func initStream(p *xml.Decoder) (sessionID string, err error) {
|
||||||
for {
|
for {
|
||||||
var t xml.Token
|
var t xml.Token
|
||||||
t, err = p.Token()
|
t, err = p.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return sessionID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch elem := t.(type) {
|
switch elem := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
if elem.Name.Space != NSStream || elem.Name.Local != "stream" {
|
if elem.Name.Space != NSStream || elem.Name.Local != "stream" {
|
||||||
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
|
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
|
||||||
return
|
return sessionID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse Stream attributes
|
// Parse XMPP stream attributes
|
||||||
for _, attrs := range elem.Attr {
|
for _, attrs := range elem.Attr {
|
||||||
switch attrs.Name.Local {
|
switch attrs.Name.Local {
|
||||||
case "id":
|
case "id":
|
||||||
sessionID = attrs.Value
|
sessionID = attrs.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return sessionID, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,12 @@ func nextStart(p *xml.Decoder) (xml.StartElement, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// next scans XML token stream for next element and then assign a structure to decode
|
// nextPacket scans XML token stream for next complete XMPP stanza.
|
||||||
// that elements.
|
// 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 Use an interface to return packets interface xmppDecoder
|
||||||
func next(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
|
// 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 {
|
if err != nil {
|
||||||
|
@ -84,6 +86,13 @@ func next(p *xml.Decoder) (Packet, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: From all the decoder, we can return a pointer to the actual concrete type, instead of directly that
|
||||||
|
type.
|
||||||
|
That way, we have a consistent way to do type assertion, always matching against pointers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// decodeStream will fully decode a stream packet
|
||||||
func decodeStream(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
func decodeStream(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||||
switch se.Name.Local {
|
switch se.Name.Local {
|
||||||
case "error":
|
case "error":
|
||||||
|
@ -96,6 +105,7 @@ func decodeStream(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeSASL decodes a packet related to SASL authentication.
|
||||||
func decodeSASL(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
func decodeSASL(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||||
switch se.Name.Local {
|
switch se.Name.Local {
|
||||||
case "success":
|
case "success":
|
||||||
|
@ -108,6 +118,7 @@ func decodeSASL(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeClient decodes all known packets in the client namespace.
|
||||||
func decodeClient(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
func decodeClient(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||||
switch se.Name.Local {
|
switch se.Name.Local {
|
||||||
case "message":
|
case "message":
|
||||||
|
@ -122,9 +133,10 @@ func decodeClient(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeClient decodes all known packets in the component namespace.
|
||||||
func decodeComponent(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
func decodeComponent(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||||
switch se.Name.Local {
|
switch se.Name.Local {
|
||||||
case "handshake":
|
case "handshake": // handshake is used to authenticate components
|
||||||
return handshake.decode(p, se)
|
return handshake.decode(p, se)
|
||||||
case "message":
|
case "message":
|
||||||
return message.decode(p, se)
|
return message.decode(p, se)
|
||||||
|
|
20
presence.go
20
presence.go
|
@ -5,28 +5,24 @@ import "encoding/xml"
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Presence Packet
|
// Presence Packet
|
||||||
|
|
||||||
|
// Presence implements RFC 6120 - A.5 Client Namespace (a part)
|
||||||
type Presence struct {
|
type Presence struct {
|
||||||
XMLName xml.Name `xml:"presence"`
|
XMLName xml.Name `xml:"presence"`
|
||||||
PacketAttrs
|
Attrs
|
||||||
Show string `xml:"show,omitempty"` // away, chat, dnd, xa
|
Show PresenceShow `xml:"show,omitempty"`
|
||||||
Status string `xml:"status,omitempty"`
|
Status string `xml:"status,omitempty"`
|
||||||
Priority int `xml:"priority,omitempty"`
|
Priority int8 `xml:"priority,omitempty"` // default: 0
|
||||||
Error Err `xml:"error,omitempty"`
|
Error Err `xml:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Presence) Name() string {
|
func (Presence) Name() string {
|
||||||
return "presence"
|
return "presence"
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPresence(from, to, id, lang string) Presence {
|
func NewPresence(a Attrs) Presence {
|
||||||
return Presence{
|
return Presence{
|
||||||
XMLName: xml.Name{Local: "presence"},
|
XMLName: xml.Name{Local: "presence"},
|
||||||
PacketAttrs: PacketAttrs{
|
Attrs: a,
|
||||||
Id: id,
|
|
||||||
From: from,
|
|
||||||
To: to,
|
|
||||||
Lang: lang,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
presence_enum.go
Normal file
12
presence_enum.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
// PresenceShow is a Enum of presence element show
|
||||||
|
type PresenceShow string
|
||||||
|
|
||||||
|
// RFC 6120: part of A.5 Client Namespace and A.6 Server Namespace
|
||||||
|
const (
|
||||||
|
PresenceShowAway PresenceShow = "away"
|
||||||
|
PresenceShowChat PresenceShow = "chat"
|
||||||
|
PresenceShowDND PresenceShow = "dnd"
|
||||||
|
PresenceShowXA PresenceShow = "xa"
|
||||||
|
)
|
|
@ -10,8 +10,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGeneratePresence(t *testing.T) {
|
func TestGeneratePresence(t *testing.T) {
|
||||||
presence := xmpp.NewPresence("admin@localhost", "test@localhost", "1", "en")
|
presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
|
||||||
presence.Show = "chat"
|
presence.Show = xmpp.PresenceShowChat
|
||||||
|
|
||||||
data, err := xml.Marshal(presence)
|
data, err := xml.Marshal(presence)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -32,13 +32,13 @@ func TestPresenceSubElt(t *testing.T) {
|
||||||
// Test structure to ensure that show, status and priority are correctly defined as presence
|
// Test structure to ensure that show, status and priority are correctly defined as presence
|
||||||
// package sub-elements
|
// package sub-elements
|
||||||
type pres struct {
|
type pres struct {
|
||||||
Show string `xml:"show"`
|
Show xmpp.PresenceShow `xml:"show"`
|
||||||
Status string `xml:"status"`
|
Status string `xml:"status"`
|
||||||
Priority int `xml:"priority"`
|
Priority int8 `xml:"priority"`
|
||||||
}
|
}
|
||||||
|
|
||||||
presence := xmpp.NewPresence("admin@localhost", "test@localhost", "1", "en")
|
presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
|
||||||
presence.Show = "xa"
|
presence.Show = xmpp.PresenceShowXA
|
||||||
presence.Status = "Coding"
|
presence.Status = "Coding"
|
||||||
presence.Priority = 10
|
presence.Priority = 10
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,7 @@ func TestNameMatcher(t *testing.T) {
|
||||||
|
|
||||||
// Check that a message packet is properly matched
|
// Check that a message packet is properly matched
|
||||||
conn := NewSenderMock()
|
conn := NewSenderMock()
|
||||||
// TODO: We want packet creation code to use struct to use default values
|
msg := xmpp.NewMessage(xmpp.Attrs{Type: xmpp.MessageTypeChat, To: "test@localhost", Id: "1"})
|
||||||
msg := xmpp.NewMessage("chat", "", "test@localhost", "1", "")
|
|
||||||
msg.Body = "Hello"
|
msg.Body = "Hello"
|
||||||
router.Route(conn, msg)
|
router.Route(conn, msg)
|
||||||
if conn.String() != successFlag {
|
if conn.String() != successFlag {
|
||||||
|
@ -29,7 +28,7 @@ func TestNameMatcher(t *testing.T) {
|
||||||
|
|
||||||
// Check that an IQ packet is not matched
|
// Check that an IQ packet is not matched
|
||||||
conn = NewSenderMock()
|
conn = NewSenderMock()
|
||||||
iq := xmpp.NewIQ("get", "", "localhost", "1", "")
|
iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeGet, To: "localhost", Id: "1"})
|
||||||
iq.Payload = &xmpp.DiscoInfo{}
|
iq.Payload = &xmpp.DiscoInfo{}
|
||||||
router.Route(conn, iq)
|
router.Route(conn, iq)
|
||||||
if conn.String() == successFlag {
|
if conn.String() == successFlag {
|
||||||
|
@ -47,7 +46,8 @@ func TestIQNSMatcher(t *testing.T) {
|
||||||
|
|
||||||
// Check that an IQ with proper namespace does match
|
// Check that an IQ with proper namespace does match
|
||||||
conn := NewSenderMock()
|
conn := NewSenderMock()
|
||||||
iqDisco := xmpp.NewIQ("get", "", "localhost", "1", "")
|
iqDisco := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeGet, To: "localhost", Id: "1"})
|
||||||
|
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||||
iqDisco.Payload = &xmpp.DiscoInfo{
|
iqDisco.Payload = &xmpp.DiscoInfo{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: xmpp.NSDiscoInfo,
|
Space: xmpp.NSDiscoInfo,
|
||||||
|
@ -60,7 +60,8 @@ func TestIQNSMatcher(t *testing.T) {
|
||||||
|
|
||||||
// Check that another namespace is not matched
|
// Check that another namespace is not matched
|
||||||
conn = NewSenderMock()
|
conn = NewSenderMock()
|
||||||
iqVersion := xmpp.NewIQ("get", "", "localhost", "1", "")
|
iqVersion := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeGet, To: "localhost", Id: "1"})
|
||||||
|
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||||
iqVersion.Payload = &xmpp.DiscoInfo{
|
iqVersion.Payload = &xmpp.DiscoInfo{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "jabber:iq:version",
|
Space: "jabber:iq:version",
|
||||||
|
@ -240,7 +241,7 @@ func (s SenderMock) String() string {
|
||||||
|
|
||||||
func TestSenderMock(t *testing.T) {
|
func TestSenderMock(t *testing.T) {
|
||||||
conn := NewSenderMock()
|
conn := NewSenderMock()
|
||||||
msg := xmpp.NewMessage("", "", "test@localhost", "1", "")
|
msg := xmpp.NewMessage(xmpp.Attrs{To: "test@localhost", Id: "1"})
|
||||||
msg.Body = "Hello"
|
msg.Body = "Hello"
|
||||||
if err := conn.Send(msg); err != nil {
|
if err := conn.Send(msg); err != nil {
|
||||||
t.Error("Could not send message")
|
t.Error("Could not send message")
|
||||||
|
|
|
@ -92,7 +92,7 @@ func (s *Session) open(domain string) (f StreamFeatures) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set xml decoder and extract streamID from reply
|
// Set xml decoder and extract streamID from reply
|
||||||
s.StreamId, s.err = initDecoder(s.decoder) // TODO refactor / rename
|
s.StreamId, s.err = initStream(s.decoder) // TODO refactor / rename
|
||||||
if s.err != nil {
|
if s.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ func (sm *StreamManager) Stop() {
|
||||||
|
|
||||||
// connect manages the reconnection loop and apply the define backoff to avoid overloading the server.
|
// connect manages the reconnection loop and apply the define backoff to avoid overloading the server.
|
||||||
func (sm *StreamManager) connect() error {
|
func (sm *StreamManager) connect() error {
|
||||||
var backoff Backoff // TODO: Group backoff calculation features with connection manager?
|
var backoff backoff // TODO: Group backoff calculation features with connection manager?
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var err error
|
var err error
|
||||||
|
@ -118,7 +118,7 @@ func (sm *StreamManager) connect() error {
|
||||||
return xerrors.Errorf("unrecoverable connect error %w", actualErr)
|
return xerrors.Errorf("unrecoverable connect error %w", actualErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
backoff.Wait()
|
backoff.wait()
|
||||||
} else { // We are connected, we can leave the retry loop
|
} else { // We are connected, we can leave the retry loop
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue