Files
voice-cat/core/proto/voicecat.proto

259 lines
12 KiB
Protocol Buffer
Raw Normal View History

// 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 1019)
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 (2029)
ServerStateSnapshot server_state = 20;
ChannelEvent channel_event = 21;
UserEvent user_event = 22;
SubscribeRequest subscribe = 23;
// Channel operations (3039)
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 (4049)
StreamAnnounce stream_announce = 40;
StreamAnnounceResult stream_announce_result = 41;
StreamStop stream_stop = 42;
StreamStateUpdate stream_state = 43;
UdpBinding udp_binding = 44;
feat: fix voice join/leave, channel edit defaults, channel-update stream restart Three bugs fixed across the full stack (proto/server/core/ABI/Win/macOS/iOS): 1. Join/Leave Voice now truly subscribes/unsubscribes from the voice plane. Previously the button only toggled the local mic — receiving was always on (gated by channel membership alone). Added a protocol-level voice subscription concept: new SubscribeVoiceRequest/UnsubscribeVoiceRequest/VoiceSubscriptionResult proto messages, User.voice_subscribed field, vc_join_voice/vc_leave_voice C ABI functions, VC_EVENT_VOICE_STATE event, server-side voice_subscribed flag checked by the SFU relay recipient filter, and core-client gating of remote-stream decoder setup. All three clients rewired to subscribe+mic on Join / unsubscribe on Leave. Text chat works regardless of voice subscription. 2. Channel edit dialog now shows the channel's actual current settings. The read struct vc_channel was missing sort_order and audio fields — only the write struct vc_channel_info had them. Extended vc_channel with both (additive, no ABI break), updated the session model and list_channels marshaling to populate them, and updated all three clients' edit callers to use actual channel info instead of hardcoded defaults. 3. Channel parameter updates now automatically restart everyone's streams. Previously editing a channel's audio config persisted and broadcast a ChannelEvent::UPDATED, but no layer restarted streams — encoders/decoders are frozen at announce time. handle_channel_event now detects audio-config changes on the user's current channel and stop->starts each active local stream. The server reads the updated config on re-announce; peers wire up fresh decoders at the new ssrc. All 29 CTest tests pass; Windows DLL + C# client build clean. Apple clients not yet compile-verified (Windows environment).
2026-06-24 14:29:39 +02:00
SubscribeVoiceRequest subscribe_voice = 45;
UnsubscribeVoiceRequest unsubscribe_voice = 46;
VoiceSubscriptionResult voice_subscription_result = 47;
// Text (5059)
TextMessage text_message = 50;
TextMessageAck text_message_ack = 51;
TypingIndicator typing = 52;
// Moderation / permissions (6069)
KickRequest kick = 60;
BanRequest ban = 61;
SetPermissionRequest set_permission = 62;
ServerMuteRequest server_mute = 63;
// Admin account management — privileged; accounts are admin-provisioned (7079)
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 = 100109. 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
bool dred = 11; // Deep REDundancy (Opus 1.6); off by default
}
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; // server-imposed deafen
feat: fix voice join/leave, channel edit defaults, channel-update stream restart Three bugs fixed across the full stack (proto/server/core/ABI/Win/macOS/iOS): 1. Join/Leave Voice now truly subscribes/unsubscribes from the voice plane. Previously the button only toggled the local mic — receiving was always on (gated by channel membership alone). Added a protocol-level voice subscription concept: new SubscribeVoiceRequest/UnsubscribeVoiceRequest/VoiceSubscriptionResult proto messages, User.voice_subscribed field, vc_join_voice/vc_leave_voice C ABI functions, VC_EVENT_VOICE_STATE event, server-side voice_subscribed flag checked by the SFU relay recipient filter, and core-client gating of remote-stream decoder setup. All three clients rewired to subscribe+mic on Join / unsubscribe on Leave. Text chat works regardless of voice subscription. 2. Channel edit dialog now shows the channel's actual current settings. The read struct vc_channel was missing sort_order and audio fields — only the write struct vc_channel_info had them. Extended vc_channel with both (additive, no ABI break), updated the session model and list_channels marshaling to populate them, and updated all three clients' edit callers to use actual channel info instead of hardcoded defaults. 3. Channel parameter updates now automatically restart everyone's streams. Previously editing a channel's audio config persisted and broadcast a ChannelEvent::UPDATED, but no layer restarted streams — encoders/decoders are frozen at announce time. handle_channel_event now detects audio-config changes on the user's current channel and stop->starts each active local stream. The server reads the updated config on re-announce; peers wire up fresh decoders at the new ssrc. All 29 CTest tests pass; Windows DLL + C# client build clean. Apple clients not yet compile-verified (Windows environment).
2026-06-24 14:29:39 +02:00
bool voice_subscribed = 10; // true when the user is on the voice plane (hears + can send)
}
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; // 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; }
feat: fix voice join/leave, channel edit defaults, channel-update stream restart Three bugs fixed across the full stack (proto/server/core/ABI/Win/macOS/iOS): 1. Join/Leave Voice now truly subscribes/unsubscribes from the voice plane. Previously the button only toggled the local mic — receiving was always on (gated by channel membership alone). Added a protocol-level voice subscription concept: new SubscribeVoiceRequest/UnsubscribeVoiceRequest/VoiceSubscriptionResult proto messages, User.voice_subscribed field, vc_join_voice/vc_leave_voice C ABI functions, VC_EVENT_VOICE_STATE event, server-side voice_subscribed flag checked by the SFU relay recipient filter, and core-client gating of remote-stream decoder setup. All three clients rewired to subscribe+mic on Join / unsubscribe on Leave. Text chat works regardless of voice subscription. 2. Channel edit dialog now shows the channel's actual current settings. The read struct vc_channel was missing sort_order and audio fields — only the write struct vc_channel_info had them. Extended vc_channel with both (additive, no ABI break), updated the session model and list_channels marshaling to populate them, and updated all three clients' edit callers to use actual channel info instead of hardcoded defaults. 3. Channel parameter updates now automatically restart everyone's streams. Previously editing a channel's audio config persisted and broadcast a ChannelEvent::UPDATED, but no layer restarted streams — encoders/decoders are frozen at announce time. handle_channel_event now detects audio-config changes on the user's current channel and stop->starts each active local stream. The server reads the updated config on re-announce; peers wire up fresh decoders at the new ssrc. All 29 CTest tests pass; Windows DLL + C# client build clean. Apple clients not yet compile-verified (Windows environment).
2026-06-24 14:29:39 +02:00
// Voice-plane subscription. Joining voice = you can hear and send voice in your channel;
// leaving voice = the server stops relaying voice frames to you and you stop receiving.
// Text chat is unaffected. The server toggles a per-session flag checked by the SFU relay's
// recipient filter (docs/protocol.md §5). Result arrives as voice_subscription_result.
message SubscribeVoiceRequest {}
message UnsubscribeVoiceRequest {}
message VoiceSubscriptionResult { bool ok = 1; string error = 2; bool subscribed = 3; }
// ── 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; }