Repository: FluuxIO/go-xmpp
Pull request #96 (2019-07-30 08:44:21)

Stanza builder for Disco


Patch:
diff --git a/README.md b/README.md
index cf546ae..2488de9 100644
--- a/README.md
+++ b/README.md
@@ -8,37 +8,11 @@ The goal is to make simple to write simple XMPP clients and components:
 
 - For automation (like for example monitoring of an XMPP service),
 - For building connected "things" by plugging them on an XMPP server,
-- For writing simple chatbot to control a service or a thing,
+- For writing simple chatbot to control a service or a thing.
 - For writing XMPP servers components.
 
 The library is designed to have minimal dependencies. For now, the library does not depend on any other library.
 
-## Configuration and connection
-
-### Allowing Insecure TLS connection during development
-
-It is not recommended to disable the check for domain name and certificate chain. Doing so would open your client
-to man-in-the-middle attacks.
-
-However, in development, XMPP servers often use self-signed certificates. In that situation, it is better to add the
-root CA that signed the certificate to your trusted list of root CA. It avoids changing the code and limit the risk
-of shipping an insecure client to production.
-
-That said, if you really want to allow your client to trust any TLS certificate, you can customize Go standard 
-`tls.Config` and set it in Config struct.
-
-Here is an example code to configure a client to allow connecting to a server with self-signed certificate. Note the 
-`InsecureSkipVerify` option. When using this `tls.Config` option, all the checks on the certificate are skipped.
-
-```go
-config := xmpp.Config{
-	Address:      "localhost:5222",
-	Jid:          "test@localhost",
-	Password:     "test",
-	TLSConfig:     tls.Config{InsecureSkipVerify: true},
-}
-```
-
 ## Supported specifications
 
 ### Clients
diff --git a/_examples/delegation/delegation.go b/_examples/delegation/delegation.go
index 31c18d3..fcf397a 100644
--- a/_examples/delegation/delegation.go
+++ b/_examples/delegation/delegation.go
@@ -99,26 +99,9 @@ func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
 }
 
 func discoInfoRoot(iqResp *stanza.IQ, opts xmpp.ComponentOptions) {
-	// Higher level discovery
-	identity := stanza.Identity{
-		Name:     opts.Name,
-		Category: opts.Category,
-		Type:     opts.Type,
-	}
-	payload := stanza.DiscoInfo{
-		XMLName: xml.Name{
-			Space: stanza.NSDiscoInfo,
-			Local: "query",
-		},
-		Identity: []stanza.Identity{identity},
-		Features: []stanza.Feature{
-			{Var: stanza.NSDiscoInfo},
-			{Var: stanza.NSDiscoItems},
-			{Var: "jabber:iq:version"},
-			{Var: "urn:xmpp:delegation:1"},
-		},
-	}
-	iqResp.Payload = &payload
+	disco := iqResp.DiscoInfo()
+	disco.AddIdentity(opts.Name, opts.Category, opts.Type)
+	disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
 }
 
 func discoInfoPubSub(iqResp *stanza.IQ) {
diff --git a/_examples/go.mod b/_examples/go.mod
index 87db1c4..48a6241 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -3,9 +3,10 @@ module gosrc.io/xmpp/_examples
 go 1.12
 
 require (
+	github.com/google/go-cmp v0.3.0 // indirect
 	github.com/processone/mpg123 v1.0.0
 	github.com/processone/soundcloud v1.0.0
-	gosrc.io/xmpp v0.1.0
+	gosrc.io/xmpp v0.1.1
 )
 
 replace gosrc.io/xmpp => ./../
diff --git a/_examples/go.sum b/_examples/go.sum
index 6b1e299..26a290b 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -1,5 +1,10 @@
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/processone/mpg123 v1.0.0 h1:o2WOyGZRM255or1Zc/LtF/jARn51B+9aQl72Qace0GA=
 github.com/processone/mpg123 v1.0.0/go.mod h1:X/FeL+h8vD1bYsG9tIWV3M2c4qNTZOficyvPVBP08go=
+github.com/processone/soundcloud v1.0.0 h1:/+i6+Yveb7Y6IFGDSkesYI+HddblzcRTQClazzVHxoE=
 github.com/processone/soundcloud v1.0.0/go.mod h1:kDLeWpkRtN3C8kIReQdxoiRi92P9xR6yW6qLOJnNWfY=
+golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
 golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/_examples/xmpp_component/xmpp_component.go b/_examples/xmpp_component/xmpp_component.go
index 4bc906b..77a3099 100644
--- a/_examples/xmpp_component/xmpp_component.go
+++ b/_examples/xmpp_component/xmpp_component.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"encoding/xml"
 	"fmt"
 	"log"
 
@@ -61,25 +60,9 @@ func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
 	}
 
 	iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
-	identity := stanza.Identity{
-		Name:     opts.Name,
-		Category: opts.Category,
-		Type:     opts.Type,
-	}
-	payload := stanza.DiscoInfo{
-		XMLName: xml.Name{
-			Space: stanza.NSDiscoInfo,
-			Local: "query",
-		},
-		Identity: []stanza.Identity{identity},
-		Features: []stanza.Feature{
-			{Var: stanza.NSDiscoInfo},
-			{Var: stanza.NSDiscoItems},
-			{Var: "jabber:iq:version"},
-			{Var: "urn:xmpp:delegation:1"},
-		},
-	}
-	iqResp.Payload = &payload
+	disco := iqResp.DiscoInfo()
+	disco.AddIdentity(opts.Name, opts.Category, opts.Type)
+	disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
 	_ = c.Send(iqResp)
 }
 
