Don't accept data on Client.Out until resource binding is
authorChris Jones <chris@cjones.org>
Thu, 29 Dec 2011 11:02:21 -0700
changeset 29 a456133ed0ac
parent 28 78961db80bae
child 30 a77fc342e013
Don't accept data on Client.Out until resource binding is complete. StartSession() won't do its work until after this happens. That means the app can call StartSession() and wait for it to return before checking Client.Jid.
examples/interact.go
stream.go
xmpp.go
--- a/examples/interact.go	Thu Dec 29 09:48:36 2011 -0700
+++ b/examples/interact.go	Thu Dec 29 11:02:21 2011 -0700
@@ -30,6 +30,11 @@
 	}
 	defer c.Close()
 
+	err = c.StartSession(&xmpp.Presence{})
+	if err != nil {
+		log.Fatalf("StartSession: %v", err)
+	}
+
 	go func(ch <-chan xmpp.Stanza) {
 		for obj := range ch {
 			fmt.Printf("s: %v\n", obj)
--- a/stream.go	Thu Dec 29 09:48:36 2011 -0700
+++ b/stream.go	Thu Dec 29 11:02:21 2011 -0700
@@ -195,6 +195,9 @@
 			default:
 				send = true
 			}
+			if !send {
+				continue
+			}
 			st, ok := x.(Stanza)
 			if !ok {
 				log.Printf("Unhandled non-stanza: %v",
@@ -213,14 +216,29 @@
 	}
 }
 
-// BUG(cjyar) Disable this loop until resource binding is
-// complete. Otherwise the app might inject something weird into our
-// negotiation stream.
-func writeStream(srvOut chan<- interface{}, cliIn <-chan Stanza) {
+// This loop is paused until resource binding is complete. Otherwise
+// the app might inject something inappropriate into our negotiations
+// with the server. The control channel controls this loop's
+// activity.
+func writeStream(srvOut chan<- interface{}, cliIn <-chan Stanza,
+	control <-chan int) {
 	defer tryClose(srvOut, cliIn)
 
-	for x := range cliIn {
-		srvOut <- x
+	var input <-chan Stanza
+	for {
+		select {
+		case status := <- control:
+			switch status {
+			case 0:
+				input = nil
+			case 1:
+				input = cliIn
+			case -1:
+				break
+			}
+		case x := <- input:
+			srvOut <- x
+		}
 	}
 }
 
@@ -513,7 +531,8 @@
 		}
 		cl.Jid = *jid
 		log.Printf("Bound resource: %s", cl.Jid.String())
-		return true
+		cl.bindDone()
+		return false
 	}
 	cl.HandleStanza(msg.Id, f)
 	cl.xmlOut <- msg
--- a/xmpp.go	Thu Dec 29 09:48:36 2011 -0700
+++ b/xmpp.go	Thu Dec 29 11:02:21 2011 -0700
@@ -45,10 +45,8 @@
 
 // The client in a client-server XMPP connection.
 type Client struct {
-	// This client's JID. This will be updated asynchronously when
-	// resource binding completes; at that time an iq stanza will
-	// be published on the In channel:
-	// <iq><bind><jid>jid</jid></bind></iq>
+	// This client's JID. This will be updated asynchronously by
+	// the time StartSession() returns.
 	Jid JID
 	password string
 	socket net.Conn
@@ -58,6 +56,7 @@
 	idMutex sync.Mutex
 	nextId int64
 	handlers chan *stanzaHandler
+	inputControl chan int
 	// Incoming XMPP stanzas from the server will be published on
 	// this channel. Information which is only used by this
 	// library to set up the XMPP stream will not appear here.
@@ -108,6 +107,7 @@
 	cl.Jid = *jid
 	cl.socket = tcp
 	cl.handlers = make(chan *stanzaHandler, 1)
+	cl.inputControl = make(chan int)
 
 	// Start the transport handler, initially unencrypted.
 	tlsr, tlsw := cl.startTransport()
@@ -119,7 +119,7 @@
 	// Start the XMPP stream handler which filters stream-level
 	// events and responds to them.
 	clIn := cl.startStreamReader(xmlIn, cl.xmlOut)
-	clOut := startStreamWriter(cl.xmlOut)
+	clOut := cl.startStreamWriter(cl.xmlOut)
 
 	// Initial handshake.
 	hsOut := &stream{To: jid.Domain, Version: Version}
@@ -162,9 +162,9 @@
 	return ch
 }
 
-func startStreamWriter(xmlOut chan<- interface{}) chan<- Stanza {
+func (cl *Client) startStreamWriter(xmlOut chan<- interface{}) chan<- Stanza {
 	ch := make(chan Stanza)
-	go writeStream(xmlOut, ch)
+	go writeStream(xmlOut, ch, cl.inputControl)
 	return ch
 }
 
@@ -231,6 +231,13 @@
 	return fmt.Sprintf("id_%d", id)
 }
 
+// bindDone is called when we've finished resource binding (and all
+// the negotiations that precede it). Now we can start accepting
+// traffic from the app.
+func (cl *Client) bindDone() {
+	cl.inputControl <- 1
+}
+
 // Start an XMPP session. This should typically be done immediately
 // after creating the new Client. Once the session has been
 // established, pr will be sent as an initial presence; nil means