Bonjour iChat's use of XMPP (Jabber)
I'd been meaning to do this for a while, but prodding from various people in comments and email has prompted me to actually start.
This post will document how iChat makes use of the XMPP protocol for its local Rendezvous/Bonjour/Zeroconf/whatever messaging. It is by no means complete, and based purely on my observations while trying to make the iChat plugin for Miranda.
If you have any corrections or want to fill in one of the many gaps, then please add comment here and I'll get it up to date.
iChat is in most respects a pretty standard jabber client. You should be able to talk to it very quickly by modifying any existing Jabber library and building in the Zeroconf stuff. For example I modified the Jabber plugin for Miranda. There is absolutely no need to start from scratch.
Just to be clear, I personally use the Apple Bonjour SDK in C for Windows, but any Zeroconf library will do. In my Zeroconf examples I refer to the Apple SDK functions here and there, it's left as an exercise for the reader to translate those bits to their own platform.
This is the biggest deviation from the Jabber spec. There is no central server, so your client has to act as both client and server (depending on who initiates the conversation).
When you become available, your client will have to open a server socket and listen for incoming connections.
The default port that iChat listens on is 5298 so that's a pretty good starting point.
Each conversation with a remote user is it's own socket connection which is maintained until the conversation is over.
Presence in iChat is handled entirely by Zeroconf. To register on the network, you simply need to publish a Zeroconf record with appropriate fields. All the clients on the network are looking for these records, and as soon as you publish one, you will appear on all their rosters.
You need to create a number of text records (using TXTRecordCreate and TXTRecordSetValue).
There are a number of records in iChat but these ones are the minimum to get a conversation going:
|status||avail / dnd / away|
|1st||User's first name|
|port.p2pj||The port you are listening on (typically 5298)|
You now need to register your Zeroconf service (using DNDServiceRegister).
You must pass "_presence._tcp" as the name, domain and host can be null, the port is 5298 (or whatever) and you must also provide your text record. See the documentation of your Zeroconf library for more details.
To build up your own roster, you need to browse for _presence._tcp records (using DNSServiceBrowse).
Starting a Conversation
iChat's use of jabber is pretty straightforward. Regardless of whether you are initiating a conversation or responding you should being by sending an xml declaration like this:
<?xml version='1.0' encoding='UTF-8'?>
This should be immediately by followed by on open stream tag
<stream:stream to='188.8.131.52' xmlns='jabber:client' stream='http://etherx.jabber.org/streams'>
You should put the target's correct ip address in the "to" attribute.
The person on the other end should send you the same stuff.
This is a slight deviation from what my jabber client library expected. That had a "from" attribute as well which iChat omits. I needed to remove the check for this otherwise the library crashed. Your mileage may vary.
Having sent this opening packet, and received one from the other side, you just need to sit there polling your socket waiting for further messages. If you're modifying an existing jabber library, you should find that all of this code already present in the library and easily modified.
Sending a message
Messages are standard jabber message packets in the following format:
<message to='184.108.40.206' type='chat' id='uniquemessageid'>
<body ichatballooncolor='#7BB5EE' ichattextcolor='#000000'>
<font face='Helvetica' ABSZ='12' color='#000000'>message text
The id field is a jabber thing which needs to be unique for each message. Your client library should handle it itself.
The extra html version of the message is an iChat addition. iChat itself does not require it but some other clients (such as Adium) do.
Terminating the conversation
If you want to close the conversation, just send the closing
tag that you opened at the start, and then close the socket. Your client library should be able to handle this.
I have no idea how these work. There is a zeroconf record that appears to contain the hash of the image but that's as far as I've got. Any further information gratefully received.