xmpp/layer2.go
author Chris Jones <chris@cjones.org>
Sun, 29 Sep 2013 21:54:38 -0600
changeset 159 fd4089f717b2
parent 147 d7679d991b17
child 163 3f891f7fe817
permissions -rw-r--r--
Listen for XMPP connection status changes, and print them out.

// This layer of the XMPP protocol translates between bytes and XMLish
// structures.

package xmpp

import (
	"encoding/xml"
	"fmt"
	"io"
	"reflect"
	"strings"
)

// Read bytes from a reader, unmarshal them as XML into structures of
// the appropriate type, and send those structures on a channel.
func recvXml(r io.Reader, ch chan<- interface{},
	extStanza map[xml.Name]reflect.Type) {
	if _, ok := Debug.(*noLog); !ok {
		pr, pw := io.Pipe()
		go tee(r, pw, "S: ")
		r = pr
	}
	defer close(ch)

	// This trick loads our namespaces into the parser.
	nsstr := fmt.Sprintf(`<a xmlns="%s" xmlns:stream="%s">`,
		NsClient, NsStream)
	nsrdr := strings.NewReader(nsstr)
	p := xml.NewDecoder(io.MultiReader(nsrdr, r))
	p.Token()

Loop:
	for {
		// Sniff the next token on the stream.
		t, err := p.Token()
		if t == nil {
			if err != io.EOF {
				Warn.Logf("read: %s", err)
			}
			break
		}
		var se xml.StartElement
		var ok bool
		if se, ok = t.(xml.StartElement); !ok {
			continue
		}

		// Allocate the appropriate structure for this token.
		var obj interface{}
		switch se.Name.Space + " " + se.Name.Local {
		case NsStream + " stream":
			st, err := parseStream(se)
			if err != nil {
				Warn.Logf("unmarshal stream: %s", err)
				break Loop
			}
			ch <- st
			continue
		case "stream error", NsStream + " error":
			obj = &streamError{}
		case NsStream + " features":
			obj = &Features{}
		case NsTLS + " proceed", NsTLS + " failure":
			obj = &starttls{}
		case NsSASL + " challenge", NsSASL + " failure",
			NsSASL + " success":
			obj = &auth{}
		case NsClient + " iq":
			obj = &Iq{}
		case NsClient + " message":
			obj = &Message{}
		case NsClient + " presence":
			obj = &Presence{}
		default:
			obj = &Generic{}
			Info.Logf("Ignoring unrecognized: %s %s", se.Name.Space,
				se.Name.Local)
		}

		// Read the complete XML stanza.
		err = p.DecodeElement(obj, &se)
		if err != nil {
			Warn.Logf("unmarshal: %s", err)
			break Loop
		}

		// If it's a Stanza, we try to unmarshal its innerxml
		// into objects of the appropriate respective
		// types. This is specified by our extensions.
		if st, ok := obj.(Stanza); ok {
			err = parseExtended(st.GetHeader(), extStanza)
			if err != nil {
				Warn.Logf("ext unmarshal: %s", err)
				break Loop
			}
		}

		// Put it on the channel.
		ch <- obj
	}
}

func parseExtended(st *Header, extStanza map[xml.Name]reflect.Type) 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.NewDecoder(reader)
	for {
		t, err := p.Token()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		if se, ok := t.(xml.StartElement); ok {
			if typ, ok := extStanza[se.Name]; ok {
				nested := reflect.New(typ).Interface()

				// Unmarshal the nested element and
				// stuff it back into the stanza.
				err := p.DecodeElement(nested, &se)
				if err != nil {
					return err
				}
				st.Nested = append(st.Nested, nested)
			}
		}
	}

	return nil
}

// Receive structures on a channel, marshal them to XML, and send the
// bytes on a writer.
func sendXml(w io.Writer, ch <-chan interface{}) {
	if _, ok := Debug.(*noLog); !ok {
		pr, pw := io.Pipe()
		go tee(pr, w, "C: ")
		w = pw
	}
	defer func(w io.Writer) {
		if c, ok := w.(io.Closer); ok {
			c.Close()
		}
	}(w)

	enc := xml.NewEncoder(w)

	for obj := range ch {
		if st, ok := obj.(*stream); ok {
			_, err := w.Write([]byte(st.String()))
			if err != nil {
				Warn.Logf("write: %s", err)
			}
		} else {
			err := enc.Encode(obj)
			if err != nil {
				Warn.Logf("marshal: %s", err)
				break
			}
		}
	}
}