From 8023777ac1b0171783e1c55f67a6179d63708272 Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 15 May 2023 21:09:26 +0200 Subject: Introduce package xengineering.eu/limox/xmpp The XMPP logic is now big enough to create a corresponding package for it. --- gui.go | 6 -- limox.go | 18 +++--- xmpp.go | 187 ---------------------------------------------------------- xmpp/xmpp.go | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 201 deletions(-) delete mode 100644 xmpp.go create mode 100644 xmpp/xmpp.go diff --git a/gui.go b/gui.go index e6e4c31..55730df 100644 --- a/gui.go +++ b/gui.go @@ -10,12 +10,6 @@ import ( "gioui.org/widget/material" ) -type GuiEvent uint8 - -const ( - Disconnect GuiEvent = iota -) - func (l *Limox) draw(e system.FrameEvent) { gtx := layout.NewContext(&l.Operations, e) diff --git a/limox.go b/limox.go index 89a5916..2dee767 100644 --- a/limox.go +++ b/limox.go @@ -11,6 +11,8 @@ import ( "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" + + "xengineering.eu/limox/xmpp" ) type LimoxState uint8 @@ -26,7 +28,7 @@ type Limox struct { PwdEditor widget.Editor MainButton widget.Clickable XmppEvents chan any - GuiEvents chan GuiEvent + GuiEvents chan xmpp.Event State LimoxState Window *app.Window Operations op.Ops @@ -42,7 +44,7 @@ func NewLimox() Limox { Operations: op.Ops{}, Theme: material.NewTheme(gofont.Collection()), XmppEvents: make(chan any), - GuiEvents: make(chan GuiEvent), + GuiEvents: make(chan xmpp.Event), State: Disconnected, } } @@ -65,14 +67,14 @@ func (l *Limox) run() error { case error: log.Print(ev) l.State = Disconnected - case XmppEvent: + case xmpp.Event: switch ev { - case XmppDisconnect: + case xmpp.DisconnectEvent: l.State = Disconnected - case XmppConnect: + case xmpp.ConnectEvent: l.State = Connected default: - log.Printf("Unknown XmppEvent '%d'\n", ev) + log.Printf("Unknown xmpp.Event '%d'\n", ev) } default: log.Printf("Unknown event type '%s'.\n", reflect.TypeOf(ev)) @@ -86,14 +88,14 @@ func (l *Limox) buttonCallback() { switch l.State { case Disconnected: log.Println("Starting connection establishment ...") - go xmpp(l.GuiEvents, l.XmppEvents, l.JidEditor.Text(), l.PwdEditor.Text()) + go xmpp.Run(l.GuiEvents, l.XmppEvents, l.JidEditor.Text(), l.PwdEditor.Text()) l.State = Connecting case Connecting: log.Println("Aborted connection establishment") l.State = Disconnected case Connected: log.Println("Disconnecting ...") - l.GuiEvents <- Disconnect + l.GuiEvents <- xmpp.ShouldDisconnectEvent l.State = Disconnected } } diff --git a/xmpp.go b/xmpp.go deleted file mode 100644 index a0aaba7..0000000 --- a/xmpp.go +++ /dev/null @@ -1,187 +0,0 @@ -package main - -import ( - "crypto/tls" - "crypto/x509" - "encoding/xml" - "log" - "os" -) - -type XmppEvent uint8 - -const ( - XmppDisconnect XmppEvent = iota - XmppConnect -) - -func xmpp(rxChan chan GuiEvent, txChan chan any, jid string, pwd string) { - conn, err := setupConn(jid) - if err != nil { - log.Print(err) - return - } - defer conn.Close() - - receiver := newXmppReceiver(conn) - go receiver.run() - defer receiver.stop() - - enc := xml.NewEncoder(conn) - defer enc.Close() - dbg := xml.NewEncoder(os.Stdout) - defer dbg.Close() - - end := sendStreamStart(enc, dbg, jid) - defer sendStreamEnd(enc, dbg, end) - - txChan <- XmppConnect - defer func() { txChan <- XmppDisconnect }() - - for { - select { - case ev := <-rxChan: - switch ev { - case Disconnect: - return - default: - log.Printf("Unknown GuiEvent '%d'!\n", ev) - } - case rx := <-receiver.data: - dbg.Indent("S: ", " ") - dbg.EncodeToken(rx) - dbg.Flush() - } - } -} - -type xmppReceiver struct { - terminator chan bool - data chan xml.Token - decoder *xml.Decoder -} - -func newXmppReceiver(conn *tls.Conn) xmppReceiver { - return xmppReceiver{ - make(chan bool), - make(chan xml.Token), - xml.NewDecoder(conn), - } -} - -func (r *xmppReceiver) run() { - for { - select { - case <-r.terminator: - return - default: - t, err := r.decoder.Token() - if err != nil { - log.Print(err) - } - if t != nil { - c := xml.CopyToken(t) - r.data <- c - } - } - } -} - -func (r *xmppReceiver) stop() { - r.terminator <- true -} - -func setupConn(jid string) (*tls.Conn, error) { - domain := domainpart(jid) - - roots, err := x509.SystemCertPool() - if err != nil { - return nil, err - } - - return tls.Dial("tcp", domain+":"+"5223", &tls.Config{RootCAs: roots}) -} - -func sendStreamStart(enc *xml.Encoder, dbg *xml.Encoder, jid string) xml.EndElement { - 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 := enc.EncodeToken(start) - if err != nil { - log.Println("Could not encode stream start to TCP channel!") - } - - err = enc.Flush() - if err != nil { - log.Println("Could not flush TCP channel XML encoder!") - } - - dbg.Indent("C: ", " ") - - err = dbg.EncodeToken(start) - if err != nil { - log.Println("Could not encode stream start to debug output!") - } - - err = dbg.Flush() - if err != nil { - log.Println("Could not flush debug XML encoder!") - } - - return start.End() -} - -func sendStreamEnd(enc *xml.Encoder, dbg *xml.Encoder, end xml.EndElement) { - - err := enc.EncodeToken(end) - if err != nil { - log.Println("Could not encode stream end to TCP channel!") - } - - err = enc.Flush() - if err != nil { - log.Println("Could not flush TCP channel XML encoder!") - } - - dbg.Indent("C: ", " ") - - err = dbg.EncodeToken(end) - if err != nil { - log.Println("Could not encode stream end to debug output!") - } - - err = dbg.Flush() - if err != nil { - log.Println("Could not flush debug XML encoder!") - } -} - -// domainpart extracts the domain name from a JID / XMPP address. See -// https://datatracker.ietf.org/doc/html/rfc7622#section-3.2 for details. -func domainpart(jid string) string { - list := []rune(jid) - - for i, v := range list { - if v == '/' { - list = list[:i] - break - } - } - - for i, v := range list { - if v == '@' { - list = list[i+1:] - break - } - } - - return string(list) -} diff --git a/xmpp/xmpp.go b/xmpp/xmpp.go new file mode 100644 index 0000000..64ca494 --- /dev/null +++ b/xmpp/xmpp.go @@ -0,0 +1,188 @@ +package xmpp + +import ( + "crypto/tls" + "crypto/x509" + "encoding/xml" + "log" + "os" +) + +type Event uint8 + +const ( + DisconnectEvent Event = iota + ConnectEvent + ShouldDisconnectEvent +) + +func Run(rxChan chan Event, txChan chan any, jid string, pwd string) { + conn, err := setupConn(jid) + if err != nil { + log.Print(err) + return + } + defer conn.Close() + + receiver := newXmppReceiver(conn) + go receiver.run() + defer receiver.stop() + + enc := xml.NewEncoder(conn) + defer enc.Close() + dbg := xml.NewEncoder(os.Stdout) + defer dbg.Close() + + end := sendStreamStart(enc, dbg, jid) + defer sendStreamEnd(enc, dbg, end) + + txChan <- ConnectEvent + defer func() { txChan <- DisconnectEvent }() + + for { + select { + case ev := <-rxChan: + switch ev { + case ShouldDisconnectEvent: + return + default: + log.Printf("Unknown Event '%d'!\n", ev) + } + case rx := <-receiver.data: + dbg.Indent("S: ", " ") + dbg.EncodeToken(rx) + dbg.Flush() + } + } +} + +type xmppReceiver struct { + terminator chan bool + data chan xml.Token + decoder *xml.Decoder +} + +func newXmppReceiver(conn *tls.Conn) xmppReceiver { + return xmppReceiver{ + make(chan bool), + make(chan xml.Token), + xml.NewDecoder(conn), + } +} + +func (r *xmppReceiver) run() { + for { + select { + case <-r.terminator: + return + default: + t, err := r.decoder.Token() + if err != nil { + log.Print(err) + } + if t != nil { + c := xml.CopyToken(t) + r.data <- c + } + } + } +} + +func (r *xmppReceiver) stop() { + r.terminator <- true +} + +func setupConn(jid string) (*tls.Conn, error) { + domain := domainpart(jid) + + roots, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + + return tls.Dial("tcp", domain+":"+"5223", &tls.Config{RootCAs: roots}) +} + +func sendStreamStart(enc *xml.Encoder, dbg *xml.Encoder, jid string) xml.EndElement { + 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 := enc.EncodeToken(start) + if err != nil { + log.Println("Could not encode stream start to TCP channel!") + } + + err = enc.Flush() + if err != nil { + log.Println("Could not flush TCP channel XML encoder!") + } + + dbg.Indent("C: ", " ") + + err = dbg.EncodeToken(start) + if err != nil { + log.Println("Could not encode stream start to debug output!") + } + + err = dbg.Flush() + if err != nil { + log.Println("Could not flush debug XML encoder!") + } + + return start.End() +} + +func sendStreamEnd(enc *xml.Encoder, dbg *xml.Encoder, end xml.EndElement) { + + err := enc.EncodeToken(end) + if err != nil { + log.Println("Could not encode stream end to TCP channel!") + } + + err = enc.Flush() + if err != nil { + log.Println("Could not flush TCP channel XML encoder!") + } + + dbg.Indent("C: ", " ") + + err = dbg.EncodeToken(end) + if err != nil { + log.Println("Could not encode stream end to debug output!") + } + + err = dbg.Flush() + if err != nil { + log.Println("Could not flush debug XML encoder!") + } +} + +// domainpart extracts the domain name from a JID / XMPP address. See +// https://datatracker.ietf.org/doc/html/rfc7622#section-3.2 for details. +func domainpart(jid string) string { + list := []rune(jid) + + for i, v := range list { + if v == '/' { + list = list[:i] + break + } + } + + for i, v := range list { + if v == '@' { + list = list[i+1:] + break + } + } + + return string(list) +} -- cgit v1.2.3-70-g09d2