~samwhited/xmpp

dd210cea1005ede37ba04035859cb11ccd804a18 — Sam Whited 3 years ago 51cf2a8 codec
codec, xmpp: add and use codec implementation
M bind.go => bind.go +3 -2
@@ 10,6 10,7 @@ import (
	"fmt"
	"io"

	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/internal"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/jid"


@@ 27,7 28,7 @@ func BindResource() StreamFeature {
		Name:       xml.Name{Space: ns.Bind, Local: "bind"},
		Necessary:  Authn,
		Prohibited: Ready,
		List: func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
		List: func(ctx context.Context, e codec.Encoder, start xml.StartElement) (req bool, err error) {
			req = true
			if err = e.EncodeToken(start); err != nil {
				return req, err


@@ 39,7 40,7 @@ func BindResource() StreamFeature {
			err = e.Flush()
			return req, err
		},
		Parse: func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) {
		Parse: func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (bool, interface{}, error) {
			parsed := struct {
				XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
			}{}

A codec/codec.go => codec/codec.go +34 -0
@@ 0,0 1,34 @@
// Copyright 2017 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package codec

import (
	"encoding/xml"
)

// A Codec is both a Decoder and an Encoder and can handle both sides of an XMPP
// stream.
type Codec interface {
	Decoder
	Encoder
}

// A Decoder is anything that can be used to decode an XML token stream
// (including an *xml.Decoder).
type Decoder interface {
	DecodeElement(v interface{}, start *xml.StartElement) error
	Decode(v interface{}) error
	Skip() error
	Token() (xml.Token, error)
}

// An Encoder is anything that can be used to encode an XML token stream
// (including an *xml.Encoder)
type Encoder interface {
	EncodeElement(v interface{}, start xml.StartElement) error
	EncodeToken(t xml.Token) error
	Encode(v interface{}) error
	Flush() error
}

A codec/doc.go => codec/doc.go +10 -0
@@ 0,0 1,10 @@
// Copyright 2017 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

// Package codec provides functionality for serializing and deserializing an
// XMPP stream from its native XML encoding and for creating new serialization
// formats.
//
// Be advised: This API is still unstable and is subject to change.
package codec // import "mellium.im/xmpp/codec"

M compress/compression.go => compress/compression.go +3 -2
@@ 14,6 14,7 @@ import (
	"io"

	"mellium.im/xmpp"
	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/stream"
)



@@ 37,7 38,7 @@ func New(methods ...Method) xmpp.StreamFeature {
	return xmpp.StreamFeature{
		Name:      xml.Name{Local: "compression", Space: NSFeatures},
		Necessary: xmpp.Secure | xmpp.Authn,
		List: func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
		List: func(ctx context.Context, e codec.Encoder, start xml.StartElement) (req bool, err error) {
			if err = e.EncodeToken(start); err != nil {
				return
			}


@@ 67,7 68,7 @@ func New(methods ...Method) xmpp.StreamFeature {
			}
			return false, e.Flush()
		},
		Parse: func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) {
		Parse: func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (bool, interface{}, error) {
			listed := struct {
				XMLName xml.Name `xml:"http://jabber.org/features/compress compression"`
				Methods []string `xml:"http://jabber.org/features/compress method"`

M features.go => features.go +3 -2
@@ 9,6 9,7 @@ import (
	"encoding/xml"
	"io"

	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/stream"
)


@@ 39,14 40,14 @@ type StreamFeature struct {
	// used as the outermost tag in the stream (but also may be ignored). List
	// implementations that call e.EncodeToken directly need to call e.Flush when
	// finished to ensure that the XML is written to the underlying writer.
	List func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error)
	List func(ctx context.Context, e codec.Encoder, start xml.StartElement) (req bool, err error)

	// Used to parse the feature that begins with the given xml start element
	// (which should have a Name that matches this stream feature's Name).
	// Returns whether or not the feature is required, and any data that will be
	// needed if the feature is selected for negotiation (eg. the list of
	// mechanisms if the feature was SASL).
	Parse func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (req bool, data interface{}, err error)
	Parse func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (req bool, data interface{}, err error)

	// A function that will take over the session temporarily while negotiating
	// the feature. The "mask" SessionState represents the state bits that should

M ibr2/challenge.go => ibr2/challenge.go +5 -3
@@ 7,6 7,8 @@ package ibr2
import (
	"context"
	"encoding/xml"

	"mellium.im/xmpp/codec"
)

// Challenge is an IBR challenge.


@@ 16,12 18,12 @@ type Challenge struct {
	Type string

	// Send is used by the server to send the challenge to the client.
	Send func(ctx context.Context, e *xml.Encoder) error
	Send func(ctx context.Context, e codec.Encoder) error

	// Respond is used by the client to send a response or reply to the challenge.
	Respond func(context.Context, *xml.Encoder) error
	Respond func(context.Context, codec.Encoder) error

	// Receive is used by the client to receive and decode the server's challenge
	// and by the server to receive and decode the clients response.
	Receive func(ctx context.Context, server bool, d *xml.Decoder, start *xml.StartElement) error
	Receive func(ctx context.Context, server bool, d codec.Decoder, start *xml.StartElement) error
}

M ibr2/ibr2.go => ibr2/ibr2.go +6 -5
@@ 16,6 16,7 @@ import (
	"io"

	"mellium.im/xmpp"
	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/stream"
)



@@ 43,8 44,8 @@ func challengeStart(typ string) xml.StartElement {
	}
}

func listFunc(challenges ...Challenge) func(context.Context, *xml.Encoder, xml.StartElement) (bool, error) {
	return func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
func listFunc(challenges ...Challenge) func(context.Context, codec.Encoder, xml.StartElement) (bool, error) {
	return func(ctx context.Context, e codec.Encoder, start xml.StartElement) (req bool, err error) {
		if err = e.EncodeToken(start); err != nil {
			return
		}


@@ 77,8 78,8 @@ func listFunc(challenges ...Challenge) func(context.Context, *xml.Encoder, xml.S
	}
}

func parseFunc(challenges ...Challenge) func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (req bool, supported interface{}, err error) {
	return func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) {
func parseFunc(challenges ...Challenge) func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (req bool, supported interface{}, err error) {
	return func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (bool, interface{}, error) {
		// Parse the list of challenge types sent down by the server.
		parsed := struct {
			Challenges []string `xml:"urn:xmpp:register:0 challenge"`


@@ 105,7 106,7 @@ func parseFunc(challenges ...Challenge) func(ctx context.Context, d *xml.Decoder
	}
}

func decodeClientResp(ctx context.Context, d *xml.Decoder, decode func(ctx context.Context, server bool, d *xml.Decoder, start *xml.StartElement) error) (cancel bool, err error) {
func decodeClientResp(ctx context.Context, d codec.Decoder, decode func(ctx context.Context, server bool, d codec.Decoder, start *xml.StartElement) error) (cancel bool, err error) {
	var tok xml.Token
	tok, err = d.Token()
	if err != nil {

M ibr2/oob.go => ibr2/oob.go +3 -2
@@ 8,6 8,7 @@ import (
	"context"
	"encoding/xml"

	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/oob"
)



@@ 19,10 20,10 @@ import (
func OOB(data *oob.Data, f func(*oob.Data) error) Challenge {
	return Challenge{
		Type: oob.NS,
		Send: func(ctx context.Context, e *xml.Encoder) error {
		Send: func(ctx context.Context, e codec.Encoder) error {
			return e.Encode(data)
		},
		Receive: func(ctx context.Context, server bool, d *xml.Decoder, start *xml.StartElement) error {
		Receive: func(ctx context.Context, server bool, d codec.Decoder, start *xml.StartElement) error {
			// The server does not receive a reply for this mechanism.
			if server {
				return nil

M sasl.go => sasl.go +4 -3
@@ 13,6 13,7 @@ import (
	"io"

	"mellium.im/sasl"
	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/internal/saslerr"
	"mellium.im/xmpp/stream"


@@ 32,7 33,7 @@ func SASL(mechanisms ...sasl.Mechanism) StreamFeature {
		Name:       xml.Name{Space: ns.SASL, Local: "mechanisms"},
		Necessary:  Secure,
		Prohibited: Authn,
		List: func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
		List: func(ctx context.Context, e codec.Encoder, start xml.StartElement) (req bool, err error) {
			req = true
			if err = e.EncodeToken(start); err != nil {
				return


@@ 58,7 59,7 @@ func SASL(mechanisms ...sasl.Mechanism) StreamFeature {
			}
			return req, e.EncodeToken(start.End())
		},
		Parse: func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) {
		Parse: func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (bool, interface{}, error) {
			parsed := struct {
				XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
				List    []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanism"`


@@ 181,7 182,7 @@ func SASL(mechanisms ...sasl.Mechanism) StreamFeature {
	}
}

func decodeSASLChallenge(d *xml.Decoder, start xml.StartElement, allowChallenge bool) (challenge []byte, success bool, err error) {
func decodeSASLChallenge(d codec.Decoder, start xml.StartElement, allowChallenge bool) (challenge []byte, success bool, err error) {
	switch start.Name {
	case xml.Name{Space: ns.SASL, Local: "challenge"}, xml.Name{Space: ns.SASL, Local: "success"}:
		if !allowChallenge && start.Name.Local == "challenge" {

M sasl2/sasl.go => sasl2/sasl.go +4 -3
@@ 20,6 20,7 @@ import (

	"mellium.im/sasl"
	"mellium.im/xmpp"
	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/internal/saslerr"
	"mellium.im/xmpp/stream"
)


@@ 48,7 49,7 @@ func SASL(mechanisms ...sasl.Mechanism) xmpp.StreamFeature {
		Name:       xml.Name{Space: NS, Local: "mechanisms"},
		Necessary:  xmpp.Secure,
		Prohibited: xmpp.Authn,
		List: func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
		List: func(ctx context.Context, e codec.Encoder, start xml.StartElement) (req bool, err error) {
			req = true
			if err = e.EncodeToken(start); err != nil {
				return


@@ 74,7 75,7 @@ func SASL(mechanisms ...sasl.Mechanism) xmpp.StreamFeature {
			}
			return req, e.EncodeToken(start.End())
		},
		Parse: func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) {
		Parse: func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (bool, interface{}, error) {
			parsed := struct {
				XMLName xml.Name `xml:"urn:xmpp:sasl:0 mechanisms"`
				List    []string `xml:"urn:xmpp:sasl:0 mechanism"`


@@ 201,7 202,7 @@ func SASL(mechanisms ...sasl.Mechanism) xmpp.StreamFeature {
	}
}

func decodeSASLChallenge(d *xml.Decoder, start xml.StartElement, allowChallenge bool) (challenge []byte, success bool, err error) {
func decodeSASLChallenge(d codec.Decoder, start xml.StartElement, allowChallenge bool) (challenge []byte, success bool, err error) {
	switch start.Name {
	case xml.Name{Space: NS, Local: "challenge"}:
		if !allowChallenge {

M session.go => session.go +6 -5
@@ 12,6 12,7 @@ import (
	"net"
	"sync"

	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stream"
)


@@ 75,7 76,7 @@ type Session struct {
	in struct {
		sync.Mutex
		streamInfo
		d      *xml.Decoder
		d      codec.Decoder
		ctx    context.Context
		cancel context.CancelFunc
	}


@@ 135,13 136,13 @@ func (s *Session) Conn() io.ReadWriter {
	return s.rw
}

// Decoder returns the XML decoder that was used to negotiate the latest stream.
func (s *Session) Decoder() *xml.Decoder {
// Decoder returns the decoder that was used to negotiate the latest stream.
func (s *Session) Decoder() codec.Decoder {
	return s.in.d
}

// Encoder returns the XML encoder that was used to negotiate the latest stream.
func (s *Session) Encoder() *xml.Encoder {
// Encoder returns the encoder that was used to negotiate the latest stream.
func (s *Session) Encoder() codec.Encoder {
	return s.out.e
}


M starttls.go => starttls.go +3 -2
@@ 13,6 13,7 @@ import (
	"io"
	"net"

	"mellium.im/xmpp/codec"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/stream"
)


@@ 31,7 32,7 @@ func StartTLS(required bool) StreamFeature {
	return StreamFeature{
		Name:       xml.Name{Local: "starttls", Space: ns.StartTLS},
		Prohibited: Secure,
		List: func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
		List: func(ctx context.Context, e codec.Encoder, start xml.StartElement) (req bool, err error) {
			if err = e.EncodeToken(start); err != nil {
				return required, err
			}


@@ 46,7 47,7 @@ func StartTLS(required bool) StreamFeature {
			}
			return required, e.EncodeToken(start.End())
		},
		Parse: func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) {
		Parse: func(ctx context.Context, d codec.Decoder, start *xml.StartElement) (bool, interface{}, error) {
			parsed := struct {
				XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
				Required struct {