~samwhited/xmpp

27b4d807f614dcb62c72b2aaa906ea1534daac1b — Sam Whited 4 years ago c71a175
ibr2: Add initial negotiation implementation
3 files changed, 145 insertions(+), 5 deletions(-)

M ibr2/challenge.go
M ibr2/ibr2.go
M ibr2/oob.go
M ibr2/challenge.go => ibr2/challenge.go +15 -1
@@ 4,10 4,24 @@

package ibr2

import (
	"context"
	"encoding/xml"
)

// Challenge is an IBR challenge.
// API WARNING: The challenge struct is not complete or usable yet.
type Challenge struct {
	// Type is the type of the challenge as it appears in the server advertised
	// challenges list.
	Type string

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

	// Respond is used by the client to send a reponse or reply to the challenge.
	Respond func(context.Context, *xml.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
}

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

	"mellium.im/xmpp"
	"mellium.im/xmpp/streamerror"
)

// Namespaces used by IBR.


@@ 27,6 28,21 @@ var (
	errNoChallenge = errors.New("No supported challenges were found")
)

func challengeStart(typ string) xml.StartElement {
	return xml.StartElement{
		Name: xml.Name{
			Space: NS,
			Local: "challenge",
		},
		Attr: []xml.Attr{
			{
				Name:  xml.Name{Local: "type"},
				Value: typ,
			},
		},
	}
}

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) {
		if err = e.EncodeToken(start); err != nil {


@@ 89,6 105,31 @@ 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) {
	var tok xml.Token
	tok, err = d.Token()
	if err != nil {
		return
	}
	start, ok := tok.(xml.StartElement)
	switch {
	case !ok:
		err = streamerror.RestrictedXML
		return
	case start.Name.Local == "cancel" && start.Name.Space == NS:
		cancel = true
		return
	case start.Name.Local == "response" && start.Name.Space == NS:
		err = decode(ctx, true, d, &start)
		if err != nil {
			return
		}
	}

	err = streamerror.BadFormat
	return
}

func negotiateFunc(challenges ...Challenge) func(context.Context, *xmpp.Session, interface{}) (xmpp.SessionState, io.ReadWriter, error) {
	return func(ctx context.Context, session *xmpp.Session, supported interface{}) (mask xmpp.SessionState, rw io.ReadWriter, err error) {
		server := (session.State() & xmpp.Received) == xmpp.Received


@@ 100,8 141,88 @@ func negotiateFunc(challenges ...Challenge) func(context.Context, *xmpp.Session,
			return
		}

		// TODO:
		panic("not yet supported")
		var tok xml.Token
		e := session.Encoder()
		d := session.Decoder()

		if server {
			for _, c := range challenges {
				// Send the challenge.
				start := challengeStart(c.Type)
				err = e.EncodeToken(start)
				if err != nil {
					return
				}
				err = c.Send(ctx, e)
				if err != nil {
					return
				}
				err = e.EncodeToken(start.End())
				if err != nil {
					return
				}
				err = e.Flush()
				if err != nil {
					return
				}

				// Decode the clients response
				var cancel bool
				cancel, err = decodeClientResp(ctx, d, c.Receive)
				if err != nil || cancel {
					return
				}
			}
			return
		}

		// If we're the client, decode the challenge.
		tok, err = d.Token()
		if err != nil {
			return
		}
		start, ok := tok.(xml.StartElement)
		switch {
		case !ok:
			err = streamerror.RestrictedXML
			return
		case start.Name.Local != "challenge" || start.Name.Space != NS:
			err = streamerror.BadFormat
			return
		}
		var typ string
		for _, attr := range start.Attr {
			if attr.Name.Local == "type" {
				typ = attr.Value
				break
			}
		}
		// If there was no type attr, an illegal challenge was sent.
		if typ == "" {
			err = streamerror.BadFormat
			return
		}

		for _, c := range challenges {
			if c.Type != typ {
				continue
			}

			err = c.Receive(ctx, false, d, &start)
			if err != nil {
				return
			}

			if c.Respond != nil {
				err = c.Respond(ctx, e)
				if err != nil {
					return
				}
			}

			break
		}
		return
	}
}


M ibr2/oob.go => ibr2/oob.go +7 -2
@@ 5,6 5,9 @@
package ibr2

import (
	"context"
	"encoding/xml"

	"mellium.im/xmpp/oob"
)



@@ 13,10 16,12 @@ import (
// If you are a client, f will be called and passed the parsed OOB data.
// If f returns an error, the client considers the negotiation a failure.
// The returned OOB data is ignored for clients.
// For servers, f is also called, but its argument should be ignored and the
// returned OOB data should be sent on the connection (error is also checked).
// For servers, f is also called.
func OOB(f func(*oob.Data) (*oob.Data, error)) Challenge {
	return Challenge{
		Type: oob.NS,
		Send: func(ctx context.Context, e *xml.Encoder) error {
			return nil
		},
	}
}