VNOX-Server/docs/02-protocol/packets.md

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