~samwhited/xmpp

d4ce42000f1abcd8cde0476c975654686b0c1502 — Sam Whited 4 years ago 9f862df
Greedily negotiate optional features

Fixes #12
3 files changed, 43 insertions(+), 11 deletions(-)

M conn.go
M features.go
M stream.go
M conn.go => conn.go +3 -0
@@ 37,6 37,9 @@ type Conn struct {
	features map[xml.Name]struct{}
	flock    sync.Mutex

	// The negotiated features for the current session.
	negotiated map[xml.Name]struct{}

	in struct {
		sync.Mutex
		stream

M features.go => features.go +39 -11
@@ 116,24 116,52 @@ func (c *Conn) negotiateFeatures(ctx context.Context) (done bool, rwc io.ReadWri
		case err != nil:
			return done, nil, err
		case list.total == 0 || len(list.cache) == 0:
			// If we received an empty list (or one with no supported features, we're
			// If we received an empty list (or one with no supported features), we're
			// done.
			return true, nil, nil
		}

		// If the list has any required items, negotiate the first required feature.
		// Otherwise just negotiate the first feature in the list.
		var data sfData
		for _, v := range list.cache {
			if !list.req || v.req {
				data = v
		// If the list has any optional items that we support, negotiate them first
		// before moving on to the required items.
		for {
			var data sfData
			for _, v := range list.cache {
				if _, ok := c.negotiated[v.feature.Name]; ok {
					// If this feature has already been negotiated, skip it (servers
					// shouldn't list them in this case, but you never know).
					continue
				}

				// If the feature is optional, select it.
				if !v.req {
					data = v
					break
				}

				// If the feature is required, tentatively select it (but finish looking
				// for optional features).
				if v.req {
					data = v
				}
			}
			// No features that haven't already been negotiated were sent… we're done.
			if data.feature.Name.Local == "" {
				return true, nil, nil
			}
			var mask SessionState
			mask, rwc, err = data.feature.Negotiate(ctx, c, data.data)
			if err == nil {
				c.state |= mask
			}
			c.negotiated[data.feature.Name] = struct{}{}

			// If we negotiated a required feature or a stream restart is required
			// we're done with this feature set.
			if rwc != nil || data.req {
				break
			}
		}
		mask, rwc, err := data.feature.Negotiate(ctx, c, data.data)
		if err == nil {
			c.state |= mask
		}

		return !list.req || (c.state&Ready == Ready), rwc, err
	}
}

M stream.go => stream.go +1 -0
@@ 215,6 215,7 @@ func (c *Conn) negotiateStreams(ctx context.Context, rwc io.ReadWriteCloser) (er
	for done := false; !done || rwc != nil; {
		if rwc != nil {
			c.features = make(map[xml.Name]struct{})
			c.negotiated = make(map[xml.Name]struct{})
			c.rwc = rwc
			c.in.d = xml.NewDecoder(c.rwc)
			c.out.e = xml.NewEncoder(c.rwc)