Skip to content
UGunixgram

Protocol

UnixProto

UnixProto is the transport Unixgram runs on. It solves one problem: get a message there and back over a bad connection without advertising what is being sent. Here is how.

The path of a message

The path of a messageWebbrowserAndroidnativeCaddy:443 TLSGatewayGo · :5222UnixProtoWS :4001Unixgramapp :3000PostgreSQLRedisgw:push · unxp:pushwssreverse_proxytcpobfuscated2fan-outКаждый push публикуется в Redis, чтобы его увидели клиенты всех реплик — WIRE.md §1.7
The web client connects over WebSocket through the TLS terminator. The native app can open a direct TCP connection to the gateway, skipping HTTP entirely. Both paths converge on the application.

The transport stack

The transport stackПакетauth_key_id · nonce · ciphertextОбфускацияAES-256-CTR · необязательноТранспортlegacy · abridged · intermediate · padded · fullTCPпоток байтовТранспорт выбирается один раз по первым байтам соединения — WIRE.md §1.2
Bottom to top: TCP bytes, the framing of the chosen transport, an optional obfuscation layer, and finally the packet — the only thing the application layer ever sees.

Handshake and session

Handshake and sessionКлиентСерверclient_hello · X25519 pubserver_hello · X25519 pub · ECDSA подписьпроверка подписи по закреплённому ключуHKDF-SHA256 → auth_key · server_saltrpc · salt + msg_id (строго возрастает)resume · последний msg_id → досылка пропущенногоНезашифрованные вызовы отклоняются: GATEWAY_REQUIRE_ENCRYPTION = true — WIRE.md §1.5
Key agreement, verification of the server's signature against a pinned key, and recovery after a drop: the client sends the id of the last message it received and the server replays what was missed.

Two independent layers

The transport decides how a payload is delimited on the TCP stream. The packet decides what is inside one payload. Neither knows about the other: a client may pick any transport and the packet format does not change.

Five transports

The transport is chosen once, from the first bytes of the connection. Each option trades overhead against how closely the stream resembles random noise.

Obfuscation

The entire stream, transport tag included, is wrapped in AES-256-CTR. This is not encryption — the key is derived from the handshake itself. Its job is different: to remove the recognisable headers that let traffic be classified as "a messenger".

The encrypted channel

Key agreement is X25519; HKDF-SHA256 then produces a 256-bit auth key. Inside the packet sit a salt, a message id, a method and its parameters. The salt is compared in constant time and the message id must strictly increase — that is the replay guard. Plaintext calls are refused.

Realtime over WebSocket

The second path is MessagePack frames on a separate port. The protocol version is negotiated as a single integer during the handshake and can only be lowered. A client asking for too old a version is refused terminally and falls back to the secondary channel.

Delivery across replicas

The connection registry lives in-process, so a push that lands on one replica is invisible to clients of the others. Every push is therefore published to a shared Redis channel and each replica delivers to its own clients. The sender waits for its own copy to come back — otherwise locally connected clients would receive the event twice.

Transports and their per-packet overhead

TransportTagFramingOverhead
legacyuint32 BE length + body4 B
abridged0xeflength/4 in one byte; 0x7f + 3 B LE when longer1–4 B
intermediate0xeeeeeeeeuint32 LE length + body4 B
padded intermediate0xdddddddduint32 LE length + body + 0–15 random bytes4–19 B
fulllength + seqno + body + CRC3212 B