Repository: FluuxIO/go-xmpp
Pull request #79 (2019-06-27 12:49:45)

Stanza package & pattern to help building stanzas
- Move parsing and stanza marshalling / unmarshalling to stanza package
- Add pattern & basic helpers to simplify stanza building.
This was requested on #61 


Patch:
diff --git a/README.md b/README.md
index b9ba409..3e7829b 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ import (
 	"os"
 
 	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func main() {
@@ -57,15 +58,15 @@ func main() {
 	log.Fatal(cm.Run())
 }
 
-func handleMessage(s xmpp.Sender, p xmpp.Packet) {
-	msg, ok := p.(xmpp.Message)
+func handleMessage(s xmpp.Sender, p stanza.Packet) {
+	msg, ok := p.(stanza.Message)
 	if !ok {
 		_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
 		return
 	}
 
 	_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
-	reply := xmpp.Message{Attrs: xmpp.Attrs{To: msg.From}, Body: msg.Body}
+	reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
 	_ = s.Send(reply)
 }
 ```
diff --git a/_examples/delegation/delegation.go b/_examples/delegation/delegation.go
index 473fefa..31c18d3 100644
--- a/_examples/delegation/delegation.go
+++ b/_examples/delegation/delegation.go
@@ -6,6 +6,7 @@ import (
 	"log"
 
 	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func main() {
@@ -23,8 +24,8 @@ func main() {
 	router := xmpp.NewRouter()
 	router.HandleFunc("message", handleMessage)
 	router.NewRoute().
-		IQNamespaces(xmpp.NSDiscoInfo).
-		HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+		IQNamespaces(stanza.NSDiscoInfo).
+		HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
 			discoInfo(s, p, opts)
 		})
 	router.NewRoute().
@@ -43,14 +44,14 @@ func main() {
 	log.Fatal(cm.Run())
 }
 
-func handleMessage(_ xmpp.Sender, p xmpp.Packet) {
-	msg, ok := p.(xmpp.Message)
+func handleMessage(_ xmpp.Sender, p stanza.Packet) {
+	msg, ok := p.(stanza.Message)
 	if !ok {
 		return
 	}
 	var msgProcessed bool
 	for _, ext := range msg.Extensions {
-		delegation, ok := ext.(*xmpp.Delegation)
+		delegation, ok := ext.(*stanza.Delegation)
 		if ok {
 			msgProcessed = true
 			fmt.Printf("Delegation confirmed for namespace %s\n", delegation.Delegated.Namespace)
@@ -72,18 +73,18 @@ const (
 // TODO: replace xmpp.Sender by ctx xmpp.Context ?
 // ctx.Stream.Send / SendRaw
 // ctx.Opts
-func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
+func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
 	// Type conversion & sanity checks
-	iq, ok := p.(xmpp.IQ)
+	iq, ok := p.(stanza.IQ)
 	if !ok {
 		return
 	}
-	info, ok := iq.Payload.(*xmpp.DiscoInfo)
+	info, ok := iq.Payload.(*stanza.DiscoInfo)
 	if !ok {
 		return
 	}
 
-	iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
+	iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
 
 	switch info.Node {
 	case "":
@@ -97,22 +98,22 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
 	_ = c.Send(iqResp)
 }
 
-func discoInfoRoot(iqResp *xmpp.IQ, opts xmpp.ComponentOptions) {
+func discoInfoRoot(iqResp *stanza.IQ, opts xmpp.ComponentOptions) {
 	// Higher level discovery
-	identity := xmpp.Identity{
+	identity := stanza.Identity{
 		Name:     opts.Name,
 		Category: opts.Category,
 		Type:     opts.Type,
 	}
-	payload := xmpp.DiscoInfo{
+	payload := stanza.DiscoInfo{
 		XMLName: xml.Name{
-			Space: xmpp.NSDiscoInfo,
+			Space: stanza.NSDiscoInfo,
 			Local: "query",
 		},
-		Identity: identity,
-		Features: []xmpp.Feature{
-			{Var: xmpp.NSDiscoInfo},
-			{Var: xmpp.NSDiscoItems},
+		Identity: []stanza.Identity{identity},
+		Features: []stanza.Feature{
+			{Var: stanza.NSDiscoInfo},
+			{Var: stanza.NSDiscoItems},
 			{Var: "jabber:iq:version"},
 			{Var: "urn:xmpp:delegation:1"},
 		},
@@ -120,14 +121,14 @@ func discoInfoRoot(iqResp *xmpp.IQ, opts xmpp.ComponentOptions) {
 	iqResp.Payload = &payload
 }
 
-func discoInfoPubSub(iqResp *xmpp.IQ) {
-	payload := xmpp.DiscoInfo{
+func discoInfoPubSub(iqResp *stanza.IQ) {
+	payload := stanza.DiscoInfo{
 		XMLName: xml.Name{
-			Space: xmpp.NSDiscoInfo,
+			Space: stanza.NSDiscoInfo,
 			Local: "query",
 		},
 		Node: pubsubNode,
-		Features: []xmpp.Feature{
+		Features: []stanza.Feature{
 			{Var: "http://jabber.org/protocol/pubsub"},
 			{Var: "http://jabber.org/protocol/pubsub#publish"},
 			{Var: "http://jabber.org/protocol/pubsub#subscribe"},
@@ -137,19 +138,19 @@ func discoInfoPubSub(iqResp *xmpp.IQ) {
 	iqResp.Payload = &payload
 }
 
-func discoInfoPEP(iqResp *xmpp.IQ) {
-	identity := xmpp.Identity{
+func discoInfoPEP(iqResp *stanza.IQ) {
+	identity := stanza.Identity{
 		Category: "pubsub",
 		Type:     "pep",
 	}
-	payload := xmpp.DiscoInfo{
+	payload := stanza.DiscoInfo{
 		XMLName: xml.Name{
-			Space: xmpp.NSDiscoInfo,
+			Space: stanza.NSDiscoInfo,
 			Local: "query",
 		},
-		Identity: identity,
+		Identity: []stanza.Identity{identity},
 		Node:     pepNode,
-		Features: []xmpp.Feature{
+		Features: []stanza.Feature{
 			{Var: "http://jabber.org/protocol/pubsub#access-presence"},
 			{Var: "http://jabber.org/protocol/pubsub#auto-create"},
 			{Var: "http://jabber.org/protocol/pubsub#auto-subscribe"},
@@ -166,25 +167,25 @@ func discoInfoPEP(iqResp *xmpp.IQ) {
 	iqResp.Payload = &payload
 }
 
-func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
+func handleDelegation(s xmpp.Sender, p stanza.Packet) {
 	// Type conversion & sanity checks
-	iq, ok := p.(xmpp.IQ)
+	iq, ok := p.(stanza.IQ)
 	if !ok {
 		return
 	}
 
-	delegation, ok := iq.Payload.(*xmpp.Delegation)
+	delegation, ok := iq.Payload.(*stanza.Delegation)
 	if !ok {
 		return
 	}
 	forwardedPacket := delegation.Forwarded.Stanza
 	fmt.Println(forwardedPacket)
-	forwardedIQ, ok := forwardedPacket.(xmpp.IQ)
+	forwardedIQ, ok := forwardedPacket.(stanza.IQ)
 	if !ok {
 		return
 	}
 
-	pubsub, ok := forwardedIQ.Payload.(*xmpp.PubSub)
+	pubsub, ok := forwardedIQ.Payload.(*stanza.PubSub)
 	if !ok {
 		// We only support pubsub delegation
 		return
@@ -192,8 +193,8 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
 
 	if pubsub.Publish.XMLName.Local == "publish" {
 		// Prepare pubsub IQ reply
-		iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
-		payload := xmpp.PubSub{
+		iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
+		payload := stanza.PubSub{
 			XMLName: xml.Name{
 				Space: "http://jabber.org/protocol/pubsub",
 				Local: "pubsub",
@@ -201,13 +202,13 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
 		}
 		iqResp.Payload = &payload
 		// Wrap the reply in delegation 'forward'
-		iqForward := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
-		delegPayload := xmpp.Delegation{
+		iqForward := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
+		delegPayload := stanza.Delegation{
 			XMLName: xml.Name{
 				Space: "urn:xmpp:delegation:1",
 				Local: "delegation",
 			},
-			Forwarded: &xmpp.Forwarded{
+			Forwarded: &stanza.Forwarded{
 				XMLName: xml.Name{
 					Space: "urn:xmpp:forward:0",
 					Local: "forward",
diff --git a/_examples/xmpp_component/xmpp_component.go b/_examples/xmpp_component/xmpp_component.go
index 17cfc1a..4bc906b 100644
--- a/_examples/xmpp_component/xmpp_component.go
+++ b/_examples/xmpp_component/xmpp_component.go
@@ -6,6 +6,7 @@ import (
 	"log"
 
 	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func main() {
@@ -21,12 +22,12 @@ func main() {
 	router := xmpp.NewRouter()
 	router.HandleFunc("message", handleMessage)
 	router.NewRoute().
-		IQNamespaces(xmpp.NSDiscoInfo).
-		HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+		IQNamespaces(stanza.NSDiscoInfo).
+		HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
 			discoInfo(s, p, opts)
 		})
 	router.NewRoute().
-		IQNamespaces(xmpp.NSDiscoItems).
+		IQNamespaces(stanza.NSDiscoItems).
 		HandlerFunc(discoItems)
 	router.NewRoute().
 		IQNamespaces("jabber:iq:version").
@@ -44,36 +45,36 @@ func main() {
 	log.Fatal(cm.Run())
 }
 
-func handleMessage(_ xmpp.Sender, p xmpp.Packet) {
-	msg, ok := p.(xmpp.Message)
+func handleMessage(_ xmpp.Sender, p stanza.Packet) {
+	msg, ok := p.(stanza.Message)
 	if !ok {
 		return
 	}
 	fmt.Println("Received message:", msg.Body)
 }
 
-func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
+func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
 	// Type conversion & sanity checks
-	iq, ok := p.(xmpp.IQ)
+	iq, ok := p.(stanza.IQ)
 	if !ok || iq.Type != "get" {
 		return
 	}
 
-	iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
-	identity := xmpp.Identity{
+	iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+	identity := stanza.Identity{
 		Name:     opts.Name,
 		Category: opts.Category,
 		Type:     opts.Type,
 	}
-	payload := xmpp.DiscoInfo{
+	payload := stanza.DiscoInfo{
 		XMLName: xml.Name{
-			Space: xmpp.NSDiscoInfo,
+			Space: stanza.NSDiscoInfo,
 			Local: "query",
 		},
-		Identity: identity,
-		Features: []xmpp.Feature{
-			{Var: xmpp.NSDiscoInfo},
-			{Var: xmpp.NSDiscoItems},
+		Identity: []stanza.Identity{identity},
+		Features: []stanza.Feature{
+			{Var: stanza.NSDiscoInfo},
+			{Var: stanza.NSDiscoItems},
 			{Var: "jabber:iq:version"},
 			{Var: "urn:xmpp:delegation:1"},
 		},
@@ -83,24 +84,24 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
 }
 
 // TODO: Handle iq error responses
-func discoItems(c xmpp.Sender, p xmpp.Packet) {
+func discoItems(c xmpp.Sender, p stanza.Packet) {
 	// Type conversion & sanity checks
-	iq, ok := p.(xmpp.IQ)
+	iq, ok := p.(stanza.IQ)
 	if !ok || iq.Type != "get" {
 		return
 	}
 
-	discoItems, ok := iq.Payload.(*xmpp.DiscoItems)
+	discoItems, ok := iq.Payload.(*stanza.DiscoItems)
 	if !ok {
 		return
 	}
 
-	iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+	iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
 
-	var payload xmpp.DiscoItems
+	var payload stanza.DiscoItems
 	if discoItems.Node == "" {
-		payload = xmpp.DiscoItems{
-			Items: []xmpp.DiscoItem{
+		payload = stanza.DiscoItems{
+			Items: []stanza.DiscoItem{
 				{Name: "test node", JID: "service.localhost", Node: "node1"},
 			},
 		}
@@ -109,15 +110,15 @@ func discoItems(c xmpp.Sender, p xmpp.Packet) {
 	_ = c.Send(iqResp)
 }
 
-func handleVersion(c xmpp.Sender, p xmpp.Packet) {
+func handleVersion(c xmpp.Sender, p stanza.Packet) {
 	// Type conversion & sanity checks
-	iq, ok := p.(xmpp.IQ)
+	iq, ok := p.(stanza.IQ)
 	if !ok {
 		return
 	}
 
-	iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
-	var payload xmpp.Version
+	iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
+	var payload stanza.Version
 	payload.Name = "Fluux XMPP Component"
 	payload.Version = "0.0.1"
 	iq.Payload = &payload
diff --git a/_examples/xmpp_echo/xmpp_echo.go b/_examples/xmpp_echo/xmpp_echo.go
index 3725f08..b4da207 100644
--- a/_examples/xmpp_echo/xmpp_echo.go
+++ b/_examples/xmpp_echo/xmpp_echo.go
@@ -10,6 +10,7 @@ import (
 	"os"
 
 	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func main() {
@@ -35,15 +36,15 @@ func main() {
 	log.Fatal(cm.Run())
 }
 
-func handleMessage(s xmpp.Sender, p xmpp.Packet) {
-	msg, ok := p.(xmpp.Message)
+func handleMessage(s xmpp.Sender, p stanza.Packet) {
+	msg, ok := p.(stanza.Message)
 	if !ok {
 		_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
 		return
 	}
 
 	_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
-	reply := xmpp.Message{Attrs: xmpp.Attrs{To: msg.From}, Body: msg.Body}
+	reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
 	_ = s.Send(reply)
 }
 
diff --git a/_examples/xmpp_jukebox/xmpp_jukebox.go b/_examples/xmpp_jukebox/xmpp_jukebox.go
index 2218279..02e2c76 100644
--- a/_examples/xmpp_jukebox/xmpp_jukebox.go
+++ b/_examples/xmpp_jukebox/xmpp_jukebox.go
@@ -12,6 +12,7 @@ import (
 	"github.com/processone/mpg123"
 	"github.com/processone/soundcloud"
 	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 // Get the actual song Stream URL from SoundCloud website song URL and play it with mpg123 player.
@@ -41,12 +42,12 @@ func main() {
 	router := xmpp.NewRouter()
 	router.NewRoute().
 		Packet("message").
-		HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+		HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
 			handleMessage(s, p, player)
 		})
 	router.NewRoute().
 		Packet("message").
-		HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
+		HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
 			handleIQ(s, p, player)
 		})
 
@@ -59,8 +60,8 @@ func main() {
 	log.Fatal(cm.Run())
 }
 
-func handleMessage(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
-	msg, ok := p.(xmpp.Message)
+func handleMessage(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
+	msg, ok := p.(stanza.Message)
 	if !ok {
 		return
 	}
@@ -73,14 +74,14 @@ func handleMessage(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
 	}
 }
 
-func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
-	iq, ok := p.(xmpp.IQ)
+func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
+	iq, ok := p.(stanza.IQ)
 	if !ok {
 		return
 	}
 	switch payload := iq.Payload.(type) {
 	// We support IOT Control IQ
-	case *xmpp.ControlSet:
+	case *stanza.ControlSet:
 		var url string
 		for _, element := range payload.Fields {
 			if element.XMLName.Local == "string" && element.Name == "url" {
@@ -90,9 +91,9 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
 		}
 
 		playSCURL(player, url)
-		setResponse := new(xmpp.ControlSetResponse)
+		setResponse := new(stanza.ControlSetResponse)
 		// FIXME: Broken
-		reply := xmpp.IQ{Attrs: xmpp.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
+		reply := stanza.IQ{Attrs: stanza.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
 		_ = s.Send(reply)
 		// TODO add Soundclound artist / title retrieval
 		sendUserTune(s, "Radiohead", "Spectre")
@@ -102,9 +103,9 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
 }
 
 func sendUserTune(s xmpp.Sender, artist string, title string) {
-	tune := xmpp.Tune{Artist: artist, Title: title}
-	iq := xmpp.NewIQ(xmpp.Attrs{Type: "set", Id: "usertune-1", Lang: "en"})
-	payload := xmpp.PubSub{Publish: &xmpp.Publish{Node: "http://jabber.org/protocol/tune", Item: xmpp.Item{Tune: &tune}}}
+	tune := stanza.Tune{Artist: artist, Title: title}
+	iq := stanza.NewIQ(stanza.Attrs{Type: "set", Id: "usertune-1", Lang: "en"})
+	payload := stanza.PubSub{Publish: &stanza.Publish{Node: "http://jabber.org/protocol/tune", Item: stanza.Item{Tune: &tune}}}
 	iq.Payload = &payload
 	_ = s.Send(iq)
 }
diff --git a/auth.go b/auth.go
index 497258d..926426d 100644
--- a/auth.go
+++ b/auth.go
@@ -6,9 +6,11 @@ import (
 	"errors"
 	"fmt"
 	"io"
+
+	"gosrc.io/xmpp/stanza"
 )
 
-func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f StreamFeatures, user string, password string) (err error) {
+func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f stanza.StreamFeatures, user string, password string) (err error) {
 	// TODO: Implement other type of SASL Authentication
 	havePlain := false
 	for _, m := range f.Mechanisms.Mechanism {
@@ -30,17 +32,17 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
 	raw := "\x00" + user + "\x00" + password
 	enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
 	base64.StdEncoding.Encode(enc, []byte(raw))
-	fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc)
+	fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", stanza.NSSASL, enc)
 
 	// Next message should be either success or failure.
-	val, err := nextPacket(decoder)
+	val, err := stanza.NextPacket(decoder)
 	if err != nil {
 		return err
 	}
 
 	switch v := val.(type) {
-	case SASLSuccess:
-	case SASLFailure:
+	case stanza.SASLSuccess:
+	case stanza.SASLFailure:
 		// v.Any is type of sub-element in failure, which gives a description of what failed.
 		err := errors.New("auth failure: " + v.Any.Local)
 		return NewConnError(err, true)
@@ -49,72 +51,3 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
 	}
 	return err
 }
-
-// ============================================================================
-// SASLSuccess
-
-type SASLSuccess struct {
-	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
-}
-
-func (SASLSuccess) Name() string {
-	return "sasl:success"
-}
-
-type saslSuccessDecoder struct{}
-
-var saslSuccess saslSuccessDecoder
-
-func (saslSuccessDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLSuccess, error) {
-	var packet SASLSuccess
-	err := p.DecodeElement(&packet, &se)
-	return packet, err
-}
-
-// ============================================================================
-// SASLFailure
-
-type SASLFailure struct {
-	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
-	Any     xml.Name // error reason is a subelement
-}
-
-func (SASLFailure) Name() string {
-	return "sasl:failure"
-}
-
-type saslFailureDecoder struct{}
-
-var saslFailure saslFailureDecoder
-
-func (saslFailureDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLFailure, error) {
-	var packet SASLFailure
-	err := p.DecodeElement(&packet, &se)
-	return packet, err
-}
-
-// ============================================================================
-
-type auth struct {
-	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
-	Mechanism string   `xml:"mecanism,attr"`
-	Value     string   `xml:",innerxml"`
-}
-
-type BindBind struct {
-	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
-	Resource string   `xml:"resource,omitempty"`
-	Jid      string   `xml:"jid,omitempty"`
-}
-
-func (b *BindBind) Namespace() string {
-	return b.XMLName.Space
-}
-
-// Session is obsolete in RFC 6121.
-// Added for compliance with RFC 3121.
-// Remove when ejabberd purely conforms to RFC 6121.
-type sessionSession struct {
-	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
-	optional xml.Name // If it does exist, it mean we are not required to open session
-}
diff --git a/check_cert.go b/check_cert.go
index 5addd87..e33d9f9 100644
--- a/check_cert.go
+++ b/check_cert.go
@@ -8,6 +8,8 @@ import (
 	"net"
 	"strings"
 	"time"
+
+	"gosrc.io/xmpp/stanza"
 )
 
 // TODO: Should I move this as an extension of the client?
@@ -49,28 +51,28 @@ func (c *ServerCheck) Check() error {
 	decoder := xml.NewDecoder(tcpconn)
 
 	// Send stream open tag
-	if _, err = fmt.Fprintf(tcpconn, xmppStreamOpen, c.domain, NSClient, NSStream); err != nil {
+	if _, err = fmt.Fprintf(tcpconn, xmppStreamOpen, c.domain, stanza.NSClient, stanza.NSStream); err != nil {
 		return err
 	}
 
 	// Set xml decoder and extract streamID from reply (not used for now)
-	_, err = initStream(decoder)
+	_, err = stanza.InitStream(decoder)
 	if err != nil {
 		return err
 	}
 
 	// extract stream features
-	var f StreamFeatures
-	packet, err := nextPacket(decoder)
+	var f stanza.StreamFeatures
+	packet, err := stanza.NextPacket(decoder)
 	if err != nil {
 		err = fmt.Errorf("stream open decode features: %s", err)
 		return err
 	}
 
 	switch p := packet.(type) {
-	case StreamFeatures:
+	case stanza.StreamFeatures:
 		f = p
-	case StreamError:
+	case stanza.StreamError:
 		return errors.New("open stream error: " + p.Error.Local)
 	default:
 		return errors.New("expected packet received while expecting features, got " + p.Name())
@@ -79,13 +81,13 @@ func (c *ServerCheck) Check() error {
 	if _, ok := f.DoesStartTLS(); ok {
 		fmt.Fprintf(tcpconn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
 
-		var k tlsProceed
+		var k stanza.TLSProceed
 		if err = decoder.DecodeElement(&k, nil); err != nil {
 			return fmt.Errorf("expecting starttls proceed: %s", err)
 		}
 
-		DefaultTlsConfig.ServerName = c.domain
-		tlsConn := tls.Client(tcpconn, &DefaultTlsConfig)
+		stanza.DefaultTlsConfig.ServerName = c.domain
+		tlsConn := tls.Client(tcpconn, &stanza.DefaultTlsConfig)
 		// We convert existing connection to TLS
 		if err = tlsConn.Handshake(); err != nil {
 			return err
diff --git a/client.go b/client.go
index 1a63041..2bd8224 100644
--- a/client.go
+++ b/client.go
@@ -6,6 +6,8 @@ import (
 	"fmt"
 	"net"
 	"time"
+
+	"gosrc.io/xmpp/stanza"
 )
 
 //=============================================================================
@@ -153,7 +155,7 @@ func (c *Client) SetHandler(handler EventHandler) {
 }
 
 // Send marshals XMPP stanza and sends it to the server.
-func (c *Client) Send(packet Packet) error {
+func (c *Client) Send(packet stanza.Packet) error {
 	conn := c.conn
 	if conn == nil {
 		return errors.New("client is not connected")
@@ -191,7 +193,7 @@ func (c *Client) SendRaw(packet string) error {
 // Loop: Receive data from server
 func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
 	for {
-		val, err := nextPacket(c.Session.decoder)
+		val, err := stanza.NextPacket(c.Session.decoder)
 		if err != nil {
 			close(keepaliveQuit)
 			c.updateState(StateDisconnected)
@@ -200,7 +202,7 @@ func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
 
 		// Handle stream errors
 		switch packet := val.(type) {
-		case StreamError:
+		case stanza.StreamError:
 			c.router.route(c, val)
 			close(keepaliveQuit)
 			c.streamError(packet.Error.Local, packet.Text)
diff --git a/component.go b/component.go
index d13822b..0176371 100644
--- a/component.go
+++ b/component.go
@@ -9,6 +9,8 @@ import (
 	"io"
 	"net"
 	"time"
+
+	"gosrc.io/xmpp/stanza"
 )
 
 const componentStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s'>"
@@ -72,13 +74,13 @@ func (c *Component) Connect() error {
 	c.conn = conn
 
 	// 1. Send stream open tag
-	if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, NSComponent, NSStream); err != nil {
+	if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, stanza.NSComponent, stanza.NSStream); err != nil {
 		return errors.New("cannot send stream open " + err.Error())
 	}
 	c.decoder = xml.NewDecoder(conn)
 
 	// 2. Initialize xml decoder and extract streamID from reply
-	streamId, err := initStream(c.decoder)
+	streamId, err := stanza.InitStream(c.decoder)
 	if err != nil {
 		return errors.New("cannot init decoder " + err.Error())
 	}
@@ -89,15 +91,15 @@ func (c *Component) Connect() error {
 	}
 
 	// 4. Check server response for authentication
-	val, err := nextPacket(c.decoder)
+	val, err := stanza.NextPacket(c.decoder)
 	if err != nil {
 		return err
 	}
 
 	switch v := val.(type) {
-	case StreamError:
+	case stanza.StreamError:
 		return errors.New("handshake failed " + v.Error.Local)
-	case Handshake:
+	case stanza.Handshake:
 		// Start the receiver go routine
 		go c.recv()
 		return nil
@@ -119,7 +121,7 @@ func (c *Component) SetHandler(handler EventHandler) {
 // Receiver Go routine receiver
 func (c *Component) recv() (err error) {
 	for {
-		val, err := nextPacket(c.decoder)
+		val, err := stanza.NextPacket(c.decoder)
 		if err != nil {
 			c.updateState(StateDisconnected)
 			return err
@@ -127,7 +129,7 @@ func (c *Component) recv() (err error) {
 
 		// Handle stream errors
 		switch p := val.(type) {
-		case StreamError:
+		case stanza.StreamError:
 			c.router.route(c, val)
 			c.streamError(p.Error.Local, p.Text)
 			return errors.New("stream error: " + p.Error.Local)
@@ -137,7 +139,7 @@ func (c *Component) recv() (err error) {
 }
 
 // Send marshalls XMPP stanza and sends it to the server.
-func (c *Component) Send(packet Packet) error {
+func (c *Component) Send(packet stanza.Packet) error {
 	conn := c.conn
 	if conn == nil {
 		return errors.New("component is not connected")
@@ -186,90 +188,6 @@ func (c *Component) handshake(streamId string) string {
 	return encodedStr
 }
 
-// ============================================================================
-// Handshake Stanza
-
-// Handshake is a stanza used by XMPP components to authenticate on XMPP
-// component port.
-type Handshake struct {
-	XMLName xml.Name `xml:"jabber:component:accept handshake"`
-	// TODO Add handshake value with test for proper serialization
-	// Value string     `xml:",innerxml"`
-}
-
-func (Handshake) Name() string {
-	return "component:handshake"
-}
-
-// Handshake decoding wrapper
-
-type handshakeDecoder struct{}
-
-var handshake handshakeDecoder
-
-func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
-	var packet Handshake
-	err := p.DecodeElement(&packet, &se)
-	return packet, err
-}
-
-// ============================================================================
-// Component delegation
-// XEP-0355
-
-// Delegation can be used both on message (for delegated) and IQ (for Forwarded),
-// depending on the context.
-type Delegation struct {
-	MsgExtension
-	XMLName   xml.Name   `xml:"urn:xmpp:delegation:1 delegation"`
-	Forwarded *Forwarded // This is used in iq to wrap delegated iqs
-	Delegated *Delegated // This is used in a message to confirm delegated namespace
-}
-
-func (d *Delegation) Namespace() string {
-	return d.XMLName.Space
-}
-
-type Forwarded struct {
-	XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
-	Stanza  Packet
-}
-
-// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
-// transform generic XML content into hierarchical Node structure.
-func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
-	// Check subelements to extract required field as boolean
-	for {
-		t, err := d.Token()
-		if err != nil {
-			return err
-		}
-
-		switch tt := t.(type) {
-
-		case xml.StartElement:
-			if packet, err := decodeClient(d, tt); err == nil {
-				f.Stanza = packet
-			}
-
-		case xml.EndElement:
-			if tt == start.End() {
-				return nil
-			}
-		}
-	}
-}
-
-type Delegated struct {
-	XMLName   xml.Name `xml:"delegated"`
-	Namespace string   `xml:"namespace,attr,omitempty"`
-}
-
-func init() {
-	TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
-	TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
-}
-
 /*
 TODO: Add support for discovery management directly in component
 TODO: Support multiple identities on disco info
diff --git a/iq.go b/iq.go
deleted file mode 100644
index d04ab36..0000000
--- a/iq.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package xmpp
-
-import (
-	"encoding/xml"
-	"fmt"
-)
-
-/*
-TODO support ability to put Raw payload inside IQ
-*/
-
-// ============================================================================
-// IQ Packet
-
-// IQ implements RFC 6120 - A.5 Client Namespace (a part)
-type IQ struct { // Info/Query
-	XMLName xml.Name `xml:"iq"`
-	// MUST have a ID
-	Attrs
-	// We can only have one payload on IQ:
-	//   "An IQ stanza of type "get" or "set" MUST contain exactly one
-	//    child element, which specifies the semantics of the particular
-	//    request."
-	Payload IQPayload `xml:",omitempty"`
-	Error   Err       `xml:"error,omitempty"`
-	// Any is used to decode unknown payload as a generique structure
-	Any *Node `xml:",any"`
-}
-
-type IQPayload interface {
-	Namespace() string
-}
-
-func NewIQ(a Attrs) IQ {
-	// TODO generate IQ ID if not set
-	// TODO ensure that type is set, as it is required
-	return IQ{
-		XMLName: xml.Name{Local: "iq"},
-		Attrs:   a,
-	}
-}
-
-func (iq IQ) MakeError(xerror Err) IQ {
-	from := iq.From
-	to := iq.To
-
-	iq.Type = "error"
-	iq.From = to
-	iq.To = from
-	iq.Error = xerror
-
-	return iq
-}
-
-func (IQ) Name() string {
-	return "iq"
-}
-
-type iqDecoder struct{}
-
-var iq iqDecoder
-
-func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
-	var packet IQ
-	err := p.DecodeElement(&packet, &se)
-	return packet, err
-}
-
-// UnmarshalXML implements custom parsing for IQs
-func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
-	iq.XMLName = start.Name
-
-	// Extract IQ attributes
-	for _, attr := range start.Attr {
-		if attr.Name.Local == "id" {
-			iq.Id = attr.Value
-		}
-		if attr.Name.Local == "type" {
-			iq.Type = StanzaType(attr.Value)
-		}
-		if attr.Name.Local == "to" {
-			iq.To = attr.Value
-		}
-		if attr.Name.Local == "from" {
-			iq.From = attr.Value
-		}
-	}
-
-	// decode inner elements
-	for {
-		t, err := d.Token()
-		if err != nil {
-			return err
-		}
-
-		switch tt := t.(type) {
-		case xml.StartElement:
-			if tt.Name.Local == "error" {
-				var xmppError Err
-				err = d.DecodeElement(&xmppError, &tt)
-				if err != nil {
-					fmt.Println(err)
-					return err
-				}
-				iq.Error = xmppError
-				continue
-			}
-			if iqExt := TypeRegistry.GetIQExtension(tt.Name); iqExt != nil {
-				// Decode payload extension
-				err = d.DecodeElement(iqExt, &tt)
-				if err != nil {
-					return err
-				}
-				iq.Payload = iqExt
-				continue
-			}
-			// TODO: If unknown decode as generic node
-			node := new(Node)
-			err = d.DecodeElement(node, &tt)
-			if err != nil {
-				return err
-			}
-			iq.Any = node
-		case xml.EndElement:
-			if tt == start.End() {
-				return nil
-			}
-		}
-	}
-}
-
-// ============================================================================
-// Generic / unknown content
-
-// Node is a generic structure to represent XML data. It is used to parse
-// unreferenced or custom stanza payload.
-type Node struct {
-	XMLName xml.Name
-	Attrs   []xml.Attr `xml:"-"`
-	Content string     `xml:",innerxml"`
-	Nodes   []Node     `xml:",any"`
-}
-
-func (n *Node) Namespace() string {
-	return n.XMLName.Space
-}
-
-// Attr represents generic XML attributes, as used on the generic XML Node
-// representation.
-type Attr struct {
-	K string
-	V string
-}
-
-// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
-// transform generic XML content into hierarchical Node structure.
-func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
-	// Assign	"n.Attrs = start.Attr", without repeating xmlns in attributes:
-	for _, attr := range start.Attr {
-		// Do not repeat xmlns, it is already in XMLName
-		if attr.Name.Local != "xmlns" {
-			n.Attrs = append(n.Attrs, attr)
-		}
-	}
-	type node Node
-	return d.DecodeElement((*node)(n), &start)
-}
-
-// MarshalXML is a custom XML serializer used by xml.Marshal to serialize a
-// Node structure to XML.
-func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
-	start.Attr = n.Attrs
-	start.Name = n.XMLName
-
-	err = e.EncodeToken(start)
-	e.EncodeElement(n.Nodes, xml.StartElement{Name: n.XMLName})
-	return e.EncodeToken(xml.EndElement{Name: start.Name})
-}
-
-// ============================================================================
-// Disco
-
-const (
-	NSDiscoInfo  = "http://jabber.org/protocol/disco#info"
-	NSDiscoItems = "http://jabber.org/protocol/disco#items"
-)
-
-// Disco Info
-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"`
-}
-
-func (d *DiscoInfo) Namespace() string {
-	return d.XMLName.Space
-}
-
-type Identity struct {
-	XMLName  xml.Name `xml:"identity,omitempty"`
-	Name     string   `xml:"name,attr,omitempty"`
-	Category string   `xml:"category,attr,omitempty"`
-	Type     string   `xml:"type,attr,omitempty"`
-}
-
-type Feature struct {
-	XMLName xml.Name `xml:"feature"`
-	Var     string   `xml:"var,attr"`
-}
-
-// Disco Items
-type DiscoItems struct {
-	XMLName xml.Name    `xml:"http://jabber.org/protocol/disco#items query"`
-	Node    string      `xml:"node,attr,omitempty"`
-	Items   []DiscoItem `xml:"item"`
-}
-
-func (d *DiscoItems) Namespace() string {
-	return d.XMLName.Space
-}
-
-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"`
-}
-
-// ============================================================================
-// Software Version (XEP-0092)
-
-// Version
-type Version struct {
-	XMLName xml.Name `xml:"jabber:iq:version query"`
-	Name    string   `xml:"name,omitempty"`
-	Version string   `xml:"version,omitempty"`
-	OS      string   `xml:"os,omitempty"`
-}
-
-func (v *Version) Namespace() string {
-	return v.XMLName.Space
-}
-
-// ============================================================================
-// 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{"urn:ietf:params:xml:ns:xmpp-bind", "bind"}, BindBind{})
-	TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:iot:control", "set"}, ControlSet{})
-	TypeRegistry.MapExtension(PKTIQ, xml.Name{"jabber:iq:version", "query"}, Version{})
-}
diff --git a/router.go b/router.go
index b252db6..e8bf4b5 100644
--- a/router.go
+++ b/router.go
@@ -3,6 +3,8 @@ package xmpp
 import (
 	"encoding/xml"
 	"strings"
+
+	"gosrc.io/xmpp/stanza"
 )
 
 /*
@@ -32,7 +34,7 @@ func NewRouter() *Router {
 
 // route is called by the XMPP client to dispatch stanza received using the set up routes.
 // It is also used by test, but is not supposed to be used directly by users of the library.
-func (r *Router) route(s Sender, p Packet) {
+func (r *Router) route(s Sender, p stanza.Packet) {
 
 	var match RouteMatch
 	if r.Match(p, &match) {
@@ -41,15 +43,15 @@ func (r *Router) route(s Sender, p Packet) {
 		return
 	}
 	// If there is no match and we receive an iq set or get, we need to send a reply
-	if iq, ok := p.(IQ); ok {
-		if iq.Type == IQTypeGet || iq.Type == IQTypeSet {
+	if iq, ok := p.(stanza.IQ); ok {
+		if iq.Type == stanza.IQTypeGet || iq.Type == stanza.IQTypeSet {
 			iqNotImplemented(s, iq)
 		}
 	}
 }
 
-func iqNotImplemented(s Sender, iq IQ) {
-	err := Err{
+func iqNotImplemented(s Sender, iq stanza.IQ) {
+	err := stanza.Err{
 		XMLName: xml.Name{Local: "error"},
 		Code:    501,
 		Type:    "cancel",
@@ -66,7 +68,7 @@ func (r *Router) NewRoute() *Route {
 	return route
 }
 
-func (r *Router) Match(p Packet, match *RouteMatch) bool {
+func (r *Router) Match(p stanza.Packet, match *RouteMatch) bool {
 	for _, route := range r.routes {
 		if route.Match(p, match) {
 			return true
@@ -83,14 +85,14 @@ func (r *Router) Handle(name string, handler Handler) *Route {
 
 // HandleFunc registers a new route with a matcher for for a given packet name (iq, message, presence)
 // See Route.Path() and Route.HandlerFunc().
-func (r *Router) HandleFunc(name string, f func(s Sender, p Packet)) *Route {
+func (r *Router) HandleFunc(name string, f func(s Sender, p stanza.Packet)) *Route {
 	return r.NewRoute().Packet(name).HandlerFunc(f)
 }
 
 // ============================================================================
 // Route
 type Handler interface {
-	HandlePacket(s Sender, p Packet)
+	HandlePacket(s Sender, p stanza.Packet)
 }
 
 type Route struct {
@@ -108,10 +110,10 @@ func (r *Route) Handler(handler Handler) *Route {
 // ordinary functions as XMPP handlers. If f is a function
 // with the appropriate signature, HandlerFunc(f) is a
 // Handler that calls f.
-type HandlerFunc func(s Sender, p Packet)
+type HandlerFunc func(s Sender, p stanza.Packet)
 
 // HandlePacket calls f(s, p)
-func (f HandlerFunc) HandlePacket(s Sender, p Packet) {
+func (f HandlerFunc) HandlePacket(s Sender, p stanza.Packet) {
 	f(s, p)
 }
 
@@ -126,7 +128,7 @@ func (r *Route) addMatcher(m matcher) *Route {
 	return r
 }
 
-func (r *Route) Match(p Packet, match *RouteMatch) bool {
+func (r *Route) Match(p stanza.Packet, match *RouteMatch) bool {
 	for _, m := range r.matchers {
 		if matched := m.Match(p, match); !matched {
 			return false
@@ -144,18 +146,18 @@ func (r *Route) Match(p Packet, match *RouteMatch) bool {
 
 type nameMatcher string
 
-func (n nameMatcher) Match(p Packet, match *RouteMatch) bool {
+func (n nameMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
 	var name string
 	// TODO: To avoid type switch everywhere in matching, I think we will need to have
 	//    to move to a concrete type for packets, to make matching and comparison more natural.
 	//    Current code structure is probably too rigid.
 	// Maybe packet types should even be from an enum.
 	switch p.(type) {
-	case Message:
+	case stanza.Message:
 		name = "message"
-	case IQ:
+	case stanza.IQ:
 		name = "iq"
-	case Presence:
+	case stanza.Presence:
 		name = "presence"
 	}
 	if name == string(n) {
@@ -177,14 +179,14 @@ func (r *Route) Packet(name string) *Route {
 // nsTypeMather matches on a list of IQ  payload namespaces
 type nsTypeMatcher []string
 
-func (m nsTypeMatcher) Match(p Packet, match *RouteMatch) bool {
-	var stanzaType StanzaType
+func (m nsTypeMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
+	var stanzaType stanza.StanzaType
 	switch packet := p.(type) {
-	case IQ:
+	case stanza.IQ:
 		stanzaType = packet.Type
-	case Presence:
+	case stanza.Presence:
 		stanzaType = packet.Type
-	case Message:
+	case stanza.Message:
 		if packet.Type == "" {
 			// optional on message, normal is the default type
 			stanzaType = "normal"
@@ -211,8 +213,8 @@ func (r *Route) StanzaType(types ...string) *Route {
 // nsIqMather matches on a list of IQ  payload namespaces
 type nsIQMatcher []string
 
-func (m nsIQMatcher) Match(p Packet, match *RouteMatch) bool {
-	iq, ok := p.(IQ)
+func (m nsIQMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
+	iq, ok := p.(stanza.IQ)
 	if !ok {
 		return false
 	}
@@ -235,7 +237,7 @@ func (r *Route) IQNamespaces(namespaces ...string) *Route {
 
 // Matchers are used to "specialize" routes and focus on specific packet features
 type matcher interface {
-	Match(Packet, *RouteMatch) bool
+	Match(stanza.Packet, *RouteMatch) bool
 }
 
 // RouteMatch extracts and gather match information
diff --git a/session.go b/session.go
index 88486c6..2077e7a 100644
--- a/session.go
+++ b/session.go
@@ -7,6 +7,8 @@ import (
 	"fmt"
 	"io"
 	"net"
+
+	"gosrc.io/xmpp/stanza"
 )
 
 const xmppStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s' version='1.0'>"
@@ -15,7 +17,7 @@ type Session struct {
 	// Session info
 	BindJid      string // Jabber ID as provided by XMPP server
 	StreamId     string
-	Features     StreamFeatures
+	Features     stanza.StreamFeatures
 	TlsEnabled   bool
 	lastPacketId int
 
@@ -85,14 +87,14 @@ func (s *Session) setProxy(conn net.Conn, newConn net.Conn, o Config) {
 	s.decoder.CharsetReader = o.CharsetReader
 }
 
-func (s *Session) open(domain string) (f StreamFeatures) {
+func (s *Session) open(domain string) (f stanza.StreamFeatures) {
 	// Send stream open tag
-	if _, s.err = fmt.Fprintf(s.socketProxy, xmppStreamOpen, domain, NSClient, NSStream); s.err != nil {
+	if _, s.err = fmt.Fprintf(s.socketProxy, xmppStreamOpen, domain, stanza.NSClient, stanza.NSStream); s.err != nil {
 		return
 	}
 
 	// Set xml decoder and extract streamID from reply
-	s.StreamId, s.err = initStream(s.decoder) // TODO refactor / rename
+	s.StreamId, s.err = stanza.InitStream(s.decoder) // TODO refactor / rename
 	if s.err != nil {
 		return
 	}
@@ -112,7 +114,7 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn {
 	if _, ok := s.Features.DoesStartTLS(); ok {
 		fmt.Fprintf(s.socketProxy, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
 
-		var k tlsProceed
+		var k stanza.TLSProceed
 		if s.err = s.decoder.DecodeElement(&k, nil); s.err != nil {
 			s.err = errors.New("expecting starttls proceed: " + s.err.Error())
 			return conn
@@ -120,8 +122,8 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn {
 		s.TlsEnabled = true
 
 		// TODO: add option to accept all TLS certificates: insecureSkipTlsVerify (DefaultTlsConfig.InsecureSkipVerify)
-		DefaultTlsConfig.ServerName = domain
-		tlsConn := tls.Client(conn, &DefaultTlsConfig)
+		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
@@ -153,12 +155,12 @@ func (s *Session) bind(o Config) {
 	var resource = o.parsedJid.Resource
 	if resource != "" {
 		fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'><resource>%s</resource></bind></iq>",
-			s.PacketId(), nsBind, resource)
+			s.PacketId(), stanza.NSBind, resource)
 	} else {
-		fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'/></iq>", s.PacketId(), nsBind)
+		fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'/></iq>", s.PacketId(), stanza.NSBind)
 	}
 
-	var iq IQ
+	var iq stanza.IQ
 	if s.err = s.decoder.Decode(&iq); s.err != nil {
 		s.err = errors.New("error decoding iq bind result: " + s.err.Error())
 		return
@@ -166,7 +168,7 @@ func (s *Session) bind(o Config) {
 
 	// TODO Check all elements
 	switch payload := iq.Payload.(type) {
-	case *BindBind:
+	case *stanza.BindBind:
 		s.BindJid = payload.Jid // our local id (with possibly randomly generated resource
 	default:
 		s.err = errors.New("iq bind result missing")
@@ -182,9 +184,9 @@ func (s *Session) rfc3921Session(o Config) {
 		return
 	}
 
-	var iq IQ
-	if s.Features.Session.optional.Local != "" {
-		fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), nsSession)
+	var iq stanza.IQ
+	if s.Features.Session.Optional.Local != "" {
+		fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), stanza.NSSession)
 		if s.err = s.decoder.Decode(&iq); s.err != nil {
 			s.err = errors.New("expecting iq result after session open: " + s.err.Error())
 			return
diff --git a/stanza/README.md b/stanza/README.md
new file mode 100644
index 0000000..466f594
--- /dev/null
+++ b/stanza/README.md
@@ -0,0 +1,76 @@
+# XMPP Stanza
+
+XMPP `stanza` package is use to parse, marshall and and unmarshal XMPP stanzas and nonzas.
+
+## Stanza creation
+
+When creating stanzas, you can use two approaches:
+
+1. You can create IQ, Presence or Message structs, set the fields and manually prepare extensions struct to add to the
+stanza.
+2. You can use `stanza` build helper to be guided when creating the stanza, and have more controls performed on the
+final stanza.
+
+The methods are equivalent and you can use whatever suits you best. The helpers will finally generate the same type of
+struct that you can build by hand.
+
+### Composing stanzas manually with structs
+
+Here is for example how you would generate an IQ discovery result:
+
+	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
+
+### Using helpers
+
+Here is for example how you would generate an IQ discovery result using Builder:
+
+	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")
+
+## Payload and extensions
+
+### Message
+
+### Presence
+
+### IQ
+
+IQ (Information Queries) contain a payload associated with the request and possibly an error. The main difference with
+Message and Presence extension is that you can only have one payload per IQ. The XMPP specification does not support
+having multiple payloads.
+
+Here is the list of structs implementing IQPayloads:
+
+- BindBind
+- Pubsub
+
+Finally, when the payload of the parsed stanza is unknown, the parser will provide the unknown payload as a generic
+`Node` element. You can also use the Node struct to add custom information on stanza generation. However, in both cases,
+you may also consider [adding your own custom extensions on stanzas]().
+
+
+## Adding your own custom extensions on stanzas
+
+Extensions are registered on launch using the `Registry`. It can be used to register you own custom payload. You may
+want to do so to support extensions we did not yet implement, or to add your own custom extensions to your XMPP stanzas.
diff --git a/stanza/auth_sasl.go b/stanza/auth_sasl.go
new file mode 100644
index 0000000..5961d9f
--- /dev/null
+++ b/stanza/auth_sasl.go
@@ -0,0 +1,79 @@
+package stanza
+
+import "encoding/xml"
+
+// ============================================================================
+// SASLSuccess
+
+type SASLSuccess struct {
+	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
+}
+
+func (SASLSuccess) Name() string {
+	return "sasl:success"
+}
+
+type saslSuccessDecoder struct{}
+
+var saslSuccess saslSuccessDecoder
+
+func (saslSuccessDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLSuccess, error) {
+	var packet SASLSuccess
+	err := p.DecodeElement(&packet, &se)
+	return packet, err
+}
+
+// ============================================================================
+// SASLFailure
+
+type SASLFailure struct {
+	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
+	Any     xml.Name // error reason is a subelement
+}
+
+func (SASLFailure) Name() string {
+	return "sasl:failure"
+}
+
+type saslFailureDecoder struct{}
+
+var saslFailure saslFailureDecoder
+
+func (saslFailureDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLFailure, error) {
+	var packet SASLFailure
+	err := p.DecodeElement(&packet, &se)
+	return packet, err
+}
+
+// ============================================================================
+
+type Auth struct {
+	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
+	Mechanism string   `xml:"mecanism,attr"`
+	Value     string   `xml:",innerxml"`
+}
+
+type BindBind struct {
+	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
+	Resource string   `xml:"resource,omitempty"`
+	Jid      string   `xml:"jid,omitempty"`
+}
+
+func (b *BindBind) Namespace() string {
+	return b.XMLName.Space
+}
+
+// Session is obsolete in RFC 6121.
+// Added for compliance with RFC 3121.
+// Remove when ejabberd purely conforms to RFC 6121.
+type sessionSession struct {
+	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
+	Optional xml.Name // If it does exist, it mean we are not required to open session
+}
+
+// ============================================================================
+// Registry init
+
+func init() {
+	TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:ietf:params:xml:ns:xmpp-bind", "bind"}, BindBind{})
+}
diff --git a/stanza/component.go b/stanza/component.go
new file mode 100644
index 0000000..196faea
--- /dev/null
+++ b/stanza/component.go
@@ -0,0 +1,89 @@
+package stanza
+
+import (
+	"encoding/xml"
+)
+
+// ============================================================================
+// Handshake Stanza
+
+// Handshake is a stanza used by XMPP components to authenticate on XMPP
+// component port.
+type Handshake struct {
+	XMLName xml.Name `xml:"jabber:component:accept handshake"`
+	// TODO Add handshake value with test for proper serialization
+	// Value string     `xml:",innerxml"`
+}
+
+func (Handshake) Name() string {
+	return "component:handshake"
+}
+
+// Handshake decoding wrapper
+
+type handshakeDecoder struct{}
+
+var handshake handshakeDecoder
+
+func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
+	var packet Handshake
+	err := p.DecodeElement(&packet, &se)
+	return packet, err
+}
+
+// ============================================================================
+// Component delegation
+// XEP-0355
+
+// Delegation can be used both on message (for delegated) and IQ (for Forwarded),
+// depending on the context.
+type Delegation struct {
+	MsgExtension
+	XMLName   xml.Name   `xml:"urn:xmpp:delegation:1 delegation"`
+	Forwarded *Forwarded // This is used in iq to wrap delegated iqs
+	Delegated *Delegated // This is used in a message to confirm delegated namespace
+}
+
+func (d *Delegation) Namespace() string {
+	return d.XMLName.Space
+}
+
+type Forwarded struct {
+	XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
+	Stanza  Packet
+}
+
+// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
+// transform generic XML content into hierarchical Node structure.
+func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+	// Check subelements to extract required field as boolean
+	for {
+		t, err := d.Token()
+		if err != nil {
+			return err
+		}
+
+		switch tt := t.(type) {
+
+		case xml.StartElement:
+			if packet, err := decodeClient(d, tt); err == nil {
+				f.Stanza = packet
+			}
+
+		case xml.EndElement:
+			if tt == start.End() {
+				return nil
+			}
+		}
+	}
+}
+
+type Delegated struct {
+	XMLName   xml.Name `xml:"delegated"`
+	Namespace string   `xml:"namespace,attr,omitempty"`
+}
+
+func init() {
+	TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
+	TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
+}
diff --git a/error.go b/stanza/error.go
similarity index 97%
rename from error.go
rename to stanza/error.go
index a4b4f17..0ac857f 100644
--- a/error.go
+++ b/stanza/error.go
@@ -1,14 +1,10 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
 	"strconv"
 )
 
-/*
-TODO support ability to put Raw payload inside IQ
-*/
-
 // ============================================================================
 // XMPP Errors
 
