Refactor parsing / improve typing
This commit is contained in:
parent
01063ec284
commit
10219ec1e6
49
auth.go
49
auth.go
|
@ -32,37 +32,72 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
|
|||
fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc)
|
||||
|
||||
// Next message should be either success or failure.
|
||||
name, val, err := next(decoder)
|
||||
val, err := next(decoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case *saslSuccess:
|
||||
case *saslFailure:
|
||||
case *SASLSuccess:
|
||||
case *SASLFailure:
|
||||
// v.Any is type of sub-element in failure, which gives a description of what failed.
|
||||
return errors.New("auth failure: " + v.Any.Local)
|
||||
default:
|
||||
return errors.New("expected success or failure, got " + name.Local + " in " + name.Space)
|
||||
return errors.New("expected SASL success or failure, got " + v.Name())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// XMPP Packet Parsing
|
||||
type saslMechanisms struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
|
||||
Mechanism []string `xml:"mechanism"`
|
||||
}
|
||||
|
||||
type saslSuccess struct {
|
||||
// ============================================================================
|
||||
// SASLSuccess
|
||||
|
||||
type SASLSuccess struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
|
||||
}
|
||||
|
||||
type saslFailure struct {
|
||||
func (SASLSuccess) Name() string {
|
||||
return "sasl:success"
|
||||
}
|
||||
|
||||
type saslSuccessDecoder struct{}
|
||||
|
||||
var saslSuccess saslSuccessDecoder
|
||||
|
||||
func (saslSuccessDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLSuccess, error) {
|
||||
var packet SASLSuccess
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SASLFailure
|
||||
|
||||
type SASLFailure struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
|
||||
Any xml.Name // error reason is a subelement
|
||||
}
|
||||
|
||||
func (SASLFailure) Name() string {
|
||||
return "sasl:failure"
|
||||
}
|
||||
|
||||
type saslFailureDecoder struct{}
|
||||
|
||||
var saslFailure saslFailureDecoder
|
||||
|
||||
func (saslFailureDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLFailure, error) {
|
||||
var packet SASLFailure
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
type auth struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
|
||||
Mechanism string `xml:"mecanism,attr"`
|
||||
|
|
|
@ -108,7 +108,7 @@ func (c *Client) Connect() (*Session, error) {
|
|||
|
||||
func (c *Client) recv(receiver chan<- interface{}) (err error) {
|
||||
for {
|
||||
_, val, err := next(c.Session.decoder)
|
||||
val, err := next(c.Session.decoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
|||
return
|
||||
}
|
||||
|
||||
iq := &ClientIQ{}
|
||||
iq := &IQ{}
|
||||
// Decode element into pointer storage
|
||||
if err = decoder.DecodeElement(&iq, &se); err != nil {
|
||||
t.Errorf("cannot decode bind iq: %s", err)
|
||||
|
|
|
@ -11,11 +11,18 @@ func main() {
|
|||
component.Connect("localhost:8888")
|
||||
|
||||
for {
|
||||
_, packet, err := component.ReadPacket()
|
||||
packet, err := component.ReadPacket()
|
||||
if err != nil {
|
||||
fmt.Println("read error", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Packet received: ", packet)
|
||||
|
||||
switch p := packet.(type) {
|
||||
case xmpp.IQ:
|
||||
fmt.Println("IQ received: ", p)
|
||||
fmt.Println("IQ type:", p.Type)
|
||||
default:
|
||||
fmt.Println("Packet unhandled packet:", packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,9 @@ func main() {
|
|||
// Iterator to receive packets coming from our XMPP connection
|
||||
for packet := range client.Recv() {
|
||||
switch packet := packet.(type) {
|
||||
case *xmpp.ClientMessage:
|
||||
case *xmpp.Message:
|
||||
fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", packet.Body, packet.From)
|
||||
reply := xmpp.ClientMessage{PacketAttrs: xmpp.PacketAttrs{To: packet.From}, Body: packet.Body}
|
||||
reply := xmpp.Message{PacketAttrs: xmpp.PacketAttrs{To: packet.From}, Body: packet.Body}
|
||||
client.Send(reply.XMPPFormat())
|
||||
default:
|
||||
fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", packet)
|
||||
|
|
|
@ -40,11 +40,11 @@ func main() {
|
|||
for packet := range client.Recv() {
|
||||
|
||||
switch packet := packet.(type) {
|
||||
case *xmpp.ClientMessage:
|
||||
case *xmpp.Message:
|
||||
processMessage(client, p, packet)
|
||||
case *xmpp.ClientIQ:
|
||||
case *xmpp.IQ:
|
||||
processIq(client, p, packet)
|
||||
case *xmpp.ClientPresence:
|
||||
case *xmpp.Presence:
|
||||
// Do nothing with received presence
|
||||
default:
|
||||
fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", packet)
|
||||
|
@ -52,7 +52,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func processMessage(client *xmpp.Client, p *mpg123.Player, packet *xmpp.ClientMessage) {
|
||||
func processMessage(client *xmpp.Client, p *mpg123.Player, packet *xmpp.Message) {
|
||||
command := strings.Trim(packet.Body, " ")
|
||||
if command == "stop" {
|
||||
p.Stop()
|
||||
|
@ -62,7 +62,7 @@ func processMessage(client *xmpp.Client, p *mpg123.Player, packet *xmpp.ClientMe
|
|||
}
|
||||
}
|
||||
|
||||
func processIq(client *xmpp.Client, p *mpg123.Player, packet *xmpp.ClientIQ) {
|
||||
func processIq(client *xmpp.Client, p *mpg123.Player, packet *xmpp.IQ) {
|
||||
switch payload := packet.Payload.(type) {
|
||||
// We support IOT Control IQ
|
||||
case *iot.ControlSet:
|
||||
|
@ -76,7 +76,7 @@ func processIq(client *xmpp.Client, p *mpg123.Player, packet *xmpp.ClientIQ) {
|
|||
|
||||
playSCURL(p, url)
|
||||
setResponse := new(iot.ControlSetResponse)
|
||||
reply := xmpp.ClientIQ{PacketAttrs: xmpp.PacketAttrs{To: packet.From, Type: "result", Id: packet.Id}, Payload: setResponse}
|
||||
reply := xmpp.IQ{PacketAttrs: xmpp.PacketAttrs{To: packet.From, Type: "result", Id: packet.Id}, Payload: setResponse}
|
||||
client.Send(reply.XMPPFormat())
|
||||
// TODO add Soundclound artist / title retrieval
|
||||
sendUserTune(client, "Radiohead", "Spectre")
|
||||
|
|
22
component.go
22
component.go
|
@ -72,7 +72,7 @@ func (c *Component) Connect(connStr string) error {
|
|||
}
|
||||
|
||||
// 4. Check server response for authentication
|
||||
name, val, err := next(c.decoder)
|
||||
val, err := next(c.decoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -83,20 +83,34 @@ func (c *Component) Connect(connStr string) error {
|
|||
case *Handshake:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("unexpected packet, got " + name.Local + " in " + name.Space)
|
||||
return errors.New("unexpected packet, got " + v.Name())
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// ReadPacket reads next incoming XMPP packet
|
||||
// TODO use defined interface Packet
|
||||
func (c *Component) ReadPacket() (xml.Name, interface{}, error) {
|
||||
func (c *Component) ReadPacket() (Packet, error) {
|
||||
return next(c.decoder)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// XMPP packets struct
|
||||
// Handshake Packet
|
||||
|
||||
type Handshake struct {
|
||||
XMLName xml.Name `xml:"jabber:component:accept handshake"`
|
||||
}
|
||||
|
||||
func (Handshake) Name() string {
|
||||
return "component:handshake"
|
||||
}
|
||||
|
||||
type handshakeDecoder struct{}
|
||||
|
||||
var handshake handshakeDecoder
|
||||
|
||||
func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
|
||||
var packet Handshake
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
|
26
iq.go
26
iq.go
|
@ -7,9 +7,11 @@ import (
|
|||
"fluux.io/xmpp/iot"
|
||||
)
|
||||
|
||||
// info/query
|
||||
type ClientIQ struct {
|
||||
XMLName xml.Name `xml:"jabber:client iq"`
|
||||
// ============================================================================
|
||||
// IQ Packet
|
||||
|
||||
type IQ struct { // Info/Query
|
||||
XMLName xml.Name `xml:"iq"`
|
||||
PacketAttrs
|
||||
Payload IQPayload `xml:",omitempty"`
|
||||
RawXML string `xml:",innerxml"`
|
||||
|
@ -17,12 +19,26 @@ type ClientIQ struct {
|
|||
// Error clientError
|
||||
}
|
||||
|
||||
func (IQ) Name() string {
|
||||
return "iq"
|
||||
}
|
||||
|
||||
type iqDecoder struct{}
|
||||
|
||||
var iq iqDecoder
|
||||
|
||||
func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
|
||||
var packet IQ
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
type IQPayload interface {
|
||||
IsIQPayload()
|
||||
}
|
||||
|
||||
// UnmarshalXML implements custom parsing for IQs
|
||||
func (iq *ClientIQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
iq.XMLName = start.Name
|
||||
// Extract IQ attributes
|
||||
for _, attr := range start.Attr {
|
||||
|
@ -80,7 +96,7 @@ func (iq *ClientIQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||
|
||||
// XMPPFormat returns the string representation of the XMPP packet.
|
||||
// TODO: Should I simply rely on xml.Marshal ?
|
||||
func (iq *ClientIQ) XMPPFormat() string {
|
||||
func (iq *IQ) XMPPFormat() string {
|
||||
if iq.Payload != nil {
|
||||
var payload []byte
|
||||
var err error
|
||||
|
|
|
@ -10,14 +10,14 @@ func TestUnmarshalIqs(t *testing.T) {
|
|||
//var cs1 = new(iot.ControlSet)
|
||||
var tests = []struct {
|
||||
iqString string
|
||||
parsedIQ ClientIQ
|
||||
parsedIQ IQ
|
||||
}{
|
||||
{"<iq id=\"1\" type=\"set\" to=\"test@localhost\"/>", ClientIQ{XMLName: xml.Name{Space: "", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", Type: "set", Id: "1"}}},
|
||||
//{"<iq xmlns=\"jabber:client\" id=\"2\" type=\"set\" to=\"test@localhost\" from=\"server\"><set xmlns=\"urn:xmpp:iot:control\"/></iq>", ClientIQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}},
|
||||
{"<iq id=\"1\" type=\"set\" to=\"test@localhost\"/>", IQ{XMLName: xml.Name{Space: "", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", Type: "set", Id: "1"}}},
|
||||
//{"<iq xmlns=\"jabber:client\" id=\"2\" type=\"set\" to=\"test@localhost\" from=\"server\"><set xmlns=\"urn:xmpp:iot:control\"/></iq>", IQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var parsedIQ = new(ClientIQ)
|
||||
var parsedIQ = new(IQ)
|
||||
err := xml.Unmarshal([]byte(test.iqString), parsedIQ)
|
||||
if err != nil {
|
||||
t.Errorf("Unmarshal(%s) returned error", test.iqString)
|
||||
|
|
30
message.go
30
message.go
|
@ -5,20 +5,36 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// XMPP Packet Parsing
|
||||
type ClientMessage struct {
|
||||
XMLName xml.Name `xml:"jabber:client message"`
|
||||
// ============================================================================
|
||||
// Message Packet
|
||||
|
||||
type Message struct {
|
||||
XMLName xml.Name `xml:"message"`
|
||||
PacketAttrs
|
||||
Subject string `xml:"subject,omitempty"`
|
||||
Body string `xml:"body,omitempty"`
|
||||
Thread string `xml:"thread,omitempty"`
|
||||
}
|
||||
|
||||
// TODO: Func new message to create an empty message structure without the XML tag matching elements
|
||||
func (Message) Name() string {
|
||||
return "message"
|
||||
}
|
||||
|
||||
func (message *ClientMessage) XMPPFormat() string {
|
||||
type messageDecoder struct{}
|
||||
|
||||
var message messageDecoder
|
||||
|
||||
func (messageDecoder) decode(p *xml.Decoder, se xml.StartElement) (Message, error) {
|
||||
var packet Message
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
func (msg *Message) XMPPFormat() string {
|
||||
return fmt.Sprintf("<message to='%s' type='chat' xml:lang='en'>"+
|
||||
"<body>%s</body></message>",
|
||||
message.To,
|
||||
xmlEscape(message.Body))
|
||||
msg.To,
|
||||
xmlEscape(msg.Body))
|
||||
}
|
||||
|
||||
// TODO: Func new message to create an empty message structure without the XML tag matching elements
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package xmpp // import "fluux.io/xmpp"
|
||||
|
||||
type Packet interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
// PacketAttrs represents the common structure for base XMPP packets.
|
||||
type PacketAttrs struct {
|
||||
Id string `xml:"id,attr,omitempty"`
|
||||
|
|
56
parser.go
56
parser.go
|
@ -63,87 +63,71 @@ func nextStart(p *xml.Decoder) (xml.StartElement, error) {
|
|||
// next scans XML token stream for next element and then assign a structure to decode
|
||||
// that elements.
|
||||
// TODO Use an interface to return packets interface xmppDecoder
|
||||
func next(p *xml.Decoder) (xml.Name, interface{}, error) {
|
||||
// Read start element to find out what type we want.
|
||||
func next(p *xml.Decoder) (Packet, error) {
|
||||
// Read start element to find out how we want to parse the XMPP packet
|
||||
se, err := nextStart(p)
|
||||
if err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Put it in an interface and allocate the right structure
|
||||
var nv interface{}
|
||||
// TODO: general case = Parse IQ / presence / message => split SASL Stream and component cases
|
||||
switch se.Name.Space {
|
||||
case NSStream:
|
||||
if nv, err = decodeStream(se); err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
}
|
||||
return decodeStream(p, se)
|
||||
case nsSASL:
|
||||
if nv, err = decodeSASL(se); err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
}
|
||||
return decodeSASL(p, se)
|
||||
case NSClient:
|
||||
if nv, err = decodeClient(se); err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
}
|
||||
return decodeClient(p, se)
|
||||
case NSComponent:
|
||||
if nv, err = decodeComponent(se); err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
}
|
||||
return decodeComponent(p, se)
|
||||
default:
|
||||
return xml.Name{}, nil, errors.New("unknown namespace " +
|
||||
return nil, errors.New("unknown namespace " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
}
|
||||
|
||||
// Decode element into pointer storage
|
||||
if err = p.DecodeElement(nv, &se); err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
}
|
||||
return se.Name, nv, err
|
||||
}
|
||||
|
||||
func decodeStream(se xml.StartElement) (interface{}, error) {
|
||||
func decodeStream(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||
switch se.Name.Local {
|
||||
case "error":
|
||||
return &StreamError{}, nil
|
||||
return streamError.decode(p, se)
|
||||
default:
|
||||
return nil, errors.New("unexpected XMPP packet " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
}
|
||||
}
|
||||
|
||||
func decodeSASL(se xml.StartElement) (interface{}, error) {
|
||||
func decodeSASL(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||
switch se.Name.Local {
|
||||
case "success":
|
||||
return &saslSuccess{}, nil
|
||||
return saslSuccess.decode(p, se)
|
||||
case "failure":
|
||||
return &saslFailure{}, nil
|
||||
return saslFailure.decode(p, se)
|
||||
default:
|
||||
return nil, errors.New("unexpected XMPP packet " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
}
|
||||
}
|
||||
|
||||
func decodeClient(se xml.StartElement) (interface{}, error) {
|
||||
func decodeClient(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||
switch se.Name.Local {
|
||||
case "message":
|
||||
return &ClientMessage{}, nil
|
||||
return message.decode(p, se)
|
||||
case "presence":
|
||||
return &ClientPresence{}, nil
|
||||
return presence.decode(p, se)
|
||||
case "iq":
|
||||
return &ClientIQ{}, nil
|
||||
return iq.decode(p, se)
|
||||
default:
|
||||
return nil, errors.New("unexpected XMPP packet " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
}
|
||||
}
|
||||
|
||||
func decodeComponent(se xml.StartElement) (interface{}, error) {
|
||||
func decodeComponent(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||
switch se.Name.Local {
|
||||
case "handshake":
|
||||
return &Handshake{}, nil
|
||||
return handshake.decode(p, se)
|
||||
case "iq":
|
||||
return &ClientIQ{}, nil
|
||||
return iq.decode(p, se)
|
||||
default:
|
||||
return nil, errors.New("unexpected XMPP packet " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
|
|
22
presence.go
22
presence.go
|
@ -2,12 +2,28 @@ package xmpp // import "fluux.io/xmpp"
|
|||
|
||||
import "encoding/xml"
|
||||
|
||||
// XMPP Packet Parsing
|
||||
type ClientPresence struct {
|
||||
XMLName xml.Name `xml:"jabber:client presence"`
|
||||
// ============================================================================
|
||||
// Presence Packet
|
||||
|
||||
type Presence struct {
|
||||
XMLName xml.Name `xml:"presence"`
|
||||
PacketAttrs
|
||||
Show string `xml:"show,attr,omitempty"` // away, chat, dnd, xa
|
||||
Status string `xml:"status,attr,omitempty"`
|
||||
Priority string `xml:"priority,attr,omitempty"`
|
||||
//Error *clientError
|
||||
}
|
||||
|
||||
func (Presence) Name() string {
|
||||
return "presence"
|
||||
}
|
||||
|
||||
type presenceDecoder struct{}
|
||||
|
||||
var presence presenceDecoder
|
||||
|
||||
func (presenceDecoder) decode(p *xml.Decoder, se xml.StartElement) (Presence, error) {
|
||||
var packet Presence
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ func (s *Session) bind(o Options) {
|
|||
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'/></iq>", s.PacketId(), nsBind)
|
||||
}
|
||||
|
||||
var iq ClientIQ
|
||||
var iq IQ
|
||||
if s.err = s.decoder.Decode(&iq); s.err != nil {
|
||||
s.err = errors.New("error decoding iq bind result: " + s.err.Error())
|
||||
return
|
||||
|
@ -180,7 +180,7 @@ func (s *Session) rfc3921Session(o Options) {
|
|||
return
|
||||
}
|
||||
|
||||
var iq ClientIQ
|
||||
var iq IQ
|
||||
if s.Features.Session.optional.Local != "" {
|
||||
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), nsSession)
|
||||
if s.err = s.decoder.Decode(&iq); s.err != nil {
|
||||
|
|
28
stream.go
28
stream.go
|
@ -1,8 +1,12 @@
|
|||
package xmpp // import "fluux.io/xmpp"
|
||||
|
||||
import "encoding/xml"
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// StreamFeatures Packet
|
||||
|
||||
// XMPP PacketAttrs Parsing
|
||||
type streamFeatures struct {
|
||||
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
|
||||
StartTLS tlsStartTLS
|
||||
|
@ -13,11 +17,31 @@ type streamFeatures struct {
|
|||
Any []xml.Name `xml:",any"`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// StreamError Packet
|
||||
|
||||
type StreamError struct {
|
||||
XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
|
||||
Error xml.Name `xml:",any"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Caps subElement
|
||||
|
||||
type Caps struct {
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"`
|
||||
Hash string `xml:"hash,attr"`
|
||||
|
|
Loading…
Reference in a new issue