For additional information, see the Open Source Documentation.
XMPP
Why XMPP?
What if you could IM a file to your friend by dragging it on to their friend icon?
The Extensible Messaging and Presence Protocol (XMPP) component is the basis of LimeWire friendlist features. The overall idea behind the "friendlist" features is that we want to connect LimeWire users with other LimeWire users that they know and allow them to do the following kinds of things:
- chat
- browse / search libraries
- download / upload files
- stream files
- activity feeds
- many more things
To fill these requirements we are adding XMPP support to LimeWire.
What is XMPP?
XMPP is an open extensible IM protocol with an active community that is interested in enabling all sorts of social features beyond simple chatting. It is the basis of Google's gTalk and has been experimented with recently by Yahoo and AOL.
In a nutshell, XMPP is an XML routing protocol, with XMPP Servers doing the routing. Clients keep a persistent connection to a server via TCP (typically with TLS layered on top). Clients send messages addressed to other XMPP clients by JID (Jabber ID), and servers route the message to the receiving client.
Resource Message
For example, WALL-E might send this message to Eve:
<message
to='eve@space.com'
from='walle@earth.com/limewire12345'
type='chat'
xml:lang='en'>
<body>Oh, Eve.</body>
</message>
The "to" address probably looks pretty familiar, but you might be wondering what the trailing "/limewire12345" is doing on the end of the "from" address? That's known as the resource.
The resource is a way to uniquely identify "which" logged in instance of the user is intended. Unique identities are necessary because users can be logged in multiple times so a unique resource binding is needed to disambiguate between them.
It helps Eve because the server will only send it to that logged in instance, not all of them. When sending an initial chat, you send it without the unique id because you don't know where she actually is and it gets broadcast to all instances. But after she replies from one instance, now you can send subsequent chats to that instance.
Presence Message
A second important message type is <presence>:
<presence
from='wall-e@earth.net/city'
to='eve@space.com'/>
When a user logs in, XMPP sends an initial presence message to the server, which propagates to all logged in clients who are subscribed to that user's presence. Therefore, Eve's icon for WALL-E can change from off to online.
Info Query Message
The third important XMPP message is <iq>, which stands for info-query. This is a way for clients to ask questions to server and to other clients.
For example, after logging in, a client will send an <iq> message requesting their contact list (known as a <roster> in XMPP):
<iq from='eve@space.com/space_station' type='get' id='roster_1'> <query xmlns='jabber:iq:roster'/> </iq>
LimeWire XMPP Extension Design
The general design philosophy for LimeWire custom features is to implement purely on the client side. This ensures LimeWire isn't reliant on XMPP servers for the implementation of our custom features, and allows the maximum adoption / reuse by current XMPP users.
It also falls within the general philosophy of LimeWire, which is to enable pure P2P interactions, with minimal reliance on a central server. For example, a user with an existing gTalk account can log into that account from within LimeWire, and be able to use the LimeWire custom XMPP extensions with other users in their friend list who are also logged in via LimeWire.
One important problem this approach solves is reuse of existing identities and friend lists.
Service Discovery
An important standard XMPP extension that allows us to take this approach is Service Discovery to allow clients to query each other to discover which features they support. For example:
<iq type='get'
from='wall-e@earth.net/city'
to='eve@space.com/space_station'
id='info1'>
<query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
The responding client replies with a list of URL's which correspond to the features they implement. For example:
<iq type='result'
from='eve@space.com/space_station'
to='wall-e@earth.net/city'
id='info1'>
<query xmlns='http://jabber.org/protocol/disco#info'>
<feature var='http://jabber.org/protocol/disco#info'/>
<feature var='http://jabber.org/protocol/disco#items'/>
<feature var='http://jabber.org/protocol/muc'/>
<feature var='jabber:iq:register'/>
<feature var='jabber:iq:search'/>
<feature var='jabber:iq:time'/>
<feature var='jabber:iq:version'/>
</query>
</iq>
Namespace
LimeWire custom features are exposed in this manner via the namespace "http://www.limewire.org/". More granular URLs will probably be created, and an URL version scheme will also be created.
Code Design
XMPP support is implemented in LimeWire as a Component and a Service:
- the Componentization is a physical code layout that helps enforce encapsulation, API's, and appropriate dependencies.
- the Service design allows the component to bootstrap into LimeWire's microkernel architecture, the ServiceRegistry.
The XMPP service is an adapter and facade around the well known smack XMPP library. Its facade nature allows it to provide a minimal set of API's to users, hiding the complexity of the implementation. Its adapter nature allows it to act as a sort of glue in between other LimeWire components (eg., filemanager, downloader) and the smack library that does much of the heavy lifting.
The XMPP service can also be thought of as a buffer in between other LimeWire components and the Smack library, to avoid tight coupling.
The recommended way to start the XMPPService is through creating a Guice Injector with the LimeWireXMPPModule and LimeWireCommonModule installed.
Note that you will have to provide a Guice Module with bindings for each of the parameters in XMPPServiceImpl; they are the touch points for hooking into XMPP functionality:
Injector injector = Guice.createInjector(Stage.PRODUCTION, new AbstractModule(){
protected void configure() {
install(new LimeWireCommonModule());
install(new LimeWireXMPPModule());
// add bindings for XMPPServiceImpl constructor dependancies
}
});
Obtain an instance of ServiceRegistry from the injector and call the initialize() and start() methods. The XMPPService is now started:
ServiceRegistry registry = injector.getInstance(ServiceRegistry.class); registry.initialize(); registry.start();
If necessary, obtain the @Singleton instance of the XMPPService from the injector:
XMPPService xmppService = injector.getInstance(XMPPService.class);
When shutting down, call the stop() method on the ServiceRegistry:
registry.stop();
XMPP API
The best way to understand the XMPP API is to look at all of the interfaces in the org.limewire.xmpp.client package (UML interface hierarchy).
The interfaces form the API, and the classes are package-private. The three idioms used in the API are listener, callback, and provider. A good place to start is the RosterListener class because if you follow the interfaces it uses you will get a wide exposure to the API, and get a general idea of how the service works. Some highlights:
- The User and Presence classes represent roster <item> and <presence> XML messages that are sent from XMPP servers to clients. These are delivered to interested LimeWire components via RosterListener and PresenceListener impls registered with the XMPPService.
- A User is a person in one's roster, and may or may not be logged in. A Presence is a logged in "instance" of a User. A User can be logged in from multiple clients and can therefore have multiple Presence's.
- The LimePresence interface represents Presence's that are running in LimeWire, as detected via ServiceDiscovery looking for the feature namespace "http://www.limewire.org/".
Custom LimeWire XMPP features
By detecting LimePresence's, this allows LimeWire to interact with those Presence's with additional features above and beyond standard XMPP features:
- subscribing to the users limewire address
- offering files
- as well as other things in the future
Address Subscription
An address is any collection of information that allows one peer to connect to another. For example, an <ip, port> pair is address that can be used to make a direct connection. There are other kinds of information that limewire uses to connect peers to each other.
When a LimePresence detects another LimePresence, it subscribes to its address information:
<iq id="6h8je4" from="limebuddy1@gmail.com/limewire12345" to="limebuddy2@gmail.com/limewire678910" type="get"> <address xmlns="jabber:iq:lw-address"/> </iq>
The receiver responds with its address, and notifies the requester anytime their address changes:
<iq id="6h8je4" from="limebuddy2@gmail.com/limewire678910" to="limebuddy1@gmail.com/limewire12345" type="result"> <direct-connect-address xmlns="jabber:iq:lw-address" value="74rhe37rh73rdgdnbewbnjr3f=="/> </iq>
This allows the limewire to connect to the remote instance and, for example, browse the users files.
Publish-Subscribe
People familiar with XMPP are probably saying to themselves "Isn't this a classic use case for the standard Publish-Subscribe extension?" The answer is yes, but in keeping with our extension design philosophy explained above, we aren't using that extension until it has widespread adoption in XMPP server implementations. In fact, it's possible we might make a pure client side version of Publish-Subscribe since we'll probably need pub-sub features for too turn the existing <library> extension into something a bit more robust; and we have other things besides libraries we want to publish.

