From 6a623ed4ec649f0fcfa775405ad5584bc1cd6762 Mon Sep 17 00:00:00 2001 From: xengineering Date: Sun, 2 Jul 2023 11:40:42 +0200 Subject: Switch to EncodeElement() for SASL auth --- xmpp/sasl.go | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 512fd58..24edc9a 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -6,32 +6,26 @@ 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) + inner := saslRequest{} + inner.Payload = make([]byte, base64.StdEncoding.EncodedLen(len(data))) + base64.StdEncoding.Encode(inner.Payload, data) - 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 - } + err := s.ed.tx.EncodeElement(inner, start) + if err != nil { + log.Println("Could not encode SASL PLAIN element!") } } -- cgit v1.2.3-70-g09d2 From bde6e0c5095c1fd73058dc8dab408fd5f27aee7c Mon Sep 17 00:00:00 2001 From: xengineering Date: Sun, 2 Jul 2023 12:04:10 +0200 Subject: Switch to EncodeElement() for resource binding --- xmpp/jid.go | 48 +++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/xmpp/jid.go b/xmpp/jid.go index fd0d7ae..332073b 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -56,47 +56,33 @@ func hasBind(e []xml.Token) bool { 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() - 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, - } + inner := bindRequest{} + inner.Bind.Xmlns = "urn:ietf:params:xml:ns:xmpp-bind" + inner.Bind.Resource.Content = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) - for _, v := range tokens { - err := s.ed.encodeToken(v) - if err != nil { - log.Println("Could not encode ressource binding!") - return - } + err := s.ed.tx.EncodeElement(inner, start) + if err != nil { + log.Println("Could not encode ressource binding!") } } -- cgit v1.2.3-70-g09d2 From eca0649c1e0a72fbea4899a295443eac69600bfd Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 3 Jul 2023 20:49:19 +0200 Subject: Use xml.Encoder.EncodeElement() for presence This required the trick of defining an empty struct as `presence` type but works pretty well. For sending data EncodeElement instead of token handling should always work. Since it is simpler it should be used. --- xmpp/presence.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/xmpp/presence.go b/xmpp/presence.go index b6ea3b5..4adae6e 100644 --- a/xmpp/presence.go +++ b/xmpp/presence.go @@ -5,20 +5,17 @@ import ( "log" ) +type presence struct{} + func (s *session) sendPresence() { start := xml.StartElement{ 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.ed.tx.EncodeElement(presence{}, start) + if err != nil { + log.Println("Could not encode presence!") + return } } -- cgit v1.2.3-70-g09d2 From 3cac662ef99366dea7bec383ba6704b3a5292d93 Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 3 Jul 2023 21:07:07 +0200 Subject: Rename stream_pair.go to streams.go The fact that it is actually about a pair of XML streams is obvious and not that relevant. A shorter name has a higher priority. --- xmpp/stream_pair.go | 131 ---------------------------------------------------- xmpp/streams.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 131 deletions(-) delete mode 100644 xmpp/stream_pair.go create mode 100644 xmpp/streams.go 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..87df86a --- /dev/null +++ b/xmpp/streams.go @@ -0,0 +1,131 @@ +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() + } + } +} -- cgit v1.2.3-70-g09d2 From cf520b079743ec95d085a439d00b841c253c564a Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 3 Jul 2023 21:10:51 +0200 Subject: Rename encoder_decoder.go to xml.go This is way shorter and serves the same purpose: It reflects the responsibility of encoding and decoding XML. The encoderDecoder struct should be removed soon. --- xmpp/encoder_decoder.go | 80 ------------------------------------------------- xmpp/xml.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 80 deletions(-) delete mode 100644 xmpp/encoder_decoder.go create mode 100644 xmpp/xml.go 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/xml.go b/xmpp/xml.go new file mode 100644 index 0000000..b0ea77b --- /dev/null +++ b/xmpp/xml.go @@ -0,0 +1,80 @@ +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 + } + } + } +} -- cgit v1.2.3-70-g09d2 From 2fade1039c1842f08b30da5c95b5542b57e38ec6 Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 3 Jul 2023 22:17:15 +0200 Subject: Move xml.Encoder to session struct The encoderDecoder sub-struct of the session struct should be removed in little steps. This is the first one. --- xmpp/jid.go | 2 +- xmpp/presence.go | 2 +- xmpp/sasl.go | 2 +- xmpp/session.go | 6 ++++++ xmpp/streams.go | 4 ++-- xmpp/xml.go | 12 +++--------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/xmpp/jid.go b/xmpp/jid.go index 332073b..83772fd 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -81,7 +81,7 @@ func (s *session) sendBind() { inner.Bind.Xmlns = "urn:ietf:params:xml:ns:xmpp-bind" inner.Bind.Resource.Content = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) - err := s.ed.tx.EncodeElement(inner, start) + err := s.tx.EncodeElement(inner, start) if err != nil { log.Println("Could not encode ressource binding!") } diff --git a/xmpp/presence.go b/xmpp/presence.go index 4adae6e..e2b1841 100644 --- a/xmpp/presence.go +++ b/xmpp/presence.go @@ -13,7 +13,7 @@ func (s *session) sendPresence() { []xml.Attr{}, } - err := s.ed.tx.EncodeElement(presence{}, start) + err := s.tx.EncodeElement(presence{}, start) if err != nil { log.Println("Could not encode presence!") return diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 24edc9a..0c13f36 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -23,7 +23,7 @@ func (s *session) sasl() { inner.Payload = make([]byte, base64.StdEncoding.EncodedLen(len(data))) base64.StdEncoding.Encode(inner.Payload, data) - err := s.ed.tx.EncodeElement(inner, start) + err := s.tx.EncodeElement(inner, start) if err != nil { log.Println("Could not encode SASL PLAIN element!") } diff --git a/xmpp/session.go b/xmpp/session.go index a43e4f4..b4a8fab 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/xml" + "io" "log" ) @@ -18,6 +19,7 @@ type session struct { out chan<- any transport *tls.Conn ed encoderDecoder + tx *xml.Encoder rx chan xml.Token resourceReq string } @@ -49,6 +51,10 @@ func (s *session) run() { go s.ed.run() defer func() { s.ed.terminator <- true }() + lw := logger{"[TX] "} + w := io.MultiWriter(s.transport, lw) + s.tx = xml.NewEncoder(w) + s.out <- SessionConnect{} runStreamPair(s) diff --git a/xmpp/streams.go b/xmpp/streams.go index 87df86a..5ba4c1d 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -47,7 +47,7 @@ func openStream(s *session) xml.EndElement { } end := start.End() - err := s.ed.encodeToken(start) + err := s.encodeToken(start) if err != nil { log.Println("Could not encode stream start!") } @@ -86,7 +86,7 @@ func syncStreams(s *session) { } func closeStream(s *session, end xml.EndElement) { - err := s.ed.encodeToken(end) + err := s.encodeToken(end) if err != nil { log.Println("Could not encode stream end!") } diff --git a/xmpp/xml.go b/xmpp/xml.go index b0ea77b..f547210 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -9,7 +9,6 @@ import ( type encoderDecoder struct { session *session - tx *xml.Encoder rx *xml.Decoder terminator chan bool } @@ -19,11 +18,6 @@ func newEncoderDecoder(s *session) 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) @@ -31,7 +25,7 @@ func newEncoderDecoder(s *session) encoderDecoder { return ed } -func (ed *encoderDecoder) encodeToken(t xml.Token) error { +func (s *session) encodeToken(t xml.Token) error { var err error defer func() { if err != nil { @@ -39,11 +33,11 @@ func (ed *encoderDecoder) encodeToken(t xml.Token) error { } }() - err = ed.tx.EncodeToken(t) + err = s.tx.EncodeToken(t) if err != nil { return err } - err = ed.tx.Flush() + err = s.tx.Flush() if err != nil { return err } -- cgit v1.2.3-70-g09d2 From 3efcd60f8ebdc962d5be85003cc8c59a2b43e610 Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 3 Jul 2023 22:28:01 +0200 Subject: Remove encoderDecoder struct completely This was not really necessary because it was all related to the xmpp.session and should thus be implemented there. Using the context package further reduced the complexity for cancelation. --- xmpp/session.go | 9 ++++---- xmpp/xml.go | 69 ++++++++++++++++++++++++--------------------------------- 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/xmpp/session.go b/xmpp/session.go index b4a8fab..6a5e646 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -1,6 +1,7 @@ package xmpp import ( + "context" "crypto/tls" "crypto/x509" "encoding/xml" @@ -18,7 +19,6 @@ type session struct { in chan any out chan<- any transport *tls.Conn - ed encoderDecoder tx *xml.Encoder rx chan xml.Token resourceReq string @@ -47,9 +47,10 @@ func (s *session) run() { } 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) diff --git a/xmpp/xml.go b/xmpp/xml.go index f547210..14c6637 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -1,57 +1,26 @@ package xmpp import ( + "context" + "crypto/tls" "encoding/xml" "errors" "io" "log" ) -type encoderDecoder struct { - session *session - rx *xml.Decoder - terminator chan bool -} - -func newEncoderDecoder(s *session) encoderDecoder { - ed := encoderDecoder{} - - ed.session = s +func runRx(ctx context.Context, chn chan xml.Token, conn *tls.Conn) { - lr := logger{"[RX] "} - r := io.TeeReader(s.transport, lr) - ed.rx = xml.NewDecoder(r) + l := logger{"[RX] "} + r := io.TeeReader(conn, l) + d := xml.NewDecoder(r) - return ed -} - -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 -} - -func (ed *encoderDecoder) run() { for { select { - case <-ed.terminator: + case <-ctx.Done(): return default: - t, err := ed.rx.Token() + t, err := d.Token() if t != nil && err == nil { switch t.(type) { case xml.ProcInst: @@ -59,7 +28,7 @@ func (ed *encoderDecoder) run() { case xml.Comment: default: c := xml.CopyToken(t) - ed.session.rx <- c + chn <- c } } if err != nil { @@ -72,3 +41,23 @@ func (ed *encoderDecoder) run() { } } } + +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 +} -- cgit v1.2.3-70-g09d2 From 5932556e7051690e262a5ee39263b7905bf1ba20 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 12:30:02 +0200 Subject: Remove element_buffer The new RX concept will not need such an element buffer and uses the xml.Decoder.DecodeElement() function instead. --- xmpp/element_buffer.go | 61 --------------------------------------------- xmpp/element_buffer_test.go | 55 ---------------------------------------- 2 files changed, 116 deletions(-) delete mode 100644 xmpp/element_buffer.go delete mode 100644 xmpp/element_buffer_test.go 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{``, []int{1, 0}}, - bufTest{``, []int{1, 0}}, - bufTest{`testing`, []int{1, 2, 2, 1, 0}}, - bufTest{`testing`, []int{1, 1, 2, 2, 1, 0}}, - bufTest{`testing`, []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 - } - } -} -- cgit v1.2.3-70-g09d2 From 9f87ef34c589825d65824a8c9210fed5bf92f94d Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 12:33:12 +0200 Subject: Remove runStreamPair() This will not be used in the new RX concept. --- xmpp/sasl.go | 1 - xmpp/session.go | 2 -- xmpp/streams.go | 29 ----------------------------- 3 files changed, 32 deletions(-) diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 0c13f36..a20ae56 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -60,7 +60,6 @@ func hasSaslPlain(e []xml.Token) bool { } func saslSuccessHandler(s *session, e []xml.Token) { - runStreamPair(s) } func saslFailureHandler(s *session, e []xml.Token) { diff --git a/xmpp/session.go b/xmpp/session.go index 6a5e646..f18fd2a 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -57,8 +57,6 @@ func (s *session) run() { s.tx = xml.NewEncoder(w) s.out <- SessionConnect{} - - runStreamPair(s) } func (s *session) startTransport() error { diff --git a/xmpp/streams.go b/xmpp/streams.go index 5ba4c1d..388be4d 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -5,35 +5,6 @@ import ( "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"}, -- cgit v1.2.3-70-g09d2 From 8e84ca8a4d340956bd0aaead59d5c79dcaede6a5 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 12:43:47 +0200 Subject: Remove xmpp.presence type This was empty and has no benefit. --- xmpp/presence.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xmpp/presence.go b/xmpp/presence.go index e2b1841..00eaa47 100644 --- a/xmpp/presence.go +++ b/xmpp/presence.go @@ -5,15 +5,13 @@ import ( "log" ) -type presence struct{} - func (s *session) sendPresence() { start := xml.StartElement{ xml.Name{"", "presence"}, []xml.Attr{}, } - err := s.tx.EncodeElement(presence{}, start) + err := s.tx.EncodeElement(struct{}{}, start) if err != nil { log.Println("Could not encode presence!") return -- cgit v1.2.3-70-g09d2 From ea98ee187477051444bbf548757af6336d333862 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 12:58:13 +0200 Subject: Re-implement stream open and close This is more suitable for the new RX concept. --- xmpp/session.go | 21 ++++++++++++++++++--- xmpp/streams.go | 53 +++++++++++++++-------------------------------------- xmpp/xml.go | 19 ++++++++++++------- 3 files changed, 45 insertions(+), 48 deletions(-) diff --git a/xmpp/session.go b/xmpp/session.go index f18fd2a..4be3386 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -39,8 +39,6 @@ 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 @@ -55,8 +53,25 @@ func (s *session) run() { lw := logger{"[TX] "} w := io.MultiWriter(s.transport, lw) s.tx = xml.NewEncoder(w) + defer s.tx.Close() + + openStream(s.tx, s.jid) + defer closeStream(s.tx) - s.out <- SessionConnect{} + s.out <- SessionConnect{} // TODO this should be sent after initial presence + defer func() { s.out <- SessionDisconnect{} }() + + for { + select { + case e := <-s.rx: + log.Print(e) + case signal := <-s.in: + switch signal.(type) { + case SessionShouldDisconnect: + return + } + } + } } func (s *session) startTransport() error { diff --git a/xmpp/streams.go b/xmpp/streams.go index 388be4d..b9c0cb7 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -5,62 +5,39 @@ import ( "log" ) -func openStream(s *session) xml.EndElement { +func openStream(e *xml.Encoder, jid string) { 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{"", "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"}, }, } - end := start.End() - err := s.encodeToken(start) + err := e.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) - } + err = e.Flush() + if err != nil { + log.Println("Could not flush after stream start!") } } -func closeStream(s *session, end xml.EndElement) { - err := s.encodeToken(end) +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!") + } } func streamFeaturesHandler(s *session, e []xml.Token) { diff --git a/xmpp/xml.go b/xmpp/xml.go index 14c6637..36d7eb1 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -22,13 +22,18 @@ func runRx(ctx context.Context, chn chan xml.Token, conn *tls.Conn) { default: t, err := d.Token() if t != nil && err == nil { - switch t.(type) { - case xml.ProcInst: - case xml.Directive: - case xml.Comment: - default: - c := xml.CopyToken(t) - chn <- c + 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, getRoutingTable()) + } + case xml.EndElement: + if e.Name.Local == "stream" { + // TODO end complete session + return + } } } if err != nil { -- cgit v1.2.3-70-g09d2 From 45b04ef092a99d0d3f355e27896c82f1dbf15d28 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 13:42:24 +0200 Subject: Remove unit test for router The new router will be so trivial that it is not worth a unit test. --- xmpp/router_test.go | 70 ----------------------------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 xmpp/router_test.go 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{``, 1}, - routerTest{``, 2}, - routerTest{``, 3}, - routerTest{``, 4}, - routerTest{``, 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") - } - } -} -- cgit v1.2.3-70-g09d2 From 92534f5af88b42665ad44f2495fe5dfb116d3406 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 13:48:34 +0200 Subject: First working version of new RX concept This uses xml.Decoder.DecodeElement() which makes parsing way easier. This first step is just able to parse stream features partially. --- xmpp/router.go | 38 ++++++++------------------------------ xmpp/session.go | 4 ++-- xmpp/streams.go | 33 +++++++++++++++++++-------------- xmpp/xml.go | 4 ++-- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/xmpp/router.go b/xmpp/router.go index 1e21c9b..d437b28 100644 --- a/xmpp/router.go +++ b/xmpp/router.go @@ -2,7 +2,6 @@ package xmpp import ( "encoding/xml" - "log" ) // routingTable is a data structure which contains routing information for XML @@ -12,7 +11,7 @@ import ( // entry of the routingTable. type routingTable []struct { name xml.Name - handler func(*session, []xml.Token) + handler func(s *xml.StartElement, d *xml.Decoder, c chan<- any) } // getRoutingTable returns the routing table used in @@ -23,40 +22,19 @@ type routingTable []struct { 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}, +// {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 +func route(s *xml.StartElement, d *xml.Decoder, c chan<- any, t routingTable) { + for _, v := range t { + if v.name == (*s).Name { + v.handler(s, d, c) } } - - log.Println("Could not route XML element") } diff --git a/xmpp/session.go b/xmpp/session.go index 4be3386..6abc343 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -20,7 +20,7 @@ type session struct { out chan<- any transport *tls.Conn tx *xml.Encoder - rx chan xml.Token + rx chan any resourceReq string } @@ -31,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() diff --git a/xmpp/streams.go b/xmpp/streams.go index b9c0cb7..9c90554 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -40,20 +40,6 @@ func closeStream(e *xml.Encoder) { } } -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 @@ -77,3 +63,22 @@ func iqHandler(s *session, e []xml.Token) { } } } + +type streamFeatures struct { + Mechanisms struct { + Items []struct { + Type string `xml:",innerxml"` + } `xml:"mechanism"` + } `xml:"mechanisms"` +} + +func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { + e := streamFeatures{} + + err := d.DecodeElement(&e, s) + if err != nil { + log.Printf("Could not decode stream features: %v\n", err) + } + + c <- e +} diff --git a/xmpp/xml.go b/xmpp/xml.go index 36d7eb1..470a2ef 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -9,7 +9,7 @@ import ( "log" ) -func runRx(ctx context.Context, chn chan xml.Token, conn *tls.Conn) { +func runRx(ctx context.Context, chn chan<- any, conn *tls.Conn) { l := logger{"[RX] "} r := io.TeeReader(conn, l) @@ -27,7 +27,7 @@ func runRx(ctx context.Context, chn chan xml.Token, conn *tls.Conn) { if e.Name.Local == "stream" { // new server-side stream TODO what to do with this info? } else { -// route(&e, &d, chn, getRoutingTable()) + route(&e, d, chn, getRoutingTable()) } case xml.EndElement: if e.Name.Local == "stream" { -- cgit v1.2.3-70-g09d2 From e5257c8a9c3dfcda52fb96fcac2ac81aefb52e55 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 13:57:26 +0200 Subject: Reduce complexity of streamFeatures struct --- xmpp/streams.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/xmpp/streams.go b/xmpp/streams.go index 9c90554..cc83934 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -65,11 +65,9 @@ func iqHandler(s *session, e []xml.Token) { } type streamFeatures struct { - Mechanisms struct { - Items []struct { - Type string `xml:",innerxml"` - } `xml:"mechanism"` - } `xml:"mechanisms"` + Mechanisms []struct { + Type string `xml:",chardata"` + } `xml:"mechanisms>mechanism"` } func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { -- cgit v1.2.3-70-g09d2 From 3826f3ee7850590142fe51747447dffece332762 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 14:01:51 +0200 Subject: Just pass parsed data if successful --- xmpp/streams.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xmpp/streams.go b/xmpp/streams.go index cc83934..b55b6f0 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -76,7 +76,7 @@ func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { err := d.DecodeElement(&e, s) if err != nil { log.Printf("Could not decode stream features: %v\n", err) + } else { + c <- e } - - c <- e } -- cgit v1.2.3-70-g09d2 From 1d3dfa5b93000bc4109ba49ea018e72fbf4f5753 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 14:06:36 +0200 Subject: Further reduce complexity of streamFeatures struct --- xmpp/streams.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xmpp/streams.go b/xmpp/streams.go index b55b6f0..b7cde3e 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -65,9 +65,7 @@ func iqHandler(s *session, e []xml.Token) { } type streamFeatures struct { - Mechanisms []struct { - Type string `xml:",chardata"` - } `xml:"mechanisms>mechanism"` + SaslMechanisms []string `xml:"mechanisms>mechanism"` } func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { -- cgit v1.2.3-70-g09d2 From 3a5e9fce02264eeed884b15781593479fda9296a Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 14:25:21 +0200 Subject: Rework routing completely --- xmpp/router.go | 40 ---------------------------------------- xmpp/routing.go | 21 +++++++++++++++++++++ xmpp/streams.go | 11 ----------- xmpp/xml.go | 2 +- 4 files changed, 22 insertions(+), 52 deletions(-) delete mode 100644 xmpp/router.go create mode 100644 xmpp/routing.go diff --git a/xmpp/router.go b/xmpp/router.go deleted file mode 100644 index d437b28..0000000 --- a/xmpp/router.go +++ /dev/null @@ -1,40 +0,0 @@ -package xmpp - -import ( - "encoding/xml" -) - -// 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(s *xml.StartElement, d *xml.Decoder, c chan<- any) -} - -// 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 *xml.StartElement, d *xml.Decoder, c chan<- any, t routingTable) { - for _, v := range t { - if v.name == (*s).Name { - v.handler(s, d, c) - } - } -} diff --git a/xmpp/routing.go b/xmpp/routing.go new file mode 100644 index 0000000..e8aa4ed --- /dev/null +++ b/xmpp/routing.go @@ -0,0 +1,21 @@ +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`}: + data := streamFeatures{} + err := d.DecodeElement(&data, s) + if err != nil { + log.Printf("Could not decode stream features: %v\n", err) + } else { + log.Print(data) // TODO + } + default: + d.Skip() + } +} diff --git a/xmpp/streams.go b/xmpp/streams.go index b7cde3e..3aca8a2 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -67,14 +67,3 @@ func iqHandler(s *session, e []xml.Token) { type streamFeatures struct { SaslMechanisms []string `xml:"mechanisms>mechanism"` } - -func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { - e := streamFeatures{} - - err := d.DecodeElement(&e, s) - if err != nil { - log.Printf("Could not decode stream features: %v\n", err) - } else { - c <- e - } -} diff --git a/xmpp/xml.go b/xmpp/xml.go index 470a2ef..e6fccee 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -27,7 +27,7 @@ func runRx(ctx context.Context, chn chan<- any, conn *tls.Conn) { if e.Name.Local == "stream" { // new server-side stream TODO what to do with this info? } else { - route(&e, d, chn, getRoutingTable()) + route(&e, d, chn) } case xml.EndElement: if e.Name.Local == "stream" { -- cgit v1.2.3-70-g09d2 From 7902764e053eb3b8b4d46f8d9caf47d2d5cddd7c Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 16:26:04 +0200 Subject: Implement XML element parsing with Generic This helps to avoid duplicated code when new XML elements are described as custom structs. --- xmpp/routing.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/xmpp/routing.go b/xmpp/routing.go index e8aa4ed..2b1680f 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -8,14 +8,17 @@ import ( func route(s *xml.StartElement, d *xml.Decoder, c chan<- any) { switch (*s).Name { case xml.Name{`http://etherx.jabber.org/streams`, `features`}: - data := streamFeatures{} - err := d.DecodeElement(&data, s) - if err != nil { - log.Printf("Could not decode stream features: %v\n", err) - } else { - log.Print(data) // TODO - } + parse(streamFeatures{}, 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 + } +} -- cgit v1.2.3-70-g09d2 From e529bab2e5df93ff8e9fd415b9d65e9bb6d17695 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 16:37:27 +0200 Subject: Introduce handle() as dummy --- xmpp/routing.go | 9 +++++++++ xmpp/session.go | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/xmpp/routing.go b/xmpp/routing.go index 2b1680f..b184b1c 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -22,3 +22,12 @@ func parse[T any](data T, s *xml.StartElement, d *xml.Decoder, c chan<- any) { c <- data } } + +func handle(element any) { + switch t := element.(type) { + case streamFeatures: + log.Println("Handling stream features ...") + default: + log.Printf("Unknown parsed element: %v", t) + } +} diff --git a/xmpp/session.go b/xmpp/session.go index 6abc343..a4120e9 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -64,7 +64,7 @@ func (s *session) run() { for { select { case e := <-s.rx: - log.Print(e) + handle(e) case signal := <-s.in: switch signal.(type) { case SessionShouldDisconnect: -- cgit v1.2.3-70-g09d2 From 2c71877e392da6c2691827160142e95142f7bea6 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 21:24:04 +0200 Subject: Re-implement SASL Was broken because of switch to new RX concept. --- xmpp/routing.go | 4 ++-- xmpp/session.go | 2 +- xmpp/streams.go | 21 +++++++++++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/xmpp/routing.go b/xmpp/routing.go index b184b1c..a9dd8b6 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -23,10 +23,10 @@ func parse[T any](data T, s *xml.StartElement, d *xml.Decoder, c chan<- any) { } } -func handle(element any) { +func handle(s *session, element any) { switch t := element.(type) { case streamFeatures: - log.Println("Handling stream features ...") + handleStreamFeatures(s, t) default: log.Printf("Unknown parsed element: %v", t) } diff --git a/xmpp/session.go b/xmpp/session.go index a4120e9..7a07280 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -64,7 +64,7 @@ func (s *session) run() { for { select { case e := <-s.rx: - handle(e) + handle(s, e) case signal := <-s.in: switch signal.(type) { case SessionShouldDisconnect: diff --git a/xmpp/streams.go b/xmpp/streams.go index 3aca8a2..8f6fd03 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -5,6 +5,23 @@ import ( "log" ) +type streamFeatures struct { + SaslMechanisms []string `xml:"mechanisms>mechanism"` +} + +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 + } +} + func openStream(e *xml.Encoder, jid string) { start := xml.StartElement{ xml.Name{"jabber:client", "stream:stream"}, @@ -63,7 +80,3 @@ func iqHandler(s *session, e []xml.Token) { } } } - -type streamFeatures struct { - SaslMechanisms []string `xml:"mechanisms>mechanism"` -} -- cgit v1.2.3-70-g09d2 From ed6b4e818f4090c0c707fab49093bc4c3cc3ac20 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 21:48:23 +0200 Subject: Add namespace to streamFeatures struct This prevents collisions. --- xmpp/streams.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmpp/streams.go b/xmpp/streams.go index 8f6fd03..b9cd4cd 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -6,7 +6,7 @@ import ( ) type streamFeatures struct { - SaslMechanisms []string `xml:"mechanisms>mechanism"` + SaslMechanisms []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms>mechanism"` } func handleStreamFeatures(s *session, f streamFeatures) { -- cgit v1.2.3-70-g09d2 From a23ba089a4e715e68b8c8d4179290692215784a2 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 22:04:05 +0200 Subject: Re-implement resource binding and presence This was removed for refactoring. --- xmpp/jid.go | 27 ++++++++++++--------------- xmpp/routing.go | 8 ++++++++ xmpp/sasl.go | 6 ++++++ xmpp/streams.go | 8 ++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/xmpp/jid.go b/xmpp/jid.go index 83772fd..c732027 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -41,21 +41,6 @@ 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"` @@ -86,3 +71,15 @@ func (s *session) sendBind() { log.Println("Could not encode ressource binding!") } } + +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/routing.go b/xmpp/routing.go index a9dd8b6..5cd2040 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -9,6 +9,10 @@ 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() } @@ -27,6 +31,10 @@ 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 a20ae56..5fac934 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -10,6 +10,12 @@ type saslRequest struct { Payload []byte `xml:",chardata"` } +type saslSuccess struct {} + +func handleSaslSuccess(s *session) { + openStream(s.tx, s.jid) +} + func (s *session) sasl() { start := xml.StartElement{ xml.Name{"urn:ietf:params:xml:ns:xmpp-sasl", "auth"}, diff --git a/xmpp/streams.go b/xmpp/streams.go index b9cd4cd..18a5e6a 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -7,9 +7,12 @@ import ( 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) { + log.Print(f) + if len(f.SaslMechanisms) > 0 { for _, v := range f.SaslMechanisms { if v == "PLAIN" { @@ -20,6 +23,11 @@ func handleStreamFeatures(s *session, f streamFeatures) { log.Println("No compatible SASL mechanism found!") return } + + if f.Bind != nil { + s.sendBind() + return + } } func openStream(e *xml.Encoder, jid string) { -- cgit v1.2.3-70-g09d2 From 4c9c6c91f9dfca9fd17731d5b0e94aaaace4a137 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 22:09:13 +0200 Subject: Remove unused code --- xmpp/sasl.go | 43 +++---------------------------------------- xmpp/streams.go | 26 -------------------------- 2 files changed, 3 insertions(+), 66 deletions(-) diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 5fac934..ae3be4a 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -10,12 +10,6 @@ type saslRequest struct { Payload []byte `xml:",chardata"` } -type saslSuccess struct {} - -func handleSaslSuccess(s *session) { - openStream(s.tx, s.jid) -} - func (s *session) sasl() { start := xml.StartElement{ xml.Name{"urn:ietf:params:xml:ns:xmpp-sasl", "auth"}, @@ -35,39 +29,8 @@ func (s *session) sasl() { } } -// 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`} - - 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 - } - } - } - } - } - - return false -} - -func saslSuccessHandler(s *session, e []xml.Token) { -} +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/streams.go b/xmpp/streams.go index 18a5e6a..ec16a02 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -11,8 +11,6 @@ type streamFeatures struct { } func handleStreamFeatures(s *session, f streamFeatures) { - log.Print(f) - if len(f.SaslMechanisms) > 0 { for _, v := range f.SaslMechanisms { if v == "PLAIN" { @@ -64,27 +62,3 @@ func closeStream(e *xml.Encoder) { log.Println("Could not flush after stream end!") } } - -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() - } - } -} -- cgit v1.2.3-70-g09d2 From d9fe0a4360770b1e4b6b4fb3686c3275ad1b6e6e Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 22:09:36 +0200 Subject: Apply go fmt --- xmpp/jid.go | 2 +- xmpp/sasl.go | 2 +- xmpp/session.go | 2 +- xmpp/streams.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xmpp/jid.go b/xmpp/jid.go index c732027..9580ad5 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -43,7 +43,7 @@ func username(jid string) string { type bindRequest struct { Bind struct { - Xmlns string `xml:"xmlns,attr"` + Xmlns string `xml:"xmlns,attr"` Resource struct { Content string `xml:",chardata"` } `xml:"resource"` diff --git a/xmpp/sasl.go b/xmpp/sasl.go index ae3be4a..69b536d 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -29,7 +29,7 @@ func (s *session) sasl() { } } -type saslSuccess struct {} +type saslSuccess struct{} func handleSaslSuccess(s *session) { openStream(s.tx, s.jid) diff --git a/xmpp/session.go b/xmpp/session.go index 7a07280..4dfd76f 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -58,7 +58,7 @@ func (s *session) run() { openStream(s.tx, s.jid) defer closeStream(s.tx) - s.out <- SessionConnect{} // TODO this should be sent after initial presence + s.out <- SessionConnect{} // TODO this should be sent after initial presence defer func() { s.out <- SessionDisconnect{} }() for { diff --git a/xmpp/streams.go b/xmpp/streams.go index ec16a02..9f6ffe8 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -7,7 +7,7 @@ import ( 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"` + Bind *bool `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` } func handleStreamFeatures(s *session, f streamFeatures) { -- cgit v1.2.3-70-g09d2