Project

General

Profile

Doc quassel protocols » History » Version 10

Sputnick, 01/18/2014 11:43 PM

1 1 Sputnick
h1. The Quassel Protocol
2 2 Sputnick
3 2 Sputnick
h2. Overview
4 2 Sputnick
5 8 Sputnick
When we talk about the "Quassel protocol", we mean the format of data sent between a Quassel core and connected Quassel clients. At the moment (i.e., as of version 0.9), only one protocol - the "legacy protocol" - is in use. It has evolved from Quassel's early days and hasn't really changed all that much over the years. However, back then we didn't really expect Quassel to ever become popular, much less other developers writing alternate clients such as QuasselDroid or iQuassel. Accordingly, instead of designing (and documenting) a well-defined and easy-to-use format, we chose a rather pragmatic approach for sending data over the wire: Because Qt already had a facility to (de)serialize arbitrary data types over a binary stream - using QDataStream - we simply went with that.
6 2 Sputnick
7 2 Sputnick
While being both straightforward and easy to implement in Quassel, this choice turned out to be rather unlucky in retrospect:
8 2 Sputnick
9 2 Sputnick
* QDataStream's serialization format is not the most efficient one. In particular, strings are serialized as UTF-16, which means that almost half of the data exchanged between client and core is nullbytes. However, this is partially compensated by Quassel using compression if possible.
10 2 Sputnick
* Speaking of which, we don't use streaming compression, which means that lots of potential for benefitting from recurring strings is not used. And since many of the objects we send are key/value maps which tend to have the same set of keys every time, this does matter in practice.
11 1 Sputnick
* And to add insult to injury, we waste even more space all over the place because we simply didn't think about optimizing the protocol. Mobile use of Quassel was just not on our radar in 2005.
12 3 Sputnick
* The serialization format is nowhere documented in a concise and complete way. Yes, there's documentation somewhere in Qt for built-in types; for Quassel's own types however, one would have to hunt through the source. And without reading (and understanding) some rather icky parts of Quassel code, it's close to impossible to understand what's going on even if one manages to deserialize the binary data into proper objects. Bad news for people wanting to write alternate clients. Amazingly, some smart people still managed to reverse-engineer the protocol...
13 3 Sputnick
14 8 Sputnick
To fix these and more issues, we're now planning to replace the legacy protocol by something more sensible. As the first (and most complicated) step, we implemented a protocol abstraction that will allow us to much more easily support alternative formats. As a neat side effect, the resulting refactoring also makes some core parts of the code (e.g. SignalProxy and the initial handshake) much nicer to understand.
15 1 Sputnick
16 8 Sputnick
h2. The Master Plan
17 1 Sputnick
18 8 Sputnick
# [DONE] Refactor the code base to have all protocol-related stuff centralized at one location.
19 8 Sputnick
# [WiP] Implement a way to probe a core for the supported protocols and options. This will allow for supporting additional features or another format later without relying on fragile guesswork; in particular, we can enable things like compression or encryption before starting the real handshake (in the legacy protocol, this information is sent as properties in QVariantMaps during the handshake phase). It would be beneficial to get this completed prior to the release of Ubuntu 14.04 LTS.
20 8 Sputnick
# [NOT STARTED] Evaluate different wire formats as alternative to QDataStream, without changing the protocol semantics. This should allow for a more efficient data exchange without immediately breaking 3rd party or older clients (or cores); it will also show if the protocol abstraction done in Step 1 is sane and working. Google Protobuf seems like a good contender for an additional wire format.
21 8 Sputnick
# [NOT STARTED] Refactor the protocol semantics. Most importantly, this includes removal of side effects for object syncing, and switching to events. It may also include moving the client state into the core. Note that this will completely break compatibility, and we are not sure if it's feasible to retain backwards compatibility at least for a while.
22 8 Sputnick
23 4 Sputnick
h3. Requirements for new protocols
24 4 Sputnick
25 4 Sputnick
h3. Keeping compatibility
26 4 Sputnick
27 4 Sputnick
TBD: for how long?
28 1 Sputnick
29 1 Sputnick
h2. Abstract View [DRAFT]
30 1 Sputnick
31 1 Sputnick
h3. Handshake
32 1 Sputnick
33 6 Sputnick
h4. Probing
34 6 Sputnick
35 8 Sputnick
Because we might want to support more than one protocol, we cannot start to send messages right away. First, both client and core need to agree on which protocol to use and if to enable things like compression or SSL. Therefore, right after the connection has been established, a few well-defined bytes are exchanged to probe for the capabilities on both ends and to determine in which way the real data is going to be exchanged. Note that the probing data is sent in network byte order (big endian), as is customary for network protocols.
36 6 Sputnick
37 10 Sputnick
# The client sends a 32 bit value to the core to initiate the connection. The upper 24 bits contain the magic number 0xca5531. The lower 8 bits contain a set of global connection features (such as compression or SSL support) as defined in the Protocol::Feature enum. Since the resulting value is larger than 0x00400000, legacy (pre-0.10) cores will immediately close the connection. The client can detect this and reconnect in compatibility mode.
38 10 Sputnick
# Immediately afterwards, the client sends a list of the protocols it supports, in order of preference. For each protocol, a 32 bit value is sent, where the lower 8 bits contain the protocol type according to the Protocol::Type enum, and bits 8-23 hold protocol-specific data (for example, the protocol version). Bit 31 being set indicates the end of the list; now the client waits for response from the core.
39 10 Sputnick
# Based on this information, the core will select the protocol to use for the connection. It will then reply with a 32 bit value of its own similar to the one it just received; it will contain the chosen protocol in the lowest byte, and protocol-specific data in bits 8-23. The upper byte holds the global connection features (Protocol::Feature) to be enabled.
40 8 Sputnick
# Immediately afterwards, compression and encryption will be enabled on both ends if applicable, and the socket is handed over to the appropriate protocol handler, ending the probing phase.
41 6 Sputnick
42 10 Sputnick
_Note: The legacy protocol determines the supported and enabled feature set, as well as the protocol version, only during the handshake phase. Therefore, both compression and encryption are turned on later in the process. Also, a client reconnecting in compatibility mode will skip the probing phase and proceed directly with the legacy handshake._
43 4 Sputnick
44 4 Sputnick
h4. Init and Authentication
45 5 Sputnick
46 4 Sputnick
h3. SigProxy Mode
47 4 Sputnick
48 4 Sputnick
h2. On-Wire Format
49 4 Sputnick
50 1 Sputnick
h3. Legacy Protocol