Added resource binding and structures for <iq>, <message>, and <presence>.
authorChris Jones <chris@cjones.org>
Tue, 27 Dec 2011 20:42:44 -0700
changeset 12 122ab6208c3c
parent 11 48be1ae93fd4
child 13 c9527bbe99a6
Added resource binding and structures for <iq>, <message>, and <presence>.
stream.go
structs.go
structs_test.go
xmpp.go
--- a/stream.go	Tue Dec 27 15:36:07 2011 -0700
+++ b/stream.go	Tue Dec 27 20:42:44 2011 -0700
@@ -114,6 +114,8 @@
 		case nsSASL + " challenge", nsSASL + " failure",
 			nsSASL + " success":
 			obj = &auth{}
+		case "jabber:client iq":
+			obj = &Iq{}
 		default:
 			obj = &Unrecognized{}
 			log.Printf("Ignoring unrecognized: %s %s\n",
@@ -208,6 +210,10 @@
 		cl.chooseSasl(fe)
 		return
 	}
+
+	if fe.Bind != nil {
+		cl.bind(fe.Bind)
+	}
 }
 
 // readTransport() is running concurrently. We need to stop it,
@@ -436,3 +442,16 @@
 		hex(h(a2))))
 	return response
 }
+
+func (cl *Client) bind(bind *Unrecognized) {
+	res := cl.Jid.Resource
+	msg := &Iq{Type: "set", Id: cl.NextId(), Any:
+		&Unrecognized{XMLName: xml.Name{Space: nsBind, Local:
+					"bind"}}}
+	if res != "" {
+		msg.Any.Any = &Unrecognized{XMLName: xml.Name{Local:
+				"resource"}, Chardata: res}
+	}
+	cl.xmlOut <- msg
+	// TODO Grab the iq result from the server and update cl.Jid.
+}
--- a/structs.go	Tue Dec 27 15:36:07 2011 -0700
+++ b/structs.go	Tue Dec 27 20:42:44 2011 -0700
@@ -21,9 +21,10 @@
 // entities. It looks like node@domain/resource. Node and resource are
 // sometimes optional.
 type JID struct {
+	// TODO Make this not a pointer.
 	Node *string
 	Domain string
-	Resource *string
+	Resource string
 }
 var _ fmt.Stringer = &JID{}
 var _ flag.Value = &JID{}
@@ -61,6 +62,7 @@
 type Features struct {
 	Starttls *starttls
 	Mechanisms mechs
+	Bind *Unrecognized
 }
 
 type starttls struct {
@@ -79,8 +81,64 @@
 	Any *Unrecognized
 }
 
+type Stanza interface {
+	XName() string
+	XTo() string
+	XFrom() string
+	XId() string
+	XType() string
+	XLang() string
+	XError() *Error
+	XChild() *Unrecognized
+}
+
+type Message struct {
+	To string `xml:"attr"`
+	From string `xml:"attr"`
+	Id string `xml:"attr"`
+	Type string `xml:"attr"`
+	Lang string `xml:"attr"`
+	Error *Error
+	Any *Unrecognized
+}
+var _ xml.Marshaler = &Message{}
+var _ Stanza = &Message{}
+
+type Presence struct {
+	To string `xml:"attr"`
+	From string `xml:"attr"`
+	Id string `xml:"attr"`
+	Type string `xml:"attr"`
+	Lang string `xml:"attr"`
+	Error *Error
+	Any *Unrecognized
+}
+var _ xml.Marshaler = &Presence{}
+var _ Stanza = &Presence{}
+
+type Iq struct {
+	To string `xml:"attr"`
+	From string `xml:"attr"`
+	Id string `xml:"attr"`
+	Type string `xml:"attr"`
+	Lang string `xml:"attr"`
+	Error *Error
+	Any *Unrecognized
+}
+var _ xml.Marshaler = &Iq{}
+var _ Stanza = &Iq{}
+
+type Error struct {
+	Type string `xml:"attr"`
+	Any *Unrecognized
+}
+var _ xml.Marshaler = &Error{}
+
+// TODO Rename this to something like Generic.
 type Unrecognized struct {
 	XMLName xml.Name
+	Any *Unrecognized
+	Chardata string `xml:"chardata"`
 }
 var _ fmt.Stringer = &Unrecognized{}
 
@@ -89,8 +147,8 @@
 	if jid.Node != nil {
 		result = *jid.Node + "@" + result
 	}
-	if jid.Resource != nil {
-		result = result + "/" + *jid.Resource
+	if jid.Resource != "" {
+		result = result + "/" + jid.Resource
 	}
 	return result
 }
@@ -107,11 +165,7 @@
 		jid.Node = &parts[2]
 	}
 	jid.Domain = parts[3]
-	if parts[5] == "" {
-		jid.Resource = nil
-	} else {
-		jid.Resource = &parts[5]
-	}
+	jid.Resource = parts[5]
 	return true
 }
 
