Files
voice-cat/core/proto/voicecat.proto
Talon fdcd8d1427 feat: DRED (Deep REDundancy) per-channel toggle
Adds Opus 1.6 DRED support end-to-end: encoder embeds 20 ms of ML
redundancy in every packet when enabled; decoder recovers lost frames
from the next buffered packet's DRED extension rather than falling back
to PLC comfort noise.

Protocol: bool dred = 11 added to AudioConfig (backward-compatible,
defaults false). C ABI: int dred added to vc_audio_config. Encoder:
OPUS_SET_DRED_DURATION(2) when dred=true. Decoder: OpusDREDDecoder +
per-stream OpusDRED scratch pre-allocated off the RT thread;
JitterBuffer::try_copy_front_payload peeks at the next packet without
popping on every PLC step; opus_decoder_dred_decode reconstructs the
lost frame if DRED data is present, otherwise falls back to PLC.

New test: test_dred_toggle (22/22 ctest green).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 13:40:47 +02:00

247 lines
11 KiB
Protocol Buffer
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
// 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; // 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; }