diff options
-rw-r--r-- | xmpp/element_buffer.go | 61 | ||||
-rw-r--r-- | xmpp/element_buffer_test.go | 55 | ||||
-rw-r--r-- | xmpp/encoder_decoder.go | 80 | ||||
-rw-r--r-- | xmpp/jid.go | 67 | ||||
-rw-r--r-- | xmpp/presence.go | 13 | ||||
-rw-r--r-- | xmpp/router.go | 62 | ||||
-rw-r--r-- | xmpp/router_test.go | 70 | ||||
-rw-r--r-- | xmpp/routing.go | 41 | ||||
-rw-r--r-- | xmpp/sasl.go | 64 | ||||
-rw-r--r-- | xmpp/session.go | 40 | ||||
-rw-r--r-- | xmpp/stream_pair.go | 131 | ||||
-rw-r--r-- | xmpp/streams.go | 64 | ||||
-rw-r--r-- | xmpp/xml.go | 68 |
13 files changed, 245 insertions, 571 deletions
diff --git a/xmpp/element_buffer.go b/xmpp/element_buffer.go deleted file mode 100644 index 7792db2..0000000 --- a/xmpp/element_buffer.go +++ /dev/null @@ -1,61 +0,0 @@ -package xmpp - -import ( - "encoding/xml" -) - -// elementBuffer is a struct to store multiple values of type xml.Token until -// they form a complete XML element with opening and closing tag which is -// suitable to be passed to an appropriate handler function. -type elementBuffer struct { - tokens []xml.Token - end xml.EndElement - level int -} - -// newElementBuffer returns a new initialized elementBuffer struct. -func newElementBuffer() elementBuffer { - buf := elementBuffer{} - buf.reset() - return buf -} - -// FIXME this function needs essential error handling for corner cases! -// -// add is able to add a new xml.Token to the buffer. There are some rules -// checked to ensure a correct and consistent elementBuffer which are checked. -// If one of these checks fail the token is not added and a corresponding error -// is returned. -func (e *elementBuffer) add(t xml.Token) error { - switch t.(type) { - case xml.StartElement: - e.level += 1 - case xml.EndElement: - e.level -= 1 - } - e.tokens = append(e.tokens, t) - return nil -} - -// FIXME isComplete would be true if a stream with only one XML comment is -// passed to the buffer. This might be unexpected behaviour. -// -// isComplete returns true if the buffer contains a slice of XML tokens which -// form a complete XML element starting with an xml.StartElement and closing -// with the corresponding xml.EndElement. -func (e *elementBuffer) isComplete() bool { - return (len(e.tokens) > 0 && e.level == 0) -} - -// reset returns the content of the buffer as a slice of XML tokens and resets -// the buffer to the initial state. This function can be used to initialize the -// elementBuffer struct. In that case the return value can be ignored. -func (e *elementBuffer) reset() (buf []xml.Token) { - retval := e.tokens - - e.tokens = make([]xml.Token, 0) - e.end = xml.EndElement{} - e.level = 0 - - return retval -} diff --git a/xmpp/element_buffer_test.go b/xmpp/element_buffer_test.go deleted file mode 100644 index af3d5c2..0000000 --- a/xmpp/element_buffer_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package xmpp - -import ( - "encoding/xml" - "strings" - "testing" -) - -// bufTest is a struct containing a test point for the -// xengineering.eu/limox/xmpp.elementBuffer. It contains a test XML string -// which has to be exactly one XML element and an array of indentation levels -// which have to be checked after each token which is parsed. -type bufTest struct { - xml string - levels []int -} - -func TestElementBuffer(t *testing.T) { - tests := []bufTest{ - bufTest{`<stream></stream>`, []int{1, 0}}, - bufTest{`<stream/>`, []int{1, 0}}, - bufTest{`<a><b>testing</b></a>`, []int{1, 2, 2, 1, 0}}, - bufTest{`<a><!-- comment --><b>testing</b></a>`, []int{1, 1, 2, 2, 1, 0}}, - bufTest{`<!-- comment --><a><b>testing</b></a>`, []int{0, 1, 2, 2, 1, 0}}, - } - - for _, v := range tests { - r := strings.NewReader(v.xml) - d := xml.NewDecoder(r) - b := newElementBuffer() - - i := 0 - - for { - token, err := d.Token() - if err != nil { - if i != len(v.levels) { - t.Fatalf("Stopped parsing at unexpected index due to error `%v`!\n", err) - } - break - } - - err = b.add(token) - if err != nil { - t.Fatalf("add(token) failed with error `%v`!\n", err) - } - - if b.level != v.levels[i] { - t.Fatalf("Indent level of xmpp.elementBuffer %d does not match value given by test data %d!\n", b.level, v.levels[i]) - } - - i += 1 - } - } -} diff --git a/xmpp/encoder_decoder.go b/xmpp/encoder_decoder.go deleted file mode 100644 index b0ea77b..0000000 --- a/xmpp/encoder_decoder.go +++ /dev/null @@ -1,80 +0,0 @@ -package xmpp - -import ( - "encoding/xml" - "errors" - "io" - "log" -) - -type encoderDecoder struct { - session *session - tx *xml.Encoder - rx *xml.Decoder - terminator chan bool -} - -func newEncoderDecoder(s *session) encoderDecoder { - ed := encoderDecoder{} - - ed.session = s - - lw := logger{"[TX] "} - w := io.MultiWriter(s.transport, lw) - ed.tx = xml.NewEncoder(w) - ed.tx.Indent("", "") - - lr := logger{"[RX] "} - r := io.TeeReader(s.transport, lr) - ed.rx = xml.NewDecoder(r) - - return ed -} - -func (ed *encoderDecoder) encodeToken(t xml.Token) error { - var err error - defer func() { - if err != nil { - log.Println(err) - } - }() - - err = ed.tx.EncodeToken(t) - if err != nil { - return err - } - err = ed.tx.Flush() - if err != nil { - return err - } - - return nil -} - -func (ed *encoderDecoder) run() { - for { - select { - case <-ed.terminator: - return - default: - t, err := ed.rx.Token() - if t != nil && err == nil { - switch t.(type) { - case xml.ProcInst: - case xml.Directive: - case xml.Comment: - default: - c := xml.CopyToken(t) - ed.session.rx <- c - } - } - if err != nil { - if errors.Is(err, io.EOF) { - return - } - log.Println(err) // FIXME terminate session on error - return - } - } - } -} diff --git a/xmpp/jid.go b/xmpp/jid.go index fd0d7ae..9580ad5 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -41,62 +41,45 @@ func username(jid string) string { return "" } -func hasBind(e []xml.Token) bool { - bind := xml.Name{`urn:ietf:params:xml:ns:xmpp-bind`, `bind`} - - for _, v := range e { - switch s := v.(type) { - case xml.StartElement: - if s.Name == bind { - return true - } - } - } - - return false +type bindRequest struct { + Bind struct { + Xmlns string `xml:"xmlns,attr"` + Resource struct { + Content string `xml:",chardata"` + } `xml:"resource"` + } `xml:"bind"` } func (s *session) sendBind() { + s.resourceReq = fmt.Sprintf("%016x", rand.Uint64()) - iqStart := xml.StartElement{ + start := xml.StartElement{ xml.Name{"jabber:client", "iq"}, []xml.Attr{ xml.Attr{xml.Name{"", "id"}, s.resourceReq}, xml.Attr{xml.Name{"", "type"}, "set"}, }, } - iqEnd := iqStart.End() - bindStart := xml.StartElement{ - xml.Name{"urn:ietf:params:xml:ns:xmpp-bind", "bind"}, - []xml.Attr{}, - } - bindEnd := bindStart.End() + inner := bindRequest{} + inner.Bind.Xmlns = "urn:ietf:params:xml:ns:xmpp-bind" + inner.Bind.Resource.Content = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) - resourceStart := xml.StartElement{ - xml.Name{"", "resource"}, - []xml.Attr{}, - } - resourceEnd := resourceStart.End() - - name := xml.CharData("limox-" + fmt.Sprintf("%08x", rand.Uint32())) - - tokens := [...]xml.Token{ - iqStart, - bindStart, - resourceStart, - name, - resourceEnd, - bindEnd, - iqEnd, + err := s.tx.EncodeElement(inner, start) + if err != nil { + log.Println("Could not encode ressource binding!") } +} - for _, v := range tokens { - err := s.ed.encodeToken(v) - if err != nil { - log.Println("Could not encode ressource binding!") - return - } +type iqResponse struct { + Jid string `xml:"urn:ietf:params:xml:ns:xmpp-bind bind>jid"` +} + +func handleIqResponse(s *session, i iqResponse) { + if i.Jid != "" { + s.jid = i.Jid + s.sendPresence() + return } } diff --git a/xmpp/presence.go b/xmpp/presence.go index b6ea3b5..00eaa47 100644 --- a/xmpp/presence.go +++ b/xmpp/presence.go @@ -10,15 +10,10 @@ func (s *session) sendPresence() { xml.Name{"", "presence"}, []xml.Attr{}, } - end := start.End() - tokens := [...]xml.Token{start, end} - - for _, v := range tokens { - err := s.ed.encodeToken(v) - if err != nil { - log.Println("Could not encode presence!") - return - } + err := s.tx.EncodeElement(struct{}{}, start) + if err != nil { + log.Println("Could not encode presence!") + return } } diff --git a/xmpp/router.go b/xmpp/router.go deleted file mode 100644 index 1e21c9b..0000000 --- a/xmpp/router.go +++ /dev/null @@ -1,62 +0,0 @@ -package xmpp - -import ( - "encoding/xml" - "log" -) - -// routingTable is a data structure which contains routing information for XML -// elements. The xml.StartElement at the beginning of an XML element has a name -// containing the XML namespace and a local name. Based on this compisition -// which forms the xml.Name the appropriate handler function is defined by each -// entry of the routingTable. -type routingTable []struct { - name xml.Name - handler func(*session, []xml.Token) -} - -// getRoutingTable returns the routing table used in -// xengineering.eu/limox/xmpp. Since Go does not allow such a datatype as a -// constant such a function is a simple yet inefficient approach to guarantee -// that an unmodified routing table is delivered to each user. A global -// variable would have the problem that it could be altered during execution. -func getRoutingTable() routingTable { - return routingTable{ - {xml.Name{`http://etherx.jabber.org/streams`, `features`}, streamFeaturesHandler}, - {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `success`}, saslSuccessHandler}, - {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `failure`}, saslFailureHandler}, - {xml.Name{`jabber:client`, `iq`}, iqHandler}, - } -} - -// route determines the correct handler function for the given XML element by a -// given routingTable. In addition it executes the determined handler function. -// If no handler function is found an error message is send via the log module. -func route(s *session, e []xml.Token, t routingTable) { - var name xml.Name - - // TODO a stronger definition of an XML element (as here - // https://www.w3schools.com/xml/xml_elements.asp) would define that the - // first Token of an element is a StartElement token. This would make this - // code easier. - escape := false - for _, token := range e { - switch s := token.(type) { - case xml.StartElement: - name = s.Name - escape = true - } - if escape { - break - } - } - - for _, r := range t { - if name == r.name { - r.handler(s, e) - return - } - } - - log.Println("Could not route XML element") -} diff --git a/xmpp/router_test.go b/xmpp/router_test.go deleted file mode 100644 index b490778..0000000 --- a/xmpp/router_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package xmpp - -import ( - "encoding/xml" - "strings" - "testing" -) - -// routerTest contains a single test case for the xmpp.router. The XML element -// is given as a string and the value int describes the expected value of a -// test variable inside the corresponding unit test. See TestRouter for -// details. -type routerTest struct { - xml string - value int -} - -// TestRouter tests the xmpp/router.go file. The central functionality is the -// route() function. To test this a fake routing table is created inside this -// test with XML element handler function produced by the factory function -// defined below. each handler simply sets the value of the testpoint variable -// to the value mentioned by the test point. That way the routing can be -// validated. -func TestRouter(t *testing.T) { - var testpoint int - var s session - - factory := func(tp *int, i int) func(*session, []xml.Token) { - return func(*session, []xml.Token) { - *tp = i - } - } - - tests := []routerTest{ - routerTest{`<a></a>`, 1}, - routerTest{`<b></b>`, 2}, - routerTest{`<c></c>`, 3}, - routerTest{`<b xmlns='https://xengineering.eu'></b>`, 4}, - routerTest{`<a xmlns='https://xengineering.eu'></a>`, 5}, - } - - testRouting := routingTable{ - {xml.Name{``, `a`}, factory(&testpoint, 1)}, - {xml.Name{``, `b`}, factory(&testpoint, 2)}, - {xml.Name{``, `c`}, factory(&testpoint, 3)}, - {xml.Name{`https://xengineering.eu`, `b`}, factory(&testpoint, 4)}, - {xml.Name{`https://xengineering.eu`, `a`}, factory(&testpoint, 5)}, - } - - for _, v := range tests { - testpoint = 0 - - r := strings.NewReader(v.xml) - d := xml.NewDecoder(r) - tokens := make([]xml.Token, 0) - for { - token, err := d.Token() - if err != nil { - break - } - tokens = append(tokens, xml.CopyToken(token)) - } - - route(&s, tokens, testRouting) - - if testpoint != v.value { - t.Fatalf("XML element was not routed correctly!\n") - } - } -} diff --git a/xmpp/routing.go b/xmpp/routing.go new file mode 100644 index 0000000..5cd2040 --- /dev/null +++ b/xmpp/routing.go @@ -0,0 +1,41 @@ +package xmpp + +import ( + "encoding/xml" + "log" +) + +func route(s *xml.StartElement, d *xml.Decoder, c chan<- any) { + switch (*s).Name { + case xml.Name{`http://etherx.jabber.org/streams`, `features`}: + parse(streamFeatures{}, s, d, c) + case xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `success`}: + parse(saslSuccess{}, s, d, c) + case xml.Name{`jabber:client`, `iq`}: + parse(iqResponse{}, s, d, c) + default: + d.Skip() + } +} + +func parse[T any](data T, s *xml.StartElement, d *xml.Decoder, c chan<- any) { + err := d.DecodeElement(&data, s) + if err != nil { + log.Printf("Could not decode stream features: %v\n", err) + } else { + c <- data + } +} + +func handle(s *session, element any) { + switch t := element.(type) { + case streamFeatures: + handleStreamFeatures(s, t) + case saslSuccess: + handleSaslSuccess(s) + case iqResponse: + handleIqResponse(s, t) + default: + log.Printf("Unknown parsed element: %v", t) + } +} diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 512fd58..69b536d 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -6,69 +6,31 @@ import ( "log" ) -func (s *session) sasl() { - tokens := make([]xml.Token, 0, 3) +type saslRequest struct { + Payload []byte `xml:",chardata"` +} +func (s *session) sasl() { start := xml.StartElement{ xml.Name{"urn:ietf:params:xml:ns:xmpp-sasl", "auth"}, []xml.Attr{ xml.Attr{xml.Name{"", "mechanism"}, "PLAIN"}, }, } - tokens = append(tokens, start) data := []byte("\x00" + username(s.jid) + "\x00" + s.pwd) - dst := make([]byte, base64.StdEncoding.EncodedLen(len(data))) - base64.StdEncoding.Encode(dst, data) - payload := xml.CharData(dst) - tokens = append(tokens, payload) - - end := start.End() - tokens = append(tokens, end) - - for _, t := range tokens { - err := s.ed.encodeToken(t) - if err != nil { - log.Println("Could not encode SASL PLAIN element!") - return - } - } -} - -// hasSaslPlain scans the given stream features XML element for the SASL PLAIN -// mechanism which is supported by xengineering.eu/limox/xmpp. It returns true -// if the stream has support for this mechanism and false otherwise. -func hasSaslPlain(e []xml.Token) bool { - mechanism := xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `mechanism`} + inner := saslRequest{} + inner.Payload = make([]byte, base64.StdEncoding.EncodedLen(len(data))) + base64.StdEncoding.Encode(inner.Payload, data) - for i, t := range e { - switch s := t.(type) { - case xml.StartElement: - if s.Name == mechanism { - if i+1 < len(e) { - subtype := func() string { - switch c := e[i+1].(type) { - case xml.CharData: - return string(c) - default: - return "" - } - }() - if subtype == `PLAIN` { - return true - } - } - } - } + err := s.tx.EncodeElement(inner, start) + if err != nil { + log.Println("Could not encode SASL PLAIN element!") } - - return false } -func saslSuccessHandler(s *session, e []xml.Token) { - runStreamPair(s) -} +type saslSuccess struct{} -func saslFailureHandler(s *session, e []xml.Token) { - log.Println("SASL autentication failed!") +func handleSaslSuccess(s *session) { + openStream(s.tx, s.jid) } diff --git a/xmpp/session.go b/xmpp/session.go index a43e4f4..4dfd76f 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -1,9 +1,11 @@ package xmpp import ( + "context" "crypto/tls" "crypto/x509" "encoding/xml" + "io" "log" ) @@ -17,8 +19,8 @@ type session struct { in chan any out chan<- any transport *tls.Conn - ed encoderDecoder - rx chan xml.Token + tx *xml.Encoder + rx chan any resourceReq string } @@ -29,7 +31,7 @@ func StartSession(out chan<- any, jid string, pwd string) (in chan<- any) { s.pwd = pwd s.in = make(chan any) s.out = out - s.rx = make(chan xml.Token, 0) + s.rx = make(chan any, 0) go s.run() @@ -37,21 +39,39 @@ func StartSession(out chan<- any, jid string, pwd string) (in chan<- any) { } func (s *session) run() { - defer func() { s.out <- SessionDisconnect{} }() - err := s.startTransport() if err != nil { return } defer s.transport.Close() - s.ed = newEncoderDecoder(s) - go s.ed.run() - defer func() { s.ed.terminator <- true }() + ctx, cancel := context.WithCancel(context.Background()) + cpy := s.rx + go runRx(ctx, cpy, s.transport) + defer cancel() + + lw := logger{"[TX] "} + w := io.MultiWriter(s.transport, lw) + s.tx = xml.NewEncoder(w) + defer s.tx.Close() - s.out <- SessionConnect{} + openStream(s.tx, s.jid) + defer closeStream(s.tx) - runStreamPair(s) + s.out <- SessionConnect{} // TODO this should be sent after initial presence + defer func() { s.out <- SessionDisconnect{} }() + + for { + select { + case e := <-s.rx: + handle(s, e) + case signal := <-s.in: + switch signal.(type) { + case SessionShouldDisconnect: + return + } + } + } } func (s *session) startTransport() error { diff --git a/xmpp/stream_pair.go b/xmpp/stream_pair.go deleted file mode 100644 index 87df86a..0000000 --- a/xmpp/stream_pair.go +++ /dev/null @@ -1,131 +0,0 @@ -package xmpp - -import ( - "encoding/xml" - "log" -) - -func runStreamPair(s *session) { - end := openStream(s) - defer closeStream(s, end) - - buf := newElementBuffer() - - for { - select { - case data := <-s.in: - switch data.(type) { - case SessionShouldDisconnect: - return - default: - log.Printf("Unknown data '%d'!\n", data) - } - case t := <-s.rx: - err := buf.add(t) - if err != nil { - log.Printf("Could not add XML token to buffer: %v\n", err) - return - } - if buf.isComplete() { - element := buf.reset() - route(s, element, getRoutingTable()) - } - } - } -} - -func openStream(s *session) xml.EndElement { - start := xml.StartElement{ - xml.Name{"jabber:client", "stream:stream"}, - []xml.Attr{ - xml.Attr{xml.Name{"", "from"}, s.jid}, - xml.Attr{xml.Name{"", "to"}, domainpart(s.jid)}, - xml.Attr{xml.Name{"", "version"}, "1.0"}, - xml.Attr{xml.Name{"", "xml:lang"}, "en"}, - xml.Attr{xml.Name{"", "xmlns:stream"}, "http://etherx.jabber.org/streams"}, - }, - } - end := start.End() - - err := s.ed.encodeToken(start) - if err != nil { - log.Println("Could not encode stream start!") - } - - syncStreams(s) - - return end -} - -// syncStreams drops XML tokens from the receiving stream until an -// xml.StartElement with the local name `stream` is received. If this function -// is called after opening a new stream in the sending direction it is ensured -// that both streams directions work on the same stream level and are in sync. -// Tokens received which are not a stream StartElement are not handled but -// logged since this should not happen. -func syncStreams(s *session) { - for { - select { - case data := <-s.in: - switch data.(type) { - case SessionShouldDisconnect: - return - default: - log.Printf("Unhandled data '%d' during stream sync!\n", data) - } - case t := <-s.rx: - switch token := t.(type) { - case xml.StartElement: - if token.Name.Local == "stream" { - return - } - } - log.Printf("Unhandled XML token '%v' during stream sync!\n", t) - } - } -} - -func closeStream(s *session, end xml.EndElement) { - err := s.ed.encodeToken(end) - if err != nil { - log.Println("Could not encode stream end!") - } -} - -func streamFeaturesHandler(s *session, e []xml.Token) { - if hasSaslPlain(e) { - s.sasl() - return - } - - if hasBind(e) { - s.sendBind() - return - } - - log.Println("Stream has no implemented features!") -} - -func iqHandler(s *session, e []xml.Token) { - isResult := false - idMatches := false - - result := xml.Attr{xml.Name{"", "type"}, "result"} - id := xml.Attr{xml.Name{"", "id"}, s.resourceReq} - - switch start := e[0].(type) { - case xml.StartElement: - for _, v := range start.Attr { - if v == result { - isResult = true - } - if v == id { - idMatches = true - } - } - - if isResult && idMatches { - s.sendPresence() - } - } -} diff --git a/xmpp/streams.go b/xmpp/streams.go new file mode 100644 index 0000000..9f6ffe8 --- /dev/null +++ b/xmpp/streams.go @@ -0,0 +1,64 @@ +package xmpp + +import ( + "encoding/xml" + "log" +) + +type streamFeatures struct { + SaslMechanisms []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms>mechanism"` + Bind *bool `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` +} + +func handleStreamFeatures(s *session, f streamFeatures) { + if len(f.SaslMechanisms) > 0 { + for _, v := range f.SaslMechanisms { + if v == "PLAIN" { + s.sasl() + return + } + } + log.Println("No compatible SASL mechanism found!") + return + } + + if f.Bind != nil { + s.sendBind() + return + } +} + +func openStream(e *xml.Encoder, jid string) { + start := xml.StartElement{ + xml.Name{"jabber:client", "stream:stream"}, + []xml.Attr{ + xml.Attr{xml.Name{"", "from"}, jid}, + xml.Attr{xml.Name{"", "to"}, domainpart(jid)}, + xml.Attr{xml.Name{"", "version"}, "1.0"}, + xml.Attr{xml.Name{"", "xml:lang"}, "en"}, + xml.Attr{xml.Name{"", "xmlns:stream"}, "http://etherx.jabber.org/streams"}, + }, + } + + err := e.EncodeToken(start) + if err != nil { + log.Println("Could not encode stream start!") + } + err = e.Flush() + if err != nil { + log.Println("Could not flush after stream start!") + } +} + +func closeStream(e *xml.Encoder) { + end := xml.EndElement{xml.Name{"jabber:client", "stream:stream"}} + + err := e.EncodeToken(end) + if err != nil { + log.Println("Could not encode stream end!") + } + err = e.Flush() + if err != nil { + log.Println("Could not flush after stream end!") + } +} diff --git a/xmpp/xml.go b/xmpp/xml.go new file mode 100644 index 0000000..e6fccee --- /dev/null +++ b/xmpp/xml.go @@ -0,0 +1,68 @@ +package xmpp + +import ( + "context" + "crypto/tls" + "encoding/xml" + "errors" + "io" + "log" +) + +func runRx(ctx context.Context, chn chan<- any, conn *tls.Conn) { + + l := logger{"[RX] "} + r := io.TeeReader(conn, l) + d := xml.NewDecoder(r) + + for { + select { + case <-ctx.Done(): + return + default: + t, err := d.Token() + if t != nil && err == nil { + switch e := t.(type) { + case xml.StartElement: + if e.Name.Local == "stream" { + // new server-side stream TODO what to do with this info? + } else { + route(&e, d, chn) + } + case xml.EndElement: + if e.Name.Local == "stream" { + // TODO end complete session + return + } + } + } + if err != nil { + if errors.Is(err, io.EOF) { + return + } + log.Println(err) // FIXME terminate session on error + return + } + } + } +} + +func (s *session) encodeToken(t xml.Token) error { + var err error + defer func() { + if err != nil { + log.Println(err) + } + }() + + err = s.tx.EncodeToken(t) + if err != nil { + return err + } + err = s.tx.Flush() + if err != nil { + return err + } + + return nil +} |