@@ -97,16 +80,11 @@ func discoItems(c xmpp.Sender, p stanza.Packet) {
 	}
 
 	iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+	items := iqResp.DiscoItems()
 
-	var payload stanza.DiscoItems
 	if discoItems.Node == "" {
-		payload = stanza.DiscoItems{
-			Items: []stanza.DiscoItem{
-				{Name: "test node", JID: "service.localhost", Node: "node1"},
-			},
-		}
+		items.AddItem("service.localhost", "node1", "test node")
 	}
-	iqResp.Payload = &payload
 	_ = c.Send(iqResp)
 }
 
@@ -118,9 +96,6 @@ func handleVersion(c xmpp.Sender, p stanza.Packet) {
 	}
 
 	iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
-	var payload stanza.Version
-	payload.Name = "Fluux XMPP Component"
-	payload.Version = "0.0.1"
-	iq.Payload = &payload
+	iqResp.Version().SetInfo("Fluux XMPP Component", "0.0.1", "")
 	_ = c.Send(iqResp)
 }
diff --git a/_examples/xmpp_echo/xmpp_echo.go b/_examples/xmpp_echo/xmpp_echo.go
index 38cbe62..8794a5a 100644
--- a/_examples/xmpp_echo/xmpp_echo.go
+++ b/_examples/xmpp_echo/xmpp_echo.go
@@ -20,7 +20,6 @@ func main() {
 		Password:     "test",
 		StreamLogger: os.Stdout,
 		Insecure:     true,
-		// TLSConfig: tls.Config{InsecureSkipVerify: true},
 	}
 
 	router := xmpp.NewRouter()
diff --git a/cert_checker.go b/cert_checker.go
index c85664b..e33d9f9 100644
--- a/cert_checker.go
+++ b/cert_checker.go
@@ -86,9 +86,8 @@ func (c *ServerCheck) Check() error {
 			return fmt.Errorf("expecting starttls proceed: %s", err)
 		}
 
