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.