import 'dart:typed_data'; import '../crypto/aes_cipher.dart'; import '../crypto/key_manager.dart'; import '../fec/reed_solomon.dart'; import '../modem/fsk_modulator.dart'; import '../../utils/audio_math.dart'; import '../../utils/constants.dart'; /// Transmit pipeline: voice bytes → packet → encrypt → RS FEC → FSK audio. /// /// Call [frameAndModulate] with each LPC super-frame (9 bytes) to get back /// a Float64List of audio samples ready for AudioTrack playback. class Framer { final KeyManager _keys; final Uint8List _sessionId; // 4-byte session identifier final FskModulator _modem = FskModulator(); int _seq = 0; // rolling 16-bit sequence counter Framer(this._keys, this._sessionId); // ── Public API ───────────────────────────────────────────────────── /// Full transmit pipeline for one 9-byte LPC payload. /// /// Steps: /// 1. Pad payload to 11 bytes (add seq-high + type tag). /// 2. Encrypt with AES-256-CTR. /// 3. Build raw packet (SYNC + SEQ + TYPE + LEN + encrypted payload + CRC). /// 4. Apply RS(15,11) FEC to the 11-byte encrypted payload → 15 bytes. /// 5. Re-assemble the on-wire frame with FEC-encoded payload. /// 6. Modulate through 4-FSK → audio samples. Float64List frameAndModulate(Uint8List lpcPayload) { assert(lpcPayload.length == 9, 'Framer: LPC payload must be 9 bytes'); final seq = _seq & 0xFFFF; _seq = (_seq + 1) & 0xFFFF; // ── Step 1: Pad payload → 11 bytes ───────────────────────────── // Layout: [lpc(9)] [seq_low(1)] [type(1)] final plaintext = Uint8List(C.pktPayloadLen); // 11 bytes plaintext.setRange(0, 9, lpcPayload); plaintext[9] = seq & 0xFF; plaintext[10] = C.pkTypeVoice; // ── Step 2: AES-256-CTR encryption ──────────────────────────── final nonce = _keys.buildNonce(_sessionId, seq); final encrypted = AesCipher.encrypt(plaintext, _keys.key, nonce); // ── Step 4: RS FEC — encode the 11-byte encrypted payload only ── final rsEncoded = ReedSolomon.encodeBlock(encrypted); // 11 B → 15 B // ── Step 5: Assemble on-wire frame (23 bytes) ────────────────── // [SYNC:2][SEQ:2][TYPE:1][RS_PAYLOAD:15][CRC:2] = 22 bytes // Recompute CRC over the RS-encoded block for the wire frame. final wire = Uint8List(22); wire[0] = C.syncWord[0]; wire[1] = C.syncWord[1]; wire[2] = (seq >> 8) & 0xFF; wire[3] = seq & 0xFF; wire[4] = C.pkTypeVoice; wire.setRange(5, 20, rsEncoded); // 15 bytes RS-encoded payload final wireCrc = AudioMath.crc16(wire.sublist(2, 20)); // CRC over [2..19] wire[20] = (wireCrc >> 8) & 0xFF; wire[21] = wireCrc & 0xFF; // ── Step 6: 4-FSK modulation ──────────────────────────────────── return _modem.modulatePacket(wire); } /// Modulate a raw control packet (handshake, ping, etc.). Float64List frameControl(int type, Uint8List payload) { final seq = _seq & 0xFFFF; _seq = (_seq + 1) & 0xFFFF; final padded = Uint8List(C.pktPayloadLen); padded.setRange(0, payload.length.clamp(0, C.pktPayloadLen), payload); final nonce = _keys.buildNonce(_sessionId, seq); final encrypted = AesCipher.encrypt(padded, _keys.key, nonce); final rsEncoded = ReedSolomon.encodeBlock(encrypted); final wire = Uint8List(22); wire[0] = C.syncWord[0]; wire[1] = C.syncWord[1]; wire[2] = (seq >> 8) & 0xFF; wire[3] = seq & 0xFF; wire[4] = type; wire.setRange(5, 20, rsEncoded); final wireCrc = AudioMath.crc16(wire.sublist(2, 20)); wire[20] = (wireCrc >> 8) & 0xFF; wire[21] = wireCrc & 0xFF; return _modem.modulatePacket(wire); } void reset() { _seq = 0; _modem.reset(); } }