-		var tlsConfig tls.Config
-		tlsConfig.ServerName = c.domain
-		tlsConn := tls.Client(tcpconn, &tlsConfig)
+		stanza.DefaultTlsConfig.ServerName = c.domain
+		tlsConn := tls.Client(tcpconn, &stanza.DefaultTlsConfig)
 		// We convert existing connection to TLS
 		if err = tlsConn.Handshake(); err != nil {
 			return err
diff --git a/client.go b/client.go
index 7bb7a80..5f134aa 100644
--- a/client.go
+++ b/client.go
@@ -96,23 +96,9 @@ func NewClient(config Config, r *Router) (c *Client, err error) {
 		return nil, NewConnError(err, true)
 	}
 
-	// Fallback to jid domain
+	// fallback to jid domain
 	if config.Address == "" {
 		config.Address = config.parsedJid.Domain
-
-		// Fetch SRV DNS-Entries
-		_, srvEntries, err := net.LookupSRV("xmpp-client", "tcp", config.parsedJid.Domain)
-
-		if err == nil && len(srvEntries) > 0 {
-			// If we found matching DNS records, use the entry with highest weight
-			bestSrv := srvEntries[0]
-			for _, srv := range srvEntries {
-				if srv.Priority <= bestSrv.Priority && srv.Weight >= bestSrv.Weight {
-					bestSrv = srv
-					config.Address = ensurePort(srv.Target, int(srv.Port))
-				}
-			}
-		}
 	}
 	config.Address = ensurePort(config.Address, 5222)
 
diff --git a/cmd/go.mod b/cmd/go.mod
deleted file mode 100644
index 9af28a8..0000000
--- a/cmd/go.mod
+++ /dev/null
@@ -1,13 +0,0 @@
-module gosrc.io/xmpp/cmd
-
-go 1.12
-
-require (
-	github.com/bdlm/log v0.1.19
-	github.com/bdlm/std v0.0.0-20180922040903-fd3b596111c7
-	github.com/spf13/cobra v0.0.5
-	github.com/spf13/viper v1.4.0
-	gosrc.io/xmpp v0.1.0
-)
-
-replace gosrc.io/xmpp => ./../
diff --git a/cmd/go.sum b/cmd/go.sum
deleted file mode 100644
index aeeae9c..0000000
--- a/cmd/go.sum
+++ /dev/null
@@ -1,159 +0,0 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/bdlm/log v0.1.19 h1:GqVFZC+khJCEbtTmkaDL/araNDwxTeLBmdMK8pbRoBE=
-github.com/bdlm/log v0.1.19/go.mod h1:30V5Zwc5Vt5ePq5rd9KJ6JQ/A5aFUcKzq5fYtO7c9qc=
-github.com/bdlm/std v0.0.0-20180922040903-fd3b596111c7 h1:ggZyn+N8eoBh/qLla2kUtqm/ysjnkbzUxTQY+6LMshY=
-github.com/bdlm/std v0.0.0-20180922040903-fd3b596111c7/go.mod h1:E4vIYZDcEPVbE/Dbxc7GpA3YJpXnsF5csRt8LptMGWI=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
-github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
-golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/cmd/sendxmpp/README.md b/cmd/sendxmpp/README.md
deleted file mode 100644
index aa5af53..0000000
--- a/cmd/sendxmpp/README.md
+++ /dev/null
@@ -1,131 +0,0 @@
-# sendXMPP
-
-sendxmpp is a tool to send messages from command-line.
-
-## Installation
-
-To install `sendxmpp` in your Go path:
-
-```
-$ go get -u gosrc.io/xmpp/cmd/sendxmpp
-```
-
-## Usage
-
-```
-$ sendxmpp --help
-Usage:
-  sendxmpp <recipient,> [message] [flags]
-
-Examples:
-sendxmpp to@chat.sum7.eu "Hello World!"
-
-Flags:
-      --addr string       host[:port]
-      --config string     config file (default is ~/.config/fluxxmpp.yml)
-  -h, --help              help for sendxmpp
-      --jid string        using jid (required)
-  -m, --muc               recipient is a muc (join it before sending messages)
-      --password string   using password for your jid (required)
-```
-
-
-## Examples
-
-Message from arguments:
-```bash
-$ sendxmpp to@example.org "Hello World!"
- info client connected
-   ⇢  cmd.go:56 main.glob..func1.1
-   ⇢  2019-07-17T23:42:43.310+02:00
- info send message
-   ⇢  muc=false text="Hello World!" to="to@example.org"
-   ⇢  send.go:31 main.send
-   ⇢  2019-07-17T23:42:43.310+02:00
-```
-
-Message from STDIN:
-```bash
-$  journalctl -f | sendxmpp to@example.org -
- info client connected
-   ⇢  cmd.go:56 main.glob..func1.1
-   ⇢  2019-07-17T23:40:03.177+02:00
- info send message
-   ⇢  muc=false text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="to@example.org"
-   ⇢  send.go:31 main.send
-   ⇢  2019-07-17T23:40:03.178+02:00
- info send message
-   ⇢  muc=false text="Jul 17 23:36:46 RECHNERNAME systemd[755]: Started Fetch mails." to="to@example.org"
-   ⇢  send.go:31 main.send
-   ⇢  2019-07-17T23:40:03.178+02:00
-^C
-```
-
-
-Multiple recipients:
-```bash
-$ sendxmpp to1@example.org,to2@example.org "Multiple recipient"
- info client connected
-   ⇢  cmd.go:56 main.glob..func1.1
-   ⇢  2019-07-17T23:47:57.650+02:00
- info send message
-   ⇢  muc=false text="Multiple recipient" to="to1@example.org"
-   ⇢  send.go:31 main.send
-   ⇢  2019-07-17T23:47:57.651+02:00
- info send message
-   ⇢  muc=false text="Multiple recipient" to="to2@example.org"
-   ⇢  send.go:31 main.send
-   ⇢  2019-07-17T23:47:57.652+02:00
-```
-
-Send to MUC:
-```bash
-journalctl -f | sendxmpp testit@conference.chat.sum7.eu - --muc
- info client connected
-   ⇢  cmd.go:56 main.glob..func1.1
-   ⇢  2019-07-17T23:52:56.269+02:00
- info send message
-   ⇢  muc=true text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="testit@conference.chat.sum7.eu"
-   ⇢  send.go:31 main.send
-   ⇢  2019-07-17T23:52:56.270+02:00
- info send message
-   ⇢  muc=true text="Jul 17 23:48:58 RECHNERNAME systemd[755]: mail.service: Succeeded." to="testit@conference.chat.sum7.eu"
-   ⇢  send.go:31 main.send
-   ⇢  2019-07-17T23:52:56.277+02:00
-^C
-```
-
-### Authentification
-
-#### Configuration file
-
-In `/etc/`, `~/.config` and `.` (here).
-You could create the file name `fluxxmpp` with you favorite file extenion (e.g. `toml`, `yml`).
-
-e.g. ~/.config/fluxxmpp.toml
-```toml
-jid      = "bot@example.org"
-password = "secret"
-
-addr     = "example.com:5222"
-```
-
-#### Environment variables
-
-```bash
-export FLUXXMPP_JID='bot@example.org';
-export FLUXXMPP_PASSWORD='secret';
-
-export FLUXXMPP_ADDR='example.com:5222';
-
-sendxmpp to@example.org "Hello Welt";
-```
-
-#### Parameters
-
-Warning: This should not be used for production systems, as all users on the system
-can read the running processes, and their parameters (and thus the password).
-
-```bash
-sendxmpp to@example.org "Hello World!" --jid bot@example.org --password secret --addr example.com:5222;
-```
diff --git a/cmd/sendxmpp/TODO.md b/cmd/sendxmpp/TODO.md
deleted file mode 100644
index 0c2d030..0000000
--- a/cmd/sendxmpp/TODO.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# TODO
-
-## Issues
-
-- Remove global variable (like mucToleave)
-- Does not report error when trying to connect to a non open port (for example localhost with no server running).
-
-## Features
-
-- configuration
-  - allow unencrypted
-  - skip tls verification
-- support muc and single user at same time
-- send html -> parse console colors to xhtml (is there a easy way or lib for it ?)
diff --git a/cmd/sendxmpp/cmd.go b/cmd/sendxmpp/cmd.go
deleted file mode 100644
index b446f26..0000000
--- a/cmd/sendxmpp/cmd.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package main
-
-import (
-	"bufio"
-	"os"
-	"strings"
-	"sync"
-
-	"github.com/bdlm/log"
-	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
-
-	"gosrc.io/xmpp"
-)
-
-var configFile = ""
-
-// FIXME: Remove global variables
-var isMUCRecipient = false
-
-var cmd = &cobra.Command{
-	Use:     "sendxmpp <recipient,> [message]",
-	Example: `sendxmpp to@chat.sum7.eu "Hello World!"`,
-	Args:    cobra.ExactArgs(2),
-	Run:     sendxmpp,
-}
-
-func sendxmpp(cmd *cobra.Command, args []string) {
-	receiver := strings.Split(args[0], ",")
-	msgText := args[1]
-
-	var err error
-	client, err := xmpp.NewClient(xmpp.Config{
-		Jid:      viper.GetString("jid"),
-		Address:  viper.GetString("addr"),
-		Password: viper.GetString("password"),
-	}, xmpp.NewRouter())
-
-	if err != nil {
-		log.Errorf("error when starting xmpp client: %s", err)
-		return
-	}
-
-	wg := sync.WaitGroup{}
-	wg.Add(1)
-
-	// FIXME: Remove global variables
-	var mucsToLeave []*xmpp.Jid
-
-	cm := xmpp.NewStreamManager(client, func(c xmpp.Sender) {
-		defer wg.Done()
-
-		log.Info("client connected")
-
-		if isMUCRecipient {
-			for _, muc := range receiver {
-				jid, err := xmpp.NewJid(muc)
-				if err != nil {
-					log.WithField("muc", muc).Errorf("skipping invalid muc jid: %w", err)
-					continue
-				}
-				jid.Resource = "sendxmpp"
-
-				if err := joinMUC(c, jid); err != nil {
-					log.WithField("muc", muc).Errorf("error joining muc: %w", err)
-					continue
-				}
-				mucsToLeave = append(mucsToLeave, jid)
-			}
-		}
-
-		if msgText != "-" {
-			send(c, receiver, msgText)
-			return
-		}
-
-		scanner := bufio.NewScanner(os.Stdin)
-		for scanner.Scan() {
-			send(c, receiver, scanner.Text())
-		}
-
-		if err := scanner.Err(); err != nil {
-			log.Errorf("error on reading stdin: %s", err)
-		}
-	})
-	go func() {
-		err := cm.Run()
-		log.Panic("closed connection:", err)
-		wg.Done()
-	}()
-
-	wg.Wait()
-
-	leaveMUCs(client, mucsToLeave)
-}
-
-func init() {
-	cobra.OnInitialize(initConfig)
-	cmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is ~/.config/fluxxmpp.yml)")
-
-	cmd.Flags().StringP("jid", "", "", "using jid (required)")
-	viper.BindPFlag("jid", cmd.Flags().Lookup("jid"))
-
-	cmd.Flags().StringP("password", "", "", "using password for your jid (required)")
-	viper.BindPFlag("password", cmd.Flags().Lookup("password"))
-
-	cmd.Flags().StringP("addr", "", "", "host[:port]")
-	viper.BindPFlag("addr", cmd.Flags().Lookup("addr"))
-
-	cmd.Flags().BoolVarP(&isMUCRecipient, "muc", "m", false, "recipient is a muc (join it before sending messages)")
-}
-
-// initConfig reads in config file and ENV variables if set.
-func initConfig() {
-	if configFile != "" {
-		viper.SetConfigFile(configFile)
-	}
-
-	viper.SetConfigName("fluxxmpp")
-	viper.AddConfigPath("/etc/")
-	viper.AddConfigPath("$HOME/.config")
-	viper.AddConfigPath(".")
-
-	viper.SetEnvPrefix("FLUXXMPP")
-	viper.AutomaticEnv()
-
-	// If a config file is found, read it in.
-	if err := viper.ReadInConfig(); err != nil {
-		log.Warnf("no configuration found (somebody could read your password from process argument list): %s", err)
-	}
-}
diff --git a/cmd/sendxmpp/doc.go b/cmd/sendxmpp/doc.go
deleted file mode 100644
index 46f9956..0000000
--- a/cmd/sendxmpp/doc.go
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
-
-sendxmpp is a command-line tool to send to send XMPP messages to users
-
-*/
-package main
diff --git a/cmd/sendxmpp/log.go b/cmd/sendxmpp/log.go
deleted file mode 100644
index eb67593..0000000
--- a/cmd/sendxmpp/log.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package main
-
-import (
-	"os"
-
-	"github.com/bdlm/log"
-	stdLogger "github.com/bdlm/std/logger"
-)
-
-type hook struct{}
-
-func (h *hook) Fire(entry *log.Entry) error {
-	switch entry.Level {
-	case log.PanicLevel:
-		entry.Logger.Out = os.Stderr
-	case log.FatalLevel:
-		entry.Logger.Out = os.Stderr
-	case log.ErrorLevel:
-		entry.Logger.Out = os.Stderr
-	case log.WarnLevel:
-		entry.Logger.Out = os.Stdout
-	case log.InfoLevel:
-		entry.Logger.Out = os.Stdout
-	case log.DebugLevel:
-		entry.Logger.Out = os.Stdout
-	default:
-	}
-
-	return nil
-}
-
-func (h *hook) Levels() []stdLogger.Level {
-	return log.AllLevels
-}
diff --git a/cmd/sendxmpp/main.go b/cmd/sendxmpp/main.go
deleted file mode 100644
index f1773cf..0000000
--- a/cmd/sendxmpp/main.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package main
-
-import (
-	"github.com/bdlm/log"
-)
-
-func main() {
-	log.AddHook(&hook{})
-	if err := cmd.Execute(); err != nil {
-		log.Fatal(err)
-	}
-}
diff --git a/cmd/sendxmpp/muc.go b/cmd/sendxmpp/muc.go
deleted file mode 100644
index 984b014..0000000
--- a/cmd/sendxmpp/muc.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package main
-
-import (
-	"github.com/bdlm/log"
-
-	"gosrc.io/xmpp"
-	"gosrc.io/xmpp/stanza"
-)
-
-func joinMUC(c xmpp.Sender, toJID *xmpp.Jid) error {
-	return c.Send(stanza.Presence{Attrs: stanza.Attrs{To: toJID.Full()},
-		Extensions: []stanza.PresExtension{
-			stanza.MucPresence{
-				History: stanza.History{MaxStanzas: stanza.NewNullableInt(0)},
-			}},
-	})
-}
-
-func leaveMUCs(c xmpp.Sender, mucsToLeave []*xmpp.Jid) {
-	for _, muc := range mucsToLeave {
-		if err := c.Send(stanza.Presence{Attrs: stanza.Attrs{
-			To:   muc.Full(),
-			Type: stanza.PresenceTypeUnavailable,
-		}}); err != nil {
-			log.WithField("muc", muc).Errorf("error on leaving muc: %s", err)
-		}
-	}
-}
diff --git a/cmd/sendxmpp/send.go b/cmd/sendxmpp/send.go
deleted file mode 100644
index a2c79c4..0000000
--- a/cmd/sendxmpp/send.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package main
-
-import (
-	"github.com/bdlm/log"
-
-	"gosrc.io/xmpp"
-	"gosrc.io/xmpp/stanza"
-)
-
-func send(c xmpp.Sender, recipient []string, msgText string) {
-	msg := stanza.Message{
-		Attrs: stanza.Attrs{Type: stanza.MessageTypeChat},
-		Body:  msgText,
-	}
-
-	if isMUCRecipient {
-		msg.Type = stanza.MessageTypeGroupchat
-	}
-
-	for _, to := range recipient {
-		msg.To = to
-		if err := c.Send(msg); err != nil {
-			log.WithFields(map[string]interface{}{
-				"muc":  isMUCRecipient,
-				"to":   to,
-				"text": msgText,
-			}).Errorf("error on send message: %s", err)
-		} else {
-			log.WithFields(map[string]interface{}{
-				"muc":  isMUCRecipient,
-				"to":   to,
-				"text": msgText,
-			}).Info("send message")
-		}
-	}
-}
diff --git a/cmd/xmpp-check/README.md b/cmd/xmpp-check/README.md
index d943248..dc177aa 100644
--- a/cmd/xmpp-check/README.md
+++ b/cmd/xmpp-check/README.md
@@ -12,37 +12,18 @@ $ go get -u gosrc.io/xmpp/cmd/xmpp-check
 
 ## Usage
 
