- Changed IQ stanzas to pointer semantics
- Fixed commands from v 0.4.0 and tests - Added primitive Result Sets support (XEP-0059)
This commit is contained in:
parent
84665d8c13
commit
8798ff6fc1
|
@ -9,7 +9,10 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "service.localhost", Id: "custom-pl-1"})
|
||||
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "service.localhost", Id: "custom-pl-1"})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
payload := CustomPayload{XMLName: xml.Name{Space: "my:custom:payload", Local: "query"}, Node: "test"}
|
||||
iq.Payload = payload
|
||||
|
||||
|
@ -45,5 +48,5 @@ func (c CustomPayload) Namespace() string {
|
|||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{"my:custom:payload", "query"}, CustomPayload{})
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "my:custom:payload", Local: "query"}, CustomPayload{})
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ func main() {
|
|||
IQNamespaces("urn:xmpp:delegation:1").
|
||||
HandlerFunc(handleDelegation)
|
||||
|
||||
component, err := xmpp.NewComponent(opts, router)
|
||||
component, err := xmpp.NewComponent(opts, router, func(err error) {
|
||||
log.Println(err)
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
|
@ -78,7 +80,7 @@ const (
|
|||
// ctx.Opts
|
||||
func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
|
||||
// Type conversion & sanity checks
|
||||
iq, ok := p.(stanza.IQ)
|
||||
iq, ok := p.(*stanza.IQ)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -87,15 +89,18 @@ func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
|
|||
return
|
||||
}
|
||||
|
||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create IQ response: %v", err)
|
||||
}
|
||||
|
||||
switch info.Node {
|
||||
case "":
|
||||
discoInfoRoot(&iqResp, opts)
|
||||
discoInfoRoot(iqResp, opts)
|
||||
case pubsubNode:
|
||||
discoInfoPubSub(&iqResp)
|
||||
discoInfoPubSub(iqResp)
|
||||
case pepNode:
|
||||
discoInfoPEP(&iqResp)
|
||||
discoInfoPEP(iqResp)
|
||||
}
|
||||
|
||||
_ = c.Send(iqResp)
|
||||
|
@ -155,7 +160,7 @@ func discoInfoPEP(iqResp *stanza.IQ) {
|
|||
|
||||
func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
||||
// Type conversion & sanity checks
|
||||
iq, ok := p.(stanza.IQ)
|
||||
iq, ok := p.(*stanza.IQ)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -166,7 +171,7 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
|||
}
|
||||
forwardedPacket := delegation.Forwarded.Stanza
|
||||
fmt.Println(forwardedPacket)
|
||||
forwardedIQ, ok := forwardedPacket.(stanza.IQ)
|
||||
forwardedIQ, ok := forwardedPacket.(*stanza.IQ)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -179,7 +184,10 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
|||
|
||||
if pubsub.Publish.XMLName.Local == "publish" {
|
||||
// Prepare pubsub IQ reply
|
||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
|
||||
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create iqResp: %v", err)
|
||||
}
|
||||
payload := stanza.PubSubGeneric{
|
||||
XMLName: xml.Name{
|
||||
Space: "http://jabber.org/protocol/pubsub",
|
||||
|
@ -188,7 +196,10 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
|||
}
|
||||
iqResp.Payload = &payload
|
||||
// Wrap the reply in delegation 'forward'
|
||||
iqForward := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||
iqForward, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create iqForward: %v", err)
|
||||
}
|
||||
delegPayload := stanza.Delegation{
|
||||
XMLName: xml.Name{
|
||||
Space: "urn:xmpp:delegation:1",
|
||||
|
|
|
@ -5,7 +5,7 @@ go 1.13
|
|||
require (
|
||||
github.com/processone/mpg123 v1.0.0
|
||||
github.com/processone/soundcloud v1.0.0
|
||||
gosrc.io/xmpp v0.1.1
|
||||
gosrc.io/xmpp v0.4.0
|
||||
)
|
||||
|
||||
replace gosrc.io/xmpp => ./../
|
||||
|
|
|
@ -6,5 +6,5 @@ require (
|
|||
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.6.1
|
||||
gosrc.io/xmpp v0.3.1-0.20191223080939-f8f820170e08
|
||||
gosrc.io/xmpp v0.4.0
|
||||
)
|
||||
|
|
|
@ -186,7 +186,7 @@ func startClient(g *gocui.Gui, config *config) {
|
|||
|
||||
// ==========================
|
||||
// Start working
|
||||
updateRosterFromConfig(g, config)
|
||||
updateRosterFromConfig(config)
|
||||
// Sending the default contact in a channel. Default value is the first contact in the list from the config.
|
||||
viewState.currentContact = strings.Split(config.Contacts, configContactSep)[0]
|
||||
// Informing user of the default contact
|
||||
|
@ -283,7 +283,7 @@ func errorHandler(err error) {
|
|||
|
||||
// Read the client roster from the config. This does not check with the server that the roster is correct.
|
||||
// If user tries to send a message to someone not registered with the server, the server will return an error.
|
||||
func updateRosterFromConfig(g *gocui.Gui, config *config) {
|
||||
func updateRosterFromConfig(config *config) {
|
||||
viewState.contacts = append(strings.Split(config.Contacts, configContactSep), backFromContacts)
|
||||
// Put a "go back" button at the end of the list
|
||||
viewState.contacts = append(viewState.contacts, backFromContacts)
|
||||
|
|
|
@ -61,12 +61,16 @@ func handleMessage(_ xmpp.Sender, p stanza.Packet) {
|
|||
|
||||
func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
|
||||
// Type conversion & sanity checks
|
||||
iq, ok := p.(stanza.IQ)
|
||||
iq, ok := p.(*stanza.IQ)
|
||||
if !ok || iq.Type != stanza.IQTypeGet {
|
||||
return
|
||||
}
|
||||
|
||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||
// TODO: fix this...
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
disco := iqResp.DiscoInfo()
|
||||
disco.AddIdentity(opts.Name, opts.Category, opts.Type)
|
||||
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
|
||||
|
@ -76,7 +80,7 @@ func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
|
|||
// TODO: Handle iq error responses
|
||||
func discoItems(c xmpp.Sender, p stanza.Packet) {
|
||||
// Type conversion & sanity checks
|
||||
iq, ok := p.(stanza.IQ)
|
||||
iq, ok := p.(*stanza.IQ)
|
||||
if !ok || iq.Type != stanza.IQTypeGet {
|
||||
return
|
||||
}
|
||||
|
@ -86,7 +90,11 @@ func discoItems(c xmpp.Sender, p stanza.Packet) {
|
|||
return
|
||||
}
|
||||
|
||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||
// TODO: fix this...
|
||||
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := iqResp.DiscoItems()
|
||||
|
||||
if discoItems.Node == "" {
|
||||
|
@ -97,12 +105,15 @@ func discoItems(c xmpp.Sender, p stanza.Packet) {
|
|||
|
||||
func handleVersion(c xmpp.Sender, p stanza.Packet) {
|
||||
// Type conversion & sanity checks
|
||||
iq, ok := p.(stanza.IQ)
|
||||
iq, ok := p.(*stanza.IQ)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
iqResp.Version().SetInfo("Fluux XMPP Component", "0.0.1", "")
|
||||
_ = c.Send(iqResp)
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ Connect to an XMPP server using XEP 114 protocol, perform a discovery query on t
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
xmpp "gosrc.io/xmpp"
|
||||
"gosrc.io/xmpp"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
|
@ -53,10 +54,13 @@ func main() {
|
|||
}
|
||||
|
||||
// make a disco iq
|
||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet,
|
||||
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet,
|
||||
From: domain,
|
||||
To: "localhost",
|
||||
Id: "my-iq1"})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
disco := iqReq.DiscoInfo()
|
||||
iqReq.Payload = disco
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ func handleMessage(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
|||
}
|
||||
|
||||
func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
||||
iq, ok := p.(stanza.IQ)
|
||||
iq, ok := p.(*stanza.IQ)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
|||
setResponse := new(stanza.ControlSetResponse)
|
||||
// FIXME: Broken
|
||||
reply := stanza.IQ{Attrs: stanza.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
|
||||
_ = s.Send(reply)
|
||||
_ = s.Send(&reply)
|
||||
// TODO add Soundclound artist / title retrieval
|
||||
sendUserTune(s, "Radiohead", "Spectre")
|
||||
default:
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gosrc.io/xmpp"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
|
@ -17,6 +18,8 @@ const (
|
|||
serviceName = "pubsub.localhost"
|
||||
)
|
||||
|
||||
var invalidResp = errors.New("invalid response")
|
||||
|
||||
func main() {
|
||||
|
||||
config := xmpp.Config{
|
||||
|
@ -52,7 +55,7 @@ func main() {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
createNode(ctx, cancel, client)
|
||||
|
||||
// =============================
|
||||
// ================================================================================
|
||||
// Configure the node. This can also be done in a single message with the creation
|
||||
configureNode(ctx, cancel, client)
|
||||
|
||||
|
@ -68,10 +71,17 @@ func main() {
|
|||
// Let's purge the node :
|
||||
purgeRq, _ := stanza.NewPurgeAllItems(serviceName, nodeName)
|
||||
purgeCh, err := client.SendIQ(ctx, purgeRq)
|
||||
if err != nil {
|
||||
log.Fatalf("could not send purge request: %v", err)
|
||||
}
|
||||
select {
|
||||
case purgeResp := <-purgeCh:
|
||||
if purgeResp.Error != nil {
|
||||
|
||||
if purgeResp.Type == stanza.IQTypeError {
|
||||
cancel()
|
||||
if vld, err := purgeResp.IsValid(); !vld {
|
||||
log.Fatalf(invalidResp.Error()+" %v"+" reason: %v", purgeResp, err)
|
||||
}
|
||||
log.Fatalf("error while purging node : %s", purgeResp.Error.Text)
|
||||
}
|
||||
log.Println("node successfully purged")
|
||||
|
@ -97,7 +107,10 @@ func createNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.Cli
|
|||
select {
|
||||
case respCr := <-createCh:
|
||||
// Got response from server
|
||||
if respCr.Error != nil {
|
||||
if respCr.Type == stanza.IQTypeError {
|
||||
if vld, err := respCr.IsValid(); !vld {
|
||||
log.Fatalf(invalidResp.Error()+" %+v"+" reason: %s", respCr, err)
|
||||
}
|
||||
if respCr.Error.Reason != "conflict" {
|
||||
log.Fatalf("%+v", respCr.Error.Text)
|
||||
}
|
||||
|
@ -148,8 +161,11 @@ func configureNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.
|
|||
c, _ := client.SendIQ(ctx, submitConf)
|
||||
select {
|
||||
case confResp := <-c:
|
||||
if confResp.Error != nil {
|
||||
if confResp.Type == stanza.IQTypeError {
|
||||
cancel()
|
||||
if vld, err := confResp.IsValid(); !vld {
|
||||
log.Fatalf(invalidResp.Error()+" %v"+" reason: %v", confResp, err)
|
||||
}
|
||||
log.Fatalf("node configuration failed : %s", confResp.Error.Text)
|
||||
}
|
||||
log.Println("node configuration was successful")
|
||||
|
|
12
bi_dir_iterator.go
Normal file
12
bi_dir_iterator.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package xmpp
|
||||
|
||||
type BiDirIterator interface {
|
||||
// Next returns the next element of this iterator, if a response is available within t milliseconds
|
||||
Next(t int) (BiDirIteratorElt, error)
|
||||
// Previous returns the previous element of this iterator, if a response is available within t milliseconds
|
||||
Previous(t int) (BiDirIteratorElt, error)
|
||||
}
|
||||
|
||||
type BiDirIteratorElt interface {
|
||||
NoOp()
|
||||
}
|
|
@ -264,7 +264,7 @@ func (c *Client) Send(packet stanza.Packet) error {
|
|||
// ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
|
||||
// result := <- client.SendIQ(ctx, iq)
|
||||
//
|
||||
func (c *Client) SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) {
|
||||
func (c *Client) SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) {
|
||||
if iq.Attrs.Type != stanza.IQTypeSet && iq.Attrs.Type != stanza.IQTypeGet {
|
||||
return nil, ErrCanOnlySendGetOrSetIq
|
||||
}
|
||||
|
|
|
@ -171,7 +171,11 @@ func TestClient_SendIQ(t *testing.T) {
|
|||
client, mock := mockClientConnection(t, h, testClientIqPort)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create the IQ request: %v", err)
|
||||
}
|
||||
|
||||
disco := iqReq.DiscoInfo()
|
||||
iqReq.Payload = disco
|
||||
|
||||
|
@ -219,7 +223,10 @@ func TestClient_SendIQFail(t *testing.T) {
|
|||
//==================
|
||||
// Create an IQ to send
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ request: %v", err)
|
||||
}
|
||||
disco := iqReq.DiscoInfo()
|
||||
iqReq.Payload = disco
|
||||
// Removing the id to make the stanza invalid. The IQ constructor makes a random one if none is specified
|
||||
|
@ -387,7 +394,7 @@ func handlerClientConnectSuccess(t *testing.T, sc *ServerConn) {
|
|||
checkClientOpenStream(t, sc)
|
||||
sendStreamFeatures(t, sc) // Send initial features
|
||||
readAuth(t, sc.decoder)
|
||||
fmt.Fprintln(sc.connection, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
||||
sc.connection.Write([]byte("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>"))
|
||||
|
||||
checkClientOpenStream(t, sc) // Reset stream
|
||||
sendBindFeature(t, sc) // Send post auth features
|
||||
|
|
|
@ -38,7 +38,11 @@ func sendxmpp(cmd *cobra.Command, args []string) {
|
|||
},
|
||||
Jid: viper.GetString("jid"),
|
||||
Credential: xmpp.Password(viper.GetString("password")),
|
||||
}, xmpp.NewRouter())
|
||||
},
|
||||
xmpp.NewRouter(),
|
||||
func(err error) {
|
||||
log.Println(err)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error when starting xmpp client: %s", err)
|
||||
|
|
|
@ -185,7 +185,7 @@ func (c *Component) sendWithWriter(writer io.Writer, packet []byte) error {
|
|||
// ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
|
||||
// result := <- client.SendIQ(ctx, iq)
|
||||
//
|
||||
func (c *Component) SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) {
|
||||
func (c *Component) SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) {
|
||||
if iq.Attrs.Type != stanza.IQTypeSet && iq.Attrs.Type != stanza.IQTypeGet {
|
||||
return nil, ErrCanOnlySendGetOrSetIq
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func TestGenerateHandshakeId(t *testing.T) {
|
|||
|
||||
// Try connecting, and storing the resulting streamID in a map.
|
||||
m := make(map[string]bool)
|
||||
for _, _ = range uuidsArray {
|
||||
for range uuidsArray {
|
||||
streamId, _ := c.transport.Connect()
|
||||
m[c.handshake(streamId)] = true
|
||||
}
|
||||
|
@ -131,7 +131,10 @@ func TestSendIq(t *testing.T) {
|
|||
c, m := mockComponentConnection(t, testSendIqPort, h)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ request: %v", err)
|
||||
}
|
||||
disco := iqReq.DiscoInfo()
|
||||
iqReq.Payload = disco
|
||||
|
||||
|
@ -173,7 +176,10 @@ func TestSendIqFail(t *testing.T) {
|
|||
c, m := mockComponentConnection(t, testSendIqFailPort, h)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ request: %v", err)
|
||||
}
|
||||
|
||||
// Removing the id to make the stanza invalid. The IQ constructor makes a random one if none is specified
|
||||
// so we need to overwrite it.
|
||||
|
|
12
router.go
12
router.go
|
@ -42,7 +42,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 stanza.Packet) {
|
||||
iq, isIq := p.(stanza.IQ)
|
||||
iq, isIq := p.(*stanza.IQ)
|
||||
if isIq {
|
||||
r.IQResultRouteLock.RLock()
|
||||
route, ok := r.IQResultRoutes[iq.Id]
|
||||
|
@ -51,7 +51,7 @@ func (r *Router) route(s Sender, p stanza.Packet) {
|
|||
r.IQResultRouteLock.Lock()
|
||||
delete(r.IQResultRoutes, iq.Id)
|
||||
r.IQResultRouteLock.Unlock()
|
||||
route.result <- iq
|
||||
route.result <- *iq
|
||||
close(route.result)
|
||||
return
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ func (r *Router) route(s Sender, p stanza.Packet) {
|
|||
}
|
||||
}
|
||||
|
||||
func iqNotImplemented(s Sender, iq stanza.IQ) {
|
||||
func iqNotImplemented(s Sender, iq *stanza.IQ) {
|
||||
err := stanza.Err{
|
||||
XMLName: xml.Name{Local: "error"},
|
||||
Code: 501,
|
||||
|
@ -232,7 +232,7 @@ func (n nameMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
|||
switch p.(type) {
|
||||
case stanza.Message:
|
||||
name = "message"
|
||||
case stanza.IQ:
|
||||
case *stanza.IQ:
|
||||
name = "iq"
|
||||
case stanza.Presence:
|
||||
name = "presence"
|
||||
|
@ -259,7 +259,7 @@ type nsTypeMatcher []string
|
|||
func (m nsTypeMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
||||
var stanzaType stanza.StanzaType
|
||||
switch packet := p.(type) {
|
||||
case stanza.IQ:
|
||||
case *stanza.IQ:
|
||||
stanzaType = packet.Type
|
||||
case stanza.Presence:
|
||||
stanzaType = packet.Type
|
||||
|
@ -291,7 +291,7 @@ func (r *Route) StanzaType(types ...string) *Route {
|
|||
type nsIQMatcher []string
|
||||
|
||||
func (m nsIQMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
||||
iq, ok := p.(stanza.IQ)
|
||||
iq, ok := p.(*stanza.IQ)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -25,7 +25,10 @@ func TestIQResultRoutes(t *testing.T) {
|
|||
// Check if the IQ handler was called
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
|
||||
defer cancel()
|
||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "1234"})
|
||||
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "1234"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
res := router.NewIQResultRoute(ctx, "1234")
|
||||
go router.route(conn, iq)
|
||||
select {
|
||||
|
@ -71,7 +74,10 @@ func TestNameMatcher(t *testing.T) {
|
|||
|
||||
// Check that an IQ packet is not matched
|
||||
conn = NewSenderMock()
|
||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
iq.Payload = &stanza.DiscoInfo{}
|
||||
router.route(conn, iq)
|
||||
if conn.String() == successFlag {
|
||||
|
@ -89,7 +95,10 @@ func TestIQNSMatcher(t *testing.T) {
|
|||
|
||||
// Check that an IQ with proper namespace does match
|
||||
conn := NewSenderMock()
|
||||
iqDisco := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||
iqDisco, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create iqDisco: %v", err)
|
||||
}
|
||||
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||
iqDisco.Payload = &stanza.DiscoInfo{
|
||||
XMLName: xml.Name{
|
||||
|
@ -103,7 +112,10 @@ func TestIQNSMatcher(t *testing.T) {
|
|||
|
||||
// Check that another namespace is not matched
|
||||
conn = NewSenderMock()
|
||||
iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||
iqVersion, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create iqVersion: %v", err)
|
||||
}
|
||||
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||
iqVersion.Payload = &stanza.DiscoInfo{
|
||||
XMLName: xml.Name{
|
||||
|
@ -146,7 +158,10 @@ func TestTypeMatcher(t *testing.T) {
|
|||
|
||||
// We do not match on other types
|
||||
conn = NewSenderMock()
|
||||
iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
iqVersion, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create iqVersion: %v", err)
|
||||
}
|
||||
iqVersion.Payload = &stanza.DiscoInfo{
|
||||
XMLName: xml.Name{
|
||||
Space: "jabber:iq:version",
|
||||
|
@ -169,22 +184,31 @@ func TestCompositeMatcher(t *testing.T) {
|
|||
})
|
||||
|
||||
// Data set
|
||||
getVersionIq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
getVersionIq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create getVersionIq: %v", err)
|
||||
}
|
||||
getVersionIq.Payload = &stanza.Version{
|
||||
XMLName: xml.Name{
|
||||
Space: "jabber:iq:version",
|
||||
Local: "query",
|
||||
}}
|
||||
|
||||
setVersionIq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
setVersionIq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create setVersionIq: %v", err)
|
||||
}
|
||||
setVersionIq.Payload = &stanza.Version{
|
||||
XMLName: xml.Name{
|
||||
Space: "jabber:iq:version",
|
||||
Local: "query",
|
||||
}}
|
||||
|
||||
GetDiscoIq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
GetDiscoIq.Payload = &stanza.DiscoInfo{
|
||||
getDiscoIq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create getDiscoIq: %v", err)
|
||||
}
|
||||
getDiscoIq.Payload = &stanza.DiscoInfo{
|
||||
XMLName: xml.Name{
|
||||
Space: "http://jabber.org/protocol/disco#info",
|
||||
Local: "query",
|
||||
|
@ -200,7 +224,7 @@ func TestCompositeMatcher(t *testing.T) {
|
|||
}{
|
||||
{name: "match get version iq", input: getVersionIq, want: true},
|
||||
{name: "ignore set version iq", input: setVersionIq, want: false},
|
||||
{name: "ignore get discoinfo iq", input: GetDiscoIq, want: false},
|
||||
{name: "ignore get discoinfo iq", input: getDiscoIq, want: false},
|
||||
{name: "ignore message", input: message, want: false},
|
||||
}
|
||||
|
||||
|
@ -238,7 +262,10 @@ func TestCatchallMatcher(t *testing.T) {
|
|||
}
|
||||
|
||||
conn = NewSenderMock()
|
||||
iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
iqVersion, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create iqVersion: %v", err)
|
||||
}
|
||||
iqVersion.Payload = &stanza.DiscoInfo{
|
||||
XMLName: xml.Name{
|
||||
Space: "jabber:iq:version",
|
||||
|
@ -274,7 +301,7 @@ func (s SenderMock) Send(packet stanza.Packet) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s SenderMock) SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) {
|
||||
func (s SenderMock) SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) {
|
||||
out, err := xml.Marshal(iq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -97,7 +97,7 @@ func (s *Session) startTlsIfSupported(o Config) {
|
|||
|
||||
if !s.transport.DoesStartTLS() {
|
||||
if !o.Insecure {
|
||||
s.err = errors.New("Transport does not support starttls")
|
||||
s.err = errors.New("transport does not support starttls")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ const (
|
|||
type Command struct {
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/commands command"`
|
||||
|
||||
CommandElement CommandElement `xml:",any"`
|
||||
CommandElement CommandElement
|
||||
|
||||
BadAction *struct{} `xml:"bad-action,omitempty"`
|
||||
BadLocale *struct{} `xml:"bad-locale,omitempty"`
|
||||
|
@ -38,12 +38,19 @@ type Command struct {
|
|||
SessionId string `xml:"sessionid,attr,omitempty"`
|
||||
Status string `xml:"status,attr,omitempty"`
|
||||
Lang string `xml:"lang,attr,omitempty"`
|
||||
|
||||
// Result sets
|
||||
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Command) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
func (c *Command) GetSet() *ResultSet {
|
||||
return c.ResultSet
|
||||
}
|
||||
|
||||
type CommandElement interface {
|
||||
Ref() string
|
||||
}
|
||||
|
@ -68,6 +75,7 @@ type Note struct {
|
|||
func (n *Note) Ref() string {
|
||||
return "note"
|
||||
}
|
||||
func (f *Form) Ref() string { return "form" }
|
||||
|
||||
func (n *Node) Ref() string {
|
||||
return "node"
|
||||
|
@ -117,6 +125,10 @@ func (c *Command) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||
nt := Note{}
|
||||
d.DecodeElement(&nt, &tt)
|
||||
c.CommandElement = &nt
|
||||
case "x":
|
||||
f := Form{}
|
||||
d.DecodeElement(&f, &tt)
|
||||
c.CommandElement = &f
|
||||
default:
|
||||
n := Node{}
|
||||
e := d.DecodeElement(&n, &tt)
|
||||
|
@ -134,3 +146,7 @@ func (c *Command) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "http://jabber.org/protocol/commands", Local: "command"}, Command{})
|
||||
}
|
||||
|
|
|
@ -7,34 +7,21 @@ import (
|
|||
)
|
||||
|
||||
func TestMarshalCommands(t *testing.T) {
|
||||
input := "<command xmlns=\"http://jabber.org/protocol/commands\" node=\"list\" sessionid=\"list:20020923T213616Z-700\" status=\"completed\"><x " +
|
||||
"xmlns=\"jabber:x:data\" type=\"result\"><title xmlns=\"jabber:x:data\">Available Servi" +
|
||||
"ces</title><reported xmlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" label=\"S" +
|
||||
"ervice\" var=\"service\"></field><field xmlns=\"jabber:x:data\" label=\"Single-User mo" +
|
||||
"de\" var=\"runlevel-1\"></field><field xmlns=\"jabber:x:data\" label=\"Non-Networked M" +
|
||||
"ulti-User mode\" var=\"runlevel-2\"></field><field xmlns=\"jabber:x:data\" label=\"Ful" +
|
||||
"l Multi-User mode\" var=\"runlevel-3\"></field><field xmlns=\"jabber:x:data\" label=\"" +
|
||||
"X-Window mode\" var=\"runlevel-5\"></field></reported><item xmlns=\"jabber:x:data\"><" +
|
||||
"field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"jabber:x:data\">httpd</va" +
|
||||
"lue></field><field xmlns=\"jabber:x:data\" var=\"runlevel-1\"><value xmlns=\"jabber:x" +
|
||||
":data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlevel-2\"><value x" +
|
||||
"mlns=\"jabber:x:data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlev" +
|
||||
"el-3\"><value xmlns=\"jabber:x:data\">on</value></field><field xmlns=\"jabber:x:data" +
|
||||
"\" var=\"runlevel-5\"><value xmlns=\"jabber:x:data\">on</value></field></item><item x" +
|
||||
"mlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"ja" +
|
||||
"bber:x:data\">postgresql</value></field><field xmlns=\"jabber:x:data\" var=\"runleve" +
|
||||
"l-1\"><value xmlns=\"jabber:x:data\">off</value></field><field xmlns=\"jabber:x:data" +
|
||||
"\" var=\"runlevel-2\"><value xmlns=\"jabber:x:data\">off</value></field><field xmlns=" +
|
||||
"\"jabber:x:data\" var=\"runlevel-3\"><value xmlns=\"jabber:x:data\">on</value></field>" +
|
||||
"<field xmlns=\"jabber:x:data\" var=\"runlevel-5\"><value xmlns=\"jabber:x:data\">on</v" +
|
||||
"alue></field></item><item xmlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" var" +
|
||||
"=\"service\"><value xmlns=\"jabber:x:data\">jabberd</value></field><field xmlns=\"jab" +
|
||||
"ber:x:data\" var=\"runlevel-1\"><value xmlns=\"jabber:x:data\">off</value></field><fi" +
|
||||
"eld xmlns=\"jabber:x:data\" var=\"runlevel-2\"><value xmlns=\"jabber:x:data\">off</val" +
|
||||
"ue></field><field xmlns=\"jabber:x:data\" var=\"runlevel-3\"><value xmlns=\"jabber:x:" +
|
||||
"data\">on</value></field><field xmlns=\"jabber:x:data\" var=\"runlevel-5\"><value xml" +
|
||||
"ns=\"jabber:x:data\">on</value></field></item></x></command>"
|
||||
|
||||
input := "<command xmlns=\"http://jabber.org/protocol/commands\" node=\"list\" " +
|
||||
"sessionid=\"list:20020923T213616Z-700\" status=\"completed\"><x xmlns=\"jabber:x:data\" " +
|
||||
"type=\"result\"><title>Available Services</title><reported xmlns=\"jabber:x:data\"><field var=\"service\" " +
|
||||
"label=\"Service\"></field><field var=\"runlevel-1\" label=\"Single-User mode\">" +
|
||||
"</field><field var=\"runlevel-2\" label=\"Non-Networked Multi-User mode\"></field><field var=\"runlevel-3\" " +
|
||||
"label=\"Full Multi-User mode\"></field><field var=\"runlevel-5\" label=\"X-Window mode\"></field></reported>" +
|
||||
"<item xmlns=\"jabber:x:data\"><field var=\"service\"><value>httpd</value></field><field var=\"runlevel-1\">" +
|
||||
"<value>off</value></field><field var=\"runlevel-2\"><value>off</value></field><field var=\"runlevel-3\">" +
|
||||
"<value>on</value></field><field var=\"runlevel-5\"><value>on</value></field></item>" +
|
||||
"<item xmlns=\"jabber:x:data\"><field var=\"service\"><value>postgresql</value></field>" +
|
||||
"<field var=\"runlevel-1\"><value>off</value></field><field var=\"runlevel-2\"><value>off</value></field>" +
|
||||
"<field var=\"runlevel-3\"><value>on</value></field><field var=\"runlevel-5\"><value>on</value></field></item>" +
|
||||
"<item xmlns=\"jabber:x:data\"><field var=\"service\"><value>jabberd</value></field><field var=\"runlevel-1\">" +
|
||||
"<value>off</value></field><field var=\"runlevel-2\"><value>off</value></field><field var=\"runlevel-3\">" +
|
||||
"<value>on</value></field><field var=\"runlevel-5\"><value>on</value></field></item></x></command>"
|
||||
var c stanza.Command
|
||||
err := xml.Unmarshal([]byte(input), &c)
|
||||
|
||||
|
|
|
@ -42,11 +42,16 @@ type Delegation struct {
|
|||
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
|
||||
// Result sets
|
||||
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
func (d *Delegation) Namespace() string {
|
||||
return d.XMLName.Space
|
||||
}
|
||||
func (d *Delegation) GetSet() *ResultSet {
|
||||
return d.ResultSet
|
||||
}
|
||||
|
||||
// Forwarded is used to wrapped forwarded stanzas.
|
||||
// TODO: Move it in another file, as it is not limited to components.
|
||||
|
@ -86,6 +91,6 @@ type Delegated struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
|
||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
|
||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "urn:xmpp:delegation:1", Local: "delegation"}, Delegation{})
|
||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "urn:xmpp:delegation:1", Local: "delegation"}, Delegation{})
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestParsingDelegationIQ(t *testing.T) {
|
|||
if iq.Payload != nil {
|
||||
if delegation, ok := iq.Payload.(*Delegation); ok {
|
||||
packet := delegation.Forwarded.Stanza
|
||||
forwardedIQ, ok := packet.(IQ)
|
||||
forwardedIQ, ok := packet.(*IQ)
|
||||
if !ok {
|
||||
t.Errorf("Could not extract packet IQ")
|
||||
return
|
||||
|
|
|
@ -14,17 +14,18 @@ const (
|
|||
// See XEP-0004 and XEP-0068
|
||||
// Pointer semantics
|
||||
type Form struct {
|
||||
XMLName xml.Name `xml:"jabber:x:data x"`
|
||||
Instructions []string `xml:"instructions"`
|
||||
Title string `xml:"title,omitempty"`
|
||||
Fields []*Field `xml:"field,omitempty"`
|
||||
Reported *FormItem `xml:"reported"`
|
||||
Items []FormItem
|
||||
Type string `xml:"type,attr"`
|
||||
XMLName xml.Name `xml:"jabber:x:data x"`
|
||||
Instructions []string `xml:"instructions"`
|
||||
Title string `xml:"title,omitempty"`
|
||||
Fields []*Field `xml:"field,omitempty"`
|
||||
Reported *FormItem `xml:"reported"`
|
||||
Items []FormItem `xml:"item,omitempty"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type FormItem struct {
|
||||
Fields []Field
|
||||
XMLName xml.Name
|
||||
Fields []Field `xml:"field,omitempty"`
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
|
|
|
@ -51,7 +51,10 @@ const (
|
|||
)
|
||||
|
||||
func TestMarshalFormSubmit(t *testing.T) {
|
||||
formIQ := NewIQ(Attrs{From: clientJid, To: serviceJid, Id: iqId, Type: IQTypeSet})
|
||||
formIQ, err := NewIQ(Attrs{From: clientJid, To: serviceJid, Id: iqId, Type: IQTypeSet})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create formIQ: %v", err)
|
||||
}
|
||||
formIQ.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &ConfigureOwner{
|
||||
Node: serviceNode,
|
||||
|
|
|
@ -7,12 +7,18 @@ import (
|
|||
type ControlSet struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:iot:control set"`
|
||||
Fields []ControlField `xml:",any"`
|
||||
// Result sets
|
||||
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ControlSet) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
func (c *ControlSet) GetSet() *ResultSet {
|
||||
return c.ResultSet
|
||||
}
|
||||
|
||||
type ControlGetForm struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:iot:control getForm"`
|
||||
}
|
||||
|
@ -30,10 +36,13 @@ type ControlSetResponse struct {
|
|||
func (c *ControlSetResponse) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
func (c *ControlSetResponse) GetSet() *ResultSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Registry init
|
||||
|
||||
func init() {
|
||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:iot:control", "set"}, ControlSet{})
|
||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "urn:xmpp:iot:control", Local: "set"}, ControlSet{})
|
||||
}
|
||||
|
|
49
stanza/iq.go
49
stanza/iq.go
|
@ -2,6 +2,7 @@ package stanza
|
|||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -31,22 +32,28 @@ type IQ struct { // Info/Query
|
|||
|
||||
type IQPayload interface {
|
||||
Namespace() string
|
||||
GetSet() *ResultSet
|
||||
}
|
||||
|
||||
func NewIQ(a Attrs) IQ {
|
||||
// TODO ensure that type is set, as it is required
|
||||
func NewIQ(a Attrs) (*IQ, error) {
|
||||
if a.Id == "" {
|
||||
if id, err := uuid.NewRandom(); err == nil {
|
||||
a.Id = id.String()
|
||||
}
|
||||
}
|
||||
return IQ{
|
||||
|
||||
iq := IQ{
|
||||
XMLName: xml.Name{Local: "iq"},
|
||||
Attrs: a,
|
||||
}
|
||||
|
||||
if iq.Type.IsEmpty() {
|
||||
return nil, IqTypeUnset
|
||||
}
|
||||
return &iq, nil
|
||||
}
|
||||
|
||||
func (iq IQ) MakeError(xerror Err) IQ {
|
||||
func (iq *IQ) MakeError(xerror Err) *IQ {
|
||||
from := iq.From
|
||||
to := iq.To
|
||||
|
||||
|
@ -58,18 +65,23 @@ func (iq IQ) MakeError(xerror Err) IQ {
|
|||
return iq
|
||||
}
|
||||
|
||||
func (IQ) Name() string {
|
||||
func (*IQ) Name() string {
|
||||
return "iq"
|
||||
}
|
||||
|
||||
// NoOp to implement BiDirIteratorElt
|
||||
func (*IQ) NoOp() {
|
||||
|
||||
}
|
||||
|
||||
type iqDecoder struct{}
|
||||
|
||||
var iq iqDecoder
|
||||
|
||||
func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
|
||||
func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (*IQ, error) {
|
||||
var packet IQ
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
return &packet, err
|
||||
}
|
||||
|
||||
// UnmarshalXML implements custom parsing for IQs
|
||||
|
@ -134,38 +146,47 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||
}
|
||||
}
|
||||
|
||||
var (
|
||||
IqTypeUnset = errors.New("iq type is not set but is mandatory")
|
||||
IqIDUnset = errors.New("iq stanza ID is not set but is mandatory")
|
||||
IqSGetNoPl = errors.New("iq is of type get or set but has no payload")
|
||||
IqResNoPl = errors.New("iq is of type result but has no payload")
|
||||
IqErrNoErrPl = errors.New("iq is of type error but has no error payload")
|
||||
)
|
||||
|
||||
// IsValid checks if the IQ is valid. If not, return an error with the reason as a message
|
||||
// Following RFC-3920 for IQs
|
||||
func (iq *IQ) IsValid() bool {
|
||||
func (iq *IQ) IsValid() (bool, error) {
|
||||
// ID is required
|
||||
if len(strings.TrimSpace(iq.Id)) == 0 {
|
||||
return false
|
||||
return false, IqIDUnset
|
||||
}
|
||||
|
||||
// Type is required
|
||||
if iq.Type.IsEmpty() {
|
||||
return false
|
||||
return false, IqTypeUnset
|
||||
}
|
||||
|
||||
// Type get and set must contain one and only one child element that specifies the semantics
|
||||
if iq.Type == IQTypeGet || iq.Type == IQTypeSet {
|
||||
if iq.Payload == nil && iq.Any == nil {
|
||||
return false
|
||||
return false, IqSGetNoPl
|
||||
}
|
||||
}
|
||||
|
||||
// A result must include zero or one child element
|
||||
if iq.Type == IQTypeResult {
|
||||
if iq.Payload != nil && iq.Any != nil {
|
||||
return false
|
||||
return false, IqResNoPl
|
||||
}
|
||||
}
|
||||
|
||||
//Error type must contain an "error" child element
|
||||
if iq.Type == IQTypeError {
|
||||
if iq.Error == nil {
|
||||
return false
|
||||
return false, IqErrNoErrPl
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -16,10 +16,11 @@ const (
|
|||
// 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"`
|
||||
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"`
|
||||
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
// Namespace lets DiscoInfo implement the IQPayload interface
|
||||
|
@ -27,6 +28,10 @@ func (d *DiscoInfo) Namespace() string {
|
|||
return d.XMLName.Space
|
||||
}
|
||||
|
||||
func (d *DiscoInfo) GetSet() *ResultSet {
|
||||
return d.ResultSet
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Builder helpers
|
||||
|
||||
|
@ -102,12 +107,19 @@ type DiscoItems struct {
|
|||
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
|
||||
Node string `xml:"node,attr,omitempty"`
|
||||
Items []DiscoItem `xml:"item"`
|
||||
|
||||
// Result sets
|
||||
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
func (d *DiscoItems) Namespace() string {
|
||||
return d.XMLName.Space
|
||||
}
|
||||
|
||||
func (d *DiscoItems) GetSet() *ResultSet {
|
||||
return d.ResultSet
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Builder helpers
|
||||
|
||||
|
@ -146,6 +158,6 @@ type DiscoItem struct {
|
|||
// 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{Space: NSDiscoInfo, Local: "query"}, DiscoInfo{})
|
||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: NSDiscoItems, Local: "query"}, DiscoItems{})
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@ import (
|
|||
|
||||
// Test DiscoInfo Builder with several features
|
||||
func TestDiscoInfo_Builder(t *testing.T) {
|
||||
iq := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
|
||||
iq, err := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
disco := iq.DiscoInfo()
|
||||
disco.AddIdentity("Test Component", "gateway", "service")
|
||||
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
|
||||
|
@ -50,8 +53,11 @@ func TestDiscoInfo_Builder(t *testing.T) {
|
|||
// Implements XEP-0030 example 17
|
||||
// https://xmpp.org/extensions/xep-0030.html#example-17
|
||||
func TestDiscoItems_Builder(t *testing.T) {
|
||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "catalog.shakespeare.lit",
|
||||
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "catalog.shakespeare.lit",
|
||||
To: "romeo@montague.net/orchard", Id: "items-2"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
iq.DiscoItems().
|
||||
AddItem("catalog.shakespeare.lit", "books", "Books by and about Shakespeare").
|
||||
AddItem("catalog.shakespeare.lit", "clothing", "Wear your literary taste with pride").
|
||||
|
|
|
@ -35,12 +35,17 @@ const (
|
|||
// Roster struct represents Roster IQs
|
||||
type Roster struct {
|
||||
XMLName xml.Name `xml:"jabber:iq:roster query"`
|
||||
// Result sets
|
||||
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
// Namespace defines the namespace for the RosterIQ
|
||||
func (r *Roster) Namespace() string {
|
||||
return r.XMLName.Space
|
||||
}
|
||||
func (r *Roster) GetSet() *ResultSet {
|
||||
return r.ResultSet
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Builder helpers
|
||||
|
@ -64,6 +69,8 @@ func (iq *IQ) RosterIQ() *Roster {
|
|||
type RosterItems struct {
|
||||
XMLName xml.Name `xml:"jabber:iq:roster query"`
|
||||
Items []RosterItem `xml:"item"`
|
||||
// Result sets
|
||||
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
// Namespace lets RosterItems implement the IQPayload interface
|
||||
|
@ -71,6 +78,10 @@ func (r *RosterItems) Namespace() string {
|
|||
return r.XMLName.Space
|
||||
}
|
||||
|
||||
func (r *RosterItems) GetSet() *ResultSet {
|
||||
return r.ResultSet
|
||||
}
|
||||
|
||||
// RosterItem represents an item in the roster iq
|
||||
type RosterItem struct {
|
||||
XMLName xml.Name `xml:"jabber:iq:roster item"`
|
||||
|
|
|
@ -7,7 +7,10 @@ import (
|
|||
)
|
||||
|
||||
func TestRosterBuilder(t *testing.T) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeResult, From: "romeo@montague.net/orchard"})
|
||||
iq, err := NewIQ(Attrs{Type: IQTypeResult, From: "romeo@montague.net/orchard"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
var noGroup []string
|
||||
|
||||
iq.RosterItems().AddItem("xl8ceawrfu8zdneomw1h6h28d@crypho.com",
|
||||
|
@ -91,7 +94,7 @@ func TestRosterBuilder(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func checkMarshalling(t *testing.T, iq IQ) (*IQ, error) {
|
||||
func checkMarshalling(t *testing.T, iq *IQ) (*IQ, error) {
|
||||
// Marshall
|
||||
data, err := xml.Marshal(iq)
|
||||
if err != nil {
|
||||
|
|
|
@ -36,24 +36,36 @@ func TestUnmarshalIqs(t *testing.T) {
|
|||
|
||||
func TestGenerateIqId(t *testing.T) {
|
||||
t.Parallel()
|
||||
iq := stanza.NewIQ(stanza.Attrs{Id: "1"})
|
||||
iq, err := stanza.NewIQ(stanza.Attrs{Id: "1", Type: "dummy type"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create IQ: %v", err)
|
||||
}
|
||||
if iq.Id != "1" {
|
||||
t.Errorf("NewIQ replaced id with %s", iq.Id)
|
||||