package stanza import ( "encoding/xml" ) // ============================================================================ // StreamFeatures Packet // Reference: The active stream features are published on // https://xmpp.org/registrar/stream-features.html // Note: That page misses draft and experimental XEP (i.e CSI, etc) type StreamFeatures struct { XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"` // Server capabilities hash Caps Caps // Stream features StartTLS TlsStartTLS Mechanisms saslMechanisms Bind Bind StreamManagement streamManagement // Obsolete Session StreamSession // ProcessOne Stream Features P1Push p1Push P1Rebind p1Rebind p1Ack p1Ack Any []xml.Name `xml:",any"` } func (StreamFeatures) Name() string { return "stream:features" } type streamFeatureDecoder struct{} var streamFeatures streamFeatureDecoder func (streamFeatureDecoder) decode(p *xml.Decoder, se xml.StartElement) (StreamFeatures, error) { var packet StreamFeatures err := p.DecodeElement(&packet, &se) return packet, err } // Capabilities // Reference: https://xmpp.org/extensions/xep-0115.html#stream // "A server MAY include its entity capabilities in a stream feature element so that connecting clients // and peer servers do not need to send service discovery requests each time they connect." // This is not a stream feature but a way to let client cache server disco info. type Caps struct { XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"` Hash string `xml:"hash,attr"` Node string `xml:"node,attr"` Ver string `xml:"ver,attr"` Ext string `xml:"ext,attr,omitempty"` } // ============================================================================ // Supported Stream Features // StartTLS feature // Reference: RFC 6120 - https://tools.ietf.org/html/rfc6120#section-5.4 type TlsStartTLS struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` Required bool } // UnmarshalXML implements custom parsing startTLS required flag func (stls *TlsStartTLS) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { stls.XMLName = start.Name // Check subelements to extract required field as boolean for { t, err := d.Token() if err != nil { return err } switch tt := t.(type) { case xml.StartElement: elt := new(Node) err = d.DecodeElement(elt, &tt) if err != nil { return err } if elt.XMLName.Local == "required" { stls.Required = true } case xml.EndElement: if tt == start.End() { return nil } } } } func (sf *StreamFeatures) DoesStartTLS() (feature TlsStartTLS, isSupported bool) { if sf.StartTLS.XMLName.Space+" "+sf.StartTLS.XMLName.Local == nsTLS+" starttls" { return sf.StartTLS, true } return feature, false } // Mechanisms // Reference: RFC 6120 - https://tools.ietf.org/html/rfc6120#section-6.4.1 type saslMechanisms struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` Mechanism []string `xml:"mechanism"` } // StreamManagement // Reference: XEP-0198 - https://xmpp.org/extensions/xep-0198.html#feature type streamManagement struct { XMLName xml.Name `xml:"urn:xmpp:sm:3 sm"` } func (sf *StreamFeatures) DoesStreamManagement() (isSupported bool) { if sf.StreamManagement.XMLName.Space+" "+sf.StreamManagement.XMLName.Local == "urn:xmpp:sm:3 sm" { return true } return false } // P1 extensions // Reference: https://docs.ejabberd.im/developer/mobile/core-features/ // p1:push support type p1Push struct { XMLName xml.Name `xml:"p1:push push"` } // p1:rebind suppor type p1Rebind struct { XMLName xml.Name `xml:"p1:rebind rebind"` } // p1:ack support type p1Ack struct { XMLName xml.Name `xml:"p1:ack ack"` } // ============================================================================ // StreamError Packet type StreamError struct { XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` Error xml.Name `xml:",any"` Text string `xml:"urn:ietf:params:xml:ns:xmpp-streams text"` } func (StreamError) Name() string { return "stream:error" } type streamErrorDecoder struct{} var streamError streamErrorDecoder func (streamErrorDecoder) decode(p *xml.Decoder, se xml.StartElement) (StreamError, error) { var packet StreamError err := p.DecodeElement(&packet, &se) return packet, err } // ============================================================================ // StreamClose "Packet" // This is just a closing tag and hold no information type StreamClosePacket struct{} func (StreamClosePacket) Name() string { return "stream:stream" } type streamCloseDecoder struct{} var streamClose streamCloseDecoder func (streamCloseDecoder) decode(_ xml.EndElement) StreamClosePacket { return StreamClosePacket{} }