-```
-$ xmpp-check --help
-Usage:
-  xmpp-check <host[:port]> [flags]
-
-Examples:
-xmpp-check chat.sum7.eu:5222 --domain meckerspace.de
-
-Flags:
-  -d, --domain string   domain if host handle multiple domains
-  -h, --help            help for xmpp-check
-```
-
 If you server is on standard port and XMPP domains matches the hostname you can simply use:
 
 ```
-$ xmpp-check chat.sum7.eu
- info All checks passed
-   ⇢  address="chat.sum7.eu" domain=""
-   ⇢  main.go:43 main.runCheck
-   ⇢  2019-07-16T22:01:39.765+02:00
+$ xmpp-check myhost.net
+2019/05/16 16:04:36 All checks passed
 ```
 
 You can also pass the port and the XMPP domain if different from the server hostname:
 
 ```
-$ xmpp-check chat.sum7.eu:5222 --domain meckerspace.de
- info All checks passed
-   ⇢  address="chat.sum7.eu:5222" domain="meckerspace.de"
-   ⇢  main.go:43 main.runCheck
-   ⇢  2019-07-16T22:01:33.270+02:00
+$ xmpp-check myhost.net:5222 xmppdomain.net
+2019/05/16 16:05:21 All checks passed
 ```
 
 Error code will be non-zero in case of error. You can thus use it directly with your usual 
