package xmpp import ( "bytes" "crypto/tls" "encoding/xml" "fmt" "io" "log" "net" ) const ( nsStream = "http://etherx.jabber.org/streams" nsTLS = "urn:ietf:params:xml:ns:xmpp-tls" ) type Stream struct { conn net.Conn dec *xml.Decoder } // Create a XML stream connection. A Steam is used by an XMPP instance to // handle sending and receiving XML data over the net connection. func NewStream(addr string) (*Stream, error) { log.Println("Connecting to", addr) conn, err := net.Dial("tcp", addr) if err != nil { return nil, err } stream := &Stream{conn, xml.NewDecoder(conn)} if err := stream.send([]byte("")); err != nil { return nil, err } return stream, nil } // Upgrade the stream's underlying net connection to TLS. func (stream *Stream) UpgradeTLS(config *tls.Config) error { conn := tls.Client(stream.conn, config) if err := conn.Handshake(); err != nil { return err } stream.conn = conn stream.dec = xml.NewDecoder(stream.conn) return nil } // Send the element's start tag. Typically used to open the stream's document. func (stream *Stream) SendStart(start *xml.StartElement) (*xml.StartElement, error) { // Write start of outgoing doc. buf := new(bytes.Buffer) if err := writeXMLStartElement(buf, start); err != nil { return nil, err } if err := stream.send(buf.Bytes()); err != nil { return nil, err } // Read and return start of incoming doc. return nextStartElement(stream.dec) } // Send a stanza. Used to write a complete, top-level element. func (stream *Stream) Send(v interface{}) error { bytes, err := xml.Marshal(v) if err != nil { return err } return stream.send(bytes) } func (stream *Stream) send(b []byte) error { log.Println("send:", string(b)) if _, err := stream.conn.Write(b); err != nil { return err } return nil } // Find start of next stanza. If match is not nil the stanza's XML name // is compared and must be equal. // Bad things are very likely to happen if a call to Next() is successful but // you don't actually decode or skip the element. func (stream *Stream) Next(match *xml.Name) (*xml.StartElement, error) { start, err := nextStartElement(stream.dec) if err != nil { return nil, err } if match != nil && start.Name != *match { return nil, fmt.Errorf("Expected %s, got %s", *match, start.Name) } return start, nil } func nextStartElement(dec *xml.Decoder) (*xml.StartElement, error) { for { t, err := dec.Token() if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return nil, err } switch e := t.(type) { case xml.StartElement: return &e, nil case xml.EndElement: log.Printf("EOF due to %s\n", e.Name) return nil, io.EOF } } panic("Unreachable") } // Skip reads tokens until it reaches the end element of the most recent start // element that has already been read. func (stream *Stream) Skip() error { return stream.dec.Skip() } // Decode the next stanza. Works like xml.Unmarshal but reads from the stream's // connection. func (stream *Stream) Decode(v interface{}) error { return stream.DecodeElement(v, nil) } // Decode the stanza with the given start element. Works like // xml.Decoder.DecodeElement. func (stream *Stream) DecodeElement(v interface{}, start *xml.StartElement) error { // Explicity lookup next start element to ensure stream is validated. if start == nil { if se, err := stream.Next(nil); err != nil { return err } else { start = se } } return stream.dec.DecodeElement(v, start) }