mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-22 07:06:05 +00:00
docs(protocol): specify live voice v1
This commit is contained in:
parent
cbc59aaf8b
commit
e9bc8e0f31
204
docs/LIVE_VOICE_V1.md
Normal file
204
docs/LIVE_VOICE_V1.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Live voice v1 wire protocol
|
||||
|
||||
This document defines the interoperable live push-to-talk protocol used by
|
||||
Android, Wear OS, and iOS clients. Live packets are ephemeral and best-effort;
|
||||
the ordinary voice note sent when the user releases the microphone remains the
|
||||
reliable, persistent fallback.
|
||||
|
||||
All multi-byte integers in this document use network byte order (big-endian).
|
||||
|
||||
## Type assignments
|
||||
|
||||
| Layer | Name | Value | Purpose |
|
||||
| --- | --- | --- | --- |
|
||||
| Outer mesh packet | `MessageType.VOICE_FRAME` | `0x29` | Signed public-mesh burst packet |
|
||||
| Noise inner payload | `NoisePayloadType.VOICE_FRAME` | `0x08` | Encrypted direct-message burst packet |
|
||||
| Outer private packet | `MessageType.NOISE_ENCRYPTED` | `0x11` | Existing recipient-directed Noise envelope |
|
||||
|
||||
These values are canonical. Implementations must not emit live voice under a
|
||||
different outer or inner type.
|
||||
|
||||
## Transport envelopes
|
||||
|
||||
### Public mesh
|
||||
|
||||
The sender emits a normal mesh packet with:
|
||||
|
||||
- version `1`;
|
||||
- type `VOICE_FRAME (0x29)`;
|
||||
- a broadcast recipient (either no recipient field or the eight-byte all-`FF`
|
||||
broadcast value; receivers accept both encodings);
|
||||
- the encoded `VoiceBurstPacket` as its payload;
|
||||
- origin TTL `7`; and
|
||||
- an Ed25519 signature over the canonical outer packet signing bytes.
|
||||
|
||||
Receivers accept public live voice only from a known peer whose announcement
|
||||
has established a verified nickname and signing key. They must reject an
|
||||
unsigned packet, an invalid signature, a non-broadcast recipient, or a packet
|
||||
whose timestamp is more than 30 seconds from the receiver's current time. A
|
||||
rejected public voice packet must not be relayed.
|
||||
|
||||
Public live voice is not added to gossip sync. Older clients ignore the unknown
|
||||
`0x29` type and therefore remain compatible.
|
||||
|
||||
### Direct message
|
||||
|
||||
The sender requires an established Noise session. It encodes:
|
||||
|
||||
```text
|
||||
[NoisePayloadType.VOICE_FRAME: 0x08][VoiceBurstPacket]
|
||||
```
|
||||
|
||||
and encrypts that value in a recipient-directed `NOISE_ENCRYPTED (0x11)` mesh
|
||||
packet. The existing Noise session provides confidentiality and authentication.
|
||||
Intermediate relays see only the ordinary Noise envelope and route it according
|
||||
to the existing directed-packet rules.
|
||||
|
||||
There are no delivery acknowledgements or retransmissions for live packets.
|
||||
If no established session exists when recording begins, the client records and
|
||||
sends only the ordinary voice note.
|
||||
|
||||
## `VoiceBurstPacket`
|
||||
|
||||
Every packet in one press-and-hold gesture shares an opaque, randomly generated
|
||||
eight-byte burst ID.
|
||||
|
||||
```text
|
||||
+------------+-------------+-------------+------------------+
|
||||
| burstID | sequence | flags | payload |
|
||||
| 8 bytes | UInt16 BE | UInt8 | variant-specific |
|
||||
+------------+-------------+-------------+------------------+
|
||||
```
|
||||
|
||||
The fixed header is 11 bytes. `flags` is a complete discriminator, not a
|
||||
bitset that may combine values. Unknown or combined flag values are invalid.
|
||||
|
||||
### START (`flags = 0x01`)
|
||||
|
||||
```text
|
||||
[codec: UInt8]
|
||||
```
|
||||
|
||||
Current senders use sequence `0`. The only v1 codec is:
|
||||
|
||||
| Codec | Value | Encoded frames |
|
||||
| --- | --- | --- |
|
||||
| AAC-LC, 16 kHz, mono | `0x01` | Raw AAC access units without ADTS headers |
|
||||
|
||||
Receivers must reject unsupported codec values. Because START can be lost on a
|
||||
best-effort mesh, a receiver may establish a v1 assembly from a valid data
|
||||
packet and use the sole v1 codec.
|
||||
|
||||
### Data (`flags = 0x00`)
|
||||
|
||||
The payload contains one to eight length-prefixed AAC access units:
|
||||
|
||||
```text
|
||||
[length: UInt16 BE][AAC access unit] ...
|
||||
```
|
||||
|
||||
Each length must be non-zero and must not extend past the packet. A sender uses
|
||||
sequence `1` for the first data packet and increments the value for each later
|
||||
data packet. The sequence counts data packets, not individual AAC frames.
|
||||
|
||||
The v1 sender budget is 210 bytes for the entire `VoiceBurstPacket`. This keeps
|
||||
one live packet below the transport's fragmentation threshold after the Noise
|
||||
type byte, authentication tag, and BLE padding are applied. An encoded frame
|
||||
that cannot fit this budget is dropped rather than fragmented.
|
||||
|
||||
### END (`flags = 0x02`)
|
||||
|
||||
```text
|
||||
[totalDataPackets: UInt16 BE][durationMs: UInt32 BE]
|
||||
```
|
||||
|
||||
`totalDataPackets` is the number of data packets emitted for the burst; it does
|
||||
not include START or END. `durationMs` describes the encoded audio duration.
|
||||
The END packet uses the next sequence value after the final data packet.
|
||||
Receivers use the total to account for tail loss before finalizing playback.
|
||||
|
||||
### CANCELED (`flags = 0x04`)
|
||||
|
||||
CANCELED has no payload and uses the next sequence value. A receiver stops live
|
||||
playback, discards buffered audio, removes the transient message, and does not
|
||||
wait for a finalized voice note.
|
||||
|
||||
## Sender lifecycle
|
||||
|
||||
A successful live gesture follows this order:
|
||||
|
||||
1. Generate one eight-byte burst ID.
|
||||
2. Emit START at sequence `0` after the first encoded AAC access unit becomes
|
||||
available.
|
||||
3. Emit data packets starting at sequence `1`.
|
||||
4. On release, flush pending data and emit END.
|
||||
5. Send the finalized M4A through the existing voice-note transfer path.
|
||||
|
||||
The finalized file name is `voice_<burst-id-hex>.m4a`, where the hexadecimal
|
||||
component is the 16 lowercase characters representing the same eight-byte
|
||||
burst ID. This lets the receiver replace the transient live capture with the
|
||||
reliable final voice note without adding another wire field.
|
||||
|
||||
If capture is canceled or does not produce a valid recording, the sender emits
|
||||
CANCELED and does not send a final note.
|
||||
|
||||
AAC-LC at 16 kHz uses 1,024 samples per access unit, so each encoded frame
|
||||
represents 64 ms of audio. Live access units are ADTS-less; receivers may add an
|
||||
ADTS header locally for streaming playback or temporary-file assembly.
|
||||
|
||||
## Receiver safety and ordering
|
||||
|
||||
Receivers must fail closed on malformed framing and must bound concurrent
|
||||
assemblies, buffered out-of-order packets, bytes per burst, and inbound byte
|
||||
rate. The Android/Wear OS v1 bounds are:
|
||||
|
||||
- 8 concurrent assemblies;
|
||||
- 384 KiB per burst;
|
||||
- 6,000 inbound bytes per second per assembly, with a two-second initial
|
||||
allowance; and
|
||||
- 128 buffered out-of-order packets.
|
||||
|
||||
Duplicate and already-delivered sequence values are ignored. Android/Wear OS
|
||||
wait 550 ms for a sequence gap before skipping it and finalize an idle partial
|
||||
burst after 3 seconds. These timers are receiver policy rather than additional
|
||||
wire fields.
|
||||
|
||||
## Relay behavior
|
||||
|
||||
Public `VOICE_FRAME` packets use the normal mesh relay path only after signature,
|
||||
sender, timestamp, recipient, and burst validation succeeds. Relays add a small
|
||||
8–25 ms jitter. In a mesh larger than six peers, the relayed TTL is capped at
|
||||
five after the normal per-hop decrement. Live packets remain excluded from
|
||||
gossip sync and file-transfer retransmission.
|
||||
|
||||
Private frames retain the existing `NOISE_ENCRYPTED` relay behavior because an
|
||||
intermediate node cannot inspect the `0x08` inner type.
|
||||
|
||||
## Golden vectors
|
||||
|
||||
For burst ID `01 02 03 04 05 06 07 08`, the canonical encodings are:
|
||||
|
||||
```text
|
||||
# START, sequence 0, AAC-LC/16 kHz/mono
|
||||
01 02 03 04 05 06 07 08 00 00 01 01
|
||||
|
||||
# One data packet, sequence 1, one three-byte frame DE AD BE
|
||||
01 02 03 04 05 06 07 08 00 01 00 00 03 DE AD BE
|
||||
|
||||
# END, sequence 42, 41 data packets, duration 2688 ms
|
||||
01 02 03 04 05 06 07 08 00 2A 02 00 29 00 00 0A 80
|
||||
|
||||
# CANCELED, sequence 3
|
||||
01 02 03 04 05 06 07 08 00 03 04
|
||||
```
|
||||
|
||||
The corresponding executable Android vectors live in
|
||||
`VoiceBurstPacketTest.encodedGoldenVectorsMatchIos`.
|
||||
|
||||
## Implementation references
|
||||
|
||||
- `app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt`
|
||||
- `app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt`
|
||||
- `app/src/main/java/com/bitchat/android/features/voice/VoiceBurstPacket.kt`
|
||||
- `app/src/main/java/com/bitchat/android/features/voice/LiveVoiceManager.kt`
|
||||
- `app/src/test/java/com/bitchat/android/features/voice/VoiceBurstPacketTest.kt`
|
||||
@ -14,7 +14,7 @@ The remaining implementation work and milestone progress are tracked in
|
||||
|---|---|---|
|
||||
| Outer mesh packet | v1/v2 header widths, big-endian fields, flags, section order, route placement, signature placement, padding, compression, signing bytes | `BinaryProtocolTest`, `ClientRewriteWireContractTest` |
|
||||
| Chat payload | Flag bits, millisecond timestamp, UTF-8 byte lengths, encrypted-content substitution, optional-field order | `ClientRewriteWireContractTest` |
|
||||
| Inner payloads | Noise type bytes, private-message TLVs, peer-state TLVs, file-transfer TLVs, fragment header, sync request TLVs | `ClientRewriteWireContractTest`, `AuthenticatedPeerStateTest`, `PrivateMediaTransferPreparerTest`, `FragmentManagerTest` |
|
||||
| Inner payloads | Noise type bytes, private-message TLVs, peer-state TLVs, file-transfer TLVs, live-voice bursts, fragment header, sync request TLVs | `ClientRewriteWireContractTest`, `AuthenticatedPeerStateTest`, `PrivateMediaTransferPreparerTest`, `VoiceBurstPacketTest`, `FragmentManagerTest` |
|
||||
| Identity/security | Announcement extensions, capability bitfield endianness, Noise static-key binding, handshake identity binding, signatures | `IdentityAnnouncementTest`, `NoiseSessionManagerIdentityBindingTest`, `ClientRewritePrimitiveContractTest` |
|
||||
| Sync/routing | Stable packet IDs, GCS bitstream, replay collapse, TTL handling, relay choice, confirmed graph edges | `ClientRewritePrimitiveContractTest`, `GCSFilterTest`, `PacketRelayManagerTest`, `MeshGraphServiceTest`, `TransportBridgeServiceTest` |
|
||||
| Nostr | Bech32, secp256k1 key derivation, NIP-01 event IDs/signatures, NIP-44 authenticated encryption, NIP-13 PoW, authenticated NIP-17 seals | `ClientRewriteNostrContractTest`, `NostrProtocolTest` |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user