@@ -187,6 +241,164 @@
 }
 
 func (u *Unrecognized) String() string {
-	return fmt.Sprintf("unrecognized{%s %s}", u.XMLName.Space,
+	var sub string
+	if u.Any != nil {
+		sub = u.Any.String()
+	}
+	return fmt.Sprintf("<%s %s>%s%s</%s %s>", u.XMLName.Space,
+		u.XMLName.Local, sub, u.Chardata, u.XMLName.Space,
 		u.XMLName.Local)
 }
+
+func marshalXML(st Stanza) ([]byte, os.Error) {
+	buf := bytes.NewBuffer(nil)
+	buf.WriteString("<")
+	buf.WriteString(st.XName())
+	if st.XTo() != "" {
+		writeField(buf, "to", st.XTo())
+	}
+	if st.XFrom() != "" {
+		writeField(buf, "from", st.XFrom())
+	}
+	if st.XId() != "" {
+		writeField(buf, "id", st.XId())
+	}
+	if st.XType() != "" {
+		writeField(buf, "type", st.XType())
+	}
+	if st.XLang() != "" {
+		writeField(buf, "xml:lang", st.XLang())
+	}
+	buf.WriteString(">")
+	if st.XError() != nil {
+		bytes, _ := st.XError().MarshalXML()
+		buf.WriteString(string(bytes))
+	}
+	if st.XChild() != nil {
+		xml.Marshal(buf, st.XChild())
+	}
+	buf.WriteString("</")
+	buf.WriteString(st.XName())
+	buf.WriteString(">")
+	return buf.Bytes(), nil
+}
+
+func (er *Error) MarshalXML() ([]byte, os.Error) {
+	buf := bytes.NewBuffer(nil)
+	buf.WriteString("<error")
+	writeField(buf, "type", er.Type)
+	buf.WriteString(">")
+	if er.Any != nil {
+		xml.Marshal(buf, er.Any)
+	}
+	buf.WriteString("</error>")
+	return buf.Bytes(), nil
+}
+
+func (m *Message) XName() string {
+	return "message"
+}
+
+func (m *Message) XTo() string {
+	return m.To
+}
+
+func (m *Message) XFrom() string {
+	return m.From
+}
+
+func (m *Message) XId() string {
+	return m.Id
+}
+
+func (m *Message) XType() string {
+	return m.Type
+	}
+
+func (m *Message) XLang() string {
+	return m.Lang
+}
+
+func (m *Message) XError() *Error {
+	return m.Error
+}
+
+func (m *Message) XChild() *Unrecognized {
+	return m.Any
+}
+
+func (m *Message) MarshalXML() ([]byte, os.Error) {
+	return marshalXML(m)
+}
+
+func (p *Presence) XName() string {
+	return "presence"
+}
+
+func (p *Presence) XTo() string {
+	return p.To
+}
+
+func (p *Presence) XFrom() string {
+	return p.From
+}
+
+func (p *Presence) XId() string {
+	return p.Id
+}
+
+func (p *Presence) XType() string {
+	return p.Type
+	}
+
+func (p *Presence) XLang() string {
+	return p.Lang
+}
+
+func (p *Presence) XError() *Error {
+	return p.Error
+}
+
+func (p *Presence) XChild() *Unrecognized {
+	return p.Any
+}
+
+func (p *Presence) MarshalXML() ([]byte, os.Error) {
+	return marshalXML(p)
+}
+
+func (iq *Iq) XName() string {
+	return "iq"
+}
+
+func (iq *Iq) XTo() string {
+	return iq.To
+}
+
+func (iq *Iq) XFrom() string {
+	return iq.From
+}
+
+func (iq *Iq) XId() string {
+	return iq.Id
+}
+
+func (iq *Iq) XType() string {
+	return iq.Type
+	}
+
+func (iq *Iq) XLang() string {
+	return iq.Lang
+}
+
+func (iq *Iq) XError() *Error {
+	return iq.Error
+}
+
+func (iq *Iq) XChild() *Unrecognized {
+	return iq.Any
+}
+
+func (iq *Iq) MarshalXML() ([]byte, os.Error) {
+	return marshalXML(iq)
+}
--- a/structs_test.go	Tue Dec 27 15:36:07 2011 -0700
+++ b/structs_test.go	Tue Dec 27 20:42:44 2011 -0700
@@ -25,7 +25,7 @@
 	}
 	assertEquals(t, "user", *jid.Node)
 	assertEquals(t, "domain", jid.Domain)
-	assertEquals(t, "res", *jid.Resource)
+	assertEquals(t, "res", jid.Resource)
 	assertEquals(t, str, jid.String())
 
 	str = "domain.tld"
@@ -36,8 +36,8 @@
 		t.Errorf("Node: %v\n", *jid.Node)
 	}
 	assertEquals(t, "domain.tld", jid.Domain)
-	if jid.Resource != nil {
-		t.Errorf("Resource: %v\n", *jid.Resource)
+	if jid.Resource != "" {
+		t.Errorf("Resource: %v\n", jid.Resource)
 	}
 	assertEquals(t, str, jid.String())
 }
@@ -81,3 +81,11 @@
 		`" xml:lang="pt">things happen</text></stream:error>`
 	assertMarshal(t, exp, e)
 }
+
+func TestIqMarshal(t *testing.T) {
+	iq := &Iq{Type: "set", Id: "3", Any: &Unrecognized{XMLName:
+			xml.Name{Space: nsBind, Local: "bind"}}}
+	exp := `<iq id="3" type="set"><bind xmlns="` + nsBind +
+		`"></bind></iq>`
+	assertMarshal(t, exp, iq)
+}
--- a/xmpp.go	Tue Dec 27 15:36:07 2011 -0700
+++ b/xmpp.go	Tue Dec 27 20:42:44 2011 -0700
@@ -24,6 +24,7 @@
 	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"
 
 	// DNS SRV names
 	serverSrv = "xmpp-server"
@@ -39,6 +40,9 @@
 	socket net.Conn
 	socketSync sync.WaitGroup
 	saslExpected string
+	authDone bool
+	idMutex sync.Mutex
+	nextId int64
 	In <-chan interface{}
 	Out chan<- interface{}
 	xmlOut chan<- interface{}
@@ -200,3 +204,11 @@
 		}
 	}
 }
+
+func (cl *Client) NextId() string {
+	cl.idMutex.Lock()
+	defer cl.idMutex.Unlock()
+	id := cl.nextId
+	cl.nextId++
+	return fmt.Sprintf("id_%d", id)
+}