Neda/call/lib/utils/constants.dart

116 lines
5.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ignore_for_file: constant_identifier_names
import 'dart:math' as math;
/// All system-wide numeric constants for the secure-voice pipeline.
///
/// Design target:
/// modem gross rate : 1 200 bps (4-FSK, 600 baud, 2 bits/symbol)
/// RS(15,11) FEC net : ~880 bps
/// voice codec : ~360 bps (LPC, 200 ms super-frames)
/// one-way latency : ~250 ms (frame + processing + buffer)
abstract final class C {
// ── Sample rate ────────────────────────────────────────────────────
/// Must match the cellular audio path (PCM 8 kHz narrowband).
static const int sampleRate = 8000;
// ── 4-FSK modem ────────────────────────────────────────────────────
/// Symbol rate in baud. 600 baud × 2 bits/symbol = 1 200 bps gross.
static const int baudRate = 600;
/// Samples per symbol as a float (8000 / 600 = 13.333…).
/// The modulator uses a phase accumulator; the demodulator uses an
/// earlylate gate timing recovery loop to handle the fraction.
static const double samplesPerSymbol = sampleRate / baudRate;
/// 4-FSK tone frequencies (Hz). Spaced 400 Hz apart, centred in
/// the 3003400 Hz cellular pass-band; chosen to survive AMR-NB codec.
/// symbol dibit → frequency
/// 00 → tone0 01 → tone1 10 → tone2 11 → tone3
static const List<double> fskToneHz = [1000.0, 1400.0, 1800.0, 2200.0];
/// Amplitude of the transmitted FSK signal (01 normalised).
/// Kept below 0.7 to avoid clipping after phone mic AGC compresses it.
static const double txAmplitude = 0.60;
/// Preamble: alternating dibit 00/11 repeated this many times before
/// each packet. Used by the demodulator for timing acquisition.
static const int preambleSymbols = 24;
/// Two-byte wire sync-word placed after preamble (0x5A, 0xA5).
static const List<int> syncWord = [0x5A, 0xA5];
// ── Packet layout (pre-FEC plaintext frame) ────────────────────────
/// Bytes: [SYNC(2)] [SEQ(2)] [TYPE(1)] [LEN(1)] [PAYLOAD(11)] [CRC16(2)]
static const int pktSyncLen = 2;
static const int pktSeqLen = 2;
static const int pktTypeLen = 1;
static const int pktLenLen = 1;
static const int pktPayloadLen = 11; // encrypted voice frame bytes
static const int pktCrcLen = 2;
static const int pktTotalBytes = pktSyncLen + pktSeqLen + pktTypeLen +
pktLenLen + pktPayloadLen + pktCrcLen; // 19 B
/// Packet type codes.
static const int pkTypeVoice = 0x01;
static const int pkTypeControl = 0x02;
static const int pkTypeHandshake = 0x03;
static const int pkTypePing = 0x04;
// ── Reed-Solomon RS(15,11) over GF(16) ────────────────────────────
/// RS codeword length n (nibbles).
static const int rsN = 15;
/// RS data symbols per codeword k (nibbles).
static const int rsK = 11;
/// RS parity symbols per codeword (n-k).
static const int rsParity = rsN - rsK; // 4
/// Correction capability: t = parity/2 = 2 nibble errors per codeword.
static const int rsT = rsParity ~/ 2;
// ── LPC voice codec ────────────────────────────────────────────────
/// Sub-frame size (samples). One LPC analysis frame = 20 ms at 8 kHz.
static const int lpcSubframeSamples = 160; // 20 ms
/// Number of sub-frames in one super-frame (transmitted as a packet).
static const int lpcSubframesPerSuper = 10; // 200 ms total
/// LPC analysis order (number of predictor coefficients).
static const int lpcOrder = 10;
/// Minimum pitch period in samples (max fundamental ~400 Hz).
static const int pitchMinSamples = 20;
/// Maximum pitch period in samples (min fundamental ~50 Hz).
static const int pitchMaxSamples = 160;
/// Pre-emphasis coefficient. Applied as s'[n] = s[n] - alpha·s[n-1].
static const double preEmphasis = 0.97;
// ── AES-256-CTR ────────────────────────────────────────────────────
/// Key length in bytes.
static const int aesKeyBytes = 32;
/// Nonce length in bytes (packed from session-ID + packet-seq).
static const int aesNonceBytes = 16; // AES block size = 128 bits
// ── PBKDF2 ─────────────────────────────────────────────────────────
static const int pbkdf2Iterations = 100000;
static const int pbkdf2SaltBytes = 16;
// ── AGC / Signal conditioning ──────────────────────────────────────
/// Target RMS level for received signal (normalised 01).
static const double agcTargetRms = 0.15;
/// AGC attack time constant (samples). Fast attack.
static const double agcAlpha = 0.001;
// ── Goertzel window length (must be ≥ samplesPerSymbol) ───────────
/// We snap to 14 integer samples which is the nearest integer above
/// samplesPerSymbol. Timing recovery compensates for the 0.67 sample drift.
static const int goertzelWindow = 14;
// ── Derived constants ──────────────────────────────────────────────
/// Pre-computed Goertzel coefficients for each FSK tone.
/// coeff[i] = 2 · cos(2π · f_i / sampleRate)
static final List<double> goertzelCoeff = fskToneHz
.map((f) => 2.0 * math.cos(2.0 * math.pi * f / sampleRate))
.toList(growable: false);
}