~samwhited/xmpp

7424ea13be32af9d3ee5bc638c986bd514558f19 — Sam Whited 5 years ago 9a5d44a
Run linter
M bind.go => bind.go +43 -43
@@ 48,54 48,54 @@ func BindResource() StreamFeature {
		Negotiate: func(ctx context.Context, conn *Conn, data interface{}) (mask SessionState, rwc io.ReadWriteCloser, err error) {
			if (conn.state & Received) == Received {
				panic("xmpp: bind not yet implemented")
			}

			reqID := internal.RandomID(internal.IDLen)
			if resource := conn.config.Origin.Resourcepart(); resource == "" {
				// Send a request for the server to set a resource part.
				_, err = fmt.Fprintf(conn, bindIQServerGeneratedRP, reqID)
			} else {
				reqID := internal.RandomID(internal.IDLen)
				if resource := conn.config.Origin.Resourcepart(); resource == "" {
					// Send a request for the server to set a resource part.
					_, err = fmt.Fprintf(conn, bindIQServerGeneratedRP, reqID)
				} else {
					// Request the provided resource part.
					_, err = fmt.Fprintf(conn, bindIQClientRequestedRP, reqID, resource)
				}
				if err != nil {
					return mask, nil, err
				}
				tok, err := conn.in.d.Token()
				if err != nil {
				// Request the provided resource part.
				_, err = fmt.Fprintf(conn, bindIQClientRequestedRP, reqID, resource)
			}
			if err != nil {
				return mask, nil, err
			}
			tok, err := conn.in.d.Token()
			if err != nil {
				return mask, nil, err
			}
			start, ok := tok.(xml.StartElement)
			if !ok {
				return mask, nil, streamerror.BadFormat
			}
			resp := struct {
				IQ
				Bind struct {
					JID *jid.JID `xml:"jid"`
				} `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
				Err StanzaError `xml:"error"`
			}{}
			switch start.Name {
			case xml.Name{Space: ns.Client, Local: "iq"}:
				if err = conn.in.d.DecodeElement(&resp, &start); err != nil {
					return mask, nil, err
				}
				start, ok := tok.(xml.StartElement)
				if !ok {
					return mask, nil, streamerror.BadFormat
				}
				resp := struct {
					IQ
					Bind struct {
						JID *jid.JID `xml:"jid"`
					} `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
					Err StanzaError `xml:"error"`
				}{}
				switch start.Name {
				case xml.Name{Space: ns.Client, Local: "iq"}:
					if err = conn.in.d.DecodeElement(&resp, &start); err != nil {
						return mask, nil, err
					}
				default:
					return mask, nil, streamerror.BadFormat
				}
			default:
				return mask, nil, streamerror.BadFormat
			}

				switch {
				case resp.ID != reqID:
					return mask, nil, streamerror.UndefinedCondition
				case resp.Type == ResultIQ:
					conn.origin = resp.Bind.JID
				case resp.Type == ErrorIQ:
					return mask, nil, resp.Err
				default:
					return mask, nil, StanzaError{Condition: BadRequest}
				}
				return Ready, nil, nil
			switch {
			case resp.ID != reqID:
				return mask, nil, streamerror.UndefinedCondition
			case resp.Type == ResultIQ:
				conn.origin = resp.Bind.JID
			case resp.Type == ErrorIQ:
				return mask, nil, resp.Err
			default:
				return mask, nil, StanzaError{Condition: BadRequest}
			}
			return Ready, nil, nil
		},
	}
}

M compress/compression.go => compress/compression.go +33 -30
@@ 2,7 2,7 @@
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

// The compress package implements stream compression as specified in XEP-0138.
// Package compress implements stream compression as specified in XEP-0138.
package compress

