xpra

Protocol Protocol

See also: network layer.

Every packet exchanged between xpra servers and clients must follow exactly the same format: an 8 byte header followed by the packet data encoded using rencodeplus or yaml, optionally compressed using lz4, zstd or brotli.
Only when a connection uses a protocol which cannot be identified, xpra may respond with a plain-text error message without any packet header.

Framing

When connecting over websockets, SSL, SSH or QUIC, the transport layer will obviously add its own framing.
Please refer to these protocols for the information on the packet framing used by each of these network protocols.
Only xpra’s own application layer protocol is documented here.


Packet Header

The packet header is made of 8 bytes, all multi-byte values use network byte order (big endian):

Index Type Length in Bytes Contents
0 Byte 1 P (0x50)
1 Byte 1 protocol flags
2 Byte 1 compression flags and level
3 Byte 1 chunk index
4..7 Unsigned Long 4 payload size

The payload size is the number of bytes that follow the header, excluding any padding added by the encryption layer - see below.

Protocol Flags

The protocol flags is an 8-bit bitmask value.
Exactly one packet encoder flag must be set:

The values 0 (bencode) and 1 (rencode) are no longer supported.
The encoder flag can then be ORed with:

The flush flag is only ever set on the main packet (chunk index of zero).

Compression Flags and Level

If this byte is zero, the payload is not compressed. Or at least, not compressed at the network layer: pixel and audio data may still be compressed but this is handled by their respective subsystem and not the transport layer.

This bitmask value is used to indicate how the packet payload is compressed.
The lower 4 bits indicate the compression level (a value between 1 and 15). The higher 4 bits indicate which compressor was used, only one of these flags may be set:

A non-zero compression level without any compressor flag used to mean zlib, which is no longer supported and is now rejected as an invalid compression flag.

Chunk Index

The chunk index is used for sending large payloads and bypassing the packet encoder.
It is a value between 0 and 15, and the main packet always uses the index zero.
Packet chunks do not use a packet encoder, so their protocol flags carry no encoder flag. They may still be compressed and encrypted, using the same header fields as any other packet.
The receiver must replace the item found at chunk index in the main packet.

Example for sending a hypothetical packet ("example-large-packet", "foo", 20MB data), send 2 chunks for better performance:

The receiver must reassemble the original packet from these two chunks.
The main packet must be sent last: the receiver buffers the chunks it receives until the packet with a chunk index of zero arrives.
A maximum of 3 chunks may be sent before the main packet.
Peers that cannot reassemble chunked packets can set the chunks capability to false in their hello packet, in which case all the data is inlined in the main packet instead.

Encryption

By default, the cipher is used as a stream cipher (the stream capability): it is initialized just once using the initialization vector exchanged during the handshake, so each payload has exactly the same size as the data it encrypts and no padding is added.

When stream is negotiated to false, each packet carries its own 16 bytes initialization vector, prepended to the encrypted data.
And if the cipher mode uses a non-zero block size (only CBC does), the data is also padded to a multiple of that block size.
In that case, the payload size is the size of the data plus the initialization vector, it does not include the padding: the receiver must calculate the padding size from the block size and read payload size + padding size bytes from the connection.

See encryption and AES for details.


Payload

The main payload has a chunk index of zero.
Once decompressed according to the compression flags if needed, it must be decoded according to the protocol flags using rencodeplus or yaml.

It consists of a list of items. The first item in that list is the packet-type string.
The packet type can be followed by a variable number of optional arguments.

Packet Type

The packet-type is a string which is used for dispatching the packet to the correct handler.
Each subsystem should use the same prefix for all its packet types.

Packet types are always sent as strings. The integer packet-type aliases negotiated via the hello packet were removed in version 6.3.

Arguments

The only data types that should be used are:

None values are invalid and are rejected when the packet is encoded.
Floating point numbers can be encoded but should be avoided.

Size Limits

The payload size found in the header is validated before any data is read:

Payloads smaller than 378 bytes (XPRA_MIN_COMPRESS_SIZE) are not compressed.


Hello

The hello packet is the initial packet used as handshake. The connection is not fully established until both ends have received a hello packet.

The hello packet contains a single argument which is a dictionary containing all the capabilities advertised by the peer.

For example, hello packets are expected to contain a version capability containing the version string.
They may also include a username value, which can be used for authentication.
Each subsystem will also add its own attributes.

The first hello packet is sent before anything has been negotiated, so it must use the packet encoder enabled by default: rencodeplus.
It may already be compressed, since the sender does not yet know what its peer supports, it can only use its own default compressor: lz4.
The capabilities relevant to this protocol layer are:

Capability Type Contents
encoders list of strings the packet encoders supported, ie: ["rencodeplus", "yaml"]
compressors list of strings the compressors supported, ie: ["lz4", "zstd", "brotli"]
compression_level integer 0 disables compression
chunks boolean set to false if chunked packets cannot be reassembled, defaults to true

Each end selects the encoder and compressor it will use for sending from the lists advertised by its peer, so the two directions may end up using different settings.
rencodeplus is preferred over yaml, and lz4 over zstd and brotli.