From 989c6ee9158f67cda2d6287d096c813075d13bd2 Mon Sep 17 00:00:00 2001 From: xengineering Date: Wed, 5 Jul 2023 21:46:12 +0200 Subject: Move bind logic to new xmpp/iq.go The bind request / response works with IQ stanzas. Because of the way encoding and decoding is done with LimoX / the Go standard library it makes sense to group IQ-based logic in a separat file since the struct for IQs will be there. --- xmpp/iq.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ xmpp/jid.go | 50 -------------------------------------------------- 2 files changed, 51 insertions(+), 50 deletions(-) create mode 100644 xmpp/iq.go diff --git a/xmpp/iq.go b/xmpp/iq.go new file mode 100644 index 0000000..1c12c8c --- /dev/null +++ b/xmpp/iq.go @@ -0,0 +1,51 @@ +package xmpp + +import ( + "encoding/xml" + "fmt" + "math/rand" + "log" +) + +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()) + + start := xml.StartElement{ + xml.Name{"jabber:client", "iq"}, + []xml.Attr{ + xml.Attr{xml.Name{"", "id"}, s.resourceReq}, + xml.Attr{xml.Name{"", "type"}, "set"}, + }, + } + + inner := bindRequest{} + inner.Bind.Xmlns = "urn:ietf:params:xml:ns:xmpp-bind" + inner.Bind.Resource.Content = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) + + err := s.tx.EncodeElement(inner, start) + if err != nil { + 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/jid.go b/xmpp/jid.go index 9580ad5..90c1509 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -1,12 +1,5 @@ package xmpp -import ( - "encoding/xml" - "fmt" - "log" - "math/rand" -) - // 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 { @@ -40,46 +33,3 @@ func username(jid string) string { return "" } - -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()) - - start := xml.StartElement{ - xml.Name{"jabber:client", "iq"}, - []xml.Attr{ - xml.Attr{xml.Name{"", "id"}, s.resourceReq}, - xml.Attr{xml.Name{"", "type"}, "set"}, - }, - } - - inner := bindRequest{} - inner.Bind.Xmlns = "urn:ietf:params:xml:ns:xmpp-bind" - inner.Bind.Resource.Content = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) - - err := s.tx.EncodeElement(inner, start) - if err != nil { - 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 - } -} -- cgit v1.2.3-70-g09d2 From 13eeca2177dc767aa7d01c9caed95ae2be736471 Mon Sep 17 00:00:00 2001 From: xengineering Date: Wed, 5 Jul 2023 21:59:37 +0200 Subject: Add new generic iq struct This will cover all iq use cases with optional arguments and sub-elements. --- xmpp/iq.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/xmpp/iq.go b/xmpp/iq.go index 1c12c8c..4c91b58 100644 --- a/xmpp/iq.go +++ b/xmpp/iq.go @@ -7,6 +7,15 @@ import ( "log" ) +type iq struct { + XMLName xml.Name `xml:"jabber:client iq"` + Type string `xml:"type,attr,omitempty"` + Id string `xml:"id,attr,omitempty"` + Bind struct{ + Jid string `xml:"jid,omitempty"` + } `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` +} + type bindRequest struct { Bind struct { Xmlns string `xml:"xmlns,attr"` -- cgit v1.2.3-70-g09d2 From 57845892dbfc6db0648b18e418a6b15218d8526c Mon Sep 17 00:00:00 2001 From: xengineering Date: Wed, 5 Jul 2023 22:01:14 +0200 Subject: Migrate bind receive to new iq struct --- xmpp/iq.go | 10 +++------- xmpp/routing.go | 6 +++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/xmpp/iq.go b/xmpp/iq.go index 4c91b58..cd96384 100644 --- a/xmpp/iq.go +++ b/xmpp/iq.go @@ -47,13 +47,9 @@ func (s *session) sendBind() { } } -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 +func handleIq(s *session, i iq) { + if i.Bind.Jid != "" { + s.jid = i.Bind.Jid s.sendPresence() return } diff --git a/xmpp/routing.go b/xmpp/routing.go index 2f2347a..df45451 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -12,7 +12,7 @@ func route(s *xml.StartElement, d *xml.Decoder, c chan<- any) { 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) + parse(iq{}, s, d, c) case xml.Name{`jabber:client`, `message`}: parse(message{}, s, d, c) default: @@ -35,8 +35,8 @@ func handle(s *session, element any) { handleStreamFeatures(s, t) case saslSuccess: handleSaslSuccess(s) - case iqResponse: - handleIqResponse(s, t) + case iq: + handleIq(s, t) case message: handleMessage(s, t) default: -- cgit v1.2.3-70-g09d2 From 4cc9ea5cd439a1f5306f38838774b46d2b145d9a Mon Sep 17 00:00:00 2001 From: xengineering Date: Wed, 5 Jul 2023 22:34:13 +0200 Subject: Migrate bind sending to new generic iq struct This makes the code way less complex. --- xmpp/iq.go | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/xmpp/iq.go b/xmpp/iq.go index cd96384..2fe7c94 100644 --- a/xmpp/iq.go +++ b/xmpp/iq.go @@ -13,35 +13,19 @@ type iq struct { Id string `xml:"id,attr,omitempty"` Bind struct{ Jid string `xml:"jid,omitempty"` + Resource string `xml:"resource,omitempty"` } `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` } -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()) - start := xml.StartElement{ - xml.Name{"jabber:client", "iq"}, - []xml.Attr{ - xml.Attr{xml.Name{"", "id"}, s.resourceReq}, - xml.Attr{xml.Name{"", "type"}, "set"}, - }, - } - - inner := bindRequest{} - inner.Bind.Xmlns = "urn:ietf:params:xml:ns:xmpp-bind" - inner.Bind.Resource.Content = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) + req := iq{} + req.Id = s.resourceReq + req.Type = "set" + req.Bind.Resource = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) - err := s.tx.EncodeElement(inner, start) + err := s.tx.Encode(req) if err != nil { log.Println("Could not encode ressource binding!") } -- cgit v1.2.3-70-g09d2 From 553537a2c9f08450614648d7184b6bbf212d5ed5 Mon Sep 17 00:00:00 2001 From: xengineering Date: Thu, 6 Jul 2023 21:21:33 +0200 Subject: Use different IQ struct for RX and TX It is quite tricky to write structs with correct XML struct tags which are suitable for RX and TX because in RX structs all possible fields have to be addressed and in TX some have to be hidden depending on the use case. --- xmpp/iq.go | 30 +++++++++++++++++++----------- xmpp/routing.go | 6 +++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/xmpp/iq.go b/xmpp/iq.go index 2fe7c94..3d0e5a2 100644 --- a/xmpp/iq.go +++ b/xmpp/iq.go @@ -7,12 +7,28 @@ import ( "log" ) -type iq struct { +type iqRx struct { + XMLName xml.Name `xml:"jabber:client iq"` + Type string `xml:"type,attr"` + Id string `xml:"id,attr"` + Bind struct{ + Jid string `xml:"jid"` + } `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` +} + +func (i iqRx) handle(s *session) { + if i.Bind.Jid != "" { + s.jid = i.Bind.Jid + s.sendPresence() + return + } +} + +type bindSet struct { XMLName xml.Name `xml:"jabber:client iq"` Type string `xml:"type,attr,omitempty"` Id string `xml:"id,attr,omitempty"` Bind struct{ - Jid string `xml:"jid,omitempty"` Resource string `xml:"resource,omitempty"` } `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` } @@ -20,7 +36,7 @@ type iq struct { func (s *session) sendBind() { s.resourceReq = fmt.Sprintf("%016x", rand.Uint64()) - req := iq{} + req := bindSet{} req.Id = s.resourceReq req.Type = "set" req.Bind.Resource = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) @@ -30,11 +46,3 @@ func (s *session) sendBind() { log.Println("Could not encode ressource binding!") } } - -func handleIq(s *session, i iq) { - if i.Bind.Jid != "" { - s.jid = i.Bind.Jid - s.sendPresence() - return - } -} diff --git a/xmpp/routing.go b/xmpp/routing.go index df45451..4058dfb 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -12,7 +12,7 @@ func route(s *xml.StartElement, d *xml.Decoder, c chan<- any) { case xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `success`}: parse(saslSuccess{}, s, d, c) case xml.Name{`jabber:client`, `iq`}: - parse(iq{}, s, d, c) + parse(iqRx{}, s, d, c) case xml.Name{`jabber:client`, `message`}: parse(message{}, s, d, c) default: @@ -35,8 +35,8 @@ func handle(s *session, element any) { handleStreamFeatures(s, t) case saslSuccess: handleSaslSuccess(s) - case iq: - handleIq(s, t) + case iqRx: + t.handle(s) case message: handleMessage(s, t) default: -- cgit v1.2.3-70-g09d2 From 7960453fb4c56d2fdf9cae08df457027bd62ae6d Mon Sep 17 00:00:00 2001 From: xengineering Date: Thu, 6 Jul 2023 21:29:34 +0200 Subject: Implement roster get request This asks the server for the roster / contact list for the current account. --- xmpp/iq.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/xmpp/iq.go b/xmpp/iq.go index 3d0e5a2..3abcc4a 100644 --- a/xmpp/iq.go +++ b/xmpp/iq.go @@ -20,6 +20,7 @@ func (i iqRx) handle(s *session) { if i.Bind.Jid != "" { s.jid = i.Bind.Jid s.sendPresence() + s.sendRosterGet() return } } @@ -46,3 +47,21 @@ func (s *session) sendBind() { log.Println("Could not encode ressource binding!") } } + +type rosterGet struct { + XMLName xml.Name `xml:"jabber:client iq"` + Type string `xml:"type,attr,omitempty"` + Id string `xml:"id,attr,omitempty"` + Query string `xml:"jabber:iq:roster query"` +} + +func (s *session) sendRosterGet() { + req := rosterGet{} + req.Id = fmt.Sprintf("%016x", rand.Uint64()) + req.Type = "get" + + err := s.tx.Encode(req) + if err != nil { + log.Println("Could not encode ressource binding!") + } +} -- cgit v1.2.3-70-g09d2 From a3e01c9a867791b466fe2a3e1d0efd77c840729b Mon Sep 17 00:00:00 2001 From: xengineering Date: Thu, 6 Jul 2023 21:42:43 +0200 Subject: Implement roster result handling This completes the roster get / result cycle which is needed for a MVP. --- xmpp/iq.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/xmpp/iq.go b/xmpp/iq.go index 3abcc4a..d3d77b1 100644 --- a/xmpp/iq.go +++ b/xmpp/iq.go @@ -14,6 +14,13 @@ type iqRx struct { Bind struct{ Jid string `xml:"jid"` } `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` + Query []RosterItem `xml:"jabber:iq:roster query>item"` +} + +type RosterItem struct { + Jid string `xml:"jid,attr"` + Subscription string `xml:"subscription,attr"` + Name string `xml:"name,attr"` } func (i iqRx) handle(s *session) { @@ -23,6 +30,13 @@ func (i iqRx) handle(s *session) { s.sendRosterGet() return } + + if len(i.Query) > 0 { + log.Println("Got roster:") + for _, v := range i.Query { + log.Printf("- %s\n", v.Jid) + } + } } type bindSet struct { -- cgit v1.2.3-70-g09d2 From 007f413e457ff0b733447acba61b11d6813dd41c Mon Sep 17 00:00:00 2001 From: xengineering Date: Thu, 6 Jul 2023 21:43:42 +0200 Subject: Apply go fmt --- xmpp/iq.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/xmpp/iq.go b/xmpp/iq.go index d3d77b1..1cb7fd5 100644 --- a/xmpp/iq.go +++ b/xmpp/iq.go @@ -3,24 +3,24 @@ package xmpp import ( "encoding/xml" "fmt" - "math/rand" "log" + "math/rand" ) type iqRx struct { XMLName xml.Name `xml:"jabber:client iq"` - Type string `xml:"type,attr"` - Id string `xml:"id,attr"` - Bind struct{ + Type string `xml:"type,attr"` + Id string `xml:"id,attr"` + Bind struct { Jid string `xml:"jid"` } `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` Query []RosterItem `xml:"jabber:iq:roster query>item"` } type RosterItem struct { - Jid string `xml:"jid,attr"` + Jid string `xml:"jid,attr"` Subscription string `xml:"subscription,attr"` - Name string `xml:"name,attr"` + Name string `xml:"name,attr"` } func (i iqRx) handle(s *session) { @@ -41,9 +41,9 @@ func (i iqRx) handle(s *session) { type bindSet struct { XMLName xml.Name `xml:"jabber:client iq"` - Type string `xml:"type,attr,omitempty"` - Id string `xml:"id,attr,omitempty"` - Bind struct{ + Type string `xml:"type,attr,omitempty"` + Id string `xml:"id,attr,omitempty"` + Bind struct { Resource string `xml:"resource,omitempty"` } `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` } @@ -64,9 +64,9 @@ func (s *session) sendBind() { type rosterGet struct { XMLName xml.Name `xml:"jabber:client iq"` - Type string `xml:"type,attr,omitempty"` - Id string `xml:"id,attr,omitempty"` - Query string `xml:"jabber:iq:roster query"` + Type string `xml:"type,attr,omitempty"` + Id string `xml:"id,attr,omitempty"` + Query string `xml:"jabber:iq:roster query"` } func (s *session) sendRosterGet() { -- cgit v1.2.3-70-g09d2