Added resource binding and structures for <iq>, <message>, and <presence>.
--- 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)
+}