Refactor parsing / improve typing

This commit is contained in:
Mickael Remond 2018-01-13 18:50:17 +01:00
parent 01063ec284
commit 10219ec1e6
No known key found for this signature in database
GPG key ID: E6F6045D79965AA3
15 changed files with 198 additions and 82 deletions

49
auth.go
View file

@ -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) fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc)
// Next message should be either success or failure. // Next message should be either success or failure.
name, val, err := next(decoder) val, err := next(decoder)
if err != nil { if err != nil {
return err return err
} }
switch v := val.(type) { switch v := val.(type) {
case *saslSuccess: case *SASLSuccess:
case *saslFailure: case *SASLFailure:
// v.Any is type of sub-element in failure, which gives a description of what failed. // v.Any is type of sub-element in failure, which gives a description of what failed.
return errors.New("auth failure: " + v.Any.Local) return errors.New("auth failure: " + v.Any.Local)
default: 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 return err
} }
// XMPP Packet Parsing
type saslMechanisms struct { type saslMechanisms struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
Mechanism []string `xml:"mechanism"` Mechanism []string `xml:"mechanism"`
} }
type saslSuccess struct { // ============================================================================
// SASLSuccess
type SASLSuccess struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"` 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"` XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
Any xml.Name // error reason is a subelement 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 { type auth struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"` XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
Mechanism string `xml:"mecanism,attr"` Mechanism string `xml:"mecanism,attr"`

View file

@ -108,7 +108,7 @@ func (c *Client) Connect() (*Session, error) {
func (c *Client) recv(receiver chan<- interface{}) (err error) { func (c *Client) recv(receiver chan<- interface{}) (err error) {
for { for {
_, val, err := next(c.Session.decoder) val, err := next(c.Session.decoder)
if err != nil { if err != nil {
return err return err
} }

View file

@ -163,7 +163,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
return return
} }
iq := &ClientIQ{} iq := &IQ{}
// Decode element into pointer storage // Decode element into pointer storage
if err = decoder.DecodeElement(&iq, &se); err != nil { if err = decoder.DecodeElement(&iq, &se); err != nil {
t.Errorf("cannot decode bind iq: %s", err) t.Errorf("cannot decode bind iq: %s", err)

View file

@ -11,11 +11,18 @@ func main() {
component.Connect("localhost:8888") component.Connect("localhost:8888")
for { for {
_, packet, err := component.ReadPacket() packet, err := component.ReadPacket()
if err != nil { if err != nil {
fmt.Println("read error", err) fmt.Println("read error", err)
return 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)
}
} }
} }

View file

@ -31,9 +31,9 @@ func main() {
// Iterator to receive packets coming from our XMPP connection // Iterator to receive packets coming from our XMPP connection
for packet := range client.Recv() { for packet := range client.Recv() {
switch packet := packet.(type) { switch packet := packet.(type) {
case *xmpp.ClientMessage: case *xmpp.Message:
fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", packet.Body, packet.From) 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()) client.Send(reply.XMPPFormat())
default: default:
fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", packet) fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", packet)

View file

@ -40,11 +40,11 @@ func main() {
for packet := range client.Recv() { for packet := range client.Recv() {
switch packet := packet.(type) { switch packet := packet.(type) {
case *xmpp.ClientMessage: case *xmpp.Message:
processMessage(client, p, packet) processMessage(client, p, packet)
case *xmpp.ClientIQ: case *xmpp.IQ:
processIq(client, p, packet) processIq(client, p, packet)
case *xmpp.ClientPresence: case *xmpp.Presence:
// Do nothing with received presence // Do nothing with received presence
default: default:
fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", packet) 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, " ") command := strings.Trim(packet.Body, " ")
if command == "stop" { if command == "stop" {
p.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) { switch payload := packet.Payload.(type) {
// We support IOT Control IQ // We support IOT Control IQ
case *iot.ControlSet: case *iot.ControlSet:
@ -76,7 +76,7 @@ func processIq(client *xmpp.Client, p *mpg123.Player, packet *xmpp.ClientIQ) {
playSCURL(p, url) playSCURL(p, url)
setResponse := new(iot.ControlSetResponse) 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()) client.Send(reply.XMPPFormat())
// TODO add Soundclound artist / title retrieval // TODO add Soundclound artist / title retrieval
sendUserTune(client, "Radiohead", "Spectre") sendUserTune(client, "Radiohead", "Spectre")

View file

@ -72,7 +72,7 @@ func (c *Component) Connect(connStr string) error {
} }
// 4. Check server response for authentication // 4. Check server response for authentication
name, val, err := next(c.decoder) val, err := next(c.decoder)
if err != nil { if err != nil {
return err return err
} }
@ -83,20 +83,34 @@ func (c *Component) Connect(connStr string) error {
case *Handshake: case *Handshake:
return nil return nil
default: default:
return errors.New("unexpected packet, got " + name.Local + " in " + name.Space) return errors.New("unexpected packet, got " + v.Name())
} }
panic("unreachable") panic("unreachable")
} }
// ReadPacket reads next incoming XMPP packet // ReadPacket reads next incoming XMPP packet
// TODO use defined interface Packet // TODO use defined interface Packet
func (c *Component) ReadPacket() (xml.Name, interface{}, error) { func (c *Component) ReadPacket() (Packet, error) {
return next(c.decoder) return next(c.decoder)
} }
// ============================================================================ // ============================================================================
// XMPP packets struct // Handshake Packet
type Handshake struct { type Handshake struct {
XMLName xml.Name `xml:"jabber:component:accept handshake"` 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
View file

@ -7,9 +7,11 @@ import (
"fluux.io/xmpp/iot" "fluux.io/xmpp/iot"
) )
// info/query // ============================================================================
type ClientIQ struct { // IQ Packet
XMLName xml.Name `xml:"jabber:client iq"`
type IQ struct { // Info/Query
XMLName xml.Name `xml:"iq"`
PacketAttrs PacketAttrs
Payload IQPayload `xml:",omitempty"` Payload IQPayload `xml:",omitempty"`
RawXML string `xml:",innerxml"` RawXML string `xml:",innerxml"`
@ -17,12 +19,26 @@ type ClientIQ struct {
// Error clientError // 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 { type IQPayload interface {
IsIQPayload() IsIQPayload()
} }
// UnmarshalXML implements custom parsing for IQs // 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 iq.XMLName = start.Name
// Extract IQ attributes // Extract IQ attributes
for _, attr := range start.Attr { 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. // XMPPFormat returns the string representation of the XMPP packet.
// TODO: Should I simply rely on xml.Marshal ? // TODO: Should I simply rely on xml.Marshal ?
func (iq *ClientIQ) XMPPFormat() string { func (iq *IQ) XMPPFormat() string {
if iq.Payload != nil { if iq.Payload != nil {
var payload []byte var payload []byte
var err error var err error

View file

@ -10,14 +10,14 @@ func TestUnmarshalIqs(t *testing.T) {
//var cs1 = new(iot.ControlSet) //var cs1 = new(iot.ControlSet)
var tests = []struct { var tests = []struct {
iqString string 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 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>", ClientIQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}}, //{"<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 { for _, test := range tests {
var parsedIQ = new(ClientIQ) var parsedIQ = new(IQ)
err := xml.Unmarshal([]byte(test.iqString), parsedIQ) err := xml.Unmarshal([]byte(test.iqString), parsedIQ)
if err != nil { if err != nil {
t.Errorf("Unmarshal(%s) returned error", test.iqString) t.Errorf("Unmarshal(%s) returned error", test.iqString)

View file

@ -5,20 +5,36 @@ import (
"fmt" "fmt"
) )
// XMPP Packet Parsing // ============================================================================
type ClientMessage struct { // Message Packet
XMLName xml.Name `xml:"jabber:client message"`
type Message struct {
XMLName xml.Name `xml:"message"`
PacketAttrs PacketAttrs
Subject string `xml:"subject,omitempty"` Subject string `xml:"subject,omitempty"`
Body string `xml:"body,omitempty"` Body string `xml:"body,omitempty"`
Thread string `xml:"thread,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'>"+ return fmt.Sprintf("<message to='%s' type='chat' xml:lang='en'>"+
"<body>%s</body></message>", "<body>%s</body></message>",
message.To, msg.To,
xmlEscape(message.Body)) xmlEscape(msg.Body))
} }
// TODO: Func new message to create an empty message structure without the XML tag matching elements

View file

@ -1,5 +1,9 @@
package xmpp // import "fluux.io/xmpp" package xmpp // import "fluux.io/xmpp"
type Packet interface {
Name() string
}
// PacketAttrs represents the common structure for base XMPP packets. // PacketAttrs represents the common structure for base XMPP packets.
type PacketAttrs struct { type PacketAttrs struct {
Id string `xml:"id,attr,omitempty"` Id string `xml:"id,attr,omitempty"`

View file

@ -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 // next scans XML token stream for next element and then assign a structure to decode
// that elements. // that elements.
// TODO Use an interface to return packets interface xmppDecoder // TODO Use an interface to return packets interface xmppDecoder
func next(p *xml.Decoder) (xml.Name, interface{}, error) { func next(p *xml.Decoder) (Packet, error) {
// Read start element to find out what type we want. // Read start element to find out how we want to parse the XMPP packet
se, err := nextStart(p) se, err := nextStart(p)
if err != nil { 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 // TODO: general case = Parse IQ / presence / message => split SASL Stream and component cases
switch se.Name.Space { switch se.Name.Space {
case NSStream: case NSStream:
if nv, err = decodeStream(se); err != nil { return decodeStream(p, se)
return xml.Name{}, nil, err
}
case nsSASL: case nsSASL:
if nv, err = decodeSASL(se); err != nil { return decodeSASL(p, se)
return xml.Name{}, nil, err
}
case NSClient: case NSClient:
if nv, err = decodeClient(se); err != nil { return decodeClient(p, se)
return xml.Name{}, nil, err
}
case NSComponent: case NSComponent:
if nv, err = decodeComponent(se); err != nil { return decodeComponent(p, se)
return xml.Name{}, nil, err
}
default: default:
return xml.Name{}, nil, errors.New("unknown namespace " + return nil, errors.New("unknown namespace " +
se.Name.Space + " <" + se.Name.Local + "/>") 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 { switch se.Name.Local {
case "error": case "error":
return &StreamError{}, nil return streamError.decode(p, se)
default: default:
return nil, errors.New("unexpected XMPP packet " + return nil, errors.New("unexpected XMPP packet " +
se.Name.Space + " <" + se.Name.Local + "/>") 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 { switch se.Name.Local {
case "success": case "success":
return &saslSuccess{}, nil return saslSuccess.decode(p, se)
case "failure": case "failure":
return &saslFailure{}, nil return saslFailure.decode(p, se)
default: default:
return nil, errors.New("unexpected XMPP packet " + return nil, errors.New("unexpected XMPP packet " +
se.Name.Space + " <" + se.Name.Local + "/>") 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 { switch se.Name.Local {
case "message": case "message":
return &ClientMessage{}, nil return message.decode(p, se)
case "presence": case "presence":
return &ClientPresence{}, nil return presence.decode(p, se)
case "iq": case "iq":
return &ClientIQ{}, nil return iq.decode(p, se)
default: default:
return nil, errors.New("unexpected XMPP packet " + return nil, errors.New("unexpected XMPP packet " +
se.Name.Space + " <" + se.Name.Local + "/>") 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 { switch se.Name.Local {
case "handshake": case "handshake":
return &Handshake{}, nil return handshake.decode(p, se)
case "iq": case "iq":
return &ClientIQ{}, nil return iq.decode(p, se)
default: default:
return nil, errors.New("unexpected XMPP packet " + return nil, errors.New("unexpected XMPP packet " +
se.Name.Space + " <" + se.Name.Local + "/>") se.Name.Space + " <" + se.Name.Local + "/>")

View file

@ -2,12 +2,28 @@ package xmpp // import "fluux.io/xmpp"
import "encoding/xml" import "encoding/xml"
// XMPP Packet Parsing // ============================================================================
type ClientPresence struct { // Presence Packet
XMLName xml.Name `xml:"jabber:client presence"`
type Presence struct {
XMLName xml.Name `xml:"presence"`
PacketAttrs PacketAttrs
Show string `xml:"show,attr,omitempty"` // away, chat, dnd, xa Show string `xml:"show,attr,omitempty"` // away, chat, dnd, xa
Status string `xml:"status,attr,omitempty"` Status string `xml:"status,attr,omitempty"`
Priority string `xml:"priority,attr,omitempty"` Priority string `xml:"priority,attr,omitempty"`
//Error *clientError //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
}

View file

@ -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) 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 { if s.err = s.decoder.Decode(&iq); s.err != nil {
s.err = errors.New("error decoding iq bind result: " + s.err.Error()) s.err = errors.New("error decoding iq bind result: " + s.err.Error())
return return
@ -180,7 +180,7 @@ func (s *Session) rfc3921Session(o Options) {
return return
} }
var iq ClientIQ var iq IQ
if s.Features.Session.optional.Local != "" { if s.Features.Session.optional.Local != "" {
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), nsSession) 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 { if s.err = s.decoder.Decode(&iq); s.err != nil {

View file

@ -1,8 +1,12 @@
package xmpp // import "fluux.io/xmpp" package xmpp // import "fluux.io/xmpp"
import "encoding/xml" import (
"encoding/xml"
)
// ============================================================================
// StreamFeatures Packet
// XMPP PacketAttrs Parsing
type streamFeatures struct { type streamFeatures struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"` XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
StartTLS tlsStartTLS StartTLS tlsStartTLS
@ -13,11 +17,31 @@ type streamFeatures struct {
Any []xml.Name `xml:",any"` Any []xml.Name `xml:",any"`
} }
// ============================================================================
// StreamError Packet
type StreamError struct { type StreamError struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
Error xml.Name `xml:",any"` 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 { type Caps struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"` XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"`
Hash string `xml:"hash,attr"` Hash string `xml:"hash,attr"`