Start .NET rewrite with wire and media crypto conformance
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user