diff --git a/cmd/xmpp-check/log.go b/cmd/xmpp-check/log.go
deleted file mode 100644
index eb67593..0000000
--- a/cmd/xmpp-check/log.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package main
-
-import (
-	"os"
-
-	"github.com/bdlm/log"
-	stdLogger "github.com/bdlm/std/logger"
-)
-
-type hook struct{}
-
-func (h *hook) Fire(entry *log.Entry) error {
-	switch entry.Level {
-	case log.PanicLevel:
-		entry.Logger.Out = os.Stderr
-	case log.FatalLevel:
-		entry.Logger.Out = os.Stderr
-	case log.ErrorLevel:
-		entry.Logger.Out = os.Stderr
-	case log.WarnLevel:
-		entry.Logger.Out = os.Stdout
-	case log.InfoLevel:
-		entry.Logger.Out = os.Stdout
-	case log.DebugLevel:
-		entry.Logger.Out = os.Stdout
-	default:
-	}
-
-	return nil
-}
-
-func (h *hook) Levels() []stdLogger.Level {
-	return log.AllLevels
-}
diff --git a/cmd/xmpp-check/main.go b/cmd/xmpp-check/main.go
deleted file mode 100644
index 88faa9b..0000000
--- a/cmd/xmpp-check/main.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package main
-
-import (
-	"github.com/bdlm/log"
-	"github.com/spf13/cobra"
-	"gosrc.io/xmpp"
-)
-
-func main() {
-	log.AddHook(&hook{})
-	cmd.Execute()
-}
-
-var domain = ""
-var cmd = &cobra.Command{
-	Use:     "xmpp-check <host[:port]>",
-	Example: "xmpp-check chat.sum7.eu:5222 --domain meckerspace.de",
-	Args:    cobra.ExactArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		runCheck(args[0], domain)
-	},
-}
-
-func init() {
-	cmd.Flags().StringVarP(&domain, "domain", "d", "", "domain if host handle multiple domains")
-}
-
-func runCheck(address, domain string) {
-	logger := log.WithFields(map[string]interface{}{
-		"address": address,
-		"domain":  domain,
-	})
-	client, err := xmpp.NewChecker(address, domain)
-
-	if err != nil {
-		log.Fatal("Error: ", err)
-	}
-
-	if err = client.Check(); err != nil {
-		logger.Fatal("Failed connection check: ", err)
-	}
-
-	logger.Println("All checks passed")
-}
diff --git a/cmd/xmpp-check/xmpp-check.go b/cmd/xmpp-check/xmpp-check.go
new file mode 100644
index 0000000..77af605
--- /dev/null
+++ b/cmd/xmpp-check/xmpp-check.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+	"log"
+	"os"
+
+	"gosrc.io/xmpp"
+)
+
+func main() {
+	args := os.Args[1:]
+
+	if len(args) == 0 {
+		log.Fatal("usage: xmpp-check host[:port] [domain]")
+	}
+
+	var address string
+	var domain string
+	if len(args) >= 1 {
+		address = args[0]
+	}
+
+	if len(args) >= 2 {
+		domain = args[1]
+	}
+
+	runCheck(address, domain)
+}
+
+func runCheck(address, domain string) {
+	client, err := xmpp.NewChecker(address, domain)
+
+	if err != nil {
+		log.Fatal("Error: ", err)
+	}
+
+	if err = client.Check(); err != nil {
+		log.Fatal("Failed connection check: ", err)
+	}
+
+	log.Println("All checks passed")
+}
diff --git a/config.go b/config.go
index d3327c1..3ae4a1c 100644
--- a/config.go
+++ b/config.go
@@ -1,7 +1,6 @@
 package xmpp
 
 import (
-	"crypto/tls"
 	"io"
 	"os"
 )
