~samwhited/xmpp

b5f6d83fa47d9b6241b1beff6f29abc3c553a2b2 — Sam Whited 5 years ago a782a43
Simplify the JID API

Fixes mellium/mel#14
6 files changed, 178 insertions(+), 508 deletions(-)

M jid/benchmark_test.go
M jid/jid.go
R jid/{safejid_test.go => jid_test.go}
D jid/safejid.go
D jid/unsafejid.go
D jid/unsafejid_test.go
M jid/benchmark_test.go => jid/benchmark_test.go +11 -11
@@ 14,46 14,46 @@ func BenchmarkSplit(b *testing.B) {
	}
}

func BenchmarkUnsafeFromString(b *testing.B) {
func BenchmarkParseString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromString("user@example.com/resource")
		ParseString("user@example.com/resource")
	}
}

func BenchmarkUnsafeFromStringIPv4(b *testing.B) {
func BenchmarkParseStringIPv4(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromString("user@127.0.0.1/resource")
		ParseString("user@127.0.0.1/resource")
	}
}

func BenchmarkUnsafeFromStringIPv6(b *testing.B) {
func BenchmarkParseStringIPv6(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromString("user@[::1]/resource")
		ParseString("user@[::1]/resource")
	}
}

func BenchmarkUnsafeFromParts(b *testing.B) {
func BenchmarkNew(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromParts("user", "example.com", "resource")
		New("user", "example.com", "resource")
	}
}

func BenchmarkCopy(b *testing.B) {
	j := &UnsafeJID{"user", "example.com", "resource"}
	j := &JID{"user", "example.com", "resource"}
	for i := 0; i < b.N; i++ {
		j.Copy()
	}
}

func BenchmarkBare(b *testing.B) {
	j := &UnsafeJID{"user", "example.com", "resource"}
	j := &JID{"user", "example.com", "resource"}
	for i := 0; i < b.N; i++ {
		j.Bare()
	}
}

