8 KiB
Packets
Phase 1 note: TCP control payloads are serialized as JSON, not Protobuf. Protobuf is the Phase 2 target. See 01-architecture.md.
Base packet
All LNEx packets share a common header, regardless of transport.
┌──────────────┬──────────────┬──────────────┬────────────────┬─────────┐
│ packet_id │ flags │ sequence │ payload_length │ payload │
│ (2 bytes) │ (2 bytes) │ (4 bytes) │ (4 bytes) │ (var) │
└──────────────┴──────────────┴──────────────┴────────────────┴─────────┘
Fields
packet_id — identifies the packet type. See packet type registry below.
flags — bitmask:
bit 0 — COMPRESSED payload is zstd compressed
bit 1 — ENCRYPTED payload is encrypted (ChaCha20-Poly1305)
bit 2 — FRAGMENTED this is a fragment of a larger payload
bit 3 — LAST_FRAG this is the last fragment
bit 4 — ACK_REQ sender requests acknowledgement (TCP path only)
bit 5-15 — reserved
sequence — monotonically increasing per-session counter.
On the UDP voice path, gaps in sequence indicate packet loss.
payload_length — byte length of the payload that follows.
payload - packet-type-specific data. Phase 1: JSON. Phase 2: Protobuf (planned).
Voice packet
Voice packets are sent over UDP. They extend the base header with audio-specific fields before the Opus payload.
┌──────────────┬──────────────┬──────────────┬────────────────┬────────────────┬────────────┐
│ packet_id │ flags │ voice_seq │ timestamp │ channel_id │ opus_data │
│ = 0x0010 │ (2 bytes) │ (4 bytes) │ (4 bytes) │ (8 bytes) │ (var) │
└──────────────┴──────────────┴──────────────┴────────────────┴────────────────┴────────────┘
Fields
voice_seq — separate sequence counter for the voice stream.
Resets per channel join. Used by jitter buffer for reordering.
timestamp — RTP-style timestamp in samples (48000 Hz clock).
Used for jitter buffer and playout scheduling.
channel_id — identifies which voice channel this frame belongs to.
Allows a single UDP socket to carry multiple channels.
opus_data — raw Opus-encoded frame. Length derived from payload_length
minus the fixed voice header (16 bytes).
Packet type registry
0x0001 HELLO server → client on connect
0x0002 AUTH client → server, identity + signature
0x0003 SESSION server → client, session established
0x0004 PING either direction
0x0005 PONG reply to PING
0x0010 VOICE_PACKET UDP, client ↔ voice-node
0x0011 VOICE_STATE speaking / silent / muted
0x0020 CHAT_MESSAGE text message in channel
0x0021 CHAT_HISTORY batch of historical messages
0x0030 JOIN_CHANNEL client → gateway
0x0031 LEAVE_CHANNEL client → gateway
0x0032 CHANNEL_STATE gateway → client, full channel snapshot
0x0040 USER_JOIN broadcast to channel members
0x0041 USER_LEAVE broadcast to channel members
0x0050 PERMISSION_CHECK gateway → client
0x0051 PERMISSION_DENY gateway → client
0x0060 DM_START client → gateway, initiate 1:1 DM conversation
0x0061 DM_MESSAGE client ↔ gateway, individual DM message
0x0062 DM_HISTORY client → gateway, fetch last 50 messages
0x0100 GUILD_CREATE client → gateway
0x0101 GUILD_DELETE client → gateway
0x0102 GUILD_LIST client → gateway
0x0103 GUILD_MEMBER_JOIN client → gateway
0x0104 GUILD_MEMBER_LEAVE client → gateway
0x0105 GUILD_MEMBER_KICK client → gateway
0x0106 ROLE_CREATE client → gateway
0x0107 ROLE_DELETE client → gateway
0x0108 INVITE_CREATE client → gateway
0x0109 INVITE_ACCEPT client → gateway
0x010A INVITE_DELETE client → gateway
0x0140 PRESENCE_UPDATE client → gateway
0x0141 PRESENCE_SYNC gateway → client
0x0142 PRESENCE_EVENT gateway → client
0x0150 FRIEND_REQUEST client → gateway
0x0151 FRIEND_ACCEPT client → gateway
0x0152 FRIEND_DECLINE client → gateway
0x0153 FRIEND_REMOVE client → gateway
0x0154 FRIEND_LIST client → gateway
0x0155 BLOCK_USER client → gateway
0x0156 UNBLOCK_USER client → gateway
0x0157 BLOCK_LIST client → gateway
0x00F0 ERROR any direction, see error codes
0x00FF DISCONNECT graceful close
Direct Messages (Phase 1.1)
DM support enables 1:1 private messaging with persistent history. DMs are identified by a canonical ID format:
dm_{lexicographically_smaller_uid}_{larger_uid}. This ensures both participants reference the same conversation.
DmStart (0x0060)
Client initiates or opens an existing DM conversation with another user.
{
"target_user_id": "user_pubkey_b64"
}
Response (server sends back as 0x0060):
{
"dm_id": "dm_userid1_userid2",
"other_user_id": "user_pubkey_b64",
"other_nickname": "Alice",
"messages": [
{
"dm_id": "dm_userid1_userid2",
"sender_id": "user_pubkey_b64",
"content": "Hello!",
"timestamp": 1234567890000
}
]
}
DmMessage (0x0061)
Send a new DM or receive one from another user.
{
"dm_id": "dm_userid1_userid2",
"sender_id": "user_pubkey_b64",
"content": "Hello Alice!",
"timestamp": 1234567890000
}
Server-authoritative: sender_id and timestamp are set by the server based on the authenticated session.
DmHistory (0x0062)
Fetch historical messages from a DM.
Request:
{
"dm_id": "dm_userid1_userid2"
}
Response:
{
"dm_id": "dm_userid1_userid2",
"messages": [
{ "dm_id": "...", "sender_id": "...", "content": "...", "timestamp": 1234567890000 }
]
}
Protobuf schema
Payload of each packet is a serialized Protobuf message.
Schemas live in protocol/ at the repository root.
Example — ChatMessage:
syntax = "proto3";
message ChatMessage {
string message_id = 1; // UUID v4
string channel_id = 2;
string sender_id = 3; // sender pubkey (short form)
string content = 4;
int64 timestamp = 5; // Unix ms
}
Example — VoiceState:
message VoiceState {
string user_id = 1;
string channel_id = 2;
enum State {
SILENT = 0;
SPEAKING = 1;
MUTED = 2;
DEAFENED = 3;
}
State state = 3;
}
Full schema reference: protocol/*.proto
Fragmentation
Payloads larger than 1400 bytes on the UDP path are fragmented.
Each fragment carries the same sequence, with FRAGMENTED and optionally
LAST_FRAG flags set. The receiver reassembles before passing to the
application layer.
On TCP, fragmentation is not used — TCP handles it at the transport layer.
Error codes
0x01 ERR_VERSION_MISMATCH unsupported LNEx version
0x02 ERR_AUTH_FAILED invalid signature or unknown identity
0x03 ERR_SESSION_EXPIRED session token no longer valid
0x04 ERR_PERMISSION_DENIED insufficient permissions for action
0x05 ERR_CHANNEL_NOT_FOUND channel does not exist on this node
0x06 ERR_RATE_LIMITED too many packets in window
0x07 ERR_INVALID_PACKET malformed header or payload
0x08 ERR_NODE_UNAVAILABLE federation target node unreachable
0x09 ERR_GUILD_NOT_FOUND guild does not exist
0x0A ERR_BLOCKED you are blocked by this user
0xFF ERR_INTERNAL server-side error