@@ -14,9 +13,6 @@ type Config struct {
 	StreamLogger   *os.File // Used for debugging
 	Lang           string   // TODO: should default to 'en'
 	ConnectTimeout int      // Client timeout in seconds. Default to 15
-	// tls.Config must not be modified after having been passed to NewClient. The
-	// Client connect method may override the tls.Config.ServerName if it was not set.
-	TLSConfig *tls.Config
 	// Insecure can be set to true to allow to open a session without TLS. If TLS
 	// is supported on the server, we will still try to use it.
 	Insecure      bool
diff --git a/go.mod b/go.mod
index 58297e4..ebd7205 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,6 @@ module gosrc.io/xmpp
 go 1.12
 
 require (
-	github.com/google/go-cmp v0.3.0
+	github.com/google/go-cmp v0.2.0
 	golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522
 )
diff --git a/go.sum b/go.sum
index 308f9eb..233fa3c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,4 @@
-github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/session.go b/session.go
index 6fa7c32..4fe8bcf 100644
--- a/session.go
+++ b/session.go
@@ -35,17 +35,15 @@ func NewSession(conn net.Conn, o Config) (net.Conn, *Session, error) {
 
 	// starttls
 	var tlsConn net.Conn
-	tlsConn = s.startTlsIfSupported(conn, o.parsedJid.Domain, o)
-
-	if !s.TlsEnabled && !o.Insecure {
-		err := fmt.Errorf("failed to negotiate TLS session : %s", s.err)
-		return nil, nil, NewConnError(err, true)
-	}
-
+	tlsConn = s.startTlsIfSupported(conn, o.parsedJid.Domain)
 	if s.TlsEnabled {
 		s.reset(conn, tlsConn, o)
 	}
 
+	if !s.TlsEnabled && !o.Insecure {
+		return nil, nil, NewConnError(errors.New("failed to negotiate TLS session"), true)
+	}
+
 	// auth
 	s.auth(o)
 	s.reset(tlsConn, tlsConn, o)
@@ -103,7 +101,7 @@ func (s *Session) open(domain string) (f stanza.StreamFeatures) {
 	return
 }
 
-func (s *Session) startTlsIfSupported(conn net.Conn, domain string, o Config) net.Conn {
+func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn {
 	if s.err != nil {
 		return conn
 	}
@@ -116,36 +114,21 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string, o Config) ne
 			s.err = errors.New("expecting starttls proceed: " + s.err.Error())
 			return conn
 		}
+		s.TlsEnabled = true
 
-		if o.TLSConfig == nil {
-			o.TLSConfig = &tls.Config{}
-		}
-
-		if o.TLSConfig.ServerName == "" {
-			o.TLSConfig.ServerName = domain
-		}
-		tlsConn := tls.Client(conn, o.TLSConfig)
+		// TODO: add option to accept all TLS certificates: insecureSkipTlsVerify (DefaultTlsConfig.InsecureSkipVerify)
+		stanza.DefaultTlsConfig.ServerName = domain
+		tlsConn := tls.Client(conn, &stanza.DefaultTlsConfig)
 		// We convert existing connection to TLS
 		if s.err = tlsConn.Handshake(); s.err != nil {
 			return tlsConn
 		}
 
-		if !o.TLSConfig.InsecureSkipVerify {
-			// We check that cert matches hostname
-			s.err = tlsConn.VerifyHostname(domain)
-		}
-
-		if s.err == nil {
-			s.TlsEnabled = true
-		}
+		// We check that cert matches hostname
+		s.err = tlsConn.VerifyHostname(domain)
 		return tlsConn
 	}
 
-	// If we do not allow cleartext connections, make it explicit that server do not support starttls
-	if !o.Insecure {
-		s.err = errors.New("XMPP server does not advertise support for starttls")
-	}
-
 	// starttls is not supported => we do not upgrade the connection:
 	return conn
 }
