xmpp/layer3.go
author Chris Jones <chris@cjones.org>
Sat, 09 Nov 2013 12:09:37 -0700
changeset 178 ccfebbd9f49b
parent 163 3f891f7fe817
permissions -rw-r--r--
Changed the JID type to be an alias of string, rather than a struct. This allows it to be used as a key in a map, among other benefits.

// This layer of the XMPP protocol reads XMLish structures and
// responds to them. It negotiates TLS and authentication.

package xmpp

import (
	"encoding/xml"
	"fmt"
	"log"
)

// Callback to handle a stanza with a particular id.
type callback struct {
	id string
	f  func(Stanza)
}

// Receive XMPP stanzas from the client and send them on to the
// remote. Don't allow the client to send us any stanzas until
// negotiation has completed.  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 sendStream(sendXml chan<- interface{}, recvXmpp <-chan Stanza,
	status <-chan Status) {
	defer close(sendXml)

	var input <-chan Stanza
	for {
		select {
		case stat, ok := <-status:
			if !ok {
				return
			}
			switch stat {
			default:
				input = nil
			case StatusRunning:
				input = recvXmpp
			}
		case x, ok := <-input:
			if !ok {
				return
			}
			if x == nil {
				if Debug {
					log.Println("Won't send nil stanza")
				}
				continue
			}
			sendXml <- x
		}
	}
}

// Receive XMLish structures, handle all the stream-related ones, and
// send XMPP stanzas on to the client once the connection is running.
func (cl *Client) recvStream(recvXml <-chan interface{}, sendXmpp chan<- Stanza,
	status <-chan Status) {
	defer close(sendXmpp)
	defer cl.statmgr.close()

	handlers := make(map[string]func(Stanza))
	doSend := false
	for {
		select {
		case stat := <-status:
			switch stat {
			default:
				doSend = false
			case StatusRunning:
				doSend = true
			}
		case h := <-cl.handlers:
			handlers[h.id] = h.f
		case x, ok := <-recvXml:
			if !ok {
				return
			}
			switch obj := x.(type) {
			case *stream:
				// Do nothing.
			case *streamError:
				cl.setError(fmt.Errorf("%#v", obj))
				return
			case *Features:
				cl.handleFeatures(obj)
			case *starttls:
				cl.handleTls(obj)
			case *auth:
				cl.handleSasl(obj)
			case Stanza:
				id := obj.GetHeader().Id
				if handlers[id] != nil {
					f := handlers[id]
					delete(handlers, id)
					f(obj)
				}
				if doSend {
					sendXmpp <- obj
				}
			default:
				if Debug {
					log.Printf("Unrecognized input: %T %#v",
						x, x)
				}
			}
		}
	}
}

func (cl *Client) handleFeatures(fe *Features) {
	cl.Features = fe
	if fe.Starttls != nil {
		start := &starttls{XMLName: xml.Name{Space: NsTLS,
			Local: "starttls"}}
		cl.sendRaw <- start
		return
	}

	if len(fe.Mechanisms.Mechanism) > 0 {
		cl.chooseSasl(fe)
		return
	}

	if fe.Bind != nil {
		cl.bind()
		return
	}
}

func (cl *Client) handleTls(t *starttls) {
	cl.layer1.startTls(&cl.tlsConfig)

	cl.setStatus(StatusConnectedTls)

	// Now re-send the initial handshake message to start the new
	// session.
	cl.sendRaw <- &stream{To: cl.Jid.Domain(), Version: XMPPVersion}
}

// Send a request to bind a resource. RFC 3920, section 7.
func (cl *Client) bind() {
	res := cl.Jid.Resource()
	bindReq := &bindIq{}
	if res != "" {
		bindReq.Resource = &res
	}
	msg := &Iq{Header: Header{Type: "set", Id: NextId(),
		Nested: []interface{}{bindReq}}}
	f := func(st Stanza) {
		iq, ok := st.(*Iq)
		if !ok {
			cl.setError(fmt.Errorf("non-iq response to bind %#v",
				st))
			return
		}
		if iq.Type == "error" {
			cl.setError(fmt.Errorf("Resource binding failed"))
			return
		}
		var bindRepl *bindIq
		for _, ele := range iq.Nested {
			if b, ok := ele.(*bindIq); ok {
				bindRepl = b
				break
			}
		}
		if bindRepl == nil {
			cl.setError(fmt.Errorf("Bad bind reply: %#v", iq))
			return
		}
		jid := bindRepl.Jid
		if jid == nil || *jid == "" {
			cl.setError(fmt.Errorf("empty resource in bind %#v",
				iq))
			return
		}
		cl.Jid = JID(*jid)
		cl.setStatus(StatusBound)
	}
	cl.SetCallback(msg.Id, f)
	cl.sendRaw <- msg
}

// Register a callback to handle the next XMPP stanza (iq, message, or
// presence) with a given id. The provided function will not be called
// more than once. If it returns false, the stanza will not be made
// available on the normal Client.Recv channel. The callback must not
// read from that channel, as deliveries on it cannot proceed until
// the handler returns true or false.
func (cl *Client) SetCallback(id string, f func(Stanza)) {
	h := &callback{id: id, f: f}
	cl.handlers <- h
}