~samwhited/xmpp

1bf2f4164e0d8b3035662ee3f2d4756d28d4c410 — Sam Whited 4 years ago 19ad38d
Initial resource List/Parse implementation

Negotiation is not yet implemented
4 files changed, 60 insertions(+), 36 deletions(-)

M bind.go
M bind_test.go
M config.go
M conn.go
M bind.go => bind.go +20 -10
@@ 11,16 11,16 @@ import (
	"io"
)

// BindResource returns a stream feature that can be used for binding a
// resource. The provided resource may (and probably "should") be empty, and is
// merely a suggestion; the server may return a completely different resource to
// prevent conflicts. If BindResource is used on a server connection, the
// resource argument is ignored.
func BindResource(resource string) *StreamFeature {
	return &StreamFeature{
// TODO: Strictly speaking, there's no reason for BindResource to be a function.
//       I have a vague feeling that it should still be one though and need to
//       figure out why…

// BindResource is a stream feature that can be used for binding a resource.
func BindResource() StreamFeature {
	return StreamFeature{
		Name:       xml.Name{Space: NSBind, Local: "bind"},
		Necessary:  Secure,
		Prohibited: Authn,
		Necessary:  Authn,
		Prohibited: Bind | Ready,
		List: func(ctx context.Context, w io.Writer) (bool, error) {
			_, err := fmt.Fprintf(w, `<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>`)
			return true, err


@@ 32,7 32,17 @@ func BindResource(resource string) *StreamFeature {
			return true, nil, d.DecodeElement(&parsed, start)
		},
		Negotiate: func(ctx context.Context, conn *Conn, data interface{}) (mask SessionState, err error) {
			panic("Not yet implemented")
			if (conn.state & Received) == Received {
				panic("xmpp: bind not yet implemented")
			} else {
				resource := conn.config.Origin.Resourcepart()
				if resource == "" {
					// Send a request for the server to set a resource part.
				} else {
					// Request the provided resource part.
				}
				panic("bind negotiation: Not yet implemented")
			}
		},
	}
}

M bind_test.go => bind_test.go +23 -24
@@ 14,7 14,7 @@ import (

func TestBindList(t *testing.T) {
	buf := &bytes.Buffer{}
	bind := BindResource("")
	bind := BindResource()
	req, err := bind.List(context.Background(), buf)
	if err != nil {
		t.Fatal(err)


@@ 28,6 28,7 @@ func TestBindList(t *testing.T) {
}

func TestBindParse(t *testing.T) {
	bind := BindResource()
	for _, test := range []struct {
		XML string
		err bool


@@ 42,29 43,27 @@ func TestBindParse(t *testing.T) {
		// Run each test twice, once without a requested resource and once for a
		// requested resource (which should be ignored, making the results
		// identical).
		for _, bind := range []*StreamFeature{BindResource(""), BindResource("ignored")} {
			d := xml.NewDecoder(strings.NewReader(test.XML))
			tok, err := d.Token()
			if err != nil {
				// We screwed up the test string…
				panic(err)
			}
			start := tok.(xml.StartElement)
			req, data, err := bind.Parse(context.Background(), d, &start)
			switch {
			case test.err && err == nil:
				t.Error("Expected error from parse")
				continue
			case !test.err && err != nil:
				t.Error(err)
				continue
			}
			if !req {
				t.Error("Expected parsed bind feature to be required")
			}
			if data != nil {
				t.Error("Expected bind data to be nil")
			}
		d := xml.NewDecoder(strings.NewReader(test.XML))
		tok, err := d.Token()
		if err != nil {
			// We screwed up the test string…
			panic(err)
		}
		start := tok.(xml.StartElement)
		req, data, err := bind.Parse(context.Background(), d, &start)
		switch {
		case test.err && err == nil:
			t.Error("Expected error from parse")
			continue
		case !test.err && err != nil:
			t.Error(err)
			continue
		}
		if !req {
			t.Error("Expected parsed bind feature to be required")
		}
		if data != nil {
			t.Error("Expected bind data to be nil")
		}
	}
}

M config.go => config.go +5 -1
@@ 18,7 18,11 @@ type Config struct {
	// An XMPP server address.
	Location *jid.JID

	// An XMPP connection origin (local address).
	// An XMPP connection origin (local address). If the origin has a resource
	// part and this is a client config, the given resource will be requested (but
	// not necessarily assigned) during the initial connection handshake.
	// Generally it is recommended to leave this a bare JID and let the server
	// assign a resource part.
	Origin *jid.JID

	// The supported stream features.

M conn.go => conn.go +12 -1
@@ 11,6 11,8 @@ import (
	"io"
	"net"
	"time"

	"mellium.im/xmpp/jid"
)

// A Conn represents an XMPP connection that can perform SRV lookups for a given


@@ 19,7 21,13 @@ type Conn struct {
	config *Config
	rwc    io.ReadWriteCloser
	state  SessionState
	in     struct {

	// The actual origin of this conn (we don't want to mutate the config, so if
	// this origin exists and is different from the one in config, eg. because the
	// server did not assign us the resourcepart we requested, this is canonical).
	origin *jid.JID

	in struct {
		stream
		d *xml.Decoder
	}


@@ 74,6 82,9 @@ func (c *Conn) LocalAddr() net.Addr {
	if (c.state & Received) == Received {
		return c.config.Location
	}
	if c.origin != nil {
		return c.origin
	}
	return c.config.Origin
}