~samwhited/xmpp

b8d4b070f83a6621be2cfadf3a31042a1ae6ecc1 — Sam Whited 4 years ago ae670e1
Add partial Send() function
7 files changed, 94 insertions(+), 16 deletions(-)

M bind.go
M internal/idgen.go
M internal/idgen_test.go
M iq.go
M session.go
A session_test.go
M stream.go
M bind.go => bind.go +1 -1
@@ 52,7 52,7 @@ func BindResource() StreamFeature {

			conn := session.Conn()

			reqID := internal.RandomID(internal.IDLen)
			reqID := internal.RandomID()
			if resource := session.config.Origin.Resourcepart(); resource == "" {
				// Send a request for the server to set a resource part.
				_, err = fmt.Fprintf(conn, bindIQServerGeneratedRP, reqID)

M internal/idgen.go => internal/idgen.go +3 -3
@@ 17,11 17,11 @@ const IDLen = 16
//       get when reading from getrandom(2). Should we use a fast userspace
//       CSPRNG and just seed with data from the OS?

// RandomID generates a new random identifier of the given length. If the OS's
// RandomID generates a new random identifier of length IDLen. If the OS's
// entropy pool isn't initialized, or we can't generate random numbers for some
// other reason, panic.
func RandomID(n int) string {
	return randomID(n, rand.Reader)
func RandomID() string {
	return randomID(IDLen, rand.Reader)
}

func randomID(n int, r io.Reader) string {

M internal/idgen_test.go => internal/idgen_test.go +7 -9
@@ 9,21 9,19 @@ import (
	"testing"
)

func BenchmarkRandomIDEven(b *testing.B) {
	for n := 0; n < b.N; n++ {
		RandomID(8)
	}
}
type zeroReader int

func BenchmarkRandomIDOdd(b *testing.B) {
	for n := 0; n < b.N; n++ {
		RandomID(9)
func (z zeroReader) Read(b []byte) (n int, err error) {
	for i := range b {
		b[i] = 0
	}

	return len(b), nil
}

func TestRandomIDLength(t *testing.T) {
	for i := 0; i <= 15; i++ {
		if s := RandomID(i); len(s) != i {
		if s := randomID(i, zeroReader); len(s) != i {
			t.Logf("Expected length %d got %d", i, len(s))
			t.Fail()
		}

M iq.go => iq.go +4 -0
@@ 25,6 25,10 @@ type IQ struct {
	Type    iqType   `xml:"type,attr"`
}

func (iq IQ) copyIQ() IQ {
	return iq
}

type iqType int

const (

M session.go => session.go +40 -0
@@ 8,10 8,13 @@ import (
	"context"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"net"
	"reflect"
	"sync"

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


@@ 86,6 89,43 @@ type Session struct {
	}
}

// Send is used to marshal an element into XML and transmit it over the egress
// stream. If the interface is composed of a stanza (IQ, Message, or Presence)
// and the from or id attributes are empty, an appropriate value is inserted in
// the output XML.
func (s *Session) Send(v interface{}) error {

	// TODO: This is horrifying, and probably buggy. There has got to be a better
	//       way that I haven't thought of…

	switch copier := v.(type) {
	case interface {
		copyIQ() IQ
	}:
		iq := copier.copyIQ()
		iq.ID = internal.RandomID()

		val, err := getCopy(reflect.ValueOf(v))
		if err != nil {
			return nil
		}
		val.FieldByName("IQ").Set(reflect.ValueOf(iq))
		v = val.Interface()
	}

	return s.out.e.Encode(v)
}

func getCopy(val reflect.Value) (v reflect.Value, err error) {
	for val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr {
		if val.IsNil() {
			return v, fmt.Errorf("Failed")
		}
		val = val.Elem()
	}
	return reflect.New(val.Type()).Elem(), nil
}

// Feature checks if a feature with the given namespace was advertised
// by the server for the current stream. If it was data will be the canonical
// representation of the feature as returned by the feature's Parse function.

A session_test.go => session_test.go +38 -0
@@ 0,0 1,38 @@
package xmpp

import (
	"bytes"
	"encoding/xml"
	"testing"
)

// ping is an XEP-0199 ping
type ping struct {
	IQ
	Ping struct{} `xml:"urn:xmpp:ping ping"`
}

func newDummySession() (*bytes.Buffer, *Session) {
	b := new(bytes.Buffer)
	s := &Session{
		rw:         b,
		features:   make(map[string]interface{}),
		negotiated: make(map[string]struct{}),
	}
	s.out.e = xml.NewEncoder(b)
	return b, s
}

func TestSendEnforcesIQSemantics(t *testing.T) {
	_, s := newDummySession()
	p := &ping{}
	err := s.Send(p)
	if err != nil {
		t.Fatal(err)
	}
	p2 := ping{}
	if *p != p2 {
		t.Fatalf("Sending mutated original struct")
	}
	// TODO: Test to make sure we set the ID
}

M stream.go => stream.go +1 -3
@@ 21,8 21,6 @@ const (
	xmlHeader = `<?xml version="1.0" encoding="UTF-8"?>`
)

const streamIDLength = 16

type stream struct {
	to      *jid.JID
	from    *jid.JID


@@ 188,7 186,7 @@ func (s *Session) negotiateStreams(ctx context.Context, rw io.ReadWriter) (err e
				if err = expectNewStream(ctx, s); err != nil {
					return err
				}
				if err = sendNewStream(s, s.config, internal.RandomID(streamIDLength)); err != nil {
				if err = sendNewStream(s, s.config, internal.RandomID()); err != nil {
					return err
				}
			} else {