import (


@@ 16,6 16,7 @@ import (
	"mellium.im/xmpp/streamerror"
)

// Namespaces used by stream compression.
const (
	FeaturesNS = "http://jabber.org/features/compress"
	ProtocolNS = "http://jabber.org/protocol/compress"


@@ 83,6 84,7 @@ func New(methods ...Method) xmpp.StreamFeature {
			return true, listed.Methods, nil
		},
		Negotiate: func(ctx context.Context, conn *xmpp.Conn, data interface{}) (mask xmpp.SessionState, rwc io.ReadWriteCloser, err error) {
			// If we're a server.
			if (conn.State() & xmpp.Received) == xmpp.Received {
				clientSelected := struct {
					XMLName xml.Name `xml:"http://jabber.org/protocol/compress compress"`


@@ 118,43 120,44 @@ func New(methods ...Method) xmpp.StreamFeature {

				rwc, err = selected.Wrapper(conn.Raw())
				return mask, rwc, err
			} else {
				var selected Method
			selectmethod:
				for _, m := range methods {
					for _, name := range data.([]string) {
						if name == m.Name {
							selected = m
							break selectmethod
						}
			}

			var selected Method
		selectmethod:
			for _, m := range methods {
				for _, name := range data.([]string) {
					if name == m.Name {
						selected = m
						break selectmethod
					}
				}
			}

				if selected.Name == "" {
					return mask, nil, errors.New(`No matching compression method found`)
				}
			if selected.Name == "" {
				return mask, nil, errors.New(`No matching compression method found`)
			}

				_, err = fmt.Fprintf(conn, `<compress xmlns='`+ProtocolNS+`'><method>%s</method></compress>`, selected.Name)
				if err != nil {
					return
				}
			_, err = fmt.Fprintf(conn, `<compress xmlns='`+ProtocolNS+`'><method>%s</method></compress>`, selected.Name)
			if err != nil {
				return
			}

				d := conn.Decoder()
				tok, err := d.Token()
				if err != nil {
			d := conn.Decoder()
			tok, err := d.Token()
			if err != nil {
				return mask, nil, err
			}

			if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "compressed" && t.Name.Space == ProtocolNS {
				if err = d.Skip(); err != nil {
					return mask, nil, err
				}
				if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "compressed" && t.Name.Space == ProtocolNS {
					if err = d.Skip(); err != nil {
						return mask, nil, err
					}
					rwc, err = selected.Wrapper(conn.Raw())
					return mask, rwc, err
				} else {
					// TODO: Use appropriate errors.
					return mask, nil, streamerror.BadFormat
				}
				rwc, err = selected.Wrapper(conn.Raw())
				return mask, rwc, err
			}

			// TODO: Use appropriate errors.
			return mask, nil, streamerror.BadFormat
		},
	}
}

M errors.go => errors.go +15 -13
@@ 16,23 16,23 @@ import (
type errorType int

const (
	// An error with type Cancel indicates that the error cannot be remedied and
	// the operation should not be retried.
	// Cancel indicates that the error cannot be remedied and the operation should
	// not be retried.
	Cancel errorType = iota

	// An error with type Auth indicates that an operation should be retried after
	// providing credentials.
	// Auth indicates that an operation should be retried after providing
	// credentials.
	Auth

	// An error with type Continue indicates that the operation can proceed (the
	// condition was only a warning).
	// Continue indicates that the operation can proceed (the condition was only a
	// warning).
	Continue

	// An error with type Modify indicates that the operation can be retried after
	// changing the data sent.
	// Modify indicates that the operation can be retried after changing the data
	// sent.
	Modify

	// An error with type Wait is temporary and may be retried after waiting.
	// Wait is indicates that an error is temporary and may be retried.
	Wait
)



@@ 99,13 99,14 @@ type StanzaError struct {

// Error satisfies the error interface and returns the text if set, or the
// condition otherwise.
func (e StanzaError) Error() string {
	if e.Text != "" {
		return e.Text
func (se StanzaError) Error() string {
	if se.Text != "" {
		return se.Text
	}
	return string(e.Condition)
	return string(se.Condition)
}

// MarshalXML satisfies the xml.Marshaler interface for StanzaError.
func (se StanzaError) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
	start = xml.StartElement{
		Name: xml.Name{Space: ``, Local: "error"},


@@ 153,6 154,7 @@ func (se StanzaError) MarshalXML(e *xml.Encoder, start xml.StartElement) (err er
	return nil
}

// UnmarshalXML satisfies the xml.Unmarshaler interface for StanzaError.
func (se *StanzaError) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	decoded := struct {
		Condition struct {

M features.go => features.go +3 -3
@@ 85,9 85,9 @@ func writeStreamFeatures(ctx context.Context, conn *Conn) (n int, req int, err e
				return
			}
			if r {
				req += 1
				req++
			}
			n += 1
			n++
		}
	}
	_, err = fmt.Fprint(conn, `</stream:features>`)


@@ 204,7 204,7 @@ parsefeatures:
		case xml.StartElement:
			// If the token is a new feature, see if it's one we handle. If so, parse
			// it. Increment the total features count regardless.
			sf.total += 1
			sf.total++
			conn.features[tok.Name] = struct{}{}
			if feature, ok := conn.config.Features[tok.Name]; ok && (conn.state&feature.Necessary) == feature.Necessary && (conn.state&feature.Prohibited) == 0 {
				req, data, err := feature.Parse(ctx, conn.in.d, &tok)

M internal/idgen.go => internal/idgen.go +1 -0
@@ 10,6 10,7 @@ import (
	"io"
)

// IDLen is the standard length of stanza identifiers in bytes.
const IDLen = 16

// TODO: This will be called a lot, and probably needs to be faster than we can

M internal/lookup.go => internal/lookup.go +2 -1
@@ 8,7 8,7 @@ import (
	"encoding/xml"
)

// Represents an Extensible Resource Descriptor (XRD) document of the form:
// XRD represents an Extensible Resource Descriptor document of the form:
//
//    <?xml version='1.0' encoding=utf-9'?>
//    <XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>


@@ 26,6 26,7 @@ type XRD struct {
	Links   []Link   `xml:"Link"`
}

// Link is an individual hyperlink in an XRD document.
type Link struct {
	Rel  string `xml:"rel,attr"`
	Href string `xml:"href,attr"`

M internal/saslerr/errors.go => internal/saslerr/errors.go +1 -0
@@ 17,6 17,7 @@ import (
// <failure/> element.
type condition string

// Standard SASL error conditions.
const (
	Aborted              condition = "aborted"
	AccountDisabled      condition = "account-disabled"

M internal/version.go => internal/version.go +4 -3
@@ 12,6 12,7 @@ import (
	"strings"
)

// Common XMPP versions.
var (
	DefaultVersion = Version{1, 0} // The default version to send.
	EmptyVersion   = Version{0, 9} // The value of a missing version attribute.


@@ 39,7 40,7 @@ func ParseVersion(s string) (Version, error) {

	versions := strings.Split(s, ".")
	if len(versions) != 2 {
		return v, errors.New("XMPP version must have a single separator.")
		return v, errors.New("XMPP version must have a single separator")
	}

	// Parse major version number


@@ 61,8 62,8 @@ func ParseVersion(s string) (Version, error) {

// Less compares the major and minor version numbers, returning true if a is
// less than b.
func (a Version) Less(b Version) bool {
	return a.Major < b.Major || (a.Major == b.Major && a.Minor < b.Minor)
func (v Version) Less(b Version) bool {
	return v.Major < b.Major || (v.Major == b.Major && v.Minor < b.Minor)
}

// Prints a string representation of the XMPP version in the form "Major.Minor".

M iq.go => iq.go +4 -4
@@ 28,17 28,17 @@ type IQ struct {
type iqType int

const (
	// A GetIQ is used to query another entity for information.
	// GetIQ is used to query another entity for information.
	GetIQ iqType = iota

	// A SetIQ is used to provide data to another entity, set new values, and
	// SetIQ is used to provide data to another entity, set new values, and
	// replace existing values.
	SetIQ

	// A ResultIQ is sent in response to a successful get or set IQ.
	// ResultIQ is sent in response to a successful get or set IQ.
	ResultIQ

	// An ErrorIQ is sent to report that an error occured during the delivery or
	// ErrorIQ is sent to report that an error occured during the delivery or
	// processing of a get or set IQ.
	ErrorIQ
)

M jid/jid.go => jid/jid.go +3 -2
@@ 242,7 242,8 @@ func (j *JID) Resourcepart() string {
	return j.resourcepart
}

// Makes a copy of the given Jid. j.Equal(j.Copy()) will always return true.
// Copy 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,


@@ 251,7 252,7 @@ func (j *JID) Copy() *JID {
	}
}

// Satisfies the net.Addr interface by returning the name of the network
// Network satisfies the net.Addr interface by returning the name of the network
// ("xmpp").
func (j *JID) Network() string {
	return "xmpp"

M lookup.go => lookup.go +1 -0
@@ 28,6 28,7 @@ const (
	hostMetaXML = "/.well-known/host-meta"
)

// Errors related to address and service lookups.
var (
	ErrNoServiceAtAddress = errors.New("This address does not offer the requested service")
)

M message.go => message.go +8 -8
@@ 27,11 27,11 @@ type Message struct {
type messageType int

const (
	// A NormalMessage is a standalone message that is sent outside the context of
	// a one-to-one conversation or groupchat, and to which it is expected that
	// the recipient will reply. Typically a receiving client will present a
	// message of type "normal" in an interface that enables the recipient to
	// reply, but without a conversation history.
	// NormalMessage is a standalone message that is sent outside the context of a
	// one-to-one conversation or groupchat, and to which it is expected that the
	// recipient will reply. Typically a receiving client will present a message
	// of type "normal" in an interface that enables the recipient to reply, but
	// without a conversation history.
	NormalMessage messageType = iota

	// ChatMessage represents a message sent in the context of a one-to-one chat


@@ 40,18 40,18 @@ const (
	// parties, including an appropriate conversation history.
	ChatMessage

	// An ErrorMessage is generated by an entity that experiences an error when
	// ErrorMessage is generated by an entity that experiences an error when
	// processing a message received from another entity.
	ErrorMessage

	// A GroupChatMessage is sent in the context of a multi-user chat environment.
	// GroupChatMessage is sent in the context of a multi-user chat environment.
	// Typically a receiving client will present a message of type "groupchat" in
	// an interface that enables many-to-many chat between the parties, including
	// a roster of parties in the chatroom and an appropriate conversation
	// history.
	GroupChatMessage

	// A HeadlineMessage provides an alert, a notification, or other transient
	// HeadlineMessage provides an alert, a notification, or other transient
	// information to which no reply is expected (e.g., news headlines, sports
	// updates, near-real-time market data, or syndicated content). Because no
	// reply to the message is expected, typically a receiving client will present

M ns/ns.go => ns/ns.go +1 -0
@@ 6,6 6,7 @@
// other internal packages.
package ns // import "mellium.im/xmpp/ns"

// Namespaces used by the mellium.im/xmpp package.
const (
	Bind     = "urn:ietf:params:xml:ns:xmpp-bind"
	Client   = "jabber:client"

M presence.go => presence.go +8 -8
@@ 31,32 31,32 @@ const (
	// stanza without a defined type (indicating availability on the network).
	NoTypePresence presenceType = iota

	// An ErrorPresence indicates that an error has occurred regarding processing
	// of a previously sent presence stanza; if the presence stanza is of type
	// ErrorPresence indicates that an error has occurred regarding processing of
	// a previously sent presence stanza; if the presence stanza is of type
	// "error", it MUST include an <error/> child element
	ErrorPresence presenceType = iota

	// A ProbePresence is a request for an entity's current presence. It should
	// ProbePresence is a request for an entity's current presence. It should
	// generally only be generated and sent by servers on behalf of a user.
	ProbePresence

	// A SubscribePresence is sent when the sender wishes to subscribe to the
	// SubscribePresence is sent when the sender wishes to subscribe to the
	// recipient's presence.
	SubscribePresence

	// A SubscribedPresence indicates that the sender has allowed the recipient to
	// SubscribedPresence indicates that the sender has allowed the recipient to
	// receive future presence broadcasts.
	SubscribedPresence

	// An UnavailablePresence indicates that the sender is no longer available for
	// UnavailablePresence indicates that the sender is no longer available for
	// communication.
	UnavailablePresence

	// An UnsubscribePresence indicates that the sender is unsubscribing from the
	// UnsubscribePresence indicates that the sender is unsubscribing from the
	// receiver's presence.
	UnsubscribePresence

	// An UnsubscribedPresence indicates that the subscription request has been
	// UnsubscribedPresence indicates that the subscription request has been
	// denied, or a previously granted subscription has been revoked.
	UnsubscribedPresence
)

M sasl.go => sasl.go +86 -86
@@ 74,110 74,110 @@ func SASL(mechanisms ...sasl.Mechanism) StreamFeature {
		Negotiate: func(ctx context.Context, conn *Conn, data interface{}) (mask SessionState, rwc io.ReadWriteCloser, err error) {
			if (conn.state & Received) == Received {
				panic("SASL server not yet implemented")
			} else {
				var selected sasl.Mechanism
				// Select a mechanism, prefering the client order.
			selectmechanism:
				for _, m := range mechanisms {
					for _, name := range data.([]string) {
						if name == m.Name {
							selected = m
							break selectmechanism
						}
			}

			var selected sasl.Mechanism
			// Select a mechanism, prefering the client order.
		selectmechanism:
			for _, m := range mechanisms {
				for _, name := range data.([]string) {
					if name == m.Name {
						selected = m
						break selectmechanism
					}
				}
				// No matching mechanism found…
				if selected.Name == "" {
					return mask, nil, errors.New(`No matching SASL mechanisms found`)
				}
			}
			// No matching mechanism found…
			if selected.Name == "" {
				return mask, nil, errors.New(`No matching SASL mechanisms found`)
			}

				c := conn.Config()
				opts := []sasl.Option{
					sasl.Authz(c.Identity),
					sasl.Credentials(conn.LocalAddr().(*jid.JID).Localpart(), c.Password),
					sasl.RemoteMechanisms(data.([]string)...),
				}
				if tlsconn, ok := conn.rwc.(*tls.Conn); ok {
					opts = append(opts, sasl.ConnState(tlsconn.ConnectionState()))
				}
				client := sasl.NewClient(selected, opts...)
			c := conn.Config()
			opts := []sasl.Option{
				sasl.Authz(c.Identity),
				sasl.Credentials(conn.LocalAddr().(*jid.JID).Localpart(), c.Password),
				sasl.RemoteMechanisms(data.([]string)...),
			}
			if tlsconn, ok := conn.rwc.(*tls.Conn); ok {
				opts = append(opts, sasl.ConnState(tlsconn.ConnectionState()))
			}
			client := sasl.NewClient(selected, opts...)

				more, resp, err := client.Step(nil)
				if err != nil {
					return mask, nil, err
				}
			more, resp, err := client.Step(nil)
			if err != nil {
				return mask, nil, err
			}

				// RFC6120 §6.4.2:
				//     If the initiating entity needs to send a zero-length initial
				//     response, it MUST transmit the response as a single equals sign
				//     character ("="), which indicates that the response is present but
				//     contains no data.
				if len(resp) == 0 {
					resp = []byte{'='}
				}
			// RFC6120 §6.4.2:
			//     If the initiating entity needs to send a zero-length initial
			//     response, it MUST transmit the response as a single equals sign
			//     character ("="), which indicates that the response is present but
			//     contains no data.
			if len(resp) == 0 {
				resp = []byte{'='}
			}

				// Send <auth/> and the initial payload to start SASL auth.
				if _, err = fmt.Fprintf(conn,
					`<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='%s'>%s</auth>`,
					selected.Name, resp,
				); err != nil {
			// Send <auth/> and the initial payload to start SASL auth.
			if _, err = fmt.Fprintf(conn,
				`<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='%s'>%s</auth>`,
				selected.Name, resp,
			); err != nil {
				return mask, nil, err
			}

			// If we're already done after the first step, decode the <success/> or
			// <failure/> before we exit.
			if !more {
				tok, err := conn.in.d.Token()
				if err != nil {
					return mask, nil, err
				}

				// If we're already done after the first step, decode the <success/> or
				// <failure/> before we exit.
				if !more {
					tok, err := conn.in.d.Token()
				if t, ok := tok.(xml.StartElement); ok {
					// TODO: Handle the additional data that could be returned if
					// success?
					_, _, err := decodeSASLChallenge(conn.in.d, t, false)
					if err != nil {
						return mask, nil, err
					}
					if t, ok := tok.(xml.StartElement); ok {
						// TODO: Handle the additional data that could be returned if
						// success?
						_, _, err := decodeSASLChallenge(conn.in.d, t, false)
						if err != nil {
							return mask, nil, err
						}
					} else {
						return mask, nil, streamerror.BadFormat
					}
				} else {
					return mask, nil, streamerror.BadFormat
				}
			}

				success := false
				for more {
					select {
					case <-ctx.Done():
						return mask, nil, ctx.Err()
					default:
					}
					tok, err := conn.in.d.Token()
			success := false
			for more {
				select {
				case <-ctx.Done():
					return mask, nil, ctx.Err()
				default:
				}
				tok, err := conn.in.d.Token()
				if err != nil {
					return mask, nil, err
				}
				var challenge []byte
				if t, ok := tok.(xml.StartElement); ok {
					challenge, success, err = decodeSASLChallenge(conn.in.d, t, true)
					if err != nil {
						return mask, nil, err
					}
					var challenge []byte
					if t, ok := tok.(xml.StartElement); ok {
						challenge, success, err = decodeSASLChallenge(conn.in.d, t, true)
						if err != nil {
							return mask, nil, err
						}
					} else {
						return mask, nil, streamerror.BadFormat
					}
					if more, resp, err = client.Step(challenge); err != nil {
						return mask, nil, err
					}
					if !more && success {
						// We're done with SASL and we're successful
						break
					}
					// TODO: What happens if there's more and success (broken server)?
					if _, err = fmt.Fprintf(conn,
						`<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>`, resp); err != nil {
						return mask, nil, err
					}
				} else {
					return mask, nil, streamerror.BadFormat
				}
				if more, resp, err = client.Step(challenge); err != nil {
					return mask, nil, err
				}
				if !more && success {
					// We're done with SASL and we're successful
					break
				}
				// TODO: What happens if there's more and success (broken server)?
				if _, err = fmt.Fprintf(conn,
					`<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>`, resp); err != nil {
					return mask, nil, err
				}
				return Authn, conn.Raw(), nil
			}
			return Authn, conn.Raw(), nil
		},
	}
}

M starttls.go => starttls.go +1 -0
@@ 20,6 20,7 @@ import (

// BUG(ssw): STARTTLS feature does not have security layer byte precision.

// Errors used by STARTTLS.
var (
	ErrTLSUpgradeFailed = errors.New("The underlying connection cannot be upgraded to TLS")
)

M stream.go => stream.go +13 -12
@@ 24,28 24,29 @@ const streamIDLength = 16
type SessionState uint8

const (
	// Indicates that the underlying connection has been secured. For instance,
	// after STARTTLS has been performed or if a pre-secured connection is being
	// used such as websockets over HTTPS.
	// Secure indicates that the underlying connection has been secured. For
	// instance, after STARTTLS has been performed or if a pre-secured connection
	// is being used such as websockets over HTTPS.
	Secure SessionState = 1 << iota

	// Indicates that the session has been authenticated (probably with SASL).
	// Authn indicates that the session has been authenticated (probably with
	// SASL).
	Authn

	// Indicates that the session is fully negotiated and that XMPP stanzas may be
	// sent and received.
	// Ready indicates that the session is fully negotiated and that XMPP stanzas
	// may be sent and received.
	Ready

	// Indicates that the session was initiated by a foreign entity.
	// Received indicates that the session was initiated by a foreign entity.
	Received

	// Indicates that the output stream has been closed with a stream end tag.
	// When set all write operations will return an error even if the underlying
	// TCP connection is still open.
	// OutputStreamClosed indicates that the output stream has been closed with a
	// stream end tag.  When set all write operations will return an error even if
	// the underlying TCP connection is still open.
	OutputStreamClosed

	// Indicates that the input stream has been closed with a stream end tag. When
	// set all read operations will return an error.
	// InputStreamClosed indicates that the input stream has been closed with a
	// stream end tag. When set all read operations will return an error.
	InputStreamClosed
)


M streamerror/streamerror.go => streamerror/streamerror.go +1 -2
@@ 2,8 2,7 @@
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

// The streamerror package contains XMPP stream errors as defined by RFC 6120
// §4.9.
// Package streamerror contains XMPP stream errors as defined by RFC 6120 §4.9.
//
// These error conditions are not in the main xmpp package to prevent naming
// conflicts with similarly named stanza error conditions and because they are