# 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, out ReadOnlySequence)` 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`; 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)` 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 through `TlsSession` media factories after its handshake. Raw-key constructors support conformance tests. `MediaEncryptor.Encrypt(VoiceFrameHeader, ReadOnlySpan, Span)` 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, Span, 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`. ## TLS sessions `TlsSession` is a single-owner, nonblocking BouncyCastle TLS 1.3 state machine. It owns no socket or worker thread. The transport owner feeds `ReceiveCiphertext`, fully drains `DrainCiphertext` to its socket (including partial sends), and reads application data through `ReadPlaintext`. Reads and drains return a byte count and may require repeated calls. `WritePlaintext` requires `IsReady`. Socket cancellation, backpressure, and connection lifetime belong to the transport owner. `CreateClient(Func)` requires an explicit certificate acceptance callback. It receives the uppercase SHA-256 fingerprint of the leaf certificate's DER bytes during the handshake. Returning false rejects the session before application data or media keys are available. This is TOFU certificate pinning; there is no PKI chain or hostname validation. The synchronous callback must have the trust decision available; an asynchronous first-connect prompt requires a subsequent connection after explicit acceptance. Never automatically accept or persist an unknown pin. `CreateServer(certificatePem, privateKeyPem)` supports ECDSA credentials; use `ServerCredentials.CreateTlsSession()` to import persisted credentials. TLS 1.2 is rejected. Handshake completion captures two 32-byte exporter keys using label `voicecat media v1` and one-byte contexts 0 (client to server) and 1 (server to client). BouncyCastle discards its exporter secrets after that callback. Media factories select the correct direction for each role and require a ready session. Create one encryptor and decryptor per connection and retain them for the connection's lifetime: constructing a second encryptor resets its counter and would reuse nonces. Dispose media objects separately from the TLS session. `Close()` queues close_notify; drain it before disposal. On socket EOF call `CompleteInput()`; missing close_notify throws `IOException`. TLS/protocol errors require closing the connection. Disposal clears the session's owned exporter arrays and scratch buffer. ## Persisted trust and credentials `TofuStore` uses the existing UTF-8 `host:port lowercase-hex-fingerprint` format. Host matching is ordinal and case sensitive, matching native behavior. `Check` returns `FirstConnect`, `Matched`, or `Mismatch` without changing persistence. Only explicit `Pin` or `Remove` changes the file. Pin replacement requires an explicit caller decision; malformed files fail closed. Changes replace the file atomically before updating memory. Use one owner per store/file. `ServerIdentity` reads and writes the native 96-byte Ed25519 format: `public-key[32] || seed[32] || public-key[32]`. Loading verifies both public-key copies against the seed. Disposal clears the owned seed. `ServerCredentials.LoadOrCreate(directory, serverName)` imports `identity.key`, `server.crt`, and `server.key` unchanged. If all are absent it creates an ECDSA-P256 self-signed certificate and identity. If only some exist it rejects startup rather than rotating identity. Restore the missing files. New certificates include SAN URI `urn:voicecat:identity:ed25519:`; legacy certificates are accepted unchanged. Checking this URI against ServerHello's identity is deferred until the managed handshake/session layer is implemented; trust currently pins the leaf certificate. Dispose credentials after their TLS sessions are created/finished as required by the application lifetime. Private file writes use a same-directory temporary file, flush, and atomic replacement. On Unix new files use owner read/write permissions; Windows inherits directory ACLs. The credential directory must have one provisioning owner. PEM strings and crypto library internal copies are managed memory; owned-array clearing does not promise erasure of every runtime/library copy.