Updated for Go 1.0 + upcoming XML fixes.
authorChris Jones <chris@cjones.org>
Sun, 16 Dec 2012 13:03:03 -0700
changeset 98 c9cc4eda6dce
parent 88 d2ec96c80efe
child 100 24231ff0016c
Updated for Go 1.0 + upcoming XML fixes.
Makefile
examples/Makefile
roster.go
roster_test.go
stream.go
structs.go
structs_test.go
xmpp.go
xmpp_test.go
--- a/Makefile	Mon Jan 23 21:54:41 2012 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-# Copyright 2009 The Go Authors.  All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-include $(GOROOT)/src/Make.inc
-
-TARG=cjyar/xmpp
-GOFILES=\
-	xmpp.go \
-	roster.go \
-	stream.go \
-	structs.go \
-
-examples: install
-	gomake -C examples all
-
-clean: clean-examples
-
-clean-examples:
-	gomake -C examples clean
-
-include $(GOROOT)/src/Make.pkg
--- a/examples/Makefile	Mon Jan 23 21:54:41 2012 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-# Copyright 2009 The Go Authors.  All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-include $(GOROOT)/src/Make.inc
-
-TARG=interact
-GOFILES=\
-	interact.go \
-
-include $(GOROOT)/src/Make.cmd
--- a/roster.go	Mon Jan 23 21:54:41 2012 -0700
+++ b/roster.go	Sun Dec 16 13:03:03 2012 -0700
@@ -5,9 +5,8 @@
 package xmpp
 
 import (
+	"encoding/xml"
 	"fmt"
-	"os"
-	"xml"
 )
 
 // This file contains support for roster management, RFC 3921, Section 7.
@@ -17,15 +16,15 @@
 // Roster query/result
 type RosterQuery struct {
 	XMLName xml.Name `xml:"jabber:iq:roster query"`
-	Item    []RosterItem
+	Item    []RosterItem `xml:"item"`
 }
 
 // See RFC 3921, Section 7.1.
 type RosterItem struct {
-	XMLName      xml.Name `xml:"item"`
-	Jid          string   `xml:"attr"`
-	Subscription string   `xml:"attr"`
-	Name         string   `xml:"attr"`
+	XMLName      xml.Name `xml:"jabber:iq:roster item"`
+	Jid          string   `xml:"jid,attr"`
+	Subscription string   `xml:"subscription,attr"`
+	Name         string   `xml:"name,attr"`
 	Group        []string
 }
 
@@ -46,28 +45,33 @@
 // Synchronously fetch this entity's roster from the server and cache
 // that information. This is called once from a fairly deep call stack
 // as part of XMPP negotiation.
