~samwhited/xmpp

a31c5e282077f36505279df988ce7353d091cecb — Sam Whited 2 months ago 76c24b3 stream_config
xmpp: use function for Web Socket negotiation

Previously this was handled by a WebSocket bool on the StreamConfig
struct, but doing it this way means that with the new API stream
features could switch back and forth between the websocket profile and
the normal TLS profile of stream negotiation. This doesn't make much
sense. Having them as two separate functions makes more sense from the
users perspective and makes it easier to discover and document the
behavior. It's also a stepping stone for eventually moving the websocket
negotiator to the websocket package.

Signed-off-by: Sam Whited <sam@samwhited.com>
3 files changed, 21 insertions(+), 18 deletions(-)

M CHANGELOG.md
M negotiator.go
M websocket/ws.go
M CHANGELOG.md => CHANGELOG.md +4 -1
@@ 8,7 8,8 @@ All notable changes to this project will be documented in this file.

- roster: rename `version` attribute to `ver`
- styling: decoding tokens now uses an iterator pattern

- xmpp: the `WebSocket` option was removed from the `StreamConfig` type in favor
  of `NewWebSocketNegotiator`

### Security



@@ 23,6 24,8 @@ All notable changes to this project will be documented in this file.
- stanza: implement [XEP-0203: Delayed Delivery]
- stanza: more general `UnmarshalError` function that doesn't focus on IQs
- stanza: add `Error` method to `Presence` and `Message`
- xmpp: a new function `NewWebSocketNegotiator` was added for negotiating
  Web Socket connections


### Fixed

M negotiator.go => negotiator.go +13 -11
@@ 42,10 42,6 @@ type StreamConfig struct {
	// A list of stream features to attempt to negotiate.
	Features []StreamFeature

	// WebSocket indicates that the negotiator should use the WebSocket
	// subprotocol defined in RFC 7395.
	WebSocket bool

	// If set a copy of any reads from the session will be written to TeeIn and
	// any writes to the session will be written to TeeOut (similar to the tee(1)
	// command).


@@ 81,7 77,13 @@ type StreamConfig struct {
// re-used or the stream features may be appended to if desired (however, this
// is not required).
func NewNegotiator(cfg func(*Session, StreamConfig) StreamConfig) Negotiator {
	return negotiator(cfg)
	return negotiator(false, cfg)
}

// NewWebSocketNegotiator is like NewNegotiator except that it uses the
// WebSocket subprotocol defined in RFC 7395.
func NewWebSocketNegotiator(cfg func(*Session, StreamConfig) StreamConfig) Negotiator {
	return negotiator(true, cfg)
}

type negotiatorState struct {


@@ 89,7 91,7 @@ type negotiatorState struct {
	cancelTee context.CancelFunc
}

func negotiator(cf func(*Session, StreamConfig) StreamConfig) Negotiator {
func negotiator(websocket bool, cf func(*Session, StreamConfig) StreamConfig) Negotiator {
	var cfg StreamConfig
	return func(ctx context.Context, in, out *stream.Info, s *Session, data interface{}) (mask SessionState, rw io.ReadWriter, restartNext interface{}, err error) {
		cfg = cf(s, cfg)


@@ 130,7 132,7 @@ func negotiator(cf func(*Session, StreamConfig) StreamConfig) Negotiator {

				location := s.LocalAddr()
				origin := s.RemoteAddr()
				err = intstream.Expect(ctx, in, s.in.d, s.State()&Received == Received, cfg.WebSocket)
				err = intstream.Expect(ctx, in, s.in.d, s.State()&Received == Received, websocket)
				if err != nil {
					nState.doRestart = false
					return mask, nil, nState, err


@@ 156,7 158,7 @@ func negotiator(cf func(*Session, StreamConfig) StreamConfig) Negotiator {
				location = in.To
				origin = in.From

				err = intstream.Send(s.Conn(), out, s.State()&S2S == S2S, cfg.WebSocket, stream.DefaultVersion, cfg.Lang, origin.String(), location.String(), attr.RandomID())
				err = intstream.Send(s.Conn(), out, s.State()&S2S == S2S, websocket, stream.DefaultVersion, cfg.Lang, origin.String(), location.String(), attr.RandomID())
				if err != nil {
					nState.doRestart = false
					return mask, nil, nState, err


@@ 166,12 168,12 @@ func negotiator(cf func(*Session, StreamConfig) StreamConfig) Negotiator {
				// one in response.
				origin := s.LocalAddr()
				location := s.RemoteAddr()
				err = intstream.Send(s.Conn(), out, s.State()&S2S == S2S, cfg.WebSocket, stream.DefaultVersion, cfg.Lang, location.String(), origin.String(), "")
				err = intstream.Send(s.Conn(), out, s.State()&S2S == S2S, websocket, stream.DefaultVersion, cfg.Lang, location.String(), origin.String(), "")
				if err != nil {
					nState.doRestart = false
					return mask, nil, nState, err
				}
				err = intstream.Expect(ctx, in, s.in.d, s.State()&Received == Received, cfg.WebSocket)
				err = intstream.Expect(ctx, in, s.in.d, s.State()&Received == Received, websocket)
				if err != nil {
					nState.doRestart = false
					return mask, nil, nState, err


@@ 190,7 192,7 @@ func negotiator(cf func(*Session, StreamConfig) StreamConfig) Negotiator {
			}
		}

		mask, rw, err = negotiateFeatures(ctx, s, data == nil, cfg.WebSocket, cfg.Features)
		mask, rw, err = negotiateFeatures(ctx, s, data == nil, websocket, cfg.Features)
		nState.doRestart = rw != nil
		return mask, rw, nState, err
	}

M websocket/ws.go => websocket/ws.go +4 -6
@@ 25,10 25,9 @@ import (
// client on rw using the WebSocket subprotocol.
// It does not perform the WebSocket handshake.
func NewSession(ctx context.Context, addr jid.JID, rw io.ReadWriter, features ...xmpp.StreamFeature) (*xmpp.Session, error) {
	n := xmpp.NewNegotiator(func(*xmpp.Session, xmpp.StreamConfig) xmpp.StreamConfig {
	n := xmpp.NewWebSocketNegotiator(func(*xmpp.Session, xmpp.StreamConfig) xmpp.StreamConfig {
		return xmpp.StreamConfig{
			Features:  features,
			WebSocket: true,
			Features: features,
		}
	})
	var mask xmpp.SessionState


@@ 42,10 41,9 @@ func NewSession(ctx context.Context, addr jid.JID, rw io.ReadWriter, features ..
// receiving server on rw using the WebSocket subprotocol.
// It does not perform the WebSocket handshake.
func ReceiveSession(ctx context.Context, rw io.ReadWriter, features ...xmpp.StreamFeature) (*xmpp.Session, error) {
	n := xmpp.NewNegotiator(func(*xmpp.Session, xmpp.StreamConfig) xmpp.StreamConfig {
	n := xmpp.NewWebSocketNegotiator(func(*xmpp.Session, xmpp.StreamConfig) xmpp.StreamConfig {
		return xmpp.StreamConfig{
			Features:  features,
			WebSocket: true,
			Features: features,
		}
	})
	var mask xmpp.SessionState