[Standards] XMPP-IM - presence subscription handling

Jiří Zárevúcký zarevucky.jiri at gmail.com
Tue Feb 24 15:53:30 UTC 2009


Hello all.

I've been thinking about the current subscription management in the
XMPP-IM for some time now. I think it's not very well designed.

For example, there's an obvious redundancy in the roster pushes and
subscription stanzas. For (almost) every subscription update /
request, there is a presence stanza and a roster push. But that only
applies to the contact, who receives the change. Also, the "ask"
attribute looks like a maimed boolean and roster pushes do not contain
all the state information - inbound request is missing.

Even though the client can track most of the changes by tracking
roster pushes and subscription stanzas, there is one change client can
never track. When there is an inbound subscription request with no
item and user declines it, other resources get no push and no presence
stanza - no notification. That is not really a big problem, but all
this inconsistency in pushes and presence stanzas makes it all seem
very chaotic and untidy.

Originally, I wanted to propose modifying roster pushes so they
contain all the state information (including eventual inbound
subscription request message), essentially leaving
subscription-managing presence stanzas only as the mean of requesting
the change, not presenting it to the other entity or the other
resources. Then one thing occured to me. Do we really need a separate
subscription handling for inbound and outbound presences? Users
generally don't want it separate. Users want mutual subscription. Is
there ever need to have one-side subscription?

Providing mechanism for just a mutual subscription, where there would
be only both-direction or pending or no subscription at all, would
immensely simplify things for both users and implementers. Yes, I
agree that immediate effect would be increasing complexity and need of
additional effort to maintain backward compatibility. That would be a
tricky task, but I believe that with proper interoperability rules, it
would be possible to make transition relatively painless. I believe
that in a long run, such change would be a huge improvement.

So, in case you are interested in this idea, I will continue with some
semantics I have on my mind. Otherwise, just tell me why do you think
it is not possible / feasible / wanted to implement it. I'm pretty
sure there is some hidden problem with this I don't realize. I suppose
many of you won't take me too seriously, since it would require too
massive change to the core protocol, but I'd appreciate if you thought
about it a bit. Thanks for any feedback.



<item/> element would have the children defined in the current XMPP-IM except:
  • there is no "ask" attribute
  • the "subscription" attribute has following values
    ∘ none - means there is no subscription sharing
    ∘ pending-in - means the contact requests subscription sharing
    ∘ pending-out - means the user requests subscription sharing
    ∘ subscribed - means user is sharing presences with the contact
  • there are three new optional child elements:
    ∘ "request", whose character data is the message associated with
the subscription request
    ∘ "ghost" (empty), which would work as a marker for items, that
are not yet in the roster (just holding information about the
request).
    ∘ "remove" (empty) - same as the subscription="remove" in the
current specification; it's changed to a separate element because the
subscription attribute should only contain information about the
subscription, not a removal request

As for the <presence/> stanza:
  • "unsubscribed" and "subscribed" types are removed, since they are
no longer needed

=== Subscription request:

Contact requesting the subscription would send the presence stanza as usual:

<presence type="subscribe" to="user at whatever.com">
	<status>Please subscribe me.</status>
</presence>

The stanza would be delivered to the server and possibly forwarded to
the contact's server, but it would NOT be delivered to the contact
itself. Instead, contact would get an updated item.

Scenario 1: contact is already in the roster

<iq type="set" id="push1">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="pending-in">
			<request>Please subscribe me.</request>
			<group>Friends</group>
		</item>
	</query>
</iq>

Success case:

<presence type="subscribe" to="contact at whatever.com" />

When there is a pending request and user includes the "status" child,
it is ignored.
Server then updates subscription.

<iq type="set" id="push2">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="subscribed">
			<group>Friends</group>
		</item>
	</query>
</iq>

Failure case:

<presence type="unsubscribe" to="contact at whatever.com" />

Server responds:

<iq type="set" id="push2">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="none">
			<group>Friends</group>
		</item>
	</query>
</iq>

Scenario 2: contact in not in the roster

Contact is added automatically to the roster with the "ghost" marker.

<iq type="set" id="push1">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="pending-in">
			<request>Please subscribe me.</request>
			<ghost />
		</item>
	</query>
</iq>

Success case:

<presence type="subscribe" to="contact at whatever.com" />

<iq type="set" id="push2">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="subscribed" />
	</query>
</iq>

Failure case:

<presence type="unsubscribe" to="contact at whatever.com" />

<iq type="set" id="push2">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com">
			<remove />
		</item>
	</query>
</iq>

Note that "ghost" item is promoted to a normal item upon success and
removed upon failure. If the contact is added via a roster set while
it is in "ghost" state, it is promoted and the subscription state is
preserved. Ghost item can't be removed by a removing roster set,
because it technically isn't in the roster.

Scenario 3: contact withdraws it's sharing proposal

<presence type="unsubscribe" to="user at whatever.com" />

The response is then exactly the same as if the user declined it.
Note: I think it is not necessary for client to know, who cancelled
the request. User knows, whether it was him or not.
Note2: Perhaps it would be wiser to disallow this, as it would open
the possibility of request spamming. Or perhaps just encourage
aggressive rate-limiting for requests.

=== Cancelling presence sharing

Server:
<iq type="set" id="push1">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="subscribed" />
	</query>
</iq>

User:
<presence type="unsubscribe" to="contact at whatever.com" />

Server:
<iq type="set" id="push2">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="none" />
	</query>
</iq>

=== Outbound subscription request

Server:
<iq type="set" id="push1">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="none" />
	</query>
</iq>

User:
<presence type="subscribe" to="contact at whatever.com"><status>My
request</status></presence>

Server would change item, note how it contains it's own message:
<iq type="set" id="push2">
	<query xmlns="jabber:iq:roster">
		<item jid="contact at whatever.com" subscription="pending-out">
			<request>My request</request>
		</item>
	</query>
</iq>

If the contact wasn't in roster prior to the outbound request, it
would contain the "ghost" mark, which would work as described earlier.

=== Summary

So, subscribe and unsubscribe presence stanzas are never sent from
server to client. They are only sent from client to server as a
request and from server to server as an update. All the updates for
clients are provided via roster pushes and the semantics are clear and
easy to understand. Subscription is defined for both directions at the
same time. What do you think?



More information about the Standards mailing list