diff --git a/stanza/iq_disco.go b/stanza/iq_disco.go
index 0097c05..cc94756 100644
--- a/stanza/iq_disco.go
+++ b/stanza/iq_disco.go
@@ -56,8 +56,9 @@ func (d *DiscoInfo) AddFeatures(namespace ...string) {
 	}
 }
 
-func (d *DiscoInfo) SetNode(node string) {
+func (d *DiscoInfo) SetNode(node string) *DiscoInfo {
 	d.Node = node
+	return d
 }
 
 func (d *DiscoInfo) SetIdentities(ident ...Identity) *DiscoInfo {
@@ -66,6 +67,7 @@ func (d *DiscoInfo) SetIdentities(ident ...Identity) *DiscoInfo {
 }
 
 func (d *DiscoInfo) SetFeatures(namespace ...string) *DiscoInfo {
+	d.Features = []Feature{}
 	for _, ns := range namespace {
 		d.Features = append(d.Features, Feature{Var: ns})
 	}
@@ -104,11 +106,38 @@ func (d *DiscoItems) Namespace() string {
 	return d.XMLName.Space
 }
 
+// ---------------
+// Builder helpers
+
+// DiscoItems builds a default DiscoItems payload
+func (iq *IQ) DiscoItems() *DiscoItems {
+	d := DiscoItems{
+		XMLName: xml.Name{Space: "http://jabber.org/protocol/disco#items", Local: "query"},
+	}
+	iq.Payload = &d
+	return &d
+}
+
+func (d *DiscoItems) SetNode(node string) *DiscoItems {
+	d.Node = node
+	return d
+}
+
+func (d *DiscoItems) AddItem(jid, node, name string) *DiscoItems {
+	item := DiscoItem{
+		JID:  jid,
+		Node: node,
+		Name: name,
+	}
+	d.Items = append(d.Items, item)
+	return d
+}
+
 type DiscoItem struct {
 	XMLName xml.Name `xml:"item"`
-	Name    string   `xml:"name,attr,omitempty"`
 	JID     string   `xml:"jid,attr,omitempty"`
 	Node    string   `xml:"node,attr,omitempty"`
+	Name    string   `xml:"name,attr,omitempty"`
 }
 
 // ============================================================================
diff --git a/stanza/iq_version.go b/stanza/iq_version.go
index ebf4452..4cfbfce 100644
--- a/stanza/iq_version.go
+++ b/stanza/iq_version.go
@@ -17,6 +17,26 @@ func (v *Version) Namespace() string {
 	return v.XMLName.Space
 }
 
+// ---------------
+// Builder helpers
+
+// Version builds a default software version payload
+func (iq *IQ) Version() *Version {
+	d := Version{
+		XMLName: xml.Name{Space: "jabber:iq:version", Local: "query"},
+	}
+	iq.Payload = &d
+	return &d
+}
+
+// Set all software version info
+func (v *Version) SetInfo(name, version, os string) *Version {
+	v.Name = name
+	v.Version = version
+	v.OS = os
+	return v
+}
+
 // ============================================================================
 // Registry init
 
diff --git a/stanza/starttls.go b/stanza/starttls.go
index b8fce94..e0a187c 100644
--- a/stanza/starttls.go
+++ b/stanza/starttls.go
@@ -1,9 +1,12 @@
 package stanza
 
 import (
+	"crypto/tls"
 	"encoding/xml"
 )
 
+var DefaultTlsConfig tls.Config
+
 // Used during stream initiation / session establishment
 type TLSProceed struct {
 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
diff --git a/stream_manager.go b/stream_manager.go
index 1aaf164..a15ce40 100644
--- a/stream_manager.go
+++ b/stream_manager.go
@@ -119,7 +119,7 @@ func (sm *StreamManager) connect() error {
 			var actualErr ConnError
 			if xerrors.As(err, &actualErr) {
 				if actualErr.Permanent {
-					return xerrors.Errorf("unrecoverable connect error %#v", actualErr)
+					return xerrors.Errorf("unrecoverable connect error %w", actualErr)
 				}
 			}
 			backoff.wait()


Test patch:
diff --git a/stanza/iq_disco_test.go b/stanza/iq_disco_test.go
index 8b76767..d659cde 100644
--- a/stanza/iq_disco_test.go
+++ b/stanza/iq_disco_test.go
@@ -7,29 +7,22 @@ import (
 	"gosrc.io/xmpp/stanza"
 )
 
-func TestDiscoInfoBuilder(t *testing.T) {
+// 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"})
 	disco := iq.DiscoInfo()
 	disco.AddIdentity("Test Component", "gateway", "service")
 	disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
 
-	// Marshall
-	data, err := xml.Marshal(iq)
+	parsedIQ, err := checkMarshalling(t, iq)
 	if err != nil {
-		t.Errorf("cannot marshal xml structure: %s", err)
 		return
 	}
 
-	// Unmarshall
-	var parsedIQ stanza.IQ
-	if err = xml.Unmarshal(data, &parsedIQ); err != nil {
-		t.Errorf("Unmarshal(%s) returned error: %s", data, err)
-	}
-
 	// Check result
 	pp, ok := parsedIQ.Payload.(*stanza.DiscoInfo)
 	if !ok {
-		t.Errorf("Parsed stanza does not contain an IQ payload")
+		t.Errorf("Parsed stanza does not contain correct IQ payload")
 	}
 
 	// Check features
@@ -53,3 +46,45 @@ func TestDiscoInfoBuilder(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: "result", From: "catalog.shakespeare.lit",
+		To: "romeo@montague.net/orchard", Id: "items-2"})
+	iq.DiscoItems().
+		AddItem("catalog.shakespeare.lit", "books", "Books by and about Shakespeare").
+		AddItem("catalog.shakespeare.lit", "clothing", "Wear your literary taste with pride").
+		AddItem("catalog.shakespeare.lit", "music", "Music from the time of Shakespeare")
+
+	parsedIQ, err := checkMarshalling(t, iq)
+	if err != nil {
+		return
+	}
+
+	// Check result
+	pp, ok := parsedIQ.Payload.(*stanza.DiscoItems)
+	if !ok {
+		t.Errorf("Parsed stanza does not contain correct IQ payload")
+	}
+
+	// Check items
+	items := []stanza.DiscoItem{{xml.Name{}, "catalog.shakespeare.lit", "books", "Books by and about Shakespeare"},
+		{xml.Name{}, "catalog.shakespeare.lit", "clothing", "Wear your literary taste with pride"},
+		{xml.Name{}, "catalog.shakespeare.lit", "music", "Music from the time of Shakespeare"}}
+	if len(pp.Items) != len(items) {
+		t.Errorf("Items length mismatch: %#v", pp.Items)
+	} else {
+		for i, item := range pp.Items {
+			if item.JID != items[i].JID {
+				t.Errorf("JID Mismatch (expected: %s): %s", items[i].JID, item.JID)
+			}
+			if item.Node != items[i].Node {
+				t.Errorf("Node Mismatch (expected: %s): %s", items[i].JID, item.JID)
+			}
+			if item.Name != items[i].Name {
+				t.Errorf("Name Mismatch (expected: %s): %s", items[i].JID, item.JID)
+			}
+		}
+	}
+}
diff --git a/stanza/iq_version_test.go b/stanza/iq_version_test.go
new file mode 100644
index 0000000..45d68f7
--- /dev/null
+++ b/stanza/iq_version_test.go
@@ -0,0 +1,40 @@
+package stanza_test
+
+import (
+	"testing"
+
+	"gosrc.io/xmpp/stanza"
+)
+
+// Build a Software Version reply
+// https://xmpp.org/extensions/xep-0092.html#example-2
+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",
+		To: "juliet@capulet.com/balcony", Id: "version_1"})
+	iq.Version().SetInfo(name, version, os)
+
+	parsedIQ, err := checkMarshalling(t, iq)
+	if err != nil {
+		return
+	}
+
+	// Check result
+	pp, ok := parsedIQ.Payload.(*stanza.Version)
+	if !ok {
+		t.Errorf("Parsed stanza does not contain correct IQ payload")
+	}
+
+	// Check version info
+	if pp.Name != name {
+		t.Errorf("Name Mismatch (expected: %s): %s", name, pp.Name)
+	}
+	if pp.Version != version {
+		t.Errorf("Version Mismatch (expected: %s): %s", version, pp.Version)
+	}
+	if pp.OS != os {
+		t.Errorf("OS Mismatch (expected: %s): %s", os, pp.OS)
+	}
+}
diff --git a/stanza/xmpp_test.go b/stanza/xmpp_test.go
index 611948a..420a053 100644
--- a/stanza/xmpp_test.go
+++ b/stanza/xmpp_test.go
@@ -2,10 +2,35 @@ package stanza_test
 
 import (
 	"encoding/xml"
+	"testing"
 
 	"github.com/google/go-cmp/cmp"
+	"gosrc.io/xmpp/stanza"
 )
 
+// ============================================================================
+// Marshaller / unmarshaller test
+
+func checkMarshalling(t *testing.T, iq stanza.IQ) (*stanza.IQ, error) {
+	// Marshall
+	data, err := xml.Marshal(iq)
+	if err != nil {
+		t.Errorf("cannot marshal iq: %s\n%#v", err, iq)
+		return nil, err
+	}
+
+	// Unmarshall
+	var parsedIQ stanza.IQ
+	err = xml.Unmarshal(data, &parsedIQ)
+	if err != nil {
+		t.Errorf("Unmarshal returned error: %s\n%s", err, data)
+	}
+	return &parsedIQ, err
+}
+
+// ============================================================================
+// XML structs comparison
+
 // Compare iq structure but ignore empty namespace as they are set properly on
 // marshal / unmarshal. There is no need to manage them on the manually
 // crafted structure.