-func fetchRoster(client *Client) os.Error {
+func fetchRoster(client *Client) error {
 	rosterUpdate := rosterClients[client.Uid].rosterUpdate
 
 	iq := &Iq{From: client.Jid.String(), Id: <-Id, Type: "get",
 		Nested: []interface{}{RosterQuery{}}}
-	ch := make(chan os.Error)
+	ch := make(chan error)
 	f := func(st Stanza) bool {
 		defer close(ch)
+		iq, ok := st.(*Iq)
+		if !ok {
+			ch <- fmt.Errorf("response to iq wasn't iq: %s", st)
+			return false
+		}
 		if iq.Type == "error" {
 			ch <- iq.Error
 			return false
 		}
 		var rq *RosterQuery
-		for _, ele := range st.GetNested() {
+		for _, ele := range iq.Nested {
 			if q, ok := ele.(*RosterQuery); ok {
 				rq = q
 				break
 			}
 		}
 		if rq == nil {
-			ch <- os.NewError(fmt.Sprintf(
-				"Roster query result not query: %v", st))
+			ch <- fmt.Errorf(
+				"Roster query result not query: %v", st)
 			return false
 		}
 		for _, item := range rq.Item {
@@ -105,22 +109,27 @@
 }
 
 func maybeUpdateRoster(client *Client, st Stanza) {
+	iq, ok := st.(*Iq)
+	if !ok {
+		return
+	}
+
 	rosterUpdate := rosterClients[client.Uid].rosterUpdate
 
 	var rq *RosterQuery
-	for _, ele := range st.GetNested() {
+	for _, ele := range iq.Nested {
 		if q, ok := ele.(*RosterQuery); ok {
 			rq = q
 			break
 		}
 	}
-	if st.GetName() == "iq" && st.GetType() == "set" && rq != nil {
+	if iq.Type == "set" && rq != nil {
 		for _, item := range rq.Item {
 			rosterUpdate <- item
 		}
 		// Send a reply.
-		iq := &Iq{To: st.GetFrom(), Id: st.GetId(), Type: "result"}
-		client.Out <- iq
+		reply := &Iq{To: iq.From, Id: iq.Id, Type: "result"}
+		client.Out <- reply
 	}
 }
 
@@ -131,7 +140,7 @@
 		select {
 		case newIt := <-rosterUpdate:
 			if newIt.Subscription == "remove" {
-				roster[newIt.Jid] = RosterItem{}, false
+				delete(roster, newIt.Jid)
 			} else {
 				roster[newIt.Jid] = newIt
 			}
--- a/roster_test.go	Mon Jan 23 21:54:41 2012 -0700
+++ b/roster_test.go	Sun Dec 16 13:03:03 2012 -0700
@@ -5,16 +5,16 @@
 package xmpp
 
 import (
+	"encoding/xml"
 	"reflect"
-	"strings"
 	"testing"
-	"xml"
 )
 
 // This is mostly just tests of the roster data structures.
 
 func TestRosterIqMarshal(t *testing.T) {
-	iq := &Iq{From: "from", Lang: "en", Nested: []interface{}{RosterQuery{}}}
+	iq := &Iq{From: "from", Lang: "en",
+		Nested: []interface{}{RosterQuery{}}}
 	exp := `<iq from="from" xml:lang="en"><query xmlns="` +
 		NsRoster + `"></query></iq>`
 	assertMarshal(t, exp, iq)
@@ -23,18 +23,17 @@
 func TestRosterIqUnmarshal(t *testing.T) {
 	str := `<iq from="from" xml:lang="en"><query xmlns="` +
 		NsRoster + `"><item jid="a@b.c"/></query></iq>`
-	r := strings.NewReader(str)
-	var st Stanza = &Iq{}
-	xml.Unmarshal(r, st)
+	iq := Iq{}
+	xml.Unmarshal([]byte(str), &iq)
 	m := map[string]func(*xml.Name) interface{}{NsRoster: newRosterQuery}
-	err := parseExtended(st, m)
+	err := parseExtended(&iq, m)
 	if err != nil {
 		t.Fatalf("parseExtended: %v", err)
 	}
-	assertEquals(t, "iq", st.GetName())
-	assertEquals(t, "from", st.GetFrom())
-	assertEquals(t, "en", st.GetLang())
-	nested := st.GetNested()
+	assertEquals(t, "iq", iq.XMLName.Local)
+	assertEquals(t, "from", iq.From)
+	assertEquals(t, "en", iq.Lang)
+	nested := iq.Nested
 	if nested == nil {
 		t.Fatalf("nested nil")
 	}
--- a/stream.go	Mon Jan 23 21:54:41 2012 -0700
+++ b/stream.go	Sun Dec 16 13:03:03 2012 -0700
@@ -11,20 +11,19 @@
 package xmpp
 
 import (
-	"big"
 	"crypto/md5"
 	"crypto/rand"
 	"crypto/tls"
 	"encoding/base64"
+	"encoding/xml"
 	"fmt"
 	"io"
+	"log/syslog"
+	"math/big"
 	"net"
-	"os"
 	"regexp"
 	"strings"
-	"syslog"
 	"time"
-	"xml"
 )
 
 // Callback to handle a stanza with a particular id.
@@ -39,12 +38,12 @@
 
 func (cl *Client) readTransport(w io.WriteCloser) {
 	defer w.Close()
-	cl.socket.SetReadTimeout(1e8)
 	p := make([]byte, 1024)
 	for {
 		if cl.socket == nil {
 			cl.waitForSocket()
 		}
+		cl.socket.SetReadDeadline(time.Now().Add(time.Second))
 		nr, err := cl.socket.Read(p)
 		if nr == 0 {
 			if errno, ok := err.(*net.OpError); ok {
@@ -53,14 +52,14 @@
 				}
 			}
 			if Log != nil {
-				Log.Err("read: " + err.String())
+				Log.Err("read: " + err.Error())
 			}
 			break
 		}
 		nw, err := w.Write(p[:nr])
 		if nw < nr {
 			if Log != nil {
-				Log.Err("read: " + err.String())
+				Log.Err("read: " + err.Error())
 			}
 			break
 		}
@@ -74,14 +73,14 @@
 		nr, err := r.Read(p)
 		if nr == 0 {
 			if Log != nil {
-				Log.Err("write: " + err.String())
+				Log.Err("write: " + err.Error())
 			}
 			break
 		}
 		nw, err := cl.socket.Write(p[:nr])
 		if nw < nr {
 			if Log != nil {
-				Log.Err("write: " + err.String())
+				Log.Err("write: " + err.Error())
 			}
 			break
 		}
@@ -97,15 +96,17 @@
 	}
 	defer close(ch)
 
-	p := xml.NewParser(r)
+	p := xml.NewDecoder(r)
+	p.Context.Map[""] = NsClient
+	p.Context.Map["stream"] = NsStream
 Loop:
 	for {
 		// Sniff the next token on the stream.
 		t, err := p.Token()
 		if t == nil {
-			if err != os.EOF {
+			if err != io.EOF {
 				if Log != nil {
-					Log.Err("read: " + err.String())
+					Log.Err("read: " + err.Error())
 				}
 			}
 			break
@@ -124,7 +125,7 @@
 			if err != nil {
 				if Log != nil {
 					Log.Err("unmarshal stream: " +
-						err.String())
+						err.Error())
 				}
 				break Loop
 			}
@@ -139,11 +140,11 @@
 		case NsSASL + " challenge", NsSASL + " failure",
 			NsSASL + " success":
 			obj = &auth{}
-		case "jabber:client iq":
+		case NsClient + " iq":
 			obj = &Iq{}
-		case "jabber:client message":
+		case NsClient + " message":
 			obj = &Message{}
-		case "jabber:client presence":
+		case NsClient + " presence":
 			obj = &Presence{}
 		default:
 			obj = &Generic{}
@@ -154,10 +155,10 @@
 		}
 
 		// Read the complete XML stanza.
-		err = p.Unmarshal(obj, &se)
+		err = p.DecodeElement(obj, &se)
 		if err != nil {
 			if Log != nil {
-				Log.Err("unmarshal: " + err.String())
+				Log.Err("unmarshal: " + err.Error())
 			}
 			break Loop
 		}
@@ -170,8 +171,9 @@
 			if err != nil {
 				if Log != nil {
 					Log.Err("ext unmarshal: " +
-						err.String())
+						err.Error())
 				}
+				fmt.Printf("ext: %v\n", err)
 				break Loop
 			}
 		}
@@ -181,14 +183,14 @@
 	}
 }
 
-func parseExtended(st Stanza, extStanza map[string]func(*xml.Name) interface{}) os.Error {
+func parseExtended(st Stanza, extStanza map[string]func(*xml.Name) interface{}) error {
 	// Now parse the stanza's innerxml to find the string that we
 	// can unmarshal this nested element from.
 	reader := strings.NewReader(st.innerxml())
-	p := xml.NewParser(reader)
+	p := xml.NewDecoder(reader)
 	for {
 		t, err := p.Token()
-		if err == os.EOF {
+		if err == io.EOF {
 			break
 		}
 		if err != nil {
@@ -201,7 +203,7 @@
 
 				// Unmarshal the nested element and
 				// stuff it back into the stanza.
-				err := p.Unmarshal(nested, &se)
+				err := p.DecodeElement(nested, &se)
 				if err != nil {
 					return err
 				}
@@ -225,13 +227,26 @@
 		}
 	}(w)
 
+	enc := xml.NewEncoder(w)
+	enc.Context.Map[NsClient] = ""
+	enc.Context.Map[NsStream] = "stream"
+
 	for obj := range ch {
-		err := xml.Marshal(w, obj)
-		if err != nil {
-			if Log != nil {
-				Log.Err("write: " + err.String())
+		if st, ok := obj.(*stream); ok {
+			_, err := w.Write([]byte(st.String()))
+			if err != nil {
+				if Log != nil {
+					Log.Err("write: " + err.Error())
+				}
 			}
-			break
+		} else {
+			err := enc.Encode(obj)
+			if err != nil {
+				if Log != nil {
+					Log.Err("marshal: " + err.Error())
+				}
+				break
+			}
 		}
 	}
 }
@@ -277,7 +292,7 @@
 			}
 			if handlers[st.GetId()] != nil {
 				f := handlers[st.GetId()]
-				handlers[st.GetId()] = nil
+				delete(handlers, st.GetId())
 				send = f(st)
 			}
 			if send {
@@ -410,10 +425,6 @@
 	cl.socket = tls
 	cl.socketSync.Wait()
 
-	// Reset the read timeout on the (underlying) socket so the
-	// reader doesn't get woken up unnecessarily.
-	tcp.SetReadTimeout(0)
-
 	if Log != nil {
 		Log.Info("TLS negotiation succeeded.")
 	}
@@ -464,7 +475,7 @@
 		if err != nil {
 			if Log != nil {
 				Log.Err("SASL challenge decode: " +
-					err.String())
+					err.Error())
 			}
 			return
 		}
@@ -531,7 +542,7 @@
 	cnonce, err := rand.Int(rand.Reader, randSize)
 	if err != nil {
 		if Log != nil {
-			Log.Err("SASL rand: " + err.String())
+			Log.Err("SASL rand: " + err.Error())
 		}
 		return
 	}
@@ -609,7 +620,7 @@
 	h := func(text string) []byte {
 		h := md5.New()
 		h.Write([]byte(text))
-		return h.Sum()
+		return h.Sum(nil)
 	}
 	hex := func(bytes []byte) string {
 		return fmt.Sprintf("%x", bytes)
@@ -636,14 +647,20 @@
 	}
 	msg := &Iq{Type: "set", Id: <-Id, Nested: []interface{}{bindReq}}
 	f := func(st Stanza) bool {
-		if st.GetType() == "error" {
+		iq, ok := st.(*Iq)
+		if !ok {
+			if Log != nil {
+				Log.Err("non-iq response")
+			}
+		}
+		if iq.Type == "error" {
 			if Log != nil {
 				Log.Err("Resource binding failed")
 			}
 			return false
 		}
 		var bindRepl *bindIq
-		for _, ele := range st.GetNested() {
+		for _, ele := range iq.Nested {
 			if b, ok := ele.(*bindIq); ok {
 				bindRepl = b
 				break
@@ -652,7 +669,7 @@
 		if bindRepl == nil {
 			if Log != nil {
 				Log.Err(fmt.Sprintf("Bad bind reply: %v",
-					st))
+					iq))
 			}
 			return false
 		}
@@ -664,9 +681,10 @@
 			return false
 		}
 		jid := new(JID)
-		if !jid.Set(*jidStr) {
+		if err := jid.Set(*jidStr); err != nil {
 			if Log != nil {
-				Log.Err("Can't parse JID " + *jidStr)
+				Log.Err(fmt.Sprintf("Can't parse JID %s: %s",
+						*jidStr, err))
 			}
 			return false
 		}
--- a/structs.go	Mon Jan 23 21:54:41 2012 -0700
+++ b/structs.go	Sun Dec 16 13:03:03 2012 -0700
@@ -8,14 +8,14 @@
 
 import (
 	"bytes"
+	"encoding/xml"
+	"errors"
 	"flag"
 	"fmt"
-	"go-idn.googlecode.com/hg/src/stringprep"
-	"io"
-	"os"
+	// BUG(cjyar): We should use stringprep
+	// "code.google.com/p/go-idn/src/stringprep"
 	"regexp"
 	"strings"
-	"xml"
 )
 
 // JID represents an entity that can communicate with other
@@ -32,31 +32,28 @@
 
 // XMPP's <stream:stream> XML element
 type stream struct {
-	To      string `xml:"attr"`
-	From    string `xml:"attr"`
-	Id      string `xml:"attr"`
-	Lang    string `xml:"attr"`
-	Version string `xml:"attr"`
+	XMLName xml.Name `xml:"stream=http://etherx.jabber.org/streams stream"`
+	To      string   `xml:"to,attr"`
+	From    string   `xml:"from,attr"`
+	Id      string   `xml:"id,attr"`
+	Lang    string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr"`
+	Version string   `xml:"version,attr"`
 }
-
-var _ xml.Marshaler = &stream{}
 var _ fmt.Stringer = &stream{}
 
 // <stream:error>
 type streamError struct {
-	Any  Generic
+	XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
+	Any  Generic     `xml:",any"`
 	Text *errText
 }
 
-var _ xml.Marshaler = &streamError{}
-
 type errText struct {
-	Lang string `xml:"attr"`
-	Text string `xml:"chardata"`
+	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-streams text"`
+	Lang    string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr"`
+	Text string      `xml:",chardata"`
 }
 
-var _ xml.Marshaler = &errText{}
-
 type Features struct {
 	Starttls   *starttls
 	Mechanisms mechs
@@ -84,88 +81,84 @@
 // One of the three core XMPP stanza types: iq, message, presence. See
 // RFC3920, section 9.
 type Stanza interface {
-	// Returns "iq", "message", or "presence".
-	GetName() string
-	// The to attribute.
-	GetTo() string
-	// The from attribute.
-	GetFrom() string
-	// The id attribute.
+	// // Returns "iq", "message", or "presence".
+	// GetName() string
+	// // The to attribute.
+	// GetTo() string
+	// // The from attribute.
+	// GetFrom() string
+	// The id attribute. TODO maybe remove this.
 	GetId() string
-	// The type attribute.
-	GetType() string
-	// The xml:lang attribute.
-	GetLang() string
-	// A nested error element, if any.
-	GetError() *Error
-	// Zero or more (non-error) nested elements. These will be in
-	// namespaces managed by extensions.
-	GetNested() []interface{}
+	// // The type attribute.
+	// GetType() string
+	// // The xml:lang attribute.
+	// GetLang() string
+	// // A nested error element, if any.
+	// GetError() *Error
+	// // Zero or more (non-error) nested elements. These will be in
+	// // namespaces managed by extensions.
+	// GetNested() []interface{}
 	addNested(interface{})
 	innerxml() string
 }
 
 // message stanza
 type Message struct {
-	To       string `xml:"attr"`
-	From     string `xml:"attr"`
-	Id       string `xml:"attr"`
-	Type     string `xml:"attr"`
-	Lang     string `xml:"attr"`
-	Innerxml string `xml:"innerxml"`
+	XMLName  xml.Name `xml:"message"`
+	To       string   `xml:"to,attr,omitempty"`
+	From     string   `xml:"from,attr,omitempty"`
+	Id       string   `xml:"id,attr,omitempty"`
+	Type     string   `xml:"type,attr,omitempty"`
+	Lang     string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
+	Innerxml string   `xml:",innerxml"`
 	Error    *Error
-	Subject  *Generic
-	Body     *Generic
-	Thread   *Generic
+	Subject  *Generic `xml:"subject"`
+	Body     *Generic `xml:"body"`
+	Thread   *Generic `xml:"thread"`
 	Nested   []interface{}
 }
-
-var _ xml.Marshaler = &Message{}
 var _ Stanza = &Message{}
 
 // presence stanza
 type Presence struct {
-	To       string `xml:"attr"`
-	From     string `xml:"attr"`
-	Id       string `xml:"attr"`
-	Type     string `xml:"attr"`
-	Lang     string `xml:"attr"`
-	Innerxml string `xml:"innerxml"`
+	XMLName  xml.Name `xml:"presence"`
+	To       string   `xml:"to,attr,omitempty"`
+	From     string   `xml:"from,attr,omitempty"`
+	Id       string   `xml:"id,attr,omitempty"`
+	Type     string   `xml:"type,attr,omitempty"`
+	Lang     string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
+	Innerxml string   `xml:",innerxml"`
 	Error    *Error
-	Show     *Generic
-	Status   *Generic
-	Priority *Generic
+	Show     *Generic `xml:"show"`
+	Status   *Generic `xml:"status"`
+	Priority *Generic `xml:"priority"`
 	Nested   []interface{}
 }
-
-var _ xml.Marshaler = &Presence{}
 var _ Stanza = &Presence{}
 
 // iq stanza
 type Iq struct {
-	To       string `xml:"attr"`
-	From     string `xml:"attr"`
-	Id       string `xml:"attr"`
-	Type     string `xml:"attr"`
-	Lang     string `xml:"attr"`
-	Innerxml string `xml:"innerxml"`
+	XMLName  xml.Name `xml:"iq"`
+	To       string   `xml:"to,attr,omitempty"`
+	From     string   `xml:"from,attr,omitempty"`
+	Id       string   `xml:"id,attr,omitempty"`
+	Type     string   `xml:"type,attr,omitempty"`
+	Lang     string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
+	Innerxml string   `xml:",innerxml"`
 	Error    *Error
 	Nested   []interface{}
 }
-
-var _ xml.Marshaler = &Iq{}
 var _ Stanza = &Iq{}
 
 // Describes an XMPP stanza error. See RFC 3920, Section 9.3.
 type Error struct {
 	XMLName xml.Name `xml:"error"`
 	// The error type attribute.
-	Type string `xml:"attr"`
+	Type string `xml:"type,attr"`
 	// Any nested element, if present.
 	Any *Generic
 }
-
-var _ os.Error = &Error{}
+var _ error = &Error{}
 
 // Used for resource binding as a nested element inside <iq/>.
 type bindIq struct {
@@ -177,10 +170,9 @@
 // Holds an XML element not described by the more specific types.
 type Generic struct {
 	XMLName  xml.Name
-	Any      *Generic
-	Chardata string `xml:"chardata"`
+	Any      *Generic `xml:",any"`
+	Chardata string `xml:",chardata"`
 }
-
 var _ fmt.Stringer = &Generic{}
 
 func (jid *JID) String() string {
@@ -196,39 +188,58 @@
 
 // Set implements flag.Value. It returns true if it successfully
 // parses the string.
-func (jid *JID) Set(val string) bool {
+func (jid *JID) Set(val string) error {
 	r := regexp.MustCompile("^(([^@/]+)@)?([^@/]+)(/([^@/]+))?$")
 	parts := r.FindStringSubmatch(val)
 	if parts == nil {
-		return false
+		return fmt.Errorf("%s doesn't match user@domain/resource", val)
 	}
-	jid.Node = stringprep.Nodeprep(parts[2])
-	jid.Domain = stringprep.Nodeprep(parts[3])
-	jid.Resource = stringprep.Resourceprep(parts[5])
-	return true
-}
-
-func (s *stream) MarshalXML() ([]byte, os.Error) {
-	buf := bytes.NewBuffer(nil)
-	buf.WriteString("<stream:stream")
-	writeField(buf, "xmlns", "jabber:client")
-	writeField(buf, "xmlns:stream", NsStream)
-	writeField(buf, "to", s.To)
-	writeField(buf, "from", s.From)
-	writeField(buf, "id", s.Id)
-	writeField(buf, "xml:lang", s.Lang)
-	writeField(buf, "version", s.Version)
-	buf.WriteString(">")
-	// We never write </stream:stream>
-	return buf.Bytes(), nil
+	// jid.Node = stringprep.Nodeprep(parts[2])
+	// jid.Domain = stringprep.Nodeprep(parts[3])
+	// jid.Resource = stringprep.Resourceprep(parts[5])
+	jid.Node = parts[2]
+	jid.Domain = parts[3]
+	jid.Resource = parts[5]
+	return nil
 }
 
 func (s *stream) String() string {
-	result, _ := s.MarshalXML()
-	return string(result)
+	var buf bytes.Buffer
+	buf.WriteString(`<stream:stream xmlns="`)
+	buf.WriteString(NsClient)
+	buf.WriteString(`" xmlns:stream="`)
+	buf.WriteString(NsStream)
+	buf.WriteString(`"`)
+	if s.To != "" {
+		buf.WriteString(` to="`)
+		xml.Escape(&buf, []byte(s.To))
+		buf.WriteString(`"`)
+	}
+	if s.From != "" {
+		buf.WriteString(` from="`)
+		xml.Escape(&buf, []byte(s.From))
+		buf.WriteString(`"`)
+	}
+	if s.Id != "" {
+		buf.WriteString(` id="`)
+		xml.Escape(&buf, []byte(s.Id))
+		buf.WriteString(`"`)
+	}
+	if s.Lang != "" {
+		buf.WriteString(` xml:lang="`)
+		xml.Escape(&buf, []byte(s.Lang))
+		buf.WriteString(`"`)
+	}
+	if s.Version != "" {
+		buf.WriteString(` version="`)
+		xml.Escape(&buf, []byte(s.Version))
+		buf.WriteString(`"`)
+	}
+	buf.WriteString(">")
+	return buf.String()
 }
 
-func parseStream(se xml.StartElement) (*stream, os.Error) {
+func parseStream(se xml.StartElement) (*stream, error) {
 	s := &stream{}
 	for _, attr := range se.Attr {
 		switch strings.ToLower(attr.Name.Local) {
@@ -247,38 +258,6 @@
 	return s, nil
 }
 
-func (s *streamError) MarshalXML() ([]byte, os.Error) {
-	buf := bytes.NewBuffer(nil)
-	buf.WriteString("<stream:error>")
-	xml.Marshal(buf, s.Any)
-	if s.Text != nil {
-		xml.Marshal(buf, s.Text)
-	}
-	buf.WriteString("</stream:error>")
-	return buf.Bytes(), nil
-}
-
-func (e *errText) MarshalXML() ([]byte, os.Error) {
-	buf := bytes.NewBuffer(nil)
-	buf.WriteString("<text")
-	writeField(buf, "xmlns", NsStreams)
-	writeField(buf, "xml:lang", e.Lang)
-	buf.WriteString(">")
-	xml.Escape(buf, []byte(e.Text))
-	buf.WriteString("</text>")
-	return buf.Bytes(), nil
-}
-
-func writeField(w io.Writer, field, value string) {
-	if value != "" {
-		io.WriteString(w, " ")
-		io.WriteString(w, field)
-		io.WriteString(w, `="`)
-		xml.Escape(w, []byte(value))
-		io.WriteString(w, `"`)
-	}
-}
-
 func (u *Generic) String() string {
 	if u == nil {
 		return "nil"
@@ -292,107 +271,21 @@
 		u.XMLName.Local)
 }
 
-func marshalXML(st Stanza) ([]byte, os.Error) {
-	buf := bytes.NewBuffer(nil)
-	buf.WriteString("<")
-	buf.WriteString(st.GetName())
-	if st.GetTo() != "" {
-		writeField(buf, "to", st.GetTo())
-	}
-	if st.GetFrom() != "" {
-		writeField(buf, "from", st.GetFrom())
-	}
-	if st.GetId() != "" {
-		writeField(buf, "id", st.GetId())
-	}
-	if st.GetType() != "" {
-		writeField(buf, "type", st.GetType())
-	}
-	if st.GetLang() != "" {
-		writeField(buf, "xml:lang", st.GetLang())
-	}
-	buf.WriteString(">")
-
-	if m, ok := st.(*Message); ok {
-		err := xml.Marshal(buf, m.Subject)
-		if err != nil {
-			return nil, err
-		}
-		err = xml.Marshal(buf, m.Body)
-		if err != nil {
-			return nil, err
-		}
-		err = xml.Marshal(buf, m.Thread)
-		if err != nil {
-			return nil, err
-		}
-	}
-	if p, ok := st.(*Presence); ok {
-		err := xml.Marshal(buf, p.Show)
-		if err != nil {
-			return nil, err
+func (er *Error) Error() string {
+	buf, err := xml.Marshal(er)
+	if err != nil {
+		if Log != nil {
+			Log.Err("double bad error couldn't marshal error")
 		}
-		err = xml.Marshal(buf, p.Status)
-		if err != nil {
-			return nil, err
-		}
-		err = xml.Marshal(buf, p.Priority)
-		if err != nil {
-			return nil, err
-		}
-	}
-	if nested := st.GetNested(); nested != nil {
-		for _, n := range nested {
-			xml.Marshal(buf, n)
-		}
-	} else if st.innerxml() != "" {
-		buf.WriteString(st.innerxml())
+		return "unreadable error"
 	}
-
-	buf.WriteString("</")
-	buf.WriteString(st.GetName())
-	buf.WriteString(">")
-	return buf.Bytes(), nil
-}
-
-func (er *Error) String() string {
-	buf := bytes.NewBuffer(nil)
-	xml.Marshal(buf, er)
-	return buf.String()
-}
-
-func (m *Message) GetName() string {
-	return "message"
-}
-
-func (m *Message) GetTo() string {
-	return m.To
-}
-
-func (m *Message) GetFrom() string {
-	return m.From
+	return string(buf)
 }
 
 func (m *Message) GetId() string {
 	return m.Id
 }
 
-func (m *Message) GetType() string {
-	return m.Type
-}
-
-func (m *Message) GetLang() string {
-	return m.Lang
-}
-
-func (m *Message) GetError() *Error {
-	return m.Error
-}
-
-func (m *Message) GetNested() []interface{} {
-	return m.Nested
-}
-
 func (m *Message) addNested(n interface{}) {
 	m.Nested = append(m.Nested, n)
 }
@@ -401,42 +294,10 @@
 	return m.Innerxml
 }
 
-func (m *Message) MarshalXML() ([]byte, os.Error) {
-	return marshalXML(m)
-}
-
-func (p *Presence) GetName() string {
-	return "presence"
-}
-
-func (p *Presence) GetTo() string {
-	return p.To
-}
-
-func (p *Presence) GetFrom() string {
-	return p.From
-}
-
 func (p *Presence) GetId() string {
 	return p.Id
 }
 
-func (p *Presence) GetType() string {
-	return p.Type
-}
-
-func (p *Presence) GetLang() string {
-	return p.Lang
-}
-
-func (p *Presence) GetError() *Error {
-	return p.Error
-}
-
-func (p *Presence) GetNested() []interface{} {
-	return p.Nested
-}
-
 func (p *Presence) addNested(n interface{}) {
 	p.Nested = append(p.Nested, n)
 }
@@ -445,42 +306,10 @@
 	return p.Innerxml
 }
 
-func (p *Presence) MarshalXML() ([]byte, os.Error) {
-	return marshalXML(p)
-}
-
-func (iq *Iq) GetName() string {
-	return "iq"
-}
-
-func (iq *Iq) GetTo() string {
-	return iq.To
-}
-
-func (iq *Iq) GetFrom() string {
-	return iq.From
-}
-
 func (iq *Iq) GetId() string {
 	return iq.Id
 }
 
-func (iq *Iq) GetType() string {
-	return iq.Type
-}
-
-func (iq *Iq) GetLang() string {
-	return iq.Lang
-}
-
-func (iq *Iq) GetError() *Error {
-	return iq.Error
-}
-
-func (iq *Iq) GetNested() []interface{} {
-	return iq.Nested
-}
-
 func (iq *Iq) addNested(n interface{}) {
 	iq.Nested = append(iq.Nested, n)
 }
@@ -489,22 +318,19 @@
 	return iq.Innerxml
 }
 
-func (iq *Iq) MarshalXML() ([]byte, os.Error) {
-	return marshalXML(iq)
-}
 
 // Parse a string into a struct implementing Stanza -- this will be
 // either an Iq, a Message, or a Presence.
-func ParseStanza(str string) (Stanza, os.Error) {
+func ParseStanza(str string) (Stanza, error) {
 	r := strings.NewReader(str)
-	p := xml.NewParser(r)
+	p := xml.NewDecoder(r)
 	tok, err := p.Token()
 	if err != nil {
 		return nil, err
 	}
 	se, ok := tok.(xml.StartElement)
 	if !ok {
-		return nil, os.NewError("Not a start element")
+		return nil, errors.New("Not a start element")
 	}
 	var stan Stanza
 	switch se.Name.Local {
@@ -515,9 +341,9 @@
 	case "presence":
 		stan = &Presence{}
 	default:
-		return nil, os.NewError("Not iq, message, or presence")
+		return nil, errors.New("Not iq, message, or presence")
 	}
-	err = p.Unmarshal(stan, &se)
+	err = p.DecodeElement(stan, &se)
 	if err != nil {
 		return nil, err
 	}
--- a/structs_test.go	Mon Jan 23 21:54:41 2012 -0700
+++ b/structs_test.go	Sun Dec 16 13:03:03 2012 -0700
@@ -6,8 +6,8 @@
 
 import (
 	"bytes"
+	"encoding/xml"
 	"testing"
-	"xml"
 )
 
 func assertEquals(t *testing.T, expected, observed string) {
@@ -20,8 +20,8 @@
 func TestJid(t *testing.T) {
 	str := "user@domain/res"
 	jid := &JID{}
-	if !jid.Set(str) {
-		t.Errorf("Set(%s) failed\n", str)
+	if err := jid.Set(str); err != nil {
+		t.Errorf("Set(%s) failed: %s", str, err)
 	}
 	assertEquals(t, "user", jid.Node)
 	assertEquals(t, "domain", jid.Domain)
@@ -29,8 +29,8 @@
 	assertEquals(t, str, jid.String())
 
 	str = "domain.tld"
-	if !jid.Set(str) {
-		t.Errorf("Set(%s) failed\n", str)
+	if err := jid.Set(str); err != nil {
+		t.Errorf("Set(%s) failed: %s", str, err)
 	}
 	if jid.Node != "" {
 		t.Errorf("Node: %v\n", jid.Node)
@@ -43,28 +43,34 @@
 }
 
 func assertMarshal(t *testing.T, expected string, marshal interface{}) {
-	buf := bytes.NewBuffer(nil)
-	xml.Marshal(buf, marshal)
-	observed := string(buf.Bytes())
+	var buf bytes.Buffer
+	enc := xml.NewEncoder(&buf)
+	enc.Context.Map[NsClient] = ""
+	enc.Context.Map[NsStream] = "stream"
+	err := enc.Encode(marshal)
+	if err != nil {
+		t.Errorf("Marshal error for %s: %s", marshal, err)
+	}
+	observed := buf.String()
 	assertEquals(t, expected, observed)
 }
 
 func TestStreamMarshal(t *testing.T) {
 	s := &stream{To: "bob"}
-	exp := `<stream:stream xmlns="jabber:client"` +
-		` xmlns:stream="` + NsStream + `" to="bob">`
-	assertMarshal(t, exp, s)
+	exp := `<stream:stream xmlns="` + NsClient +
+		`" xmlns:stream="` + NsStream + `" to="bob">`
+	assertEquals(t, exp, s.String())
 
 	s = &stream{To: "bob", From: "alice", Id: "#3", Version: "5.3"}
-	exp = `<stream:stream xmlns="jabber:client"` +
-		` xmlns:stream="` + NsStream + `" to="bob" from="alice"` +
+	exp = `<stream:stream xmlns="` + NsClient +
+		`" xmlns:stream="` + NsStream + `" to="bob" from="alice"` +
 		` id="#3" version="5.3">`
-	assertMarshal(t, exp, s)
+	assertEquals(t, exp, s.String())
 
 	s = &stream{Lang: "en_US"}
-	exp = `<stream:stream xmlns="jabber:client"` +
-		` xmlns:stream="` + NsStream + `" xml:lang="en_US">`
-	assertMarshal(t, exp, s)
+	exp = `<stream:stream xmlns="` + NsClient +
+		`" xmlns:stream="` + NsStream + `" xml:lang="en_US">`
+	assertEquals(t, exp, s.String())
 }
 
 func TestStreamErrorMarshal(t *testing.T) {
@@ -97,32 +103,41 @@
 	if err != nil {
 		t.Fatalf("iq: %v", err)
 	}
-	assertEquals(t, "iq", st.GetName())
-	assertEquals(t, "alice", st.GetTo())
-	assertEquals(t, "bob", st.GetFrom())
-	assertEquals(t, "1", st.GetId())
-	assertEquals(t, "A", st.GetType())
-	assertEquals(t, "en", st.GetLang())
-	if st.GetError() != nil {
-		t.Errorf("iq: error %v", st.GetError())
+	iq, ok := st.(*Iq)
+	if !ok {
+		t.Fatalf("not iq: %v", st)
+	}
+	assertEquals(t, "iq", iq.XMLName.Local)
+	assertEquals(t, "alice", iq.To)
+	assertEquals(t, "bob", iq.From)
+	assertEquals(t, "1", iq.Id)
+	assertEquals(t, "A", iq.Type)
+	assertEquals(t, "en", iq.Lang)
+	if iq.Error != nil {
+		t.Errorf("iq: error %v", iq.Error)
 	}
 	if st.innerxml() == "" {
 		t.Errorf("iq: empty child")
 	}
 	assertEquals(t, "<foo>text</foo>", st.innerxml())
+	assertEquals(t, st.innerxml(), iq.Innerxml)
 
 	str = `<message to="alice" from="bob"/>`
 	st, err = ParseStanza(str)
 	if err != nil {
 		t.Fatalf("message: %v", err)
 	}
-	assertEquals(t, "message", st.GetName())
-	assertEquals(t, "alice", st.GetTo())
-	assertEquals(t, "bob", st.GetFrom())
-	assertEquals(t, "", st.GetId())
-	assertEquals(t, "", st.GetLang())
-	if st.GetError() != nil {
-		t.Errorf("message: error %v", st.GetError())
+	m, ok := st.(*Message)
+	if !ok {
+		t.Fatalf("not message: %v", st)
+	}
+	assertEquals(t, "message", m.XMLName.Local)
+	assertEquals(t, "alice", m.To)
+	assertEquals(t, "bob", m.From)
+	assertEquals(t, "", m.Id)
+	assertEquals(t, "", m.Lang)
+	if m.Error != nil {
+		t.Errorf("message: error %v", m.Error)
 	}
 	if st.innerxml() != "" {
 		t.Errorf("message: child %v", st.innerxml())
@@ -133,7 +148,10 @@
 	if err != nil {
 		t.Fatalf("presence: %v", err)
 	}
-	assertEquals(t, "presence", st.GetName())
+	_, ok = st.(*Presence)
+	if !ok {
+		t.Fatalf("not presence: %v", st)
+	}
 }
 
 func TestMarshalEscaping(t *testing.T) {
--- a/xmpp.go	Mon Jan 23 21:54:41 2012 -0700
+++ b/xmpp.go	Sun Dec 16 13:03:03 2012 -0700
@@ -8,13 +8,13 @@
 
 import (
 	"bytes"
+	"encoding/xml"
+	"errors"
 	"fmt"
 	"io"
+	"log/syslog"
 	"net"
-	"os"
 	"sync"
-	"syslog"
-	"xml"
 )
 
 const (
@@ -22,6 +22,7 @@
 	Version = "1.0"
 
 	// Various XML namespaces.
+	NsClient  = "jabber:client"
 	NsStreams = "urn:ietf:params:xml:ns:xmpp-streams"
 	NsStream  = "http://etherx.jabber.org/streams"
 	NsTLS     = "urn:ietf:params:xml:ns:xmpp-tls"
@@ -106,8 +107,7 @@
 // has completed. The negotiation will occur asynchronously, and any
 // send operation to Client.Out will block until negotiation (resource
 // binding) is complete.
-func NewClient(jid *JID, password string, exts []Extension) (*Client,
-os.Error) {
+func NewClient(jid *JID, password string, exts []Extension) (*Client, error) {
 	// Include the mandatory extensions.
 	exts = append(exts, rosterExt)
 	exts = append(exts, bindExt)
@@ -115,8 +115,8 @@
 	// Resolve the domain in the JID.
 	_, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain)
 	if err != nil {
-		return nil, os.NewError("LookupSrv " + jid.Domain +
-			": " + err.String())
+		return nil, errors.New("LookupSrv " + jid.Domain +
+			": " + err.Error())
 	}
 
 	var tcp *net.TCPConn
@@ -124,14 +124,14 @@
 		addrStr := fmt.Sprintf("%s:%d", srv.Target, srv.Port)
 		addr, err := net.ResolveTCPAddr("tcp", addrStr)
 		if err != nil {
-			err = os.NewError(fmt.Sprintf("ResolveTCPAddr(%s): %s",
-				addrStr, err.String()))
+			err = fmt.Errorf("ResolveTCPAddr(%s): %s",
+				addrStr, err.Error())
 			continue
 		}
 		tcp, err = net.DialTCP("tcp", nil, addr)
 		if err != nil {
-			err = os.NewError(fmt.Sprintf("DialTCP(%s): %s",
-				addr, err.String()))
+			err = fmt.Errorf("DialTCP(%s): %s",
+				addr, err)
 			continue
 		}
 	}
@@ -271,17 +271,25 @@
 // session, retrieve the roster, and broadcast an initial
 // presence. The presence can be as simple as a newly-initialized
 // Presence struct.  See RFC 3921, Section 3.
-func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error {
+func (cl *Client) StartSession(getRoster bool, pr *Presence) error {
 	id := <-Id
 	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}
-	ch := make(chan os.Error)
+	ch := make(chan error)
 	f := func(st Stanza) bool {
-		if st.GetType() == "error" {
+		iq, ok := st.(*Iq)
+		if !ok {
+			if Log != nil {
+				Log.Err("iq reply not iq; can't start session")
+			}
+			ch <- errors.New("bad session start reply")
+			return false
+		}
+		if iq.Type == "error" {
 			if Log != nil {
 				Log.Err(fmt.Sprintf("Can't start session: %v",
-					st))
+					iq))
 			}
-			ch <- st.GetError()
+			ch <- iq.Error
 			return false
 		}
 		ch <- nil
--- a/xmpp_test.go	Mon Jan 23 21:54:41 2012 -0700
+++ b/xmpp_test.go	Sun Dec 16 13:03:03 2012 -0700
@@ -6,29 +6,30 @@
 
 import (
 	"bytes"
+	"encoding/xml"
 	"reflect"
 	"strings"
 	"sync"
 	"testing"
-	"xml"
 )
 
 func TestReadError(t *testing.T) {
-	r := strings.NewReader(`<stream:error><bad-foo/></stream:error>`)
+	r := strings.NewReader(`<stream:error><bad-foo xmlns="blah"/>` +
+		`</stream:error>`)
 	ch := make(chan interface{})
 	go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
 	x := <-ch
 	se, ok := x.(*streamError)
 	if !ok {
-		t.Fatalf("not StreamError: %v", reflect.TypeOf(x))
+		t.Fatalf("not StreamError: %T", x)
 	}
 	assertEquals(t, "bad-foo", se.Any.XMLName.Local)
-	assertEquals(t, "", se.Any.XMLName.Space)
+	assertEquals(t, "blah", se.Any.XMLName.Space)
 	if se.Text != nil {
 		t.Errorf("text not nil: %v", se.Text)
 	}
 
-	r = strings.NewReader(`<stream:error><bad-foo/>` +
+	r = strings.NewReader(`<stream:error><bad-foo xmlns="blah"/>` +
 		`<text xml:lang="en" xmlns="` + NsStreams +
 		`">Error text</text></stream:error>`)
 	ch = make(chan interface{})
@@ -39,7 +40,7 @@
 		t.Fatalf("not StreamError: %v", reflect.TypeOf(x))
 	}
 	assertEquals(t, "bad-foo", se.Any.XMLName.Local)
-	assertEquals(t, "", se.Any.XMLName.Space)
+	assertEquals(t, "blah", se.Any.XMLName.Space)
 	assertEquals(t, "Error text", se.Text.Text)
 	assertEquals(t, "en", se.Text.Lang)
 }
@@ -47,7 +48,7 @@
 func TestReadStream(t *testing.T) {
 	r := strings.NewReader(`<stream:stream to="foo.com" ` +
 		`from="bar.org" id="42"` +
-		`xmlns="jabber:client" xmlns:stream="` + NsStream +
+		`xmlns="` + NsClient + `" xmlns:stream="` + NsStream +
 		`" version="1.0">`)
 	ch := make(chan interface{})
 	go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
@@ -94,8 +95,8 @@
 func TestWriteStream(t *testing.T) {
 	ss := &stream{To: "foo.org", From: "bar.com", Id: "42", Lang: "en", Version: "1.0"}
 	str := testWrite(ss)
-	exp := `<stream:stream xmlns="jabber:client"` +
-		` xmlns:stream="` + NsStream + `" to="foo.org"` +
+	exp := `<stream:stream xmlns="` + NsClient +
+		`" xmlns:stream="` + NsStream + `" to="foo.org"` +
 		` from="bar.com" id="42" xml:lang="en" version="1.0">`
 	assertEquals(t, exp, str)
 }