// VoiceCat control-plane wire format. SOURCE OF TRUTH for the protocol. // Spec: docs/protocol.md. Each TCP/TLS frame is [u32 length][encoded Envelope]. // Media frames (voice) are NOT here — they use the fixed binary header in docs/voice.md §2. // // Extensibility rules (docs/protocol.md §8): never reuse/renumber tags; new oneof arms and // fields are additive; gate new features behind capability strings in ClientHello/ServerHello. syntax = "proto3"; package voicecat.v1; // ── Envelope ────────────────────────────────────────────────────────────────── message Envelope { // Nonzero on a request; echoed in the matching response for correlation. 0 = unsolicited. uint64 request_id = 1; oneof body { // Session / handshake (tags 10–19) ClientHello client_hello = 10; ServerHello server_hello = 11; AuthRequest auth_request = 12; AuthResult auth_result = 13; Disconnect disconnect = 14; Ping ping = 15; Pong pong = 16; // State sync (20–29) ServerStateSnapshot server_state = 20; ChannelEvent channel_event = 21; UserEvent user_event = 22; SubscribeRequest subscribe = 23; // Channel operations (30–39) JoinChannelRequest join_channel = 30; JoinChannelResult join_channel_result = 31; LeaveChannelRequest leave_channel = 32; CreateChannelRequest create_channel = 33; EditChannelRequest edit_channel = 34; DeleteChannelRequest delete_channel = 35; MoveUserRequest move_user = 36; GenericResult generic_result = 37; // Voice signaling — media is on UDP (40–49) StreamAnnounce stream_announce = 40; StreamAnnounceResult stream_announce_result = 41; StreamStop stream_stop = 42; StreamStateUpdate stream_state = 43; UdpBinding udp_binding = 44; // Text (50–59) TextMessage text_message = 50; TextMessageAck text_message_ack = 51; TypingIndicator typing = 52; // Moderation / permissions (60–69) KickRequest kick = 60; BanRequest ban = 61; SetPermissionRequest set_permission = 62; ServerMuteRequest server_mute = 63; // Admin account management — privileged; accounts are admin-provisioned (70–79) CreateAccountRequest create_account = 70; ResetPasswordRequest reset_password = 71; DeleteAccountRequest delete_account = 72; ListAccountsRequest list_accounts = 73; ListAccountsResult list_accounts_result = 74; // Future families: file transfer = 100–109. Extension escape hatch = 200+. Extension extension = 200; } } // ── Enums ────────────────────────────────────────────────────────────────────── enum ChannelType { CHANNEL_PERMANENT = 0; CHANNEL_TEMPORARY = 1; } enum ChannelMode { MODE_MONO = 0; MODE_STEREO = 1; } enum StreamKind { STREAM_MIC = 0; STREAM_SCREEN_AUDIO = 1; STREAM_AUX_DEVICE = 2; } enum TextScope { TEXT_CHANNEL = 0; TEXT_PRIVATE = 1; TEXT_SERVER = 2; } enum OpusApplication { OPUS_VOIP = 0; OPUS_AUDIO = 1; OPUS_LOWDELAY = 2; } // ── Common types ──────────────────────────────────────────────────────────────── message AudioConfig { uint32 codec = 1; // 0 = OPUS ChannelMode mode = 2; uint32 sample_rate = 3; // 48000 recommended uint32 bitrate_bps = 4; uint32 frame_ms = 5; // 2.5/5/10/20/40/60 OpusApplication application = 6; bool fec = 7; uint32 expected_packet_loss = 8; // % bool dtx = 9; uint32 complexity = 10; // 0..10 } message StreamInfo { uint32 stream_id = 1; // unique within the user uint32 ssrc = 2; // media-plane id assigned by server StreamKind kind = 3; AudioConfig audio = 4; string label = 5; } message Channel { uint32 id = 1; uint32 parent_id = 2; // 0 = root string name = 3; string topic = 4; bool password_protected = 5; uint32 max_users = 6; ChannelType type = 7; AudioConfig audio = 8; int32 order = 9; } message User { uint32 id = 1; string nickname = 2; bool is_guest = 3; uint32 channel_id = 4; bool self_mic_muted = 5; bool self_deafened = 6; bool server_muted = 7; repeated StreamInfo streams = 8; bool server_deafened = 9; // M5: server-imposed deafen } message Permissions { // Minimal v1 flag set; the moderation milestone expands this. Server-side is authoritative. bool can_create_temp_channel = 1; bool can_kick = 2; bool can_ban = 3; bool can_move_users = 4; bool can_admin_accounts = 5; bool is_admin = 6; } // ── Session / handshake ───────────────────────────────────────────────────────── message ClientHello { uint32 proto_version = 1; repeated string features = 2; // "opus", "fec", "screen-audio", ... string client_name = 3; string client_version = 4; string preferred_locale = 5; } message ServerHello { uint32 proto_version = 1; repeated string features = 2; string server_name = 3; string server_version = 4; repeated string auth_methods = 5; // "guest", "password" uint32 udp_port = 6; bytes server_identity_fingerprint = 7; // Ed25519 fp for TOFU } message GuestAuth { string nickname = 1; } message PasswordAuth { string username = 1; string password = 2; } message AuthRequest { oneof method { GuestAuth guest = 1; PasswordAuth password = 2; } } message AuthResult { bool ok = 1; string error = 2; uint64 session_id = 3; User self = 4; Permissions permissions = 5; bytes udp_token = 6; // bind the UDP 5-tuple with this (security.md §3) } message Disconnect { uint32 code = 1; string reason = 2; } message Ping { uint64 nonce = 1; } message Pong { uint64 nonce = 1; } // ── State sync ────────────────────────────────────────────────────────────────── message ServerStateSnapshot { repeated Channel channels = 1; repeated User users = 2; } message ChannelEvent { enum Kind { CREATED = 0; UPDATED = 1; DELETED = 2; } Kind kind = 1; Channel channel = 2; uint32 deleted_id = 3; } message UserEvent { enum Kind { JOINED = 0; LEFT = 1; UPDATED = 2; } Kind kind = 1; User user = 2; uint32 left_id = 3; string reason = 4; // M5: kick/ban reason for LEFT events } message SubscribeRequest { repeated uint32 channel_ids = 1; bool presence = 2; } // ── Channel operations ────────────────────────────────────────────────────────── message JoinChannelRequest { uint32 channel_id = 1; string password = 2; } message JoinChannelResult { bool ok = 1; string error = 2; uint32 channel_id = 3; repeated User members = 4; AudioConfig audio = 5; // authoritative channel Opus params } message LeaveChannelRequest {} message CreateChannelRequest { Channel channel = 1; string password = 2; } message EditChannelRequest { Channel channel = 1; string password = 2; } message DeleteChannelRequest { uint32 channel_id = 1; } message MoveUserRequest { uint32 user_id = 1; uint32 channel_id = 2; } message GenericResult { bool ok = 1; uint32 code = 2; string message = 3; } // ── Voice signaling ───────────────────────────────────────────────────────────── message StreamAnnounce { StreamKind kind = 1; AudioConfig requested_audio = 2; string label = 3; } message StreamAnnounceResult{ bool ok = 1; string error = 2; uint32 stream_id = 3; uint32 ssrc = 4; AudioConfig effective_audio = 5; } message StreamStop { uint32 stream_id = 1; } message StreamStateUpdate { uint32 user_id = 1; uint32 stream_id = 2; bool muted = 3; bool talking = 4; } message UdpBinding { bytes udp_token = 1; bool ack = 2; } // ── Text (ephemeral — server does not persist history, docs/protocol.md §5) ────── message TextMessage { TextScope scope = 1; uint32 target_id = 2; // channel_id or user_id per scope uint32 sender_id = 3; // set by server on relay string body = 4; // UTF-8, server-bounded length uint64 sent_at_unix_ms = 5; // server timestamp on relay string client_msg_id = 6; // echoed in ack (dedup) } message TextMessageAck { string client_msg_id = 1; bool ok = 2; } message TypingIndicator { TextScope scope = 1; uint32 target_id = 2; uint32 user_id = 3; } // ── Moderation / permissions ───────────────────────────────────────────────────── message KickRequest { uint32 user_id = 1; string reason = 2; } message BanRequest { uint32 user_id = 1; string reason = 2; uint64 expires_unix_ms = 3; } message SetPermissionRequest { uint32 user_id = 1; Permissions permissions = 2; } message ServerMuteRequest { uint32 user_id = 1; bool muted = 2; bool deafened = 3; } // ── Admin account management (privileged) ──────────────────────────────────────── message CreateAccountRequest { string username = 1; string password = 2; } message ResetPasswordRequest { string username = 1; string new_password = 2; } message DeleteAccountRequest { string username = 1; } message ListAccountsRequest {} message AccountEntry { string username = 1; bool is_admin = 2; uint64 created_at_unix_ms = 3; uint64 last_login_unix_ms = 4; } message ListAccountsResult { repeated AccountEntry accounts = 1; } // ── Extension escape hatch ─────────────────────────────────────────────────────── message Extension { string ns = 1; bytes payload = 2; }