diff --git a/_examples/custom_stanza/custom_stanza.go b/_examples/custom_stanza/custom_stanza.go index 46043f2..5320565 100644 --- a/_examples/custom_stanza/custom_stanza.go +++ b/_examples/custom_stanza/custom_stanza.go @@ -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{}) } diff --git a/_examples/delegation/delegation.go b/_examples/delegation/delegation.go index d07587a..d022633 100644 --- a/_examples/delegation/delegation.go +++ b/_examples/delegation/delegation.go @@ -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", diff --git a/_examples/go.mod b/_examples/go.mod index 779d698..1dd46c2 100644 --- a/_examples/go.mod +++ b/_examples/go.mod @@ -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 => ./../ diff --git a/_examples/xmpp_chat_client/go.mod b/_examples/xmpp_chat_client/go.mod index 267b416..60d9744 100644 --- a/_examples/xmpp_chat_client/go.mod +++ b/_examples/xmpp_chat_client/go.mod @@ -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 ) diff --git a/_examples/xmpp_chat_client/xmpp_chat_client.go b/_examples/xmpp_chat_client/xmpp_chat_client.go index 0d4b94b..51e3bcf 100644 --- a/_examples/xmpp_chat_client/xmpp_chat_client.go +++ b/_examples/xmpp_chat_client/xmpp_chat_client.go @@ -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) diff --git a/_examples/xmpp_component/xmpp_component.go b/_examples/xmpp_component/xmpp_component.go index 7f676cb..e3a70ce 100644 --- a/_examples/xmpp_component/xmpp_component.go +++ b/_examples/xmpp_component/xmpp_component.go @@ -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) } diff --git a/_examples/xmpp_component2/main.go b/_examples/xmpp_component2/main.go index 6bde7eb..01415a0 100644 --- a/_examples/xmpp_component2/main.go +++ b/_examples/xmpp_component2/main.go @@ -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 diff --git a/_examples/xmpp_jukebox/xmpp_jukebox.go b/_examples/xmpp_jukebox/xmpp_jukebox.go index 57137b8..b8075a2 100644 --- a/_examples/xmpp_jukebox/xmpp_jukebox.go +++ b/_examples/xmpp_jukebox/xmpp_jukebox.go @@ -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: diff --git a/_examples/xmpp_pubsub_client/xmpp_ps_client.go b/_examples/xmpp_pubsub_client/xmpp_ps_client.go index 14f0fb0..b2e9cf6 100644 --- a/_examples/xmpp_pubsub_client/xmpp_ps_client.go +++ b/_examples/xmpp_pubsub_client/xmpp_ps_client.go @@ -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") diff --git a/bi_dir_iterator.go b/bi_dir_iterator.go new file mode 100644 index 0000000..5293b1b --- /dev/null +++ b/bi_dir_iterator.go @@ -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() +} diff --git a/client.go b/client.go index a795934..bd40c38 100644 --- a/client.go +++ b/client.go @@ -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 } diff --git a/client_test.go b/client_test.go index 5af099a..d6ebf99 100644 --- a/client_test.go +++ b/client_test.go @@ -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, "") + sc.connection.Write([]byte("")) checkClientOpenStream(t, sc) // Reset stream sendBindFeature(t, sc) // Send post auth features diff --git a/cmd/fluuxmpp/send.go b/cmd/fluuxmpp/send.go index 5a10c9e..7e7ed97 100644 --- a/cmd/fluuxmpp/send.go +++ b/cmd/fluuxmpp/send.go @@ -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) diff --git a/component.go b/component.go index bd85aa2..a57b07b 100644 --- a/component.go +++ b/component.go @@ -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 } diff --git a/component_test.go b/component_test.go index 73d8947..4af4b2c 100644 --- a/component_test.go +++ b/component_test.go @@ -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. diff --git a/router.go b/router.go index 23a134e..f20af5b 100644 --- a/router.go +++ b/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 } diff --git a/router_test.go b/router_test.go index 2b5cf82..677ad0e 100644 --- a/router_test.go +++ b/router_test.go @@ -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 diff --git a/session.go b/session.go index 6b9c75a..182e32b 100644 --- a/session.go +++ b/session.go @@ -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 } diff --git a/stanza/commands.go b/stanza/commands.go index 5a3191f..6f9d3bc 100644 --- a/stanza/commands.go +++ b/stanza/commands.go @@ -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{}) +} diff --git a/stanza/commands_test.go b/stanza/commands_test.go index 4cdee0f..a72e5aa 100644 --- a/stanza/commands_test.go +++ b/stanza/commands_test.go @@ -7,34 +7,21 @@ import ( ) func TestMarshalCommands(t *testing.T) { - input := "Available Servi" + - "ces<" + - "field xmlns=\"jabber:x:data\" var=\"service\">httpdoffoffononpostgresqloffoffon" + - "onjabberdoffoffonon" - + input := "Available Services" + + "" + + "httpd" + + "offoff" + + "onon" + + "postgresql" + + "offoff" + + "onon" + + "jabberd" + + "offoff" + + "onon" var c stanza.Command err := xml.Unmarshal([]byte(input), &c) diff --git a/stanza/component.go b/stanza/component.go index 32a36b0..ba3b81e 100644 --- a/stanza/component.go +++ b/stanza/component.go @@ -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{}) } diff --git a/stanza/component_test.go b/stanza/component_test.go index 648131c..2fb1672 100644 --- a/stanza/component_test.go +++ b/stanza/component_test.go @@ -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 diff --git a/stanza/form.go b/stanza/form.go index f22c6ce..f758f74 100644 --- a/stanza/form.go +++ b/stanza/form.go @@ -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 { diff --git a/stanza/form_test.go b/stanza/form_test.go index 7346db2..ea6c613 100644 --- a/stanza/form_test.go +++ b/stanza/form_test.go @@ -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, diff --git a/stanza/iot.go b/stanza/iot.go index 5e15056..b6952e0 100644 --- a/stanza/iot.go +++ b/stanza/iot.go @@ -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{}) } diff --git a/stanza/iq.go b/stanza/iq.go index 499c261..a8cd62a 100644 --- a/stanza/iq.go +++ b/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 } diff --git a/stanza/iq_disco.go b/stanza/iq_disco.go index 7b9acac..8e50f90 100644 --- a/stanza/iq_disco.go +++ b/stanza/iq_disco.go @@ -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{}) } diff --git a/stanza/iq_disco_test.go b/stanza/iq_disco_test.go index 1f8ab8b..87b7001 100644 --- a/stanza/iq_disco_test.go +++ b/stanza/iq_disco_test.go @@ -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"). diff --git a/stanza/iq_roster.go b/stanza/iq_roster.go index 1923013..34ab5f0 100644 --- a/stanza/iq_roster.go +++ b/stanza/iq_roster.go @@ -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"` diff --git a/stanza/iq_roster_test.go b/stanza/iq_roster_test.go index ca891df..8eee77f 100644 --- a/stanza/iq_roster_test.go +++ b/stanza/iq_roster_test.go @@ -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 { diff --git a/stanza/iq_test.go b/stanza/iq_test.go index 3223566..e5af6de 100644 --- a/stanza/iq_test.go +++ b/stanza/iq_test.go @@ -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) } - iq = stanza.NewIQ(stanza.Attrs{}) + iq, err = stanza.NewIQ(stanza.Attrs{Type: "dummy type"}) + if err != nil { + t.Fatalf("failed to create IQ: %v", err) + } if iq.Id == "" { t.Error("NewIQ did not generate an Id") } - otherIq := stanza.NewIQ(stanza.Attrs{}) + otherIq, err := stanza.NewIQ(stanza.Attrs{Type: "dummy type"}) + if err != nil { + t.Fatalf("failed to create IQ: %v", err) + } if iq.Id == otherIq.Id { t.Errorf("NewIQ generated two identical ids: %s", iq.Id) } } func TestGenerateIq(t *testing.T) { - iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"}) + iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"}) + if err != nil { + t.Fatalf("failed to create IQ: %v", err) + } payload := stanza.DiscoInfo{ Identity: []stanza.Identity{ {Name: "Test Gateway", @@ -111,7 +123,10 @@ func TestErrorTag(t *testing.T) { } func TestDiscoItems(t *testing.T) { - iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"}) + iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"}) + if err != nil { + t.Fatalf("failed to create IQ: %v", err) + } payload := stanza.DiscoItems{ Node: "music", } @@ -215,8 +230,9 @@ func TestIsValid(t *testing.T) { t.Errorf("Unmarshal error: %#v (%s)", err, tcase.iq) return } - if !parsedIQ.IsValid() && !tcase.shouldErr { - t.Errorf("failed iq validation for : %s", tcase.iq) + isValid, err := parsedIQ.IsValid() + if !isValid && !tcase.shouldErr { + t.Errorf("failed validation for iq because: %s\nin test case : %s", err, tcase.iq) } }) } diff --git a/stanza/iq_version.go b/stanza/iq_version.go index 4cfbfce..4661dba 100644 --- a/stanza/iq_version.go +++ b/stanza/iq_version.go @@ -11,12 +11,18 @@ type Version struct { Name string `xml:"name,omitempty"` Version string `xml:"version,omitempty"` OS string `xml:"os,omitempty"` + // Result sets + ResultSet *ResultSet `xml:"set,omitempty"` } func (v *Version) Namespace() string { return v.XMLName.Space } +func (v *Version) GetSet() *ResultSet { + return v.ResultSet +} + // --------------- // Builder helpers @@ -41,5 +47,5 @@ func (v *Version) SetInfo(name, version, os string) *Version { // Registry init func init() { - TypeRegistry.MapExtension(PKTIQ, xml.Name{"jabber:iq:version", "query"}, Version{}) + TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "jabber:iq:version", Local: "query"}, Version{}) } diff --git a/stanza/iq_version_test.go b/stanza/iq_version_test.go index 45d68f7..bdd621e 100644 --- a/stanza/iq_version_test.go +++ b/stanza/iq_version_test.go @@ -12,8 +12,11 @@ func TestVersion_Builder(t *testing.T) { name := "Exodus" version := "0.7.0.4" os := "Windows-XP 5.01.2600" - iq := stanza.NewIQ(stanza.Attrs{Type: "result", From: "romeo@montague.net/orchard", + iq, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: "romeo@montague.net/orchard", To: "juliet@capulet.com/balcony", Id: "version_1"}) + if err != nil { + t.Fatalf("failed to create IQ: %v", err) + } iq.Version().SetInfo(name, version, os) parsedIQ, err := checkMarshalling(t, iq) diff --git a/stanza/msg_chat_markers.go b/stanza/msg_chat_markers.go index f226379..66276f9 100644 --- a/stanza/msg_chat_markers.go +++ b/stanza/msg_chat_markers.go @@ -35,8 +35,8 @@ type MarkAcknowledged struct { } func init() { - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "markable"}, Markable{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "received"}, MarkReceived{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "displayed"}, MarkDisplayed{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "acknowledged"}, MarkAcknowledged{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "markable"}, Markable{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "received"}, MarkReceived{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "displayed"}, MarkDisplayed{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "acknowledged"}, MarkAcknowledged{}) } diff --git a/stanza/msg_chat_state.go b/stanza/msg_chat_state.go index 728a52e..553a314 100644 --- a/stanza/msg_chat_state.go +++ b/stanza/msg_chat_state.go @@ -37,9 +37,9 @@ type StatePaused struct { } func init() { - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "active"}, StateActive{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "composing"}, StateComposing{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "gone"}, StateGone{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "inactive"}, StateInactive{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "paused"}, StatePaused{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "active"}, StateActive{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "composing"}, StateComposing{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "gone"}, StateGone{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "inactive"}, StateInactive{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "paused"}, StatePaused{}) } diff --git a/stanza/msg_html.go b/stanza/msg_html.go index 1b4016c..f8fbba6 100644 --- a/stanza/msg_html.go +++ b/stanza/msg_html.go @@ -18,5 +18,5 @@ type HTMLBody struct { } func init() { - TypeRegistry.MapExtension(PKTMessage, xml.Name{"http://jabber.org/protocol/xhtml-im", "html"}, HTML{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "http://jabber.org/protocol/xhtml-im", Local: "html"}, HTML{}) } diff --git a/stanza/msg_oob.go b/stanza/msg_oob.go index 039fac1..92ccadf 100644 --- a/stanza/msg_oob.go +++ b/stanza/msg_oob.go @@ -17,5 +17,5 @@ type OOB struct { } func init() { - TypeRegistry.MapExtension(PKTMessage, xml.Name{"jabber:x:oob", "x"}, OOB{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "jabber:x:oob", Local: "x"}, OOB{}) } diff --git a/stanza/msg_pubsub_event.go b/stanza/msg_pubsub_event.go index 6ee3dfb..70db228 100644 --- a/stanza/msg_pubsub_event.go +++ b/stanza/msg_pubsub_event.go @@ -210,5 +210,4 @@ func (pse *PubSubEvent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err } } - return nil } diff --git a/stanza/msg_receipts.go b/stanza/msg_receipts.go index 85c2783..71fdd52 100644 --- a/stanza/msg_receipts.go +++ b/stanza/msg_receipts.go @@ -24,6 +24,6 @@ type ReceiptReceived struct { } func init() { - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgReceipts, "request"}, ReceiptRequest{}) - TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgReceipts, "received"}, ReceiptReceived{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgReceipts, Local: "request"}, ReceiptRequest{}) + TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgReceipts, Local: "received"}, ReceiptReceived{}) } diff --git a/stanza/node_test.go b/stanza/node_test.go index aae699d..33649d9 100644 --- a/stanza/node_test.go +++ b/stanza/node_test.go @@ -8,7 +8,10 @@ import ( func TestNode_Marshal(t *testing.T) { jsonData := []byte("{\"key\":\"value\"}") - iqResp := NewIQ(Attrs{Type: "result", From: "admin@localhost", To: "test@localhost", Id: "1"}) + iqResp, err := NewIQ(Attrs{Type: "result", From: "admin@localhost", To: "test@localhost", Id: "1"}) + if err != nil { + t.Fatalf("failed to create IQ: %v", err) + } iqResp.Any = &Node{ XMLName: xml.Name{Space: "myNS", Local: "space"}, Content: string(jsonData), diff --git a/stanza/pres_muc.go b/stanza/pres_muc.go index bc0e75e..e103a48 100644 --- a/stanza/pres_muc.go +++ b/stanza/pres_muc.go @@ -144,5 +144,5 @@ func (h History) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) } func init() { - TypeRegistry.MapExtension(PKTPresence, xml.Name{"http://jabber.org/protocol/muc", "x"}, MucPresence{}) + TypeRegistry.MapExtension(PKTPresence, xml.Name{Space: "http://jabber.org/protocol/muc", Local: "x"}, MucPresence{}) } diff --git a/stanza/pubsub.go b/stanza/pubsub.go index 1cbdf16..f0aa1ce 100644 --- a/stanza/pubsub.go +++ b/stanza/pubsub.go @@ -29,12 +29,19 @@ type PubSubGeneric struct { // To use in responses to sub/unsub for instance // Subscription options Unsubscribe *SubInfo `xml:"unsubscribe,omitempty"` + + // Result sets + ResultSet *ResultSet `xml:"set,omitempty"` } func (p *PubSubGeneric) Namespace() string { return p.XMLName.Space } +func (p *PubSubGeneric) GetSet() *ResultSet { + return p.ResultSet +} + type Affiliations struct { List []Affiliation `xml:"affiliation"` Node string `xml:"node,attr,omitempty"` @@ -156,12 +163,15 @@ type PubSubOption struct { // It's a Set type IQ. // Information about the subscription and the requester are separated. subInfo contains information about the subscription. // 6.1 Subscribe to a Node -func NewSubRq(serviceId string, subInfo SubInfo) (IQ, error) { +func NewSubRq(serviceId string, subInfo SubInfo) (*IQ, error) { if e := subInfo.validate(); e != nil { - return IQ{}, e + return nil, e } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Subscribe: &subInfo, } @@ -172,12 +182,15 @@ func NewSubRq(serviceId string, subInfo SubInfo) (IQ, error) { // It's a Set type IQ // Information about the subscription and the requester are separated. subInfo contains information about the subscription. // 6.2 Unsubscribe from a Node -func NewUnsubRq(serviceId string, subInfo SubInfo) (IQ, error) { +func NewUnsubRq(serviceId string, subInfo SubInfo) (*IQ, error) { if e := subInfo.validate(); e != nil { - return IQ{}, e + return nil, e } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Unsubscribe: &subInfo, } @@ -188,12 +201,15 @@ func NewUnsubRq(serviceId string, subInfo SubInfo) (IQ, error) { // It's a Get type IQ // Information about the subscription and the requester are separated. subInfo contains information about the subscription. // 6.3 Configure Subscription Options -func NewSubOptsRq(serviceId string, subInfo SubInfo) (IQ, error) { +func NewSubOptsRq(serviceId string, subInfo SubInfo) (*IQ, error) { if e := subInfo.validate(); e != nil { - return IQ{}, e + return nil, e } - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ SubOptions: &SubOptions{ SubInfo: subInfo, @@ -205,15 +221,18 @@ func NewSubOptsRq(serviceId string, subInfo SubInfo) (IQ, error) { // NewFormSubmission builds a form submission pubsub IQ // Information about the subscription and the requester are separated. subInfo contains information about the subscription. // 6.3.5 Form Submission -func NewFormSubmission(serviceId string, subInfo SubInfo, form *Form) (IQ, error) { +func NewFormSubmission(serviceId string, subInfo SubInfo, form *Form) (*IQ, error) { if e := subInfo.validate(); e != nil { - return IQ{}, e + return nil, e } if form.Type != FormTypeSubmit { - return IQ{}, errors.New("form type was expected to be submit but was : " + form.Type) + return nil, errors.New("form type was expected to be submit but was : " + form.Type) } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ SubOptions: &SubOptions{ SubInfo: subInfo, @@ -229,14 +248,17 @@ func NewFormSubmission(serviceId string, subInfo SubInfo, form *Form) (IQ, error // since the value of the element's 'node' attribute specifies the desired NodeID and // the value of the element's 'jid' attribute specifies the subscriber's JID // 6.3.7 Subscribe and Configure -func NewSubAndConfig(serviceId string, subInfo SubInfo, form *Form) (IQ, error) { +func NewSubAndConfig(serviceId string, subInfo SubInfo, form *Form) (*IQ, error) { if e := subInfo.validate(); e != nil { - return IQ{}, e + return nil, e } if form.Type != FormTypeSubmit { - return IQ{}, errors.New("form type was expected to be submit but was : " + form.Type) + return nil, errors.New("form type was expected to be submit but was : " + form.Type) + } + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) iq.Payload = &PubSubGeneric{ Subscribe: &subInfo, SubOptions: &SubOptions{ @@ -251,8 +273,11 @@ func NewSubAndConfig(serviceId string, subInfo SubInfo, form *Form) (IQ, error) // NewItemsRequest creates a request to query existing items from a node. // Specify a "maxItems" value to request only the last maxItems items. If 0, requests all items. // 6.5.2 Requesting All List AND 6.5.7 Requesting the Most Recent List -func NewItemsRequest(serviceId string, node string, maxItems int) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewItemsRequest(serviceId string, node string, maxItems int) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Items: &Items{Node: node}, } @@ -266,8 +291,11 @@ func NewItemsRequest(serviceId string, node string, maxItems int) (IQ, error) { // NewItemsRequest creates a request to get a specific item from a node. // 6.5.8 Requesting a Particular Item -func NewSpecificItemRequest(serviceId, node, itemId string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewSpecificItemRequest(serviceId, node, itemId string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Items: &Items{Node: node, List: []Item{ @@ -281,13 +309,16 @@ func NewSpecificItemRequest(serviceId, node, itemId string) (IQ, error) { } // NewPublishItemRq creates a request to publish a single item to a node identified by its provided ID -func NewPublishItemRq(serviceId, nodeID, pubItemID string, item Item) (IQ, error) { +func NewPublishItemRq(serviceId, nodeID, pubItemID string, item Item) (*IQ, error) { // "The element MUST possess a 'node' attribute, specifying the NodeID of the node." if strings.TrimSpace(nodeID) == "" { - return IQ{}, errors.New("cannot publish without a target node ID") + return nil, errors.New("cannot publish without a target node ID") } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Publish: &Publish{Node: nodeID, Items: []Item{item}}, } @@ -306,13 +337,16 @@ func NewPublishItemRq(serviceId, nodeID, pubItemID string, item Item) (IQ, error // NewPublishItemOptsRq creates a request to publish items to a node identified by its provided ID, along with configuration options // A pubsub service MAY support the ability to specify options along with a publish request //(if so, it MUST advertise support for the "http://jabber.org/protocol/pubsub#publish-options" feature). -func NewPublishItemOptsRq(serviceId, nodeID string, items []Item, options *PublishOptions) (IQ, error) { +func NewPublishItemOptsRq(serviceId, nodeID string, items []Item, options *PublishOptions) (*IQ, error) { // "The element MUST possess a 'node' attribute, specifying the NodeID of the node." if strings.TrimSpace(nodeID) == "" { - return IQ{}, errors.New("cannot publish without a target node ID") + return nil, errors.New("cannot publish without a target node ID") } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Publish: &Publish{Node: nodeID, Items: items}, PublishOptions: options, @@ -324,13 +358,16 @@ func NewPublishItemOptsRq(serviceId, nodeID string, items []Item, options *Publi // NewDelItemFromNode creates a request to delete and item from a node, given its id. // To delete an item, the publisher sends a retract request. // This helper function follows 7.2 Delete an Item from a Node -func NewDelItemFromNode(serviceId, nodeID, itemId string, notify *bool) (IQ, error) { +func NewDelItemFromNode(serviceId, nodeID, itemId string, notify *bool) (*IQ, error) { // "The element MUST possess a 'node' attribute, specifying the NodeID of the node." if strings.TrimSpace(nodeID) == "" { - return IQ{}, errors.New("cannot delete item without a target node ID") + return nil, errors.New("cannot delete item without a target node ID") } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Retract: &Retract{Node: nodeID, Items: []Item{{Id: itemId}}, Notify: notify}, } @@ -339,8 +376,11 @@ func NewDelItemFromNode(serviceId, nodeID, itemId string, notify *bool) (IQ, err // NewCreateAndConfigNode makes a request for node creation that has the desired node configuration. // See 8.1.3 Create and Configure a Node -func NewCreateAndConfigNode(serviceId, nodeID string, confForm *Form) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) +func NewCreateAndConfigNode(serviceId, nodeID string, confForm *Form) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Create: &Create{Node: nodeID}, Configure: &Configure{Form: confForm}, @@ -350,8 +390,11 @@ func NewCreateAndConfigNode(serviceId, nodeID string, confForm *Form) (IQ, error // NewCreateNode builds a request to create a node on the service referenced by "serviceId" // See 8.1 Create a Node -func NewCreateNode(serviceId, nodeName string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) +func NewCreateNode(serviceId, nodeName string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Create: &Create{Node: nodeName}, } @@ -361,8 +404,11 @@ func NewCreateNode(serviceId, nodeName string) (IQ, error) { // NewRetrieveAllSubsRequest builds a request to retrieve all subscriptions from all nodes // In order to make the request, the requesting entity MUST send an IQ-get whose // child contains an empty element with no attributes. -func NewRetrieveAllSubsRequest(serviceId string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewRetrieveAllSubsRequest(serviceId string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Subscriptions: &Subscriptions{}, } @@ -371,8 +417,11 @@ func NewRetrieveAllSubsRequest(serviceId string) (IQ, error) { // NewRetrieveAllAffilsRequest builds a request to retrieve all affiliations from all nodes // In order to make the request of the service, the requesting entity includes an empty element with no attributes. -func NewRetrieveAllAffilsRequest(serviceId string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewRetrieveAllAffilsRequest(serviceId string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubGeneric{ Affiliations: &Affiliations{}, } diff --git a/stanza/pubsub_owner.go b/stanza/pubsub_owner.go index 9adfbef..2f63bbb 100644 --- a/stanza/pubsub_owner.go +++ b/stanza/pubsub_owner.go @@ -9,12 +9,18 @@ import ( type PubSubOwner struct { XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub#owner pubsub"` OwnerUseCase OwnerUseCase + // Result sets + ResultSet *ResultSet `xml:"set,omitempty"` } func (pso *PubSubOwner) Namespace() string { return pso.XMLName.Space } +func (pso *PubSubOwner) GetSet() *ResultSet { + return pso.ResultSet +} + type OwnerUseCase interface { UseCase() string } @@ -112,8 +118,11 @@ const ( // NewConfigureNode creates a request to configure a node on the given service. // A form will be returned by the service, to which the user must respond using for instance the NewFormSubmission function. // See 8.2 Configure a Node -func NewConfigureNode(serviceId, nodeName string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewConfigureNode(serviceId, nodeName string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubOwner{ OwnerUseCase: &ConfigureOwner{Node: nodeName}, } @@ -122,11 +131,14 @@ func NewConfigureNode(serviceId, nodeName string) (IQ, error) { // NewDelNode creates a request to delete node "nodeID" from the "serviceId" service // See 8.4 Delete a Node -func NewDelNode(serviceId, nodeID string) (IQ, error) { +func NewDelNode(serviceId, nodeID string) (*IQ, error) { if strings.TrimSpace(nodeID) == "" { - return IQ{}, errors.New("cannot delete a node without a target node ID") + return nil, errors.New("cannot delete a node without a target node ID") + } + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err } - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) iq.Payload = &PubSubOwner{ OwnerUseCase: &DeleteOwner{Node: nodeID}, } @@ -135,8 +147,11 @@ func NewDelNode(serviceId, nodeID string) (IQ, error) { // NewPurgeAllItems creates a new purge request for the "nodeId" node, at "serviceId" service // See 8.5 Purge All Node Items -func NewPurgeAllItems(serviceId, nodeId string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) +func NewPurgeAllItems(serviceId, nodeId string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubOwner{ OwnerUseCase: &PurgeOwner{Node: nodeId}, } @@ -145,8 +160,11 @@ func NewPurgeAllItems(serviceId, nodeId string) (IQ, error) { // NewRequestDefaultConfig build a request to ask the service for the default config of its nodes // See 8.3 Request Default Node Configuration Options -func NewRequestDefaultConfig(serviceId string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewRequestDefaultConfig(serviceId string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubOwner{ OwnerUseCase: &DefaultOwner{}, } @@ -177,8 +195,11 @@ func NewApproveSubRequest(serviceId, reqID string, apprForm *Form) (Message, err // NewGetPendingSubRequests creates a new request for all pending subscriptions to all their nodes at a service // This feature MUST be implemented using the Ad-Hoc Commands (XEP-0050) protocol // 8.7 Process Pending Subscription Requests -func NewGetPendingSubRequests(serviceId string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) +func NewGetPendingSubRequests(serviceId string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &Command{ // the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending" Node: "http://jabber.org/protocol/pubsub#get-pending", @@ -191,9 +212,9 @@ func NewGetPendingSubRequests(serviceId string) (IQ, error) { // Upon receiving the data form for managing subscription requests, the owner then MAY request pending subscription // approval requests for a given node. // See 8.7.4 Per-Node Request -func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error) { +func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (*IQ, error) { if sessionId == "" { - return IQ{}, errors.New("the sessionId must be maintained for the command") + return nil, errors.New("the sessionId must be maintained for the command") } form := &Form{ @@ -202,12 +223,15 @@ func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error } data, err := xml.Marshal(form) if err != nil { - return IQ{}, err + return nil, err } var n Node xml.Unmarshal(data, &n) - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &Command{ // the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending" Node: "http://jabber.org/protocol/pubsub#get-pending", @@ -221,16 +245,22 @@ func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error // NewSubListRequest creates a request to list subscriptions of the client, for all nodes at the service. // It's a Get type IQ // 8.8.1 Retrieve Subscriptions -func NewSubListRqPl(serviceId, nodeID string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewSubListRqPl(serviceId, nodeID string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubOwner{ OwnerUseCase: &SubscriptionsOwner{Node: nodeID}, } return iq, nil } -func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwner) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) +func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwner) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubOwner{ OwnerUseCase: &SubscriptionsOwner{Node: nodeID, Subscriptions: subs}, } @@ -239,8 +269,11 @@ func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwne // NewModifAffiliationRequest creates a request to either modify one or more affiliations, or delete one or more affiliations // 8.9.2 Modify Affiliation & 8.9.2.4 Multiple Simultaneous Modifications & 8.9.3 Delete an Entity (just set the status to "none") -func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []AffiliationOwner) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) +func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []AffiliationOwner) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubOwner{ OwnerUseCase: &AffiliationsOwner{ Node: nodeID, @@ -252,8 +285,11 @@ func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []Affiliatio // NewAffiliationListRequest creates a request to list all affiliated entities // See 8.9.1 Retrieve List List -func NewAffiliationListRequest(serviceId, nodeID string) (IQ, error) { - iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) +func NewAffiliationListRequest(serviceId, nodeID string) (*IQ, error) { + iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId}) + if err != nil { + return nil, err + } iq.Payload = &PubSubOwner{ OwnerUseCase: &AffiliationsOwner{ Node: nodeID, @@ -265,12 +301,15 @@ func NewAffiliationListRequest(serviceId, nodeID string) (IQ, error) { // NewFormSubmission builds a form submission pubsub IQ, in the Owner namespace // This is typically used to respond to a form issued by the server when configuring a node. // See 8.2.4 Form Submission -func NewFormSubmissionOwner(serviceId, nodeName string, fields []*Field) (IQ, error) { +func NewFormSubmissionOwner(serviceId, nodeName string, fields []*Field) (*IQ, error) { if serviceId == "" || nodeName == "" { - return IQ{}, errors.New("serviceId and nodeName must be filled for this request to be valid") + return nil, errors.New("serviceId and nodeName must be filled for this request to be valid") } - submitConf := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + submitConf, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId}) + if err != nil { + return nil, err + } submitConf.Payload = &PubSubOwner{ OwnerUseCase: &ConfigureOwner{ Node: nodeName, @@ -308,6 +347,17 @@ func (iq *IQ) GetFormFields() (map[string]*Field, error) { fieldMap[elt.Var] = elt } return fieldMap, nil + + case *Command: + fieldMap := make(map[string]*Field) + co, ok := payload.CommandElement.(*Form) + if !ok { + return nil, errors.New("this IQ does not contain a command payload with a form") + } + for _, elt := range co.Fields { + fieldMap[elt.Var] = elt + } + return fieldMap, nil default: if iq.Any != nil { fieldMap := make(map[string]*Field) diff --git a/stanza/pubsub_owner_test.go b/stanza/pubsub_owner_test.go index b7cf6db..6ff1c1b 100644 --- a/stanza/pubsub_owner_test.go +++ b/stanza/pubsub_owner_test.go @@ -16,6 +16,9 @@ func TestNewConfigureNode(t *testing.T) { " " subR, err := stanza.NewConfigureNode("pubsub.shakespeare.lit", "princely_musings") + if err != nil { + t.Fatalf("failed to create a configure node request: %v", err) + } subR.Id = "config1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -129,6 +132,9 @@ func TestNewRequestDefaultConfig(t *testing.T) { " " subR, err := stanza.NewRequestDefaultConfig("pubsub.shakespeare.lit") + if err != nil { + t.Fatalf("failed to create a default config request: %v", err) + } subR.Id = "def1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -239,6 +245,9 @@ func TestNewDelNode(t *testing.T) { " " subR, err := stanza.NewDelNode("pubsub.shakespeare.lit", "princely_musings") + if err != nil { + t.Fatalf("failed to create a node delete request: %v", err) + } subR.Id = "delete1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -311,6 +320,9 @@ func TestNewPurgeAllItems(t *testing.T) { " " subR, err := stanza.NewPurgeAllItems("pubsub.shakespeare.lit", "princely_musings") + if err != nil { + t.Fatalf("failed to create a purge all items request: %v", err) + } subR.Id = "purge1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -367,6 +379,9 @@ func TestNewApproveSubRequest(t *testing.T) { } subR, err := stanza.NewApproveSubRequest("pubsub.shakespeare.lit", "approve1", apprForm) + if err != nil { + t.Fatalf("failed to create a sub approval request: %v", err) + } subR.Id = "approve1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -404,6 +419,9 @@ func TestNewGetPendingSubRequests(t *testing.T) { " " subR, err := stanza.NewGetPendingSubRequests("pubsub.shakespeare.lit") + if err != nil { + t.Fatalf("failed to create a get pending subs request: %v", err) + } subR.Id = "pending1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -461,12 +479,12 @@ func TestNewGetPendingSubRequestsResp(t *testing.T) { _, ok := respIQ.Payload.(*stanza.Command) if !ok { - errors.New("this iq payload is not a command") + t.Fatal("this iq payload is not a command") } fMap, err := respIQ.GetFormFields() if err != nil || len(fMap) != 2 { - errors.New("could not parse command form fields") + t.Fatal("could not parse command form fields") } } @@ -485,6 +503,9 @@ func TestNewApprovePendingSubRequest(t *testing.T) { subR, err := stanza.NewApprovePendingSubRequest("pubsub.shakespeare.lit", "pubsub-get-pending:20031021T150901Z-600", "princely_musings") + if err != nil { + t.Fatalf("failed to create a approve pending sub request: %v", err) + } subR.Id = "pending2" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -524,6 +545,9 @@ func TestNewSubListRqPl(t *testing.T) { " " subR, err := stanza.NewSubListRqPl("pubsub.shakespeare.lit", "princely_musings") + if err != nil { + t.Fatalf("failed to create a sub list request: %v", err) + } subR.Id = "subman1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -575,7 +599,7 @@ func TestNewSubListRqPlResp(t *testing.T) { pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner) if !ok { - errors.New("this iq payload is not a command") + t.Fatal("this iq payload is not a command") } subs, ok := pubsub.OwnerUseCase.(*stanza.SubscriptionsOwner) @@ -599,6 +623,9 @@ func TestNewAffiliationListRequest(t *testing.T) { " " subR, err := stanza.NewAffiliationListRequest("pubsub.shakespeare.lit", "princely_musings") + if err != nil { + t.Fatalf("failed to create an affiliations list request: %v", err) + } subR.Id = "ent1" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -648,7 +675,7 @@ func TestNewAffiliationListRequestResp(t *testing.T) { pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner) if !ok { - errors.New("this iq payload is not a command") + t.Fatal("this iq payload is not a command") } affils, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner) @@ -690,6 +717,9 @@ func TestNewModifAffiliationRequest(t *testing.T) { } subR, err := stanza.NewModifAffiliationRequest("pubsub.shakespeare.lit", "princely_musings", affils) + if err != nil { + t.Fatalf("failed to create a modif affiliation request: %v", err) + } subR.Id = "ent3" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -833,6 +863,10 @@ func TestNewFormSubmissionOwner(t *testing.T) { {Var: "pubsub#access_model", ValuesList: []string{"roster"}}, {Var: "pubsub#roster_groups_allowed", ValuesList: []string{"friends", "servants", "courtiers"}}, }) + if err != nil { + t.Fatalf("failed to create a form submission request: %v", err) + } + subR.Id = "config2" if err != nil { t.Fatalf("Could not create request : %s", err) @@ -878,7 +912,7 @@ func getPubSubOwnerPayload(response string) (*stanza.PubSubOwner, error) { pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner) if !ok { - errors.New("this iq payload is not a pubsub of the owner namespace") + return nil, errors.New("this iq payload is not a pubsub of the owner namespace") } return pubsub, nil diff --git a/stanza/pubsub_test.go b/stanza/pubsub_test.go index 6b413c4..ac227d2 100644 --- a/stanza/pubsub_test.go +++ b/stanza/pubsub_test.go @@ -40,6 +40,9 @@ func TestNewSubRequest(t *testing.T) { Node: "princely_musings", Jid: "francisco@denmark.lit", } subR, err := stanza.NewSubRq("pubsub.shakespeare.lit", subInfo) + if err != nil { + t.Fatalf("failed to create a sub request: %v", err) + } subR.Id = "sub1" if err != nil { t.Fatalf("Could not create a sub request : %s", err) @@ -96,6 +99,9 @@ func TestNewUnSubRequest(t *testing.T) { Node: "princely_musings", Jid: "francisco@denmark.lit", } subR, err := stanza.NewUnsubRq("pubsub.shakespeare.lit", subInfo) + if err != nil { + t.Fatalf("failed to create an unsub request: %v", err) + } subR.Id = "unsub1" if err != nil { t.Fatalf("Could not create a sub request : %s", err) @@ -157,6 +163,9 @@ func TestNewSubOptsRq(t *testing.T) { Node: "princely_musings", Jid: "francisco@denmark.lit", } subR, err := stanza.NewSubOptsRq("pubsub.shakespeare.lit", subInfo) + if err != nil { + t.Fatalf("failed to create a sub options request: %v", err) + } subR.Id = "options1" if err != nil { t.Fatalf("Could not create a sub request : %s", err) @@ -264,6 +273,9 @@ func TestNewFormSubmission(t *testing.T) { } subR, err := stanza.NewFormSubmission("pubsub.shakespeare.lit", subInfo, submitFormExample) + if err != nil { + t.Fatalf("failed to create a form submission request: %v", err) + } subR.Id = "options2" if err != nil { t.Fatalf("Could not create a sub request : %s", err) @@ -313,6 +325,9 @@ func TestNewSubAndConfig(t *testing.T) { } subR, err := stanza.NewSubAndConfig("pubsub.shakespeare.lit", subInfo, submitFormExample) + if err != nil { + t.Fatalf("failed to create a sub and config request: %v", err) + } subR.Id = "sub1" if err != nil { t.Fatalf("Could not create a sub request : %s", err) @@ -482,6 +497,9 @@ func TestNewSpecificItemRequest(t *testing.T) { " " subR, err := stanza.NewSpecificItemRequest("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901") + if err != nil { + t.Fatalf("failed to create a specific item request: %v", err) + } subR.Id = "items3" if err != nil { t.Fatalf("Could not create an items request : %s", err) @@ -638,6 +656,9 @@ func TestNewDelItemFromNode(t *testing.T) { " " subR, err := stanza.NewDelItemFromNode("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901", nil) + if err != nil { + t.Fatalf("failed to create a delete item from node request: %v", err) + } subR.Id = "retract1" if err != nil { t.Fatalf("Could not create a del item request : %s", err) @@ -677,6 +698,9 @@ func TestNewCreateNode(t *testing.T) { " " subR, err := stanza.NewCreateNode("pubsub.shakespeare.lit", "princely_musings") + if err != nil { + t.Fatalf("failed to create a create node request: %v", err) + } subR.Id = "create1" if err != nil { t.Fatalf("Could not create a create node request : %s", err) @@ -748,6 +772,10 @@ func TestNewCreateAndConfigNode(t *testing.T) { {Var: "pubsub#max_payload_size", ValuesList: []string{"1028"}}, }, }) + + if err != nil { + t.Fatalf("failed to create a create and config node request: %v", err) + } subR.Id = "create1" if err != nil { t.Fatalf("Could not create a create node request : %s", err) @@ -796,6 +824,9 @@ func TestNewRetrieveAllSubsRequest(t *testing.T) { " " subR, err := stanza.NewRetrieveAllSubsRequest("pubsub.shakespeare.lit") + if err != nil { + t.Fatalf("failed to create a get all subs request: %v", err) + } subR.Id = "subscriptions1" if err != nil { t.Fatalf("Could not create a create node request : %s", err) @@ -856,6 +887,9 @@ func TestNewRetrieveAllAffilsRequest(t *testing.T) { " " subR, err := stanza.NewRetrieveAllAffilsRequest("pubsub.shakespeare.lit") + if err != nil { + t.Fatalf("failed to create a get all affiliations request: %v", err) + } subR.Id = "affil1" if err != nil { t.Fatalf("Could not create retreive all affiliations request : %s", err) @@ -914,7 +948,7 @@ func getPubSubGenericPayload(response string) (*stanza.PubSubGeneric, error) { pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric) if !ok { - errors.New("this iq payload is not a pubsub") + return nil, errors.New("this iq payload is not a pubsub") } return pubsub, nil diff --git a/stanza/results_sets.go b/stanza/results_sets.go new file mode 100644 index 0000000..6cbab0f --- /dev/null +++ b/stanza/results_sets.go @@ -0,0 +1,29 @@ +package stanza + +import ( + "encoding/xml" +) + +// Support for XEP-0059 +// See https://xmpp.org/extensions/xep-0059 +const ( + // Common but not only possible namespace for query blocks in a result set context + NSQuerySet = "jabber:iq:search" +) + +type ResultSet struct { + XMLName xml.Name `xml:"http://jabber.org/protocol/rsm set"` + After *string `xml:"after,omitempty"` + Before *string `xml:"before,omitempty"` + Count *int `xml:"count,omitempty"` + First *First `xml:"first,omitempty"` + Index *int `xml:"index,omitempty"` + Last *string `xml:"last,omitempty"` + Max *int `xml:"max,omitempty"` +} + +type First struct { + XMLName xml.Name `xml:"first"` + Content string + Index *int `xml:"index,attr,omitempty"` +} diff --git a/stanza/results_sets_test.go b/stanza/results_sets_test.go new file mode 100644 index 0000000..2ddd123 --- /dev/null +++ b/stanza/results_sets_test.go @@ -0,0 +1,28 @@ +package stanza_test + +import ( + "gosrc.io/xmpp/stanza" + "testing" +) + +// Limiting the number of items +func TestNewResultSetReq(t *testing.T) { + expectedRq := " " + + " " + + "urn:xmpp:mam:2 2010-08-07T00:00:00Z " + + " 10 " + + maxVal := 10 + rs := &stanza.ResultSet{ + Max: &maxVal, + } + + // TODO when Mam is implemented + _ = expectedRq + _ = rs +} + +func TestUnmarshalResultSeqReq(t *testing.T) { + // TODO when Mam is implemented + +} diff --git a/stanza/sasl_auth.go b/stanza/sasl_auth.go index 29648ee..9dfe557 100644 --- a/stanza/sasl_auth.go +++ b/stanza/sasl_auth.go @@ -69,12 +69,18 @@ type Bind struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` Resource string `xml:"resource,omitempty"` Jid string `xml:"jid,omitempty"` + // Result sets + ResultSet *ResultSet `xml:"set,omitempty"` } func (b *Bind) Namespace() string { return b.XMLName.Space } +func (b *Bind) GetSet() *ResultSet { + return b.ResultSet +} + // ============================================================================ // Session (Obsolete) @@ -89,12 +95,18 @@ func (b *Bind) Namespace() string { type StreamSession struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"` Optional bool // If element does exist, it mean we are not required to open session + // Result sets + ResultSet *ResultSet `xml:"set,omitempty"` } func (s *StreamSession) Namespace() string { return s.XMLName.Space } +func (s *StreamSession) GetSet() *ResultSet { + return s.ResultSet +} + func (s *StreamSession) IsOptional() bool { if s.XMLName.Local == "session" { return s.Optional diff --git a/stanza/sasl_auth_test.go b/stanza/sasl_auth_test.go index d9ba1dc..3e37453 100644 --- a/stanza/sasl_auth_test.go +++ b/stanza/sasl_auth_test.go @@ -28,7 +28,10 @@ func TestSessionFeatures(t *testing.T) { // Check that the Session tag can be used in IQ decoding func TestSessionIQ(t *testing.T) { - iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, Id: "session"}) + iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, Id: "session"}) + if err != nil { + t.Fatalf("failed to create IQ: %v", err) + } iq.Payload = &stanza.StreamSession{XMLName: xml.Name{Local: "session"}, Optional: true} data, err := xml.Marshal(iq) diff --git a/stanza/xmpp_test.go b/stanza/xmpp_test.go index 473616a..b39613b 100644 --- a/stanza/xmpp_test.go +++ b/stanza/xmpp_test.go @@ -16,7 +16,7 @@ var reInsideWhtsp = regexp.MustCompile(`[\s\p{Zs}]`) // ============================================================================ // Marshaller / unmarshaller test -func checkMarshalling(t *testing.T, iq stanza.IQ) (*stanza.IQ, error) { +func checkMarshalling(t *testing.T, iq *stanza.IQ) (*stanza.IQ, error) { // Marshall data, err := xml.Marshal(iq) if err != nil { diff --git a/stream_manager.go b/stream_manager.go index 18e1434..ebef1fa 100644 --- a/stream_manager.go +++ b/stream_manager.go @@ -27,7 +27,7 @@ type StreamClient interface { Connect() error Resume(state SMState) error Send(packet stanza.Packet) error - SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) + SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) SendRaw(packet string) error Disconnect() error SetHandler(handler EventHandler) @@ -37,7 +37,7 @@ type StreamClient interface { // It is mostly use in callback to pass a limited subset of the stream client interface type Sender interface { Send(packet stanza.Packet) error - SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) + SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) SendRaw(packet string) error } diff --git a/tcp_server_mock.go b/tcp_server_mock.go index 1a4f92e..117513a 100644 --- a/tcp_server_mock.go +++ b/tcp_server_mock.go @@ -130,13 +130,16 @@ func respondToIQ(t *testing.T, sc *ServerConn) { t.Fatalf("failed to receive IQ : %s", err.Error()) } - if !iqReq.IsValid() { + if vld, _ := iqReq.IsValid(); !vld { mockIQError(sc.connection) return } // Crafting response - iqResp := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: iqReq.To, To: iqReq.From, Id: iqReq.Id, Lang: "en"}) + iqResp, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: iqReq.To, To: iqReq.From, Id: iqReq.Id, Lang: "en"}) + if err != nil { + t.Fatalf("failed to create iqResp: %v", err) + } disco := iqResp.DiscoInfo() disco.AddFeatures("vcard-temp", `http://jabber.org/protocol/address`) diff --git a/transport.go b/transport.go index abf7f4a..fa124ea 100644 --- a/transport.go +++ b/transport.go @@ -9,8 +9,8 @@ import ( "strings" ) -var ErrTransportProtocolNotSupported = errors.New("Transport protocol not supported") -var ErrTLSNotSupported = errors.New("Transport does not support StartTLS") +var ErrTransportProtocolNotSupported = errors.New("transport protocol not supported") +var ErrTLSNotSupported = errors.New("transport does not support StartTLS") // TODO: rename to transport config? type TransportConfiguration struct { @@ -67,7 +67,7 @@ func NewClientTransport(config TransportConfiguration) Transport { // will be returned. func NewComponentTransport(config TransportConfiguration) (Transport, error) { if strings.HasPrefix(config.Address, "ws:") || strings.HasPrefix(config.Address, "wss:") { - return nil, fmt.Errorf("Components only support XMPP transport: %w", ErrTransportProtocolNotSupported) + return nil, fmt.Errorf("components only support XMPP transport: %w", ErrTransportProtocolNotSupported) } config.Address = ensurePort(config.Address, 5222) diff --git a/xmpp_transport.go b/xmpp_transport.go index 6e1209f..092b95d 100644 --- a/xmpp_transport.go +++ b/xmpp_transport.go @@ -113,7 +113,7 @@ func (t *XMPPTransport) Ping() error { return err } if n != 1 { - return errors.New("Could not write ping") + return errors.New("could not write ping") } return nil }