diff --git a/error_enum.go b/stanza/error_enum.go
similarity index 95%
rename from error_enum.go
rename to stanza/error_enum.go
index 314df4c..710de0e 100644
--- a/error_enum.go
+++ b/stanza/error_enum.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 // ErrorType is a Enum of error attribute type
 type ErrorType string
diff --git a/iot_control.go b/stanza/iot.go
similarity index 73%
rename from iot_control.go
rename to stanza/iot.go
index 67a54a1..5e15056 100644
--- a/iot_control.go
+++ b/stanza/iot.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
@@ -24,10 +24,16 @@ type ControlField struct {
 }
 
 type ControlSetResponse struct {
-	IQPayload
 	XMLName xml.Name `xml:"urn:xmpp:iot:control setResponse"`
 }
 
 func (c *ControlSetResponse) Namespace() string {
 	return c.XMLName.Space
 }
+
+// ============================================================================
+// Registry init
+
+func init() {
+	TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:iot:control", "set"}, ControlSet{})
+}
diff --git a/stanza/iq.go b/stanza/iq.go
new file mode 100644
index 0000000..34c74bb
--- /dev/null
+++ b/stanza/iq.go
@@ -0,0 +1,130 @@
+package stanza
+
+import (
+	"encoding/xml"
+	"fmt"
+)
+
+/*
+TODO support ability to put Raw payload inside IQ
+*/
+
+// ============================================================================
+// IQ Packet
+
+// IQ implements RFC 6120 - A.5 Client Namespace (a part)
+type IQ struct { // Info/Query
+	XMLName xml.Name `xml:"iq"`
+	// MUST have a ID
+	Attrs
+	// We can only have one payload on IQ:
+	//   "An IQ stanza of type "get" or "set" MUST contain exactly one
+	//    child element, which specifies the semantics of the particular
+	//    request."
+	Payload IQPayload `xml:",omitempty"`
+	Error   Err       `xml:"error,omitempty"`
+	// Any is used to decode unknown payload as a generique structure
+	Any *Node `xml:",any"`
+}
+
+type IQPayload interface {
+	Namespace() string
+}
+
+func NewIQ(a Attrs) IQ {
+	// TODO generate IQ ID if not set
+	// TODO ensure that type is set, as it is required
+	return IQ{
+		XMLName: xml.Name{Local: "iq"},
+		Attrs:   a,
+	}
+}
+
+func (iq IQ) MakeError(xerror Err) IQ {
+	from := iq.From
+	to := iq.To
+
+	iq.Type = "error"
+	iq.From = to
+	iq.To = from
+	iq.Error = xerror
+
+	return iq
+}
+
+func (IQ) Name() string {
+	return "iq"
+}
+
+type iqDecoder struct{}
+
+var iq iqDecoder
+
+func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
+	var packet IQ
+	err := p.DecodeElement(&packet, &se)
+	return packet, err
+}
+
+// UnmarshalXML implements custom parsing for IQs
+func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+	iq.XMLName = start.Name
+
+	// Extract IQ attributes
+	for _, attr := range start.Attr {
+		if attr.Name.Local == "id" {
+			iq.Id = attr.Value
+		}
+		if attr.Name.Local == "type" {
+			iq.Type = StanzaType(attr.Value)
+		}
+		if attr.Name.Local == "to" {
+			iq.To = attr.Value
+		}
+		if attr.Name.Local == "from" {
+			iq.From = attr.Value
+		}
+	}
+
+	// decode inner elements
+	for {
+		t, err := d.Token()
+		if err != nil {
+			return err
+		}
+
+		switch tt := t.(type) {
+		case xml.StartElement:
+			if tt.Name.Local == "error" {
+				var xmppError Err
+				err = d.DecodeElement(&xmppError, &tt)
+				if err != nil {
+					fmt.Println(err)
+					return err
+				}
+				iq.Error = xmppError
+				continue
+			}
+			if iqExt := TypeRegistry.GetIQExtension(tt.Name); iqExt != nil {
+				// Decode payload extension
+				err = d.DecodeElement(iqExt, &tt)
+				if err != nil {
+					return err
+				}
+				iq.Payload = iqExt
+				continue
+			}
+			// TODO: If unknown decode as generic node
+			node := new(Node)
+			err = d.DecodeElement(node, &tt)
+			if err != nil {
+				return err
+			}
+			iq.Any = node
+		case xml.EndElement:
+			if tt == start.End() {
+				return nil
+			}
+		}
+	}
+}
diff --git a/stanza/iq_disco.go b/stanza/iq_disco.go
new file mode 100644
index 0000000..0097c05
--- /dev/null
+++ b/stanza/iq_disco.go
@@ -0,0 +1,120 @@
+package stanza
+
+import (
+	"encoding/xml"
+)
+
+// ============================================================================
+// Disco Info
+
+const (
+	NSDiscoInfo = "http://jabber.org/protocol/disco#info"
+)
+
+// ----------
+// 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"`
+}
+
+func (d *DiscoInfo) Namespace() string {
+	return d.XMLName.Space
+}
+
+// ---------------
+// Builder helpers
+
+// DiscoInfo builds a default DiscoInfo payload
+func (iq *IQ) DiscoInfo() *DiscoInfo {
+	d := DiscoInfo{
+		XMLName: xml.Name{
+			Space: NSDiscoInfo,
+			Local: "query",
+		},
+	}
+	iq.Payload = &d
+	return &d
+}
+
+func (d *DiscoInfo) AddIdentity(name, category, typ string) {
+	identity := Identity{
+		XMLName:  xml.Name{Local: "identity"},
+		Name:     name,
+		Category: category,
+		Type:     typ,
+	}
+	d.Identity = append(d.Identity, identity)
+}
+
+func (d *DiscoInfo) AddFeatures(namespace ...string) {
+	for _, ns := range namespace {
+		d.Features = append(d.Features, Feature{Var: ns})
+	}
+}
+
+func (d *DiscoInfo) SetNode(node string) {
+	d.Node = node
+}
+
+func (d *DiscoInfo) SetIdentities(ident ...Identity) *DiscoInfo {
+	d.Identity = ident
+	return d
+}
+
+func (d *DiscoInfo) SetFeatures(namespace ...string) *DiscoInfo {
+	for _, ns := range namespace {
+		d.Features = append(d.Features, Feature{Var: ns})
+	}
+	return d
+}
+
+// -----------
+// SubElements
+
+type Identity struct {
+	XMLName  xml.Name `xml:"identity,omitempty"`
+	Name     string   `xml:"name,attr,omitempty"`
+	Category string   `xml:"category,attr,omitempty"`
+	Type     string   `xml:"type,attr,omitempty"`
+}
+
+type Feature struct {
+	XMLName xml.Name `xml:"feature"`
+	Var     string   `xml:"var,attr"`
+}
+
+// ============================================================================
+// Disco Info
+
+const (
+	NSDiscoItems = "http://jabber.org/protocol/disco#items"
+)
+
+type DiscoItems struct {
+	XMLName xml.Name    `xml:"http://jabber.org/protocol/disco#items query"`
+	Node    string      `xml:"node,attr,omitempty"`
+	Items   []DiscoItem `xml:"item"`
+}
+
+func (d *DiscoItems) Namespace() string {
+	return d.XMLName.Space
+}
+
+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"`
+}
+
+// ============================================================================
+// Registry init
+
+func init() {
+	TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoInfo, "query"}, DiscoInfo{})
+	TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoItems, "query"}, DiscoItems{})
+}
diff --git a/stanza/iq_version.go b/stanza/iq_version.go
new file mode 100644
index 0000000..ebf4452
--- /dev/null
+++ b/stanza/iq_version.go
@@ -0,0 +1,25 @@
+package stanza
+
+import "encoding/xml"
+
+// ============================================================================
+// Software Version (XEP-0092)
+
+// Version
+type Version struct {
+	XMLName xml.Name `xml:"jabber:iq:version query"`
+	Name    string   `xml:"name,omitempty"`
+	Version string   `xml:"version,omitempty"`
+	OS      string   `xml:"os,omitempty"`
+}
+
+func (v *Version) Namespace() string {
+	return v.XMLName.Space
+}
+
+// ============================================================================
+// Registry init
+
+func init() {
+	TypeRegistry.MapExtension(PKTIQ, xml.Name{"jabber:iq:version", "query"}, Version{})
+}
diff --git a/message.go b/stanza/message.go
similarity index 99%
rename from message.go
rename to stanza/message.go
index 6b57f65..35e44c1 100644
--- a/message.go
+++ b/stanza/message.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/msg_chat_markers.go b/stanza/msg_chat_markers.go
similarity index 96%
rename from msg_chat_markers.go
rename to stanza/msg_chat_markers.go
index b33aa3c..f226379 100644
--- a/msg_chat_markers.go
+++ b/stanza/msg_chat_markers.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
 