func BenchmarkString(b *testing.B) {
	j := &UnsafeJID{"user", "example.com", "resource"}
	j := &JID{"user", "example.com", "resource"}
	for i := 0; i < b.N; i++ {
		j.String()
	}

M jid/jid.go => jid/jid.go +151 -23
@@ 7,24 7,163 @@ package jid
import (
	"encoding/xml"
	"errors"
	"fmt"
	"net"
	"strings"
	"unicode/utf8"

	"golang.org/x/net/idna"
	"golang.org/x/text/unicode/precis"
)

// JID defines methods that are common to all XMPP addresses (historically,
// "Jabber ID") implementations.
type JID interface {
	Localpart() string
	Domainpart() string
	Resourcepart() string
	Bare() JID
// JID represents an XMPP address (Jabber ID) comprising a localpart,
// domainpart, and resourcepart. All parts of a JID are guaranteed to be valid
// UTF-8 and will be represented in their canonical form which gives comparison
// the greatest chance of succeeding unless any of the "unsafe" methods are used
// to create the JID.
type JID struct {
	localpart    string
	domainpart   string
	resourcepart string
}

// ParseString constructs a new JID from the given string
// representation.
func ParseString(s string) (*JID, error) {
	localpart, domainpart, resourcepart, err := SplitString(s)
	if err != nil {
		return nil, err
	}
	return New(localpart, domainpart, resourcepart)
}

// New constructs a new JID from the given localpart, domainpart, and
// resourcepart.
func New(localpart, domainpart, resourcepart string) (*JID, error) {
	// Ensure that parts are valid UTF-8 (and short circuit the rest of the
	// process if they're not). We'll check the domainpart after performing
	// the IDNA ToUnicode operation.
	if !utf8.ValidString(localpart) || !utf8.ValidString(resourcepart) {
		return nil, errors.New("JID contains invalid UTF-8")
	}

	// RFC 7622 §3.2.1.  Preparation
	//
	//    An entity that prepares a string for inclusion in an XMPP domainpart
	//    slot MUST ensure that the string consists only of Unicode code points
	//    that are allowed in NR-LDH labels or U-labels as defined in
	//    [RFC5890].  This implies that the string MUST NOT include A-labels as
	//    defined in [RFC5890]; each A-label MUST be converted to a U-label
	//    during preparation of a string for inclusion in a domainpart slot.

	var err error
	domainpart, err = idna.ToUnicode(domainpart)
	if err != nil {
		return nil, err
	}

	if !utf8.ValidString(domainpart) {
		return nil, errors.New("Domainpart contains invalid UTF-8")
	}

	// RFC 7622 §3.2.2.  Enforcement
	//
	//   An entity that performs enforcement in XMPP domainpart slots MUST
	//   prepare a string as described in Section 3.2.1 and MUST also apply
	//   the normalization, case-mapping, and width-mapping rules defined in
	//   [RFC5892].
	//
	// TODO: I have no idea what this is talking about… what rules? RFC 5892 is a
	//       bunch of property lists. Maybe it meant RFC 5895?

	localpart, err = precis.UsernameCaseMapped.String(localpart)
	if err != nil {
		return nil, err
	}

	if resourcepart != "" {
		resourcepart, err = precis.OpaqueString.String(resourcepart)
		if err != nil {
			return nil, err
		}
	}

	if err := commonChecks(localpart, domainpart, resourcepart); err != nil {
		return nil, err
	}

	return &JID{
		localpart:    localpart,
		domainpart:   domainpart,
		resourcepart: resourcepart,
	}, nil
}

// Bare returns a copy of the Jid without a resourcepart. This is sometimes
// called a "bare" JID.
func (j *JID) Bare() JID {
	return JID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: "",
	}
}

	Equal(other JID) bool
// Localpart gets the localpart of a JID (eg "username").
func (j *JID) Localpart() string {
	return j.localpart
}

	fmt.Stringer
	xml.MarshalerAttr
	xml.UnmarshalerAttr
// Domainpart gets the domainpart of a JID (eg. "example.net").
func (j *JID) Domainpart() string {
	return j.domainpart
}

// Resourcepart gets the resourcepart of a JID (eg. "someclient-abc123").
func (j *JID) Resourcepart() string {
	return j.resourcepart
}

// Makes a copy of the given Jid. j.Equal(j.Copy()) will always return true.
func (j *JID) Copy() *JID {
	return &JID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: j.resourcepart,
	}
}

// String converts an JID to its string representation.
func (j JID) String() string {
	s := j.domainpart
	if j.localpart != "" {
		s = j.localpart + "@" + s
	}
	if j.resourcepart != "" {
		s = s + "/" + j.resourcepart
	}
	return s
}

// Equal performs an octet-for-octet comparison with the given JID.
func (j *JID) Equal(j2 JID) bool {
	return j.Localpart() == j2.Localpart() &&
		j.Domainpart() == j2.Domainpart() && j.Resourcepart() == j2.Resourcepart()
}

// MarshalXMLAttr satisfies the MarshalerAttr interface and marshals the JID as
// an XML attribute.
func (j JID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	return xml.Attr{Name: name, Value: j.String()}, nil
}

// UnmarshalXMLAttr satisfies the UnmarshalerAttr interface and unmarshals an
// XML attribute into a valid JID (or returns an error).
func (j *JID) UnmarshalXMLAttr(attr xml.Attr) error {
	jid, err := ParseString(attr.Value)
	j.localpart = jid.localpart
	j.domainpart = jid.domainpart
	j.resourcepart = jid.resourcepart
	return err
}

// SplitString splits out the localpart, domainpart, and resourcepart from a


@@ 99,17 238,6 @@ func SplitString(s string) (localpart, domainpart, resourcepart string, err erro
	return
}

func stringify(j JID) string {
	s := j.Domainpart()
	if lp := j.Localpart(); lp != "" {
		s = lp + "@" + s
	}
	if rp := j.Resourcepart(); rp != "" {
		s = s + "/" + rp
	}
	return s
}

