Files
voice-cat/docs/api-dotnet.md
T
Talon b76181d9fb
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / cpp-conformance (push) Canceled after 0s
Start .NET rewrite with wire and media crypto conformance
2026-09-15 17:54:16 +02:00

3.4 KiB

Initial managed API contract

Status: initial port slice, API revision 1. No change to protobuf or media wire formats. These are shared infrastructure APIs; the client-facing API follows with the client core.

Protocol

VoiceCat.Protocol generates Voicecat.V1 protobuf messages from the existing schema.

ControlFraming.TryReadFrame(ref ReadOnlySequence<byte>, out ReadOnlySequence<byte>) extracts a payload and advances input only when a full frame exists. Returned memory borrows the input's lifetime. Lengths above 16 MiB throw InvalidDataException. Empty payloads are valid. WriteFrame and WriteEnvelope target IBufferWriter<byte>; oversized outgoing payloads throw before output is written.

ReadEnvelopesAsync(PipeReader, CancellationToken) produces parsed envelopes and advances consumed pipe data. It does not complete or dispose the caller's reader. Clean EOF ends enumeration; partial EOF and oversized frames throw InvalidDataException; malformed protobuf throws InvalidProtocolBufferException. Cancellation propagates. A connection owner must close on protocol errors or cancellation partway through a frame; partial frame bytes may already be consumed. Fragments are consumed as they arrive so frames larger than pipe backpressure thresholds make progress. Stopping enumeration between envelopes preserves the next frame.

VoiceFrameHeader is an immutable value with type, flags, codec, SSRC, sequence, and timestamp. Write(Span<byte>) writes its 20-byte big-endian representation; TryRead accepts at least 20 bytes and preserves unknown type/flag/codec values. Higher layers decide which values they support.

Media encryption

MediaEncryptor and MediaDecryptor each own one directional 32-byte session key and mutable packet state. Use one owner at a time; they provide no synchronization. Production constructs them from TLS exporter keys when TLS is implemented. Raw-key constructors support conformance tests and the future TLS integration.

MediaEncryptor.Encrypt(VoiceFrameHeader, ReadOnlySpan<byte>, Span<byte>) writes the full header plus ciphertext and 16-byte tag and returns packet length. It replaces the supplied sequence with its own counter, starting at zero. Capacity and overlap errors throw before reserving a counter. Reserved counters are never reused after encryption failure. At ulong.MaxValue, encryption throws and requires a new session.

MediaDecryptor.TryDecrypt(ReadOnlySpan<byte>, Span<byte>, out VoiceFrameHeader, out int) authenticates and decrypts a complete packet. Short packets, failed tags, replays, and packets outside the 64-packet window return false with default header and zero bytes written. Authentication failure clears the attempted plaintext region; structural/replay rejection leaves storage untouched. Callers must only consume output after success. Invalid storage capacity and overlapping buffers throw.

The nonce is four zero bytes plus the big-endian header counter. All 20 header bytes are authenticated associated data. The replay window advances after authentication. The platform ChaCha20-Poly1305 implementation is preferred; BouncyCastle is used when platform support is absent. Both produce the same wire bytes. The fallback currently allocates per packet; audio and relay allocation guarantees are later checkpoints.

Dispose both objects to clear their owned key arrays and release platform crypto resources. Use after disposal throws ObjectDisposedException.