~samwhited/xmpp

27563abce43c56a372a98131a0722680239df662 — Sam Whited 4 years ago 446fe1a
Send IDs for presences and messages too

Only send IDs if one isn't already set (for all stanzas)
Test to make sure we don't accidentally mutate stanzas
4 files changed, 87 insertions(+), 11 deletions(-)

M message.go
M presence.go
M session.go
M session_test.go
M message.go => message.go +4 -0
@@ 26,6 26,10 @@ type Message struct {
	Type    messageType `xml:"type,attr,omitempty"`
}

func (m Message) copyMessage() Message {
	return m
}

type messageType int

const (

M presence.go => presence.go +4 -0
@@ 24,6 24,10 @@ type Presence struct {
	Type    presenceType `xml:"type,attr,omitempty"`
}

func (p Presence) copyPresence() Presence {
	return p
}

type presenceType int

const (

M session.go => session.go +39 -9
@@ 94,28 94,58 @@ type Session struct {
// and the from or id attributes are empty, an appropriate value is inserted in
// the output XML.
func (s *Session) Send(v interface{}) error {
	v = enforceStanzaSemantics(v)
	return s.out.e.Encode(v)
}

func enforceStanzaSemantics(v interface{}) interface{} {
	// TODO: This is horrifying, and probably buggy. There has got to be a better
	//       way that I haven't thought of…

	var (
		stanza    interface{}
		fieldname string
	)
	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
		if iq.ID == "" {
			iq.ID = internal.RandomID()
		}
		stanza = iq
		fieldname = "IQ"
	case interface {
		copyMessage() Message
	}:
		m := copier.copyMessage()
		if m.ID == "" {
			m.ID = internal.RandomID()
		}
		val.FieldByName("IQ").Set(reflect.ValueOf(iq))
		v = val.Interface()
		stanza = m
		fieldname = "Message"
	case interface {
		copyPresence() Presence
	}:
		p := copier.copyPresence()
		if p.ID == "" {
			p.ID = internal.RandomID()
		}
		stanza = p
		fieldname = "Presence"
	default:
		panic("xmpp: not yet implemented")
		return v
	}

	return s.out.e.Encode(v)
	val, err := getCopy(reflect.ValueOf(v))
	if err != nil {
		return nil
	}
	val.FieldByName(fieldname).Set(reflect.ValueOf(stanza))
	v = val.Interface()

	return v
}

func getCopy(val reflect.Value) (v reflect.Value, err error) {

M session_test.go => session_test.go +40 -2
@@ 12,6 12,16 @@ type ping struct {
	Ping struct{} `xml:"urn:xmpp:ping ping"`
}

type dummyMsg struct {
	Message
	Dummy struct{}
}

type dummyPresence struct {
	Presence
	Dummy struct{}
}

func newDummySession() (*bytes.Buffer, *Session) {
	b := new(bytes.Buffer)
	s := &Session{


@@ 23,7 33,7 @@ func newDummySession() (*bytes.Buffer, *Session) {
	return b, s
}

func TestSendEnforcesIQSemantics(t *testing.T) {
func TestSendDoesNotMutateStanza(t *testing.T) {
	_, s := newDummySession()
	p := &ping{}
	err := s.Send(p)


@@ 34,5 44,33 @@ func TestSendEnforcesIQSemantics(t *testing.T) {
	if *p != p2 {
		t.Fatalf("Sending mutated original struct")
	}
	// TODO: Test to make sure we set the ID
}

func TestEnforceStanzaSemantics(t *testing.T) {
	t.Run("IQ", func(t *testing.T) {
		s := enforceStanzaSemantics(ping{})
		if s.(ping).ID == "" {
			t.Fatal("Expected ID to be set")
		}
	})
	t.Run("Message", func(t *testing.T) {
		s := enforceStanzaSemantics(dummyMsg{})
		if s.(dummyMsg).ID == "" {
			t.Fatal("Expected ID to be set")
		}
	})
	t.Run("Message", func(t *testing.T) {
		s := enforceStanzaSemantics(dummyPresence{})
		if s.(dummyPresence).ID == "" {
			t.Fatal("Expected ID to be set")
		}
	})
	t.Run("Nonza", func(t *testing.T) {
		var a interface{}
		a = struct{}{}
		b := enforceStanzaSemantics(a)
		if a != b {
			t.Fatal("Expected the same type of nonza to be returned")
		}
	})
}