-import "encoding/xml"
+import (
+	"encoding/xml"
+)
 
 /*
 Support for:
diff --git a/msg_chat_state.go b/stanza/msg_chat_state.go
similarity index 96%
rename from msg_chat_state.go
rename to stanza/msg_chat_state.go
index 7f2f89e..728a52e 100644
--- a/msg_chat_state.go
+++ b/stanza/msg_chat_state.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
 
-import "encoding/xml"
+import (
+	"encoding/xml"
+)
 
 /*
 Support for:
diff --git a/msg_html.go b/stanza/msg_html.go
similarity index 92%
rename from msg_html.go
rename to stanza/msg_html.go
index fbb4aae..1b4016c 100644
--- a/msg_html.go
+++ b/stanza/msg_html.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
 
-import "encoding/xml"
+import (
+	"encoding/xml"
+)
 
 type HTML struct {
 	MsgExtension
diff --git a/msg_oob.go b/stanza/msg_oob.go
similarity index 88%
rename from msg_oob.go
rename to stanza/msg_oob.go
index 38b6aeb..039fac1 100644
--- a/msg_oob.go
+++ b/stanza/msg_oob.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
 
-import "encoding/xml"
+import (
+	"encoding/xml"
+)
 
 /*
 Support for:
diff --git a/msg_receipts.go b/stanza/msg_receipts.go
similarity index 94%
rename from msg_receipts.go
rename to stanza/msg_receipts.go
index 87a1f73..85c2783 100644
--- a/msg_receipts.go
+++ b/stanza/msg_receipts.go
@@ -1,6 +1,8 @@
-package xmpp
+package stanza
 
-import "encoding/xml"
+import (
+	"encoding/xml"
+)
 
 /*
 Support for:
diff --git a/stanza/node.go b/stanza/node.go
new file mode 100644
index 0000000..26a3e03
--- /dev/null
+++ b/stanza/node.go
@@ -0,0 +1,51 @@
+package stanza
+
+import "encoding/xml"
+
+// ============================================================================
+// Generic / unknown content
+
+// Node is a generic structure to represent XML data. It is used to parse
+// unreferenced or custom stanza payload.
+type Node struct {
+	XMLName xml.Name
+	Attrs   []xml.Attr `xml:"-"`
+	Content string     `xml:",innerxml"`
+	Nodes   []Node     `xml:",any"`
+}
+
+func (n *Node) Namespace() string {
+	return n.XMLName.Space
+}
+
+// Attr represents generic XML attributes, as used on the generic XML Node
+// representation.
+type Attr struct {
+	K string
+	V string
+}
+
+// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
+// transform generic XML content into hierarchical Node structure.
+func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+	// Assign	"n.Attrs = start.Attr", without repeating xmlns in attributes:
+	for _, attr := range start.Attr {
+		// Do not repeat xmlns, it is already in XMLName
+		if attr.Name.Local != "xmlns" {
+			n.Attrs = append(n.Attrs, attr)
+		}
+	}
+	type node Node
+	return d.DecodeElement((*node)(n), &start)
+}
+
+// MarshalXML is a custom XML serializer used by xml.Marshal to serialize a
+// Node structure to XML.
+func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
+	start.Attr = n.Attrs
+	start.Name = n.XMLName
+
+	err = e.EncodeToken(start)
+	e.EncodeElement(n.Nodes, xml.StartElement{Name: n.XMLName})
+	return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
diff --git a/ns.go b/stanza/ns.go
similarity index 52%
rename from ns.go
rename to stanza/ns.go
index d36e3db..e955a35 100644
--- a/ns.go
+++ b/stanza/ns.go
@@ -1,11 +1,11 @@
-package xmpp
+package stanza
 
 const (
 	NSStream    = "http://etherx.jabber.org/streams"
 	nsTLS       = "urn:ietf:params:xml:ns:xmpp-tls"
-	nsSASL      = "urn:ietf:params:xml:ns:xmpp-sasl"
-	nsBind      = "urn:ietf:params:xml:ns:xmpp-bind"
-	nsSession   = "urn:ietf:params:xml:ns:xmpp-session"
+	NSSASL      = "urn:ietf:params:xml:ns:xmpp-sasl"
+	NSBind      = "urn:ietf:params:xml:ns:xmpp-bind"
+	NSSession   = "urn:ietf:params:xml:ns:xmpp-session"
 	NSClient    = "jabber:client"
 	NSComponent = "jabber:component:accept"
 )
diff --git a/packet.go b/stanza/packet.go
similarity index 96%
rename from packet.go
rename to stanza/packet.go
index ce42236..567796d 100644
--- a/packet.go
+++ b/stanza/packet.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 type Packet interface {
 	Name() string
diff --git a/packet_enum.go b/stanza/packet_enum.go
similarity index 98%
rename from packet_enum.go
rename to stanza/packet_enum.go
index 9bc30e4..103966a 100644
--- a/packet_enum.go
+++ b/stanza/packet_enum.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 type StanzaType string
 
diff --git a/parser.go b/stanza/parser.go
similarity index 90%
rename from parser.go
rename to stanza/parser.go
index 465db5d..c83e17e 100644
--- a/parser.go
+++ b/stanza/parser.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
@@ -14,7 +14,7 @@ import (
 //       reattach features (allowing to resume an existing stream at the point the connection was interrupted, without
 //       getting through the authentication process.
 // TODO We should handle stream error from XEP-0114 ( <conflict/> or <host-unknown/> )
-func initStream(p *xml.Decoder) (sessionID string, err error) {
+func InitStream(p *xml.Decoder) (sessionID string, err error) {
 	for {
 		var t xml.Token
 		t, err = p.Token()
@@ -41,31 +41,14 @@ func initStream(p *xml.Decoder) (sessionID string, err error) {
 	}
 }
 
-// Scan XML token stream to find next StartElement.
-func nextStart(p *xml.Decoder) (xml.StartElement, error) {
-	for {
-		t, err := p.Token()
-		if err == io.EOF {
-			return xml.StartElement{}, errors.New("connection closed")
-		}
-		if err != nil {
-			return xml.StartElement{}, fmt.Errorf("nextStart %s", err)
-		}
-		switch t := t.(type) {
-		case xml.StartElement:
-			return t, nil
-		}
-	}
-}
-
-// nextPacket scans XML token stream for next complete XMPP stanza.
+// NextPacket scans XML token stream for next complete XMPP stanza.
 // Once the type of stanza has been identified, a structure is created to decode
 // that stanza and returned.
 // TODO Use an interface to return packets interface xmppDecoder
-// TODO make auth and bind use nextPacket instead of directly nextStart
-func nextPacket(p *xml.Decoder) (Packet, error) {
+// TODO make auth and bind use NextPacket instead of directly NextStart
+func NextPacket(p *xml.Decoder) (Packet, error) {
 	// Read start element to find out how we want to parse the XMPP packet
-	se, err := nextStart(p)
+	se, err := NextStart(p)
 	if err != nil {
 		return nil, err
 	}
@@ -74,7 +57,7 @@ func nextPacket(p *xml.Decoder) (Packet, error) {
 	switch se.Name.Space {
 	case NSStream:
 		return decodeStream(p, se)
-	case nsSASL:
+	case NSSASL:
 		return decodeSASL(p, se)
 	case NSClient:
 		return decodeClient(p, se)
@@ -86,6 +69,23 @@ func nextPacket(p *xml.Decoder) (Packet, error) {
 	}
 }
 
+// Scan XML token stream to find next StartElement.
+func NextStart(p *xml.Decoder) (xml.StartElement, error) {
+	for {
+		t, err := p.Token()
+		if err == io.EOF {
+			return xml.StartElement{}, errors.New("connection closed")
+		}
+		if err != nil {
+			return xml.StartElement{}, fmt.Errorf("NextStart %s", err)
+		}
+		switch t := t.(type) {
+		case xml.StartElement:
+			return t, nil
+		}
+	}
+}
+
 /*
 TODO: From all the decoder, we can return a pointer to the actual concrete type, instead of directly that
    type.
diff --git a/pep.go b/stanza/pep.go
similarity index 98%
rename from pep.go
rename to stanza/pep.go
index 7223dc4..7de57ea 100644
--- a/pep.go
+++ b/stanza/pep.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/pres_muc.go b/stanza/pres_muc.go
similarity index 99%
rename from pres_muc.go
rename to stanza/pres_muc.go
index f7ae599..bc0e75e 100644
--- a/pres_muc.go
+++ b/stanza/pres_muc.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/presence.go b/stanza/presence.go
similarity index 99%
rename from presence.go
rename to stanza/presence.go
index 466b9e1..d3a8d1a 100644
--- a/presence.go
+++ b/stanza/presence.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/presence_enum.go b/stanza/presence_enum.go
similarity index 95%
rename from presence_enum.go
rename to stanza/presence_enum.go
index c0723bd..c92691f 100644
--- a/presence_enum.go
+++ b/stanza/presence_enum.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 // PresenceShow is a Enum of presence element show
 type PresenceShow string
diff --git a/pubsub.go b/stanza/pubsub.go
similarity index 98%
rename from pubsub.go
rename to stanza/pubsub.go
index 42b28e0..010f8b0 100644
--- a/pubsub.go
+++ b/stanza/pubsub.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/registry.go b/stanza/registry.go
similarity index 99%
rename from registry.go
rename to stanza/registry.go
index 8edacb4..beb35df 100644
--- a/registry.go
+++ b/stanza/registry.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/starttls.go b/stanza/starttls.go
similarity index 88%
rename from starttls.go
rename to stanza/starttls.go
index edb20ec..e0a187c 100644
--- a/starttls.go
+++ b/stanza/starttls.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"crypto/tls"
@@ -8,7 +8,7 @@ import (
 var DefaultTlsConfig tls.Config
 
 // Used during stream initiation / session establishment
-type tlsProceed struct {
+type TLSProceed struct {
 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
 }
 
diff --git a/stream.go b/stanza/stream.go
similarity index 99%
rename from stream.go
rename to stanza/stream.go
index 0173254..221e7fb 100644
--- a/stream.go
+++ b/stanza/stream.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/stream_manager.go b/stream_manager.go
index 9c7e020..c2dd78b 100644
--- a/stream_manager.go
+++ b/stream_manager.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"golang.org/x/xerrors"
+	"gosrc.io/xmpp/stanza"
 )
 
 // The Fluux XMPP lib can manage client or component XMPP streams.
@@ -29,7 +30,7 @@ type StreamClient interface {
 
 // Sender is an interface provided by Stream clients to allow sending XMPP data.
 type Sender interface {
-	Send(packet Packet) error
+	Send(packet stanza.Packet) error
 	SendRaw(packet string) error
 }
 


Test patch:
diff --git a/client_test.go b/client_test.go
index 813f2be..a599d5b 100644
--- a/client_test.go
+++ b/client_test.go
@@ -7,6 +7,8 @@ import (
 	"net"
 	"testing"
 	"time"
+
+	"gosrc.io/xmpp/stanza"
 )
 
 const (
@@ -126,11 +128,11 @@ func checkOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
 		switch elem := token.(type) {
 		// Wait for first startElement
 		case xml.StartElement:
-			if elem.Name.Space != NSStream || elem.Name.Local != "stream" {
+			if elem.Name.Space != stanza.NSStream || elem.Name.Local != "stream" {
 				err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
 				return
 			}
-			if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", NSClient, NSStream); err != nil {
+			if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", stanza.NSClient, stanza.NSStream); err != nil {
 				t.Errorf("cannot write server stream open: %s", err)
 			}
 			return
@@ -152,14 +154,14 @@ func sendStreamFeatures(t *testing.T, c net.Conn, _ *xml.Decoder) {
 
 // TODO return err in case of error reading the auth params
 func readAuth(t *testing.T, decoder *xml.Decoder) string {
-	se, err := nextStart(decoder)
+	se, err := stanza.NextStart(decoder)
 	if err != nil {
 		t.Errorf("cannot read auth: %s", err)
 		return ""
 	}
 
 	var nv interface{}
-	nv = &auth{}
+	nv = &stanza.Auth{}
 	// Decode element into pointer storage
 	if err = decoder.DecodeElement(nv, &se); err != nil {
 		t.Errorf("cannot decode auth: %s", err)
@@ -167,7 +169,7 @@ func readAuth(t *testing.T, decoder *xml.Decoder) string {
 	}
 
 	switch v := nv.(type) {
-	case *auth:
+	case *stanza.Auth:
 		return v.Value
 	}
 	return ""
@@ -184,13 +186,13 @@ func sendBindFeature(t *testing.T, c net.Conn, _ *xml.Decoder) {
 }
 
 func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
-	se, err := nextStart(decoder)
+	se, err := stanza.NextStart(decoder)
 	if err != nil {
 		t.Errorf("cannot read bind: %s", err)
 		return
 	}
 
-	iq := &IQ{}
+	iq := &stanza.IQ{}
 	// Decode element into pointer storage
 	if err = decoder.DecodeElement(&iq, &se); err != nil {
 		t.Errorf("cannot decode bind iq: %s", err)
@@ -199,7 +201,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
 
 	// TODO Check all elements
 	switch iq.Payload.(type) {
-	case *BindBind:
+	case *stanza.BindBind:
 		result := `<iq id='%s' type='result'>
   <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
   	<jid>%s</jid>
diff --git a/component_test.go b/component_test.go
index fd7ae3c..222b471 100644
--- a/component_test.go
+++ b/component_test.go
@@ -1,7 +1,6 @@
 package xmpp
 
 import (
-	"encoding/xml"
 	"testing"
 )
 
@@ -24,76 +23,3 @@ func TestHandshake(t *testing.T) {
 func TestGenerateHandshake(t *testing.T) {
 	// TODO
 }
-
-// We should be able to properly parse delegation confirmation messages
-func TestParsingDelegationMessage(t *testing.T) {
-	packetStr := `<message to='service.localhost' from='localhost'>
- <delegation xmlns='urn:xmpp:delegation:1'>
-  <delegated namespace='http://jabber.org/protocol/pubsub'/>
- </delegation>
-</message>`
-	var msg Message
-	data := []byte(packetStr)
-	if err := xml.Unmarshal(data, &msg); err != nil {
-		t.Errorf("Unmarshal(%s) returned error", data)
-	}
-
-	// Check that we have extracted the delegation info as MsgExtension
-	var nsDelegated string
-	for _, ext := range msg.Extensions {
-		if delegation, ok := ext.(*Delegation); ok {
-			nsDelegated = delegation.Delegated.Namespace
-		}
-	}
-	if nsDelegated != "http://jabber.org/protocol/pubsub" {
-		t.Errorf("Could not find delegated namespace in delegation: %#v\n", msg)
-	}
-}
-
-// Check that we can parse a delegation IQ.
-// The most important thing is to be able to
-func TestParsingDelegationIQ(t *testing.T) {
-	packetStr := `<iq to='service.localhost' from='localhost' type='set' id='1'>
- <delegation xmlns='urn:xmpp:delegation:1'>
-  <forwarded xmlns='urn:xmpp:forward:0'>
-   <iq xml:lang='en' to='test1@localhost' from='test1@localhost/mremond-mbp' type='set' id='aaf3a' xmlns='jabber:client'>
-    <pubsub xmlns='http://jabber.org/protocol/pubsub'>
-     <publish node='http://jabber.org/protocol/mood'>
-      <item id='current'>
-       <mood xmlns='http://jabber.org/protocol/mood'>
-        <excited/>
-       </mood>
-      </item>
-     </publish>
-    </pubsub>
-   </iq>
-  </forwarded>
- </delegation>
-</iq>`
-	var iq IQ
-	data := []byte(packetStr)
-	if err := xml.Unmarshal(data, &iq); err != nil {
-		t.Errorf("Unmarshal(%s) returned error", data)
-	}
-
-	// Check that we have extracted the delegation info as IQPayload
-	var node string
-	if iq.Payload != nil {
-		if delegation, ok := iq.Payload.(*Delegation); ok {
-			packet := delegation.Forwarded.Stanza
-			forwardedIQ, ok := packet.(IQ)
-			if !ok {
-				t.Errorf("Could not extract packet IQ")
-				return
-			}
-			if forwardedIQ.Payload != nil {
-				if pubsub, ok := forwardedIQ.Payload.(*PubSub); ok {
-					node = pubsub.Publish.Node
-				}
-			}
-		}
-	}
-	if node != "http://jabber.org/protocol/mood" {
-		t.Errorf("Could not find mood node name on delegated publish: %#v\n", iq)
-	}
-}
diff --git a/router_test.go b/router_test.go
index c1049bf..98a4697 100644
--- a/router_test.go
+++ b/router_test.go
@@ -4,6 +4,8 @@ import (
 	"bytes"
 	"encoding/xml"
 	"testing"
+
+	"gosrc.io/xmpp/stanza"
 )
 
 // ============================================================================
@@ -11,13 +13,13 @@ import (
 
 func TestNameMatcher(t *testing.T) {
 	router := NewRouter()
-	router.HandleFunc("message", func(s Sender, p Packet) {
+	router.HandleFunc("message", func(s Sender, p stanza.Packet) {
 		_ = s.SendRaw(successFlag)
 	})
 
 	// Check that a message packet is properly matched
 	conn := NewSenderMock()
-	msg := NewMessage(Attrs{Type: MessageTypeChat, To: "test@localhost", Id: "1"})
+	msg := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, To: "test@localhost", Id: "1"})
 	msg.Body = "Hello"
 	router.route(conn, msg)
 	if conn.String() != successFlag {
@@ -26,8 +28,8 @@ func TestNameMatcher(t *testing.T) {
 
 	// Check that an IQ packet is not matched
 	conn = NewSenderMock()
-	iq := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
-	iq.Payload = &DiscoInfo{}
+	iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
+	iq.Payload = &stanza.DiscoInfo{}
 	router.route(conn, iq)
 	if conn.String() == successFlag {
 		t.Error("IQ should not have been matched and routed")
@@ -37,18 +39,18 @@ func TestNameMatcher(t *testing.T) {
 func TestIQNSMatcher(t *testing.T) {
 	router := NewRouter()
 	router.NewRoute().
-		IQNamespaces(NSDiscoInfo, NSDiscoItems).
-		HandlerFunc(func(s Sender, p Packet) {
+		IQNamespaces(stanza.NSDiscoInfo, stanza.NSDiscoItems).
+		HandlerFunc(func(s Sender, p stanza.Packet) {
 			_ = s.SendRaw(successFlag)
 		})
 
 	// Check that an IQ with proper namespace does match
 	conn := NewSenderMock()
-	iqDisco := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
+	iqDisco := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
 	// TODO: Add a function to generate payload with proper namespace initialisation
-	iqDisco.Payload = &DiscoInfo{
+	iqDisco.Payload = &stanza.DiscoInfo{
 		XMLName: xml.Name{
-			Space: NSDiscoInfo,
+			Space: stanza.NSDiscoInfo,
 			Local: "query",
 		}}
 	router.route(conn, iqDisco)
@@ -58,9 +60,9 @@ func TestIQNSMatcher(t *testing.T) {
 
 	// Check that another namespace is not matched
 	conn = NewSenderMock()
-	iqVersion := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
+	iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
 	// TODO: Add a function to generate payload with proper namespace initialisation
-	iqVersion.Payload = &DiscoInfo{
+	iqVersion.Payload = &stanza.DiscoInfo{
 		XMLName: xml.Name{
 			Space: "jabber:iq:version",
 			Local: "query",
@@ -75,13 +77,13 @@ func TestTypeMatcher(t *testing.T) {
 	router := NewRouter()
 	router.NewRoute().
 		StanzaType("normal").
-		HandlerFunc(func(s Sender, p Packet) {
+		HandlerFunc(func(s Sender, p stanza.Packet) {
 			_ = s.SendRaw(successFlag)
 		})
 
 	// Check that a packet with the proper type matches
 	conn := NewSenderMock()
-	message := NewMessage(Attrs{Type: "normal", To: "test@localhost", Id: "1"})
+	message := stanza.NewMessage(stanza.Attrs{Type: "normal", To: "test@localhost", Id: "1"})
 	message.Body = "hello"
 	router.route(conn, message)
 
@@ -91,7 +93,7 @@ func TestTypeMatcher(t *testing.T) {
 
 	// We should match on default type 'normal' for message without a type
 	conn = NewSenderMock()
-	message = NewMessage(Attrs{To: "test@localhost", Id: "1"})
+	message = stanza.NewMessage(stanza.Attrs{To: "test@localhost", Id: "1"})
 	message.Body = "hello"
 	router.route(conn, message)
 
@@ -101,8 +103,8 @@ func TestTypeMatcher(t *testing.T) {
 
 	// We do not match on other types
 	conn = NewSenderMock()
-	iqVersion := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
-	iqVersion.Payload = &DiscoInfo{
+	iqVersion := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+	iqVersion.Payload = &stanza.DiscoInfo{
 		XMLName: xml.Name{
 			Space: "jabber:iq:version",
 			Local: "query",
@@ -119,38 +121,38 @@ func TestCompositeMatcher(t *testing.T) {
 	router.NewRoute().
 		IQNamespaces("jabber:iq:version").
 		StanzaType("get").
-		HandlerFunc(func(s Sender, p Packet) {
+		HandlerFunc(func(s Sender, p stanza.Packet) {
 			_ = s.SendRaw(successFlag)
 		})
 
 	// Data set
-	getVersionIq := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
-	getVersionIq.Payload = &Version{
+	getVersionIq := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+	getVersionIq.Payload = &stanza.Version{
 		XMLName: xml.Name{
 			Space: "jabber:iq:version",
 			Local: "query",
 		}}
 
-	setVersionIq := NewIQ(Attrs{Type: "set", From: "service.localhost", To: "test@localhost", Id: "1"})
-	setVersionIq.Payload = &Version{
+	setVersionIq := stanza.NewIQ(stanza.Attrs{Type: "set", From: "service.localhost", To: "test@localhost", Id: "1"})
+	setVersionIq.Payload = &stanza.Version{
 		XMLName: xml.Name{
 			Space: "jabber:iq:version",
 			Local: "query",
 		}}
 
-	GetDiscoIq := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
-	GetDiscoIq.Payload = &DiscoInfo{
+	GetDiscoIq := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+	GetDiscoIq.Payload = &stanza.DiscoInfo{
 		XMLName: xml.Name{
 			Space: "http://jabber.org/protocol/disco#info",
 			Local: "query",
 		}}
 
-	message := NewMessage(Attrs{Type: "normal", To: "test@localhost", Id: "1"})
+	message := stanza.NewMessage(stanza.Attrs{Type: "normal", To: "test@localhost", Id: "1"})
 	message.Body = "hello"
 
 	tests := []struct {
 		name  string
-		input Packet
+		input stanza.Packet
 		want  bool
 	}{
 		{name: "match get version iq", input: getVersionIq, want: true},
@@ -178,13 +180,13 @@ func TestCompositeMatcher(t *testing.T) {
 func TestCatchallMatcher(t *testing.T) {
 	router := NewRouter()
 	router.NewRoute().
-		HandlerFunc(func(s Sender, p Packet) {
+		HandlerFunc(func(s Sender, p stanza.Packet) {
 			_ = s.SendRaw(successFlag)
 		})
 
 	// Check that we match on several packets
 	conn := NewSenderMock()
-	message := NewMessage(Attrs{Type: "chat", To: "test@localhost", Id: "1"})
+	message := stanza.NewMessage(stanza.Attrs{Type: "chat", To: "test@localhost", Id: "1"})
 	message.Body = "hello"
 	router.route(conn, message)
 
@@ -193,8 +195,8 @@ func TestCatchallMatcher(t *testing.T) {
 	}
 
 	conn = NewSenderMock()
-	iqVersion := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
-	iqVersion.Payload = &DiscoInfo{
+	iqVersion := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
+	iqVersion.Payload = &stanza.DiscoInfo{
 		XMLName: xml.Name{
 			Space: "jabber:iq:version",
 			Local: "query",
@@ -219,7 +221,7 @@ func NewSenderMock() SenderMock {
 	return SenderMock{buffer: new(bytes.Buffer)}
 }
 
-func (s SenderMock) Send(packet Packet) error {
+func (s SenderMock) Send(packet stanza.Packet) error {
 	out, err := xml.Marshal(packet)
 	if err != nil {
 		return err
@@ -239,7 +241,7 @@ func (s SenderMock) String() string {
 
 func TestSenderMock(t *testing.T) {
 	conn := NewSenderMock()
-	msg := NewMessage(Attrs{To: "test@localhost", Id: "1"})
+	msg := stanza.NewMessage(stanza.Attrs{To: "test@localhost", Id: "1"})
 	msg.Body = "Hello"
 	if err := conn.Send(msg); err != nil {
 		t.Error("Could not send message")
diff --git a/stanza/component_test.go b/stanza/component_test.go
new file mode 100644
index 0000000..d02531e
--- /dev/null
+++ b/stanza/component_test.go
@@ -0,0 +1,79 @@
+package stanza
+
+import (
+	"encoding/xml"
+	"testing"
+)
+
+// We should be able to properly parse delegation confirmation messages
+func TestParsingDelegationMessage(t *testing.T) {
+	packetStr := `<message to='service.localhost' from='localhost'>
+ <delegation xmlns='urn:xmpp:delegation:1'>
+  <delegated namespace='http://jabber.org/protocol/pubsub'/>
+ </delegation>
+</message>`
+	var msg Message
+	data := []byte(packetStr)
+	if err := xml.Unmarshal(data, &msg); err != nil {
+		t.Errorf("Unmarshal(%s) returned error", data)
+	}
+
+	// Check that we have extracted the delegation info as MsgExtension
+	var nsDelegated string
+	for _, ext := range msg.Extensions {
+		if delegation, ok := ext.(*Delegation); ok {
+			nsDelegated = delegation.Delegated.Namespace
+		}
+	}
+	if nsDelegated != "http://jabber.org/protocol/pubsub" {
+		t.Errorf("Could not find delegated namespace in delegation: %#v\n", msg)
+	}
+}
+
+// Check that we can parse a delegation IQ.
+// The most important thing is to be able to
+func TestParsingDelegationIQ(t *testing.T) {
+	packetStr := `<iq to='service.localhost' from='localhost' type='set' id='1'>
+ <delegation xmlns='urn:xmpp:delegation:1'>
+  <forwarded xmlns='urn:xmpp:forward:0'>
+   <iq xml:lang='en' to='test1@localhost' from='test1@localhost/mremond-mbp' type='set' id='aaf3a' xmlns='jabber:client'>
+    <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+     <publish node='http://jabber.org/protocol/mood'>
+      <item id='current'>
+       <mood xmlns='http://jabber.org/protocol/mood'>
+        <excited/>
+       </mood>
+      </item>
+     </publish>
+    </pubsub>
+   </iq>
+  </forwarded>
+ </delegation>
+</iq>`
+	var iq IQ
+	data := []byte(packetStr)
+	if err := xml.Unmarshal(data, &iq); err != nil {
+		t.Errorf("Unmarshal(%s) returned error", data)
+	}
+
+	// Check that we have extracted the delegation info as IQPayload
+	var node string
+	if iq.Payload != nil {
+		if delegation, ok := iq.Payload.(*Delegation); ok {
+			packet := delegation.Forwarded.Stanza
+			forwardedIQ, ok := packet.(IQ)
+			if !ok {
+				t.Errorf("Could not extract packet IQ")
+				return
+			}
+			if forwardedIQ.Payload != nil {
+				if pubsub, ok := forwardedIQ.Payload.(*PubSub); ok {
+					node = pubsub.Publish.Node
+				}
+			}
+		}
+	}
+	if node != "http://jabber.org/protocol/mood" {
+		t.Errorf("Could not find mood node name on delegated publish: %#v\n", iq)
+	}
+}
diff --git a/iot_control_test.go b/stanza/iot_test.go
similarity index 97%
rename from iot_control_test.go
rename to stanza/iot_test.go
index aaf26c9..43a60ec 100644
--- a/iot_control_test.go
+++ b/stanza/iot_test.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/stanza/iq_disco_test.go b/stanza/iq_disco_test.go
new file mode 100644
index 0000000..8b76767
--- /dev/null
+++ b/stanza/iq_disco_test.go
@@ -0,0 +1,55 @@
+package stanza_test
+
+import (
+	"encoding/xml"
+	"testing"
+
+	"gosrc.io/xmpp/stanza"
+)
+
+func TestDiscoInfoBuilder(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)
+	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")
+	}
+
+	// Check features
+	features := []string{stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1"}
+	if len(pp.Features) != len(features) {
+		t.Errorf("Features length mismatch: %#v", pp.Features)
+	} else {
+		for i, f := range pp.Features {
+			if f.Var != features[i] {
+				t.Errorf("Missing feature: %s", features[i])
+			}
+		}
+	}
+
+	// Check identity
+	if len(pp.Identity) != 1 {
+		t.Errorf("Identity length mismatch: %#v", pp.Identity)
+	} else {
+		if pp.Identity[0].Name != "Test Component" {
+			t.Errorf("Incorrect identity name: %#v", pp.Identity[0].Name)
+		}
+	}
+}
diff --git a/iq_test.go b/stanza/iq_test.go
similarity index 77%
rename from iq_test.go
rename to stanza/iq_test.go
index df9b360..04a868a 100644
--- a/iq_test.go
+++ b/stanza/iq_test.go
@@ -1,4 +1,4 @@
-package xmpp_test
+package stanza_test
 
 import (
 	"encoding/xml"
@@ -6,22 +6,22 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func TestUnmarshalIqs(t *testing.T) {
 	//var cs1 = new(iot.ControlSet)
 	var tests = []struct {
 		iqString string
-		parsedIQ xmpp.IQ
+		parsedIQ stanza.IQ
 	}{
 		{"<iq id=\"1\" type=\"set\" to=\"test@localhost\"/>",
-			xmpp.IQ{XMLName: xml.Name{Local: "iq"}, Attrs: xmpp.Attrs{Type: xmpp.IQTypeSet, To: "test@localhost", Id: "1"}}},
+			stanza.IQ{XMLName: xml.Name{Local: "iq"}, Attrs: stanza.Attrs{Type: stanza.IQTypeSet, To: "test@localhost", Id: "1"}}},
 		//{"<iq xmlns=\"jabber:client\" id=\"2\" type=\"set\" to=\"test@localhost\" from=\"server\"><set xmlns=\"urn:xmpp:iot:control\"/></iq>", IQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}},
 	}
 
 	for _, test := range tests {
-		parsedIQ := xmpp.IQ{}
+		parsedIQ := stanza.IQ{}
 		err := xml.Unmarshal([]byte(test.iqString), &parsedIQ)
 		if err != nil {
 			t.Errorf("Unmarshal(%s) returned error", test.iqString)
@@ -35,16 +35,16 @@ func TestUnmarshalIqs(t *testing.T) {
 }
 
 func TestGenerateIq(t *testing.T) {
-	iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
-	payload := xmpp.DiscoInfo{
-		Identity: xmpp.Identity{
-			Name:     "Test Gateway",
-			Category: "gateway",
-			Type:     "mqtt",
-		},
-		Features: []xmpp.Feature{
-			{Var: xmpp.NSDiscoInfo},
-			{Var: xmpp.NSDiscoItems},
+	iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
+	payload := stanza.DiscoInfo{
+		Identity: []stanza.Identity{
+			{Name: "Test Gateway",
+				Category: "gateway",
+				Type:     "mqtt",
+			}},
+		Features: []stanza.Feature{
+			{Var: stanza.NSDiscoInfo},
+			{Var: stanza.NSDiscoItems},
 		},
 	}
 	iq.Payload = &payload
@@ -58,18 +58,18 @@ func TestGenerateIq(t *testing.T) {
 		t.Error("empty error should not be serialized")
 	}
 
-	parsedIQ := xmpp.IQ{}
+	parsedIQ := stanza.IQ{}
 	if err = xml.Unmarshal(data, &parsedIQ); err != nil {
 		t.Errorf("Unmarshal(%s) returned error", data)
 	}
 
-	if !xmlEqual(parsedIQ.Payload, iq.Payload) {
-		t.Errorf("non matching items\n%s", cmp.Diff(parsedIQ.Payload, iq.Payload))
+	if !xmlEqual(iq.Payload, parsedIQ.Payload) {
+		t.Errorf("non matching items\n%s", xmlDiff(iq.Payload, parsedIQ.Payload))
 	}
 }
 
 func TestErrorTag(t *testing.T) {
-	xError := xmpp.Err{
+	xError := stanza.Err{
 		XMLName: xml.Name{Local: "error"},
 		Code:    503,
 		Type:    "cancel",
@@ -82,7 +82,7 @@ func TestErrorTag(t *testing.T) {
 		t.Errorf("cannot marshal xml structure: %s", err)
 	}
 
-	parsedError := xmpp.Err{}
+	parsedError := stanza.Err{}
 	if err = xml.Unmarshal(data, &parsedError); err != nil {
 		t.Errorf("Unmarshal(%s) returned error", data)
 	}
@@ -93,8 +93,8 @@ func TestErrorTag(t *testing.T) {
 }
 
 func TestDiscoItems(t *testing.T) {
-	iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
-	payload := xmpp.DiscoItems{
+	iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
+	payload := stanza.DiscoItems{
 		Node: "music",
 	}
 	iq.Payload = &payload
@@ -104,7 +104,7 @@ func TestDiscoItems(t *testing.T) {
 		t.Errorf("cannot marshal xml structure")
 	}
 
-	parsedIQ := xmpp.IQ{}
+	parsedIQ := stanza.IQ{}
 	if err = xml.Unmarshal(data, &parsedIQ); err != nil {
 		t.Errorf("Unmarshal(%s) returned error", data)
 	}
@@ -117,7 +117,7 @@ func TestDiscoItems(t *testing.T) {
 func TestUnmarshalPayload(t *testing.T) {
 	query := "<iq to='service.localhost' type='get' id='1'><query xmlns='jabber:iq:version'/></iq>"
 
-	parsedIQ := xmpp.IQ{}
+	parsedIQ := stanza.IQ{}
 	err := xml.Unmarshal([]byte(query), &parsedIQ)
 	if err != nil {
 		t.Errorf("Unmarshal(%s) returned error", query)
@@ -142,7 +142,7 @@ func TestPayloadWithError(t *testing.T) {
  </error>
 </iq>`
 
-	parsedIQ := xmpp.IQ{}
+	parsedIQ := stanza.IQ{}
 	err := xml.Unmarshal([]byte(iq), &parsedIQ)
 	if err != nil {
 		t.Errorf("Unmarshal error: %s", iq)
@@ -158,7 +158,7 @@ func TestUnknownPayload(t *testing.T) {
 	iq := `<iq type="get" to="service.localhost" id="1" >
  <query xmlns="unknown:ns"/>
 </iq>`
-	parsedIQ := xmpp.IQ{}
+	parsedIQ := stanza.IQ{}
 	err := xml.Unmarshal([]byte(iq), &parsedIQ)
 	if err != nil {
 		t.Errorf("Unmarshal error: %#v (%s)", err, iq)
diff --git a/message_test.go b/stanza/message_test.go
similarity index 80%
rename from message_test.go
rename to stanza/message_test.go
index 8f18d39..4681694 100644
--- a/message_test.go
+++ b/stanza/message_test.go
@@ -1,15 +1,15 @@
-package xmpp_test
+package stanza_test
 
 import (
 	"encoding/xml"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func TestGenerateMessage(t *testing.T) {
-	message := xmpp.NewMessage(xmpp.Attrs{Type: xmpp.MessageTypeChat, From: "admin@localhost", To: "test@localhost", Id: "1"})
+	message := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, From: "admin@localhost", To: "test@localhost", Id: "1"})
 	message.Body = "Hi"
 	message.Subject = "Msg Subject"
 
@@ -18,7 +18,7 @@ func TestGenerateMessage(t *testing.T) {
 		t.Errorf("cannot marshal xml structure")
 	}
 
-	parsedMessage := xmpp.Message{}
+	parsedMessage := stanza.Message{}
 	if err = xml.Unmarshal(data, &parsedMessage); err != nil {
 		t.Errorf("Unmarshal(%s) returned error", data)
 	}
@@ -38,7 +38,7 @@ func TestDecodeError(t *testing.T) {
   </error>
 </message>`
 
-	parsedMessage := xmpp.Message{}
+	parsedMessage := stanza.Message{}
 	if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
 		t.Errorf("message error stanza unmarshall error: %v", err)
 		return
@@ -50,15 +50,15 @@ func TestDecodeError(t *testing.T) {
 
 func TestGetOOB(t *testing.T) {
 	image := "https://localhost/image.png"
-	msg := xmpp.NewMessage(xmpp.Attrs{To: "test@localhost"})
-	ext := xmpp.OOB{
+	msg := stanza.NewMessage(stanza.Attrs{To: "test@localhost"})
+	ext := stanza.OOB{
 		XMLName: xml.Name{Space: "jabber:x:oob", Local: "x"},
 		URL:     image,
 	}
 	msg.Extensions = append(msg.Extensions, &ext)
 
 	// OOB can properly be found
-	var oob xmpp.OOB
+	var oob stanza.OOB
 	// Try to find and
 	if ok := msg.Get(&oob); !ok {
 		t.Error("could not find oob extension")
@@ -69,7 +69,7 @@ func TestGetOOB(t *testing.T) {
 	}
 
 	// Markable is not found
-	var m xmpp.Markable
+	var m stanza.Markable
 	if ok := msg.Get(&m); ok {
 		t.Error("we should not have found markable extension")
 	}
diff --git a/msg_html_test.go b/stanza/msg_html_test.go
similarity index 81%
rename from msg_html_test.go
rename to stanza/msg_html_test.go
index b88104a..b37e9d6 100644
--- a/msg_html_test.go
+++ b/stanza/msg_html_test.go
@@ -1,20 +1,20 @@
-package xmpp_test
+package stanza_test
 
 import (
 	"encoding/xml"
 	"testing"
 
-	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func TestHTMLGen(t *testing.T) {
 	htmlBody := "<p>Hello <b>World</b></p>"
-	msg := xmpp.NewMessage(xmpp.Attrs{To: "test@localhost"})
+	msg := stanza.NewMessage(stanza.Attrs{To: "test@localhost"})
 	msg.Body = "Hello World"
-	body := xmpp.HTMLBody{
+	body := stanza.HTMLBody{
 		InnerXML: htmlBody,
 	}
-	html := xmpp.HTML{Body: body}
+	html := stanza.HTML{Body: body}
 	msg.Extensions = append(msg.Extensions, html)
 
 	result := msg.XMPPFormat()
@@ -23,7 +23,7 @@ func TestHTMLGen(t *testing.T) {
 		t.Errorf("incorrect serialize message:\n%s", result)
 	}
 
-	parsedMessage := xmpp.Message{}
+	parsedMessage := stanza.Message{}
 	if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
 		t.Errorf("message HTML unmarshall error: %v", err)
 		return
@@ -33,7 +33,7 @@ func TestHTMLGen(t *testing.T) {
 		t.Errorf("incorrect parsed body: '%s'", parsedMessage.Body)
 	}
 
-	var h xmpp.HTML
+	var h stanza.HTML
 	if ok := parsedMessage.Get(&h); !ok {
 		t.Error("could not extract HTML body")
 	}
diff --git a/msg_receipts_test.go b/stanza/msg_receipts_test.go
similarity index 89%
rename from msg_receipts_test.go
rename to stanza/msg_receipts_test.go
index 0db0c64..bf379a1 100644
--- a/msg_receipts_test.go
+++ b/stanza/msg_receipts_test.go
@@ -1,10 +1,10 @@
-package xmpp_test
+package stanza_test
 
 import (
 	"encoding/xml"
 	"testing"
 
-	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func TestDecodeRequest(t *testing.T) {
@@ -15,7 +15,7 @@ func TestDecodeRequest(t *testing.T) {
   <body>My lord, dispatch; read o'er these articles.</body>
   <request xmlns='urn:xmpp:receipts'/>
 </message>`
-	parsedMessage := xmpp.Message{}
+	parsedMessage := stanza.Message{}
 	if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
 		t.Errorf("message receipt unmarshall error: %v", err)
 		return
@@ -31,7 +31,7 @@ func TestDecodeRequest(t *testing.T) {
 	}
 
 	switch ext := parsedMessage.Extensions[0].(type) {
-	case *xmpp.ReceiptRequest:
+	case *stanza.ReceiptRequest:
 		if ext.XMLName.Local != "request" {
 			t.Errorf("unexpected extension: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
 		}
diff --git a/pres_muc_test.go b/stanza/pres_muc_test.go
similarity index 85%
rename from pres_muc_test.go
rename to stanza/pres_muc_test.go
index 5b8db83..209dc93 100644
--- a/pres_muc_test.go
+++ b/stanza/pres_muc_test.go
@@ -1,10 +1,10 @@
-package xmpp_test
+package stanza_test
 
 import (
 	"encoding/xml"
 	"testing"
 
-	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 // https://xmpp.org/extensions/xep-0045.html#example-27
@@ -18,12 +18,12 @@ func TestMucPassword(t *testing.T) {
   </x>
 </presence>`
 
-	var parsedPresence xmpp.Presence
+	var parsedPresence stanza.Presence
 	if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
 		t.Errorf("Unmarshal(%s) returned error", str)
 	}
 
-	var muc xmpp.MucPresence
+	var muc stanza.MucPresence
 	if ok := parsedPresence.Get(&muc); !ok {
 		t.Error("muc presence extension was not found")
 	}
@@ -44,13 +44,13 @@ func TestMucHistory(t *testing.T) {
   </x>
 </presence>`
 
-	var parsedPresence xmpp.Presence
+	var parsedPresence stanza.Presence
 	if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
 		t.Errorf("Unmarshal(%s) returned error: %s", str, err)
 		return
 	}
 
-	var muc xmpp.MucPresence
+	var muc stanza.MucPresence
 	if ok := parsedPresence.Get(&muc); !ok {
 		t.Error("muc presence extension was not found")
 		return
@@ -74,14 +74,14 @@ func TestMucNoHistory(t *testing.T) {
 
 	maxstanzas := 0
 
-	pres := xmpp.Presence{Attrs: xmpp.Attrs{
+	pres := stanza.Presence{Attrs: stanza.Attrs{
 		From: "hag66@shakespeare.lit/pda",
 		Id:   "n13mt3l",
 		To:   "coven@chat.shakespeare.lit/thirdwitch",
 	},
-		Extensions: []xmpp.PresExtension{
-			xmpp.MucPresence{
-				History: xmpp.History{MaxStanzas: xmpp.NewNullableInt(maxstanzas)},
+		Extensions: []stanza.PresExtension{
+			stanza.MucPresence{
+				History: stanza.History{MaxStanzas: stanza.NewNullableInt(maxstanzas)},
 			},
 		},
 	}
diff --git a/presence_test.go b/stanza/presence_test.go
similarity index 72%
rename from presence_test.go
rename to stanza/presence_test.go
index fb8e09a..94431d8 100644
--- a/presence_test.go
+++ b/stanza/presence_test.go
@@ -1,24 +1,23 @@
-package xmpp_test
+package stanza_test
 
 import (
 	"encoding/xml"
 	"testing"
 
-	"gosrc.io/xmpp"
-
 	"github.com/google/go-cmp/cmp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func TestGeneratePresence(t *testing.T) {
-	presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
-	presence.Show = xmpp.PresenceShowChat
+	presence := stanza.NewPresence(stanza.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
+	presence.Show = stanza.PresenceShowChat
 
 	data, err := xml.Marshal(presence)
 	if err != nil {
 		t.Errorf("cannot marshal xml structure")
 	}
 
-	var parsedPresence xmpp.Presence
+	var parsedPresence stanza.Presence
 	if err = xml.Unmarshal(data, &parsedPresence); err != nil {
 		t.Errorf("Unmarshal(%s) returned error", data)
 	}
@@ -32,13 +31,13 @@ func TestPresenceSubElt(t *testing.T) {
 	// Test structure to ensure that show, status and priority are correctly defined as presence
 	// package sub-elements
 	type pres struct {
-		Show     xmpp.PresenceShow `xml:"show"`
-		Status   string            `xml:"status"`
-		Priority int8              `xml:"priority"`
+		Show     stanza.PresenceShow `xml:"show"`
+		Status   string              `xml:"status"`
+		Priority int8                `xml:"priority"`
 	}
 
-	presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
-	presence.Show = xmpp.PresenceShowXA
+	presence := stanza.NewPresence(stanza.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
+	presence.Show = stanza.PresenceShowXA
 	presence.Status = "Coding"
 	presence.Priority = 10
 
diff --git a/registry_test.go b/stanza/registry_test.go
similarity index 98%
rename from registry_test.go
rename to stanza/registry_test.go
index 63feea3..02d321b 100644
--- a/registry_test.go
+++ b/stanza/registry_test.go
@@ -1,4 +1,4 @@
-package xmpp
+package stanza
 
 import (
 	"encoding/xml"
diff --git a/xmpp_test.go b/stanza/xmpp_test.go
similarity index 66%
rename from xmpp_test.go
rename to stanza/xmpp_test.go
index 7f7e6de..611948a 100644
--- a/xmpp_test.go
+++ b/stanza/xmpp_test.go
@@ -1,4 +1,4 @@
-package xmpp_test
+package stanza_test
 
 import (
 	"encoding/xml"
@@ -10,6 +10,15 @@ import (
 // marshal / unmarshal. There is no need to manage them on the manually
 // crafted structure.
 func xmlEqual(x, y interface{}) bool {
+	return cmp.Equal(x, y, xmlOpts())
+}
+
+// xmlDiff compares xml structures ignoring namespace preferences
+func xmlDiff(x, y interface{}) string {
+	return cmp.Diff(x, y, xmlOpts())
+}
+
+func xmlOpts() cmp.Options {
 	alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
 	opts := cmp.Options{
 		cmp.FilterValues(func(x, y interface{}) bool {
@@ -20,10 +29,12 @@ func xmlEqual(x, y interface{}) bool {
 				if xx == zero || yy == zero {
 					return true
 				}
+				if xx.Space == "" || yy.Space == "" {
+					return true
+				}
 			}
 			return false
 		}, alwaysEqual),
 	}
-
-	return cmp.Equal(x, y, opts)
+	return opts
 }
diff --git a/stream_test.go b/stream_test.go
index fac1446..663cad9 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -4,14 +4,14 @@ import (
 	"encoding/xml"
 	"testing"
 
-	"gosrc.io/xmpp"
+	"gosrc.io/xmpp/stanza"
 )
 
 func TestNoStartTLS(t *testing.T) {
 	streamFeatures := `<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
 </stream:features>`
 
-	var parsedSF xmpp.StreamFeatures
+	var parsedSF stanza.StreamFeatures
 	if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
 		t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
 	}
@@ -32,7 +32,7 @@ func TestStartTLS(t *testing.T) {
   </starttls>
 </stream:features>`
 
-	var parsedSF xmpp.StreamFeatures
+	var parsedSF stanza.StreamFeatures
 	if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
 		t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
 	}
@@ -52,7 +52,7 @@ func TestStreamManagement(t *testing.T) {
     <sm xmlns='urn:xmpp:sm:3'/>
 </stream:features>`
 
-	var parsedSF xmpp.StreamFeatures
+	var parsedSF stanza.StreamFeatures
 	if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
 		t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
 	}