use super::ofdm::{ConstellationOrder, OfdmConfig, OfdmMod};
use crate::codec::{crc16, crc32};
use crate::fec::{
Bch, CrcKind, FramePacket, HeaderFormat, InnerFec, InterleaverKind, Ldpc, LdpcCode, OuterFec,
PnScrambler, ReedSolomon, ScramblerKind, ScramblerPos, SeedMode, conv_encode_punctured,
punctured_coded_len,
};
use crate::multicarrier::CarrierPlan;
use crate::sync::{OfdmPreamble, generate_ofdm_preamble};
use num_complex::Complex32 as C32;
use std::sync::{Arc, Mutex};
type CodeMemo<K, V> = Mutex<Vec<(K, Arc<V>)>>;
#[derive(Debug, Default)]
pub struct CodecCache {
ldpc: CodeMemo<LdpcCode, Ldpc>,
bch: CodeMemo<(usize, usize), Bch>,
rs: CodeMemo<(usize, usize), ReedSolomon>,
}
impl Clone for CodecCache {
fn clone(&self) -> Self {
Self::default()
}
}
impl CodecCache {
pub fn new() -> Self {
Self::default()
}
pub fn ldpc(&self, code: LdpcCode) -> Arc<Ldpc> {
let mut table = self.ldpc.lock().unwrap();
if let Some((_, c)) = table.iter().find(|(k, _)| *k == code) {
return Arc::clone(c);
}
let built = Arc::new(Ldpc::new(code));
table.push((code, Arc::clone(&built)));
built
}
pub fn bch(&self, t: usize, msg_bits: usize) -> Arc<Bch> {
let key = (t, msg_bits);
let mut table = self.bch.lock().unwrap();
if let Some((_, c)) = table.iter().find(|(k, _)| *k == key) {
return Arc::clone(c);
}
let built = Arc::new(shortened_bch_for(t, msg_bits));
table.push((key, Arc::clone(&built)));
built
}
pub fn rs(&self, n: usize, n_parity: usize) -> Arc<ReedSolomon> {
let key = (n, n_parity);
let mut table = self.rs.lock().unwrap();
if let Some((_, c)) = table.iter().find(|(k, _)| *k == key) {
return Arc::clone(c);
}
let built = Arc::new(ReedSolomon::new(n, n_parity).expect("valid RS config"));
table.push((key, Arc::clone(&built)));
built
}
}
pub const HEADER_FIELD_BYTES: usize = 14;
pub const HEADER_CONSTELLATION: ConstellationOrder = ConstellationOrder::Bpsk;
pub const HEADER_LDPC: LdpcCode = LdpcCode::N512R12;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mcs {
pub constellation: ConstellationOrder,
pub inner_fec: InnerFec,
pub outer_fec: OuterFec,
}
impl Mcs {
pub const fn new(
constellation: ConstellationOrder,
inner_fec: InnerFec,
outer_fec: OuterFec,
) -> Self {
Self {
constellation,
inner_fec,
outer_fec,
}
}
}
#[derive(Debug, Clone)]
pub struct McsTable {
entries: Vec<Mcs>,
}
impl McsTable {
pub fn new(entries: Vec<Mcs>) -> Self {
assert!(
!entries.is_empty(),
"MCS table must have at least one entry"
);
Self { entries }
}
pub fn default_ladder() -> Self {
let inner = InnerFec::Ldpc(LdpcCode::N512R12);
let outer = OuterFec::Bch { t: 8 };
Self::new(vec![
Mcs::new(ConstellationOrder::Bpsk, inner, outer),
Mcs::new(ConstellationOrder::Qpsk, inner, outer),
Mcs::new(ConstellationOrder::Qam16, inner, outer),
Mcs::new(ConstellationOrder::Qam64, inner, outer),
])
}
pub fn get(&self, mcs_index: u8) -> Option<Mcs> {
self.entries.get(mcs_index as usize).copied()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub fn bytes_to_bits(bytes: &[u8]) -> Vec<u8> {
let mut bits = Vec::with_capacity(bytes.len() * 8);
for &b in bytes {
for i in (0..8).rev() {
bits.push((b >> i) & 1);
}
}
bits
}
pub fn bits_to_bytes(bits: &[u8]) -> Vec<u8> {
assert_eq!(
bits.len() % 8,
0,
"bit count must be a whole number of bytes"
);
let mut bytes = Vec::with_capacity(bits.len() / 8);
for chunk in bits.chunks(8) {
let mut b = 0u8;
for &bit in chunk {
b = (b << 1) | (bit & 1);
}
bytes.push(b);
}
bytes
}
pub fn append_crc(crc: CrcKind, data: &[u8]) -> Vec<u8> {
let mut out = data.to_vec();
match crc {
CrcKind::None => {}
CrcKind::Crc16 => out.extend_from_slice(&crc16(data).to_be_bytes()),
CrcKind::Crc32 => out.extend_from_slice(&crc32(data).to_be_bytes()),
}
out
}
pub fn check_and_strip_crc(crc: CrcKind, data: &[u8]) -> Option<(Vec<u8>, bool)> {
let clen = crc.len_bytes();
if data.len() < clen {
return None;
}
let (payload, tail) = data.split_at(data.len() - clen);
let ok = match crc {
CrcKind::None => true,
CrcKind::Crc16 => crc16(payload).to_be_bytes()[..] == *tail,
CrcKind::Crc32 => crc32(payload).to_be_bytes()[..] == *tail,
};
Some((payload.to_vec(), ok))
}
pub fn build_scrambler(kind: ScramblerKind, per_frame_seed: u32) -> Option<PnScrambler> {
match kind {
ScramblerKind::None => None,
ScramblerKind::Additive { poly, width, seed } => {
let raw = match seed {
SeedMode::Fixed(v) => v,
SeedMode::PerFrameRandom => per_frame_seed,
};
let mask = if width >= 32 {
u32::MAX
} else {
(1u32 << width) - 1
};
let s = {
let m = raw & mask;
if m == 0 { 1 } else { m }
};
Some(PnScrambler::new(poly, width as u32, s))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockPlan {
pub info_bytes: usize,
pub framed_bytes: usize,
pub outer_coded_bits: usize,
pub outer_il_bits: usize,
pub inner_coded_bits: usize,
pub coded_bits: usize,
}
fn round_up(n: usize, block: usize) -> usize {
if block == 0 {
n
} else {
n.div_ceil(block) * block
}
}
pub fn block_plan(
info_bytes: usize,
crc: CrcKind,
outer: OuterFec,
inner: InnerFec,
outer_il: InterleaverKind,
inner_il: InterleaverKind,
cache: &CodecCache,
) -> BlockPlan {
let framed_bytes = info_bytes + crc.len_bytes();
let framed_bits = framed_bytes * 8;
let outer_coded_bits = match outer {
OuterFec::None => framed_bits,
OuterFec::Bch { t } => {
let code = cache.bch(t, BCH_INFO_BITS);
let n_blocks = framed_bits.div_ceil(BCH_INFO_BITS);
n_blocks * code.n()
}
OuterFec::ReedSolomon { n, n_parity } => {
let rs = cache.rs(n, n_parity);
let n_blocks = framed_bytes.div_ceil(rs.k());
n_blocks * rs.n() * 8
}
};
let outer_il_bits = match outer_il {
InterleaverKind::None => outer_coded_bits,
InterleaverKind::Block { rows, cols } => round_up(outer_coded_bits, rows * cols),
};
let inner_coded_bits = match inner {
InnerFec::None => outer_il_bits,
InnerFec::Ldpc(code) => {
let ldpc = cache.ldpc(code);
let n_blocks = outer_il_bits.div_ceil(ldpc.k());
n_blocks * ldpc.n()
}
InnerFec::Convolutional { rate } => punctured_coded_len(outer_il_bits, rate),
};
let coded_bits = match inner_il {
InterleaverKind::None => inner_coded_bits,
InterleaverKind::Block { rows, cols } => round_up(inner_coded_bits, rows * cols),
};
BlockPlan {
info_bytes,
framed_bytes,
outer_coded_bits,
outer_il_bits,
inner_coded_bits,
coded_bits,
}
}
pub fn symbols_for_coded_bits(
base: &OfdmConfig,
constellation: ConstellationOrder,
bits: usize,
) -> usize {
let bps = base.carrier_plan.data_carriers().len() * constellation.bits_per_symbol();
bits.div_ceil(bps)
}
pub fn interleave_bits(il: InterleaverKind, bits: &[u8]) -> Vec<u8> {
match il {
InterleaverKind::None => bits.to_vec(),
InterleaverKind::Block { rows, cols } => {
let block = rows * cols;
let bi = crate::fec::BlockInterleaver::new(rows, cols);
let mut out = Vec::with_capacity(bits.len().div_ceil(block) * block);
let mut padded = vec![0u8; block];
let mut permuted = vec![0u8; block];
for chunk in bits.chunks(block) {
padded[..chunk.len()].copy_from_slice(chunk);
padded[chunk.len()..].fill(0);
bi.interleave(&padded, &mut permuted);
out.extend_from_slice(&permuted);
}
out
}
}
}
pub const BCH_INFO_BITS: usize = 120;
pub fn outer_encode(outer: OuterFec, message_bytes: &[u8], cache: &CodecCache) -> Vec<u8> {
match outer {
OuterFec::None => bytes_to_bits(message_bytes),
OuterFec::Bch { t } => {
let msg_bits = bytes_to_bits(message_bytes);
let code = cache.bch(t, BCH_INFO_BITS);
let mut out = Vec::new();
for chunk in msg_bits.chunks(BCH_INFO_BITS) {
let mut block = chunk.to_vec();
block.resize(BCH_INFO_BITS, 0);
out.extend_from_slice(&code.encode(&block));
}
out
}
OuterFec::ReedSolomon { n, n_parity } => {
let rs = cache.rs(n, n_parity);
let k = rs.k();
let mut out_bytes = Vec::new();
for chunk in message_bytes.chunks(k) {
let mut block = chunk.to_vec();
block.resize(k, 0);
out_bytes.extend_from_slice(&rs.encode(&block));
}
bytes_to_bits(&out_bytes)
}
}
}
pub fn inner_encode(inner: InnerFec, info_bits: &[u8], cache: &CodecCache) -> Vec<u8> {
match inner {
InnerFec::None => info_bits.to_vec(),
InnerFec::Ldpc(code) => {
let ldpc = cache.ldpc(code);
let k = ldpc.k();
let mut out = Vec::new();
for chunk in info_bits.chunks(k) {
let mut msg = chunk.to_vec();
msg.resize(k, 0);
out.extend_from_slice(&ldpc.encode(&msg));
}
out
}
InnerFec::Convolutional { rate } => conv_encode_punctured(info_bits, rate),
}
}
pub fn shortened_bch_for(t: usize, msg_bits: usize) -> Bch {
let full = Bch::new(t).expect("valid BCH t");
let parity = full.parity_bits();
Bch::shortened(msg_bits + parity, t).expect("valid shortened BCH")
}
#[allow(clippy::too_many_arguments)]
pub fn encode_chain(
bytes: &[u8],
crc: CrcKind,
outer: OuterFec,
inner: InnerFec,
outer_il: InterleaverKind,
inner_il: InterleaverKind,
scrambler: ScramblerKind,
scrambler_pos: ScramblerPos,
per_frame_seed: u32,
cache: &CodecCache,
) -> Vec<u8> {
let mut framed = append_crc(crc, bytes);
let sc = build_scrambler(scrambler, per_frame_seed);
if scrambler_pos == ScramblerPos::BeforeOuterFec
&& let Some(ref s) = sc
{
s.scramble(&mut framed);
}
let outer_bits = outer_encode(outer, &framed, cache);
let outer_il_bits = interleave_bits(outer_il, &outer_bits);
let inner_bits = inner_encode(inner, &outer_il_bits, cache);
let mut coded = interleave_bits(inner_il, &inner_bits);
if scrambler_pos == ScramblerPos::AfterInnerFec
&& let Some(ref s) = sc
{
scramble_bits(s, &mut coded);
}
coded
}
pub fn scramble_bits(s: &PnScrambler, bits: &mut [u8]) {
let mut bytes = pack_bits_padded(bits);
s.scramble(&mut bytes);
let unpacked = bytes_to_bits(&bytes);
bits.copy_from_slice(&unpacked[..bits.len()]);
}
fn pack_bits_padded(bits: &[u8]) -> Vec<u8> {
let mut padded = bits.to_vec();
let rem = padded.len() % 8;
if rem != 0 {
padded.resize(padded.len() + (8 - rem), 0);
}
bits_to_bytes(&padded)
}
pub fn pack_header_fields(
mcs_index: u8,
payload_len: u32,
sequence_num: u32,
flags: u8,
scrambler_seed: u32,
) -> [u8; HEADER_FIELD_BYTES] {
let mut out = [0u8; HEADER_FIELD_BYTES];
out[0] = mcs_index;
out[1..5].copy_from_slice(&payload_len.to_be_bytes());
out[5..9].copy_from_slice(&sequence_num.to_be_bytes());
out[9] = flags;
out[10..14].copy_from_slice(&scrambler_seed.to_be_bytes());
out
}
fn map_bits_to_iq(base: &OfdmConfig, constellation: ConstellationOrder, bits: &[u8]) -> Vec<C32> {
let cfg = symbol_config(base, constellation);
let mut modstage = OfdmMod::new(&cfg);
modstage.modulate(bits)
}
pub fn symbol_config(base: &OfdmConfig, constellation: ConstellationOrder) -> OfdmConfig {
OfdmConfig::new(
base.carrier_plan.clone(),
base.fs,
base.rf_hz,
base.gain,
constellation,
)
}
#[derive(Debug, Clone)]
pub struct OfdmFrameMod {
cfg: OfdmConfig,
mcs_table: McsTable,
preamble: OfdmPreamble,
cache: Arc<CodecCache>,
}
impl OfdmFrameMod {
pub fn new(cfg: OfdmConfig, mcs_table: McsTable, preamble: OfdmPreamble) -> Self {
Self::with_cache(cfg, mcs_table, preamble, Arc::new(CodecCache::new()))
}
pub fn with_cache(
cfg: OfdmConfig,
mcs_table: McsTable,
preamble: OfdmPreamble,
cache: Arc<CodecCache>,
) -> Self {
Self {
cfg,
mcs_table,
preamble,
cache,
}
}
pub fn config(&self) -> &OfdmConfig {
&self.cfg
}
pub fn preamble(&self) -> &OfdmPreamble {
&self.preamble
}
pub fn modulate_frame(&self, frame: &FramePacket, per_frame_seed: u32) -> Vec<C32> {
let mut out = Vec::new();
out.extend_from_slice(&generate_ofdm_preamble(&self.preamble, &self.cfg));
if self.cfg.header_format == HeaderFormat::OrionSdr {
let fields = pack_header_fields(
frame.metadata.mcs_index,
frame.payload.len() as u32,
frame.metadata.sequence_num,
frame.metadata.flags,
per_frame_seed,
);
let header_bits = encode_chain(
&fields,
self.cfg.header_crc,
OuterFec::None,
InnerFec::Ldpc(HEADER_LDPC),
InterleaverKind::None,
InterleaverKind::None,
ScramblerKind::None,
ScramblerPos::BeforeOuterFec,
0,
&self.cache,
);
out.extend_from_slice(&map_bits_to_iq(
&self.cfg,
HEADER_CONSTELLATION,
&header_bits,
));
}
let mcs = self
.mcs_table
.get(frame.metadata.mcs_index)
.expect("mcs_index must be in the MCS table");
let payload_bits = encode_chain(
&frame.payload,
self.cfg.payload_crc,
mcs.outer_fec,
mcs.inner_fec,
self.cfg.outer_interleaver,
self.cfg.inner_interleaver,
self.cfg.scrambler,
self.cfg.scrambler_pos,
per_frame_seed,
&self.cache,
);
out.extend_from_slice(&map_bits_to_iq(&self.cfg, mcs.constellation, &payload_bits));
out
}
}
pub fn plan_of(cfg: &OfdmConfig) -> CarrierPlan {
cfg.carrier_plan.clone()
}