func checkIP6String(domainpart string) error {
	// If the domainpart is a valid IPv6 address (with brackets), short circuit.
	if l := len(domainpart); l > 2 && strings.HasPrefix(domainpart, "[") &&

R jid/safejid_test.go => jid/jid_test.go +16 -11
@@ 5,34 5,39 @@
package jid

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

// Compile time check ot make sure that SafeJID is a JID
var _ JID = (*SafeJID)(nil)
// Compile time check ot make sure that JID and *JID match several interfaces.
var _ fmt.Stringer = &JID{}
var _ fmt.Stringer = JID{}
var _ xml.MarshalerAttr = &JID{}
var _ xml.MarshalerAttr = JID{}
var _ xml.UnmarshalerAttr = (*JID)(nil)

// SafeJID's cannot contain invalid UTF8 in the localpart.
var invalid = string([]byte{0xff, 0xfe, 0xfd})

// JID's cannot contain invalid UTF8 in the localpart.
func TestNewInvalidUtf8Localpart(t *testing.T) {
	invalid := string([]byte{0xff, 0xfe, 0xfd})
	_, err := SafeFromString(invalid + "@example.com/resourcepart")
	_, err := ParseString(invalid + "@example.com/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// SafeJID's cannot contain invalid UTF8 in the domainpart.
// JID's cannot contain invalid UTF8 in the domainpart.
func TestNewInvalidUtf8Domainpart(t *testing.T) {
	invalid := string([]byte{0xff, 0xfe, 0xfd})
	_, err := SafeFromString("example@" + invalid + "/resourcepart")
	_, err := ParseString("example@" + invalid + "/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// SafeJID's cannot contain invalid UTF8 in the resourcepart.
// JID's cannot contain invalid UTF8 in the resourcepart.
func TestNewInvalidUtf8Resourcepart(t *testing.T) {
	invalid := string([]byte{0xff, 0xfe, 0xfd})
	_, err := SafeFromString("example@example.com/" + invalid)
	_, err := ParseString("example@example.com/" + invalid)
	if err == nil {
		t.FailNow()
	}

D jid/safejid.go => jid/safejid.go +0 -158
@@ 1,158 0,0 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

import (
	"encoding/xml"
	"errors"
	"unicode/utf8"

	"golang.org/x/net/idna"
	"golang.org/x/text/unicode/precis"
)

// SafeJID represents an XMPP address (Jabber ID) comprising a localpart,
// domainpart, and resourcepart, that is safe to display in a user interface,
// send over the wire, or compare with another SafeJID. All parts of a safe JID
// are guaranteed to be valid UTF-8 and will be represented in their canonical
// form which gives comparison the greatest chance of succeeding.
type SafeJID struct {
	localpart    string
	domainpart   string
	resourcepart string
}

// SafeFromString constructs a new SafeJID from the given string
// representation.
func SafeFromString(s string) (*SafeJID, error) {
	localpart, domainpart, resourcepart, err := SplitString(s)
	if err != nil {
		return nil, err
	}
	return SafeFromParts(localpart, domainpart, resourcepart)
}

// SafeFromParts constructs a new SafeJID from the given localpart,
// domainpart, and resourcepart.
func SafeFromParts(localpart, domainpart, resourcepart string) (*SafeJID, error) {
	// Ensure that parts are valid UTF-8 (and short circuit the rest of the
	// process if they're not). We'll check the domainpart after performing
	// the IDNA ToUnicode operation.
	if !utf8.ValidString(localpart) || !utf8.ValidString(resourcepart) {
		return nil, errors.New("JID contains invalid UTF-8")
	}

	// RFC 7622 §3.2.1.  Preparation
	//
	//    An entity that prepares a string for inclusion in an XMPP domainpart
	//    slot MUST ensure that the string consists only of Unicode code points
	//    that are allowed in NR-LDH labels or U-labels as defined in
	//    [RFC5890].  This implies that the string MUST NOT include A-labels as
	//    defined in [RFC5890]; each A-label MUST be converted to a U-label
	//    during preparation of a string for inclusion in a domainpart slot.

	var err error
	domainpart, err = idna.ToUnicode(domainpart)
	if err != nil {
		return nil, err
	}

	if !utf8.ValidString(domainpart) {
		return nil, errors.New("Domainpart contains invalid UTF-8")
	}

	// RFC 7622 §3.2.2.  Enforcement
	//
	//   An entity that performs enforcement in XMPP domainpart slots MUST
	//   prepare a string as described in Section 3.2.1 and MUST also apply
	//   the normalization, case-mapping, and width-mapping rules defined in
	//   [RFC5892].
	//
	// TODO: I have no idea what this is talking about… what rules? RFC 5892 is a
	//       bunch of property lists. Maybe it meant RFC 5895?

	localpart, err = precis.UsernameCaseMapped.String(localpart)
	if err != nil {
		return nil, err
	}

	if resourcepart != "" {
		resourcepart, err = precis.OpaqueString.String(resourcepart)
		if err != nil {
			return nil, err
		}
	}

	if err := commonChecks(localpart, domainpart, resourcepart); err != nil {
		return nil, err
	}

	return &SafeJID{
		localpart:    localpart,
		domainpart:   domainpart,
		resourcepart: resourcepart,
	}, nil
}

// Bare returns a copy of the Jid without a resourcepart. This is sometimes
// called a "bare" JID.
func (j *SafeJID) Bare() JID {
	return &SafeJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: "",
	}
}

// Localpart gets the localpart of a JID (eg "username").
func (j *SafeJID) Localpart() string {
	return j.localpart
}

// Domainpart gets the domainpart of a JID (eg. "example.net").
func (j *SafeJID) Domainpart() string {
	return j.domainpart
}

// Resourcepart gets the resourcepart of a JID (eg. "someclient-abc123").
func (j *SafeJID) Resourcepart() string {
	return j.resourcepart
}

// Makes a copy of the given Jid. j.Equal(j.Copy()) will always return true.
func (j *SafeJID) Copy() *SafeJID {
	return &SafeJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: j.resourcepart,
	}
}

// String converts an SafeJID to its string representation.
func (j *SafeJID) String() string {
	return stringify(j)
}

// Equal performs an octet-for-octet comparison with the given JID.
func (j *SafeJID) Equal(j2 JID) bool {
	return j.Localpart() == j2.Localpart() &&
		j.Domainpart() == j2.Domainpart() && j.Resourcepart() == j2.Resourcepart()
}

// MarshalXMLAttr satisfies the MarshalerAttr interface and marshals the JID as
// an XML attribute.
func (j *SafeJID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	return xml.Attr{Name: name, Value: j.String()}, nil
}

// UnmarshalXMLAttr satisfies the UnmarshalerAttr interface and unmarshals an
// XML attribute into a valid JID (or returns an error).
func (j *SafeJID) UnmarshalXMLAttr(attr xml.Attr) error {
	jid, err := SafeFromString(attr.Value)
	j.localpart = jid.Localpart()
	j.domainpart = jid.Domainpart()
	j.resourcepart = jid.Resourcepart()
	return err
}

D jid/unsafejid.go => jid/unsafejid.go +0 -105
@@ 1,105 0,0 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

import (
	"encoding/xml"
)

// UnsafeJID represents an XMPP address (Jabber ID) comprising a localpart,
// domainpart, and resourcepart, that is not Unicode safe.
type UnsafeJID struct {
	localpart    string
	domainpart   string
	resourcepart string
}

// UnsafeFromString constructs a new UnsafeJID from the given string
// representation. The string may be any valid bare or full JID including
// raw domain names, IP literals, or hosts.
func UnsafeFromString(s string) (*UnsafeJID, error) {
	localpart, domainpart, resourcepart, err := SplitString(s)
	if err != nil {
		return nil, err
	}
	return UnsafeFromParts(localpart, domainpart, resourcepart)
}

// UnsafeFromParts constructs a new UnsafeJID from the given localpart,
// domainpart, and resourcepart. The only required part is the domainpart
// ('example.net' and 'hostname' are valid Jids).
func UnsafeFromParts(localpart, domainpart, resourcepart string) (*UnsafeJID, error) {

	if err := commonChecks(localpart, domainpart, resourcepart); err != nil {
		return nil, err
	}

	return &UnsafeJID{
		localpart:    localpart,
		domainpart:   domainpart,
		resourcepart: resourcepart,
	}, nil
}

// Bare returns a copy of the Jid without a resourcepart. This is sometimes
// called a "bare" JID.
func (j *UnsafeJID) Bare() JID {
	return &UnsafeJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: "",
	}
}

// Localpart gets the localpart of a JID (eg "username").
func (j *UnsafeJID) Localpart() string {
	return j.localpart
}

// Domainpart gets the domainpart of a JID (eg. "example.net").
func (j *UnsafeJID) Domainpart() string {
	return j.domainpart
}

// Resourcepart gets the resourcepart of a JID (eg. "someclient-abc123").
func (j *UnsafeJID) Resourcepart() string {
	return j.resourcepart
}

// Makes a copy of the given Jid. j.Equals(j.Copy()) will always return true.
func (j *UnsafeJID) Copy() *UnsafeJID {
	return &UnsafeJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: j.resourcepart,
	}
}

// String converts an UnsafeJID to its string representation.
func (j *UnsafeJID) String() string {
	return stringify(j)
}

// Equal performs an octet-for-octet comparison with the given JID.
func (j *UnsafeJID) Equal(j2 JID) bool {
	return j.Localpart() == j2.Localpart() &&
		j.Domainpart() == j2.Domainpart() && j.Resourcepart() == j2.Resourcepart()
}

// MarshalXMLAttr satisfies the MarshalerAttr interface and marshals the JID as
// an XML attribute.
func (j *UnsafeJID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	return xml.Attr{Name: name, Value: j.String()}, nil
}

// UnmarshalXMLAttr satisfies the UnmarshalerAttr interface and unmarshals an
// XML attribute into a valid JID (or returns an error).
func (j *UnsafeJID) UnmarshalXMLAttr(attr xml.Attr) error {
	jid, err := UnsafeFromString(attr.Value)
	j.localpart = jid.Localpart()
	j.domainpart = jid.Domainpart()
	j.resourcepart = jid.Resourcepart()
	return err
}

D jid/unsafejid_test.go => jid/unsafejid_test.go +0 -200
@@ 1,200 0,0 @@
// Copyright 2015 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

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

// Compile time check ot make sure that UnsafeJID is a JID
var _ JID = (*UnsafeJID)(nil)

// Ensure that JID parts are split properly.
func TestValidPartsFromString(t *testing.T) {
	for _, d := range [][]string{
		{"lp@dp/rp", "lp", "dp", "rp"},
		{"dp/rp", "", "dp", "rp"},
		{"dp", "", "dp", ""},
		{"lp@dp//rp", "lp", "dp", "/rp"},
		{"lp@dp/rp/", "lp", "dp", "rp/"},
		{"lp@dp/@rp/", "lp", "dp", "@rp/"},
		{"lp@dp/lp@dp/rp", "lp", "dp", "lp@dp/rp"},
		{"dp//rp", "", "dp", "/rp"},
		{"dp/rp/", "", "dp", "rp/"},
		{"dp/@rp/", "", "dp", "@rp/"},
		{"dp/lp@dp/rp", "", "dp", "lp@dp/rp"},
		{"₩", "", "₩", ""},
	} {
		lp, dp, rp, err := SplitString(d[0])
		if err != nil || lp != d[1] || dp != d[2] || rp != d[3] {
			t.FailNow()
		}
	}
}

// Ensure that JIDs that are too long return an error.
func TestLongParts(t *testing.T) {
	// Generate a part that is too long.
	pb := bytes.NewBuffer(make([]byte, 0, 1024))
	for i := 0; i < 64; i++ {
		pb.WriteString("aaaaaaaaaaaaaaaa")
	}
	ps := pb.String()
	jids := []string{
		ps + "@example.com/test",
		"lp@" + ps + "/test",
		"lp@example.com/" + ps,
		ps + "@" + ps + "/" + ps,
	}
	for _, d := range jids {
		if _, _, _, err := SplitString(d); err != nil {
			t.FailNow()
		}
	}
}

// Trying to create a JID with an empty localpart should error.
func TestNewEmptyLocalpart(t *testing.T) {
	_, err := UnsafeFromString("@example.com/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID with no localpart should work.
func TestNewNoLocalpart(t *testing.T) {
	jid, err := UnsafeFromString("example.com/resourcepart")
	if err != nil || jid.Localpart() != "" {
		t.FailNow()
	}
}

// Trying to create a JID with no domainpart should error.
func TestNewNoDomainpart(t *testing.T) {
	_, err := UnsafeFromString("text@/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID with no anything should error.
func TestNewNoAnything(t *testing.T) {
	_, err := UnsafeFromString("@/")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID from an empty string should error.
func TestNewEmptyString(t *testing.T) {
	_, err := UnsafeFromString("")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID with '@' or '/' in the resourcepart should work.
func TestNewJIDInResourcepart(t *testing.T) {
	_, err := UnsafeFromString("this/is@/fine")
	if err != nil {
		t.FailNow()
	}
}

// Trying to create a JID with an empty resource part should error.
func TestNewEmptyResourcepart(t *testing.T) {
	_, err := UnsafeFromString("text@example.com/")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a new bare JID (no resource part) should work.
func TestNewBareUnsafeJID(t *testing.T) {
	jid, err := UnsafeFromString("barejid@example.com")
	if err != nil || jid.Resourcepart() != "" {
		t.FailNow()
	}
}

// Creating a new JID from a valid JID string should work and contain all the
// correct parts.
func TestNewValid(t *testing.T) {
	s := "jid@example.com/resourcepart"
	jid, err := UnsafeFromString(s)
	if err != nil {
		t.FailNow()
	}
	switch {
	case err != nil:
		t.FailNow()
	case jid.Localpart() != "jid":
		t.FailNow()
	case jid.Domainpart() != "example.com":
		t.FailNow()
	case jid.Resourcepart() != "resourcepart":
		t.FailNow()
	}
}

// Two identical JIDs should be equal.
func TestEqualJIDs(t *testing.T) {
	jid := &UnsafeJID{"newjid", "example.com", "equal"}
	jid2 := &UnsafeJID{"newjid", "example.com", "equal"}
	if !jid.Equal(jid2) {
		t.FailNow()
	}
}

// An UnsafeJID should equal a copy of itself.
func TestCopy(t *testing.T) {
	j := &UnsafeJID{"newjid", "example.com", "equal"}
	if !j.Equal(j.Copy()) {
		t.FailNow()
	}
}

// Two different JIDs should not be equal.
func TestNotEqualJIDs(t *testing.T) {
	jid := &UnsafeJID{"newjid", "example.com", "notequal"}
	jid2 := &UnsafeJID{"newjid2", "example.com", "notequal"}
	if jid.Equal(jid2) {
		t.FailNow()
	}
	jid = &UnsafeJID{"newjid", "example.com", "notequal"}
	jid2 = &UnsafeJID{"newjid", "example.net", "notequal"}
	if jid.Equal(jid2) {
		t.FailNow()
	}
	jid = &UnsafeJID{"newjid", "example.com", "notequal"}
	jid2 = &UnsafeJID{"newjid", "example.com", "notequal2"}
	if jid.Equal(jid2) {
		t.FailNow()
	}
}

// &UnsafeJIDs should be marshalable to an XML attribute
func TestMarshal(t *testing.T) {
	jid := &UnsafeJID{"newjid", "example.com", "marshal"}
	attr, err := jid.MarshalXMLAttr(xml.Name{Space: "", Local: "to"})

	if err != nil || attr.Name.Local != "to" || attr.Name.Space != "" || attr.Value != "newjid@example.com/marshal" {
		t.FailNow()
	}
}

// &UnsafeJIDs should be unmarshalable from an XML attribute
func TestUnmarshal(t *testing.T) {
	jid := &UnsafeJID{}
	err := jid.UnmarshalXMLAttr(
		xml.Attr{xml.Name{"space", ""}, "newjid@example.com/unmarshal"},
	)

	if err != nil || jid.Localpart() != "newjid" || jid.Domainpart() != "example.com" || jid.Resourcepart() != "unmarshal" {
		t.FailNow()
	}
}