use core::fmt;
use crate::error::ConfigError;
use crate::types::SampleRate;
mod fec;
mod modem;
pub use fec::*;
pub use modem::*;
use fec::{get_bit, set_bit};
use modem::{MAX_TAPS, checked_sps, design_rrc};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum M17Error {
CallsignLength {
len: usize,
},
CallsignChar {
ch: char,
},
ReservedAddress {
value: u64,
},
AddressRange {
value: u64,
},
PayloadTooLong {
len: usize,
},
Crc,
SampleRateInexact {
got: u32,
},
Config(ConfigError),
}
impl fmt::Display for M17Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::CallsignLength { len } => {
write!(f, "callsign must be 1..=9 characters, got {len}")
}
Self::CallsignChar { ch } => {
write!(f, "character {ch:?} is outside the M17 base-40 alphabet")
}
Self::ReservedAddress { value } => {
write!(f, "address {value:#014x} is in the reserved range")
}
Self::AddressRange { value } => {
write!(f, "address {value:#x} does not fit in 48 bits")
}
Self::PayloadTooLong { len } => write!(
f,
"packet payload is {len} bytes, maximum is {MAX_PACKET_PAYLOAD}"
),
Self::Crc => write!(f, "CRC-16 mismatch"),
Self::SampleRateInexact { got } => write!(
f,
"sample rate {got} Hz is not a multiple of the 4800 Hz symbol rate"
),
Self::Config(e) => write!(f, "{e}"),
}
}
}
impl core::error::Error for M17Error {}
impl From<ConfigError> for M17Error {
fn from(e: ConfigError) -> Self {
Self::Config(e)
}
}
pub const CRC16_POLY: u16 = 0x5935;
pub const CRC16_INIT: u16 = 0xFFFF;
#[must_use]
pub fn crc16(data: &[u8]) -> u16 {
let mut crc = CRC16_INIT;
for &byte in data {
crc ^= u16::from(byte) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ CRC16_POLY;
} else {
crc <<= 1;
}
}
}
crc
}
const BASE40: &[u8; 40] = b" ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.";
pub const BROADCAST_ADDRESS: u64 = 0xFFFF_FFFF_FFFF;
pub const MAX_CALLSIGN_ADDRESS: u64 = 40u64.pow(9) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Address(u64);
impl Address {
#[must_use]
pub const fn broadcast() -> Self {
Self(BROADCAST_ADDRESS)
}
#[must_use]
pub const fn is_broadcast(self) -> bool {
self.0 == BROADCAST_ADDRESS
}
pub fn from_callsign(callsign: &str) -> Result<Self, M17Error> {
let bytes = callsign.as_bytes();
if bytes.is_empty() || bytes.len() > 9 {
return Err(M17Error::CallsignLength { len: bytes.len() });
}
let mut value: u64 = 0;
for &b in bytes.iter().rev() {
let up = b.to_ascii_uppercase();
let idx = BASE40[1..]
.iter()
.position(|&c| c == up)
.ok_or(M17Error::CallsignChar { ch: char::from(b) })?;
value = value * 40 + (idx as u64 + 1);
}
Ok(Self(value))
}
pub const fn from_raw(value: u64) -> Result<Self, M17Error> {
if value > BROADCAST_ADDRESS {
return Err(M17Error::AddressRange { value });
}
if value == 0 || (value > MAX_CALLSIGN_ADDRESS && value != BROADCAST_ADDRESS) {
return Err(M17Error::ReservedAddress { value });
}
Ok(Self(value))
}
#[must_use]
pub const fn raw(self) -> u64 {
self.0
}
pub fn callsign(self, buf: &mut [u8; 9]) -> &str {
if self.is_broadcast() {
buf[..4].copy_from_slice(b"@ALL");
return core::str::from_utf8(&buf[..4]).unwrap_or("@ALL");
}
let mut v = self.0;
let mut n = 0;
while v > 0 && n < 9 {
buf[n] = BASE40[(v % 40) as usize];
v /= 40;
n += 1;
}
core::str::from_utf8(&buf[..n]).unwrap_or("")
}
}
pub const LSF_BYTES: usize = 30;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Lsf {
pub dst: Address,
pub src: Address,
pub lsf_type: u16,
pub meta: [u8; 14],
}
impl Lsf {
#[must_use]
pub const fn packet_data(dst: Address, src: Address, can: u8) -> Self {
let lsf_type = (0b01 << 1) | ((can as u16 & 0xF) << 7);
Self {
dst,
src,
lsf_type,
meta: [0; 14],
}
}
#[must_use]
pub fn to_bytes(&self) -> [u8; LSF_BYTES] {
let mut out = [0u8; LSF_BYTES];
out[..6].copy_from_slice(&self.dst.raw().to_be_bytes()[2..]);
out[6..12].copy_from_slice(&self.src.raw().to_be_bytes()[2..]);
out[12..14].copy_from_slice(&self.lsf_type.to_be_bytes());
out[14..28].copy_from_slice(&self.meta);
let crc = crc16(&out[..28]);
out[28..].copy_from_slice(&crc.to_be_bytes());
out
}
pub fn from_bytes(bytes: &[u8; LSF_BYTES]) -> Result<Self, M17Error> {
let crc = u16::from_be_bytes([bytes[28], bytes[29]]);
if crc != crc16(&bytes[..28]) {
return Err(M17Error::Crc);
}
let mut d = [0u8; 8];
d[2..].copy_from_slice(&bytes[..6]);
let dst = Address::from_raw(u64::from_be_bytes(d))?;
let mut s = [0u8; 8];
s[2..].copy_from_slice(&bytes[6..12]);
let src = Address::from_raw(u64::from_be_bytes(s))?;
let lsf_type = u16::from_be_bytes([bytes[12], bytes[13]]);
let mut meta = [0u8; 14];
meta.copy_from_slice(&bytes[14..28]);
Ok(Self {
dst,
src,
lsf_type,
meta,
})
}
}
pub const SYNC_LSF: u16 = 0x55F7;
pub const SYNC_STREAM: u16 = 0xFF5D;
pub const SYNC_PACKET: u16 = 0x75FF;
pub const SYNC_BERT: u16 = 0xDF55;
pub const EOT_MARKER: u16 = 0x555D;
pub const PACKET_FRAME_PAYLOAD: usize = 25;
pub const MAX_PACKET_PAYLOAD: usize = 33 * PACKET_FRAME_PAYLOAD - 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PacketFrame {
pub data: [u8; PACKET_FRAME_PAYLOAD],
pub eof: bool,
pub counter: u8,
}
impl PacketFrame {
#[must_use]
pub fn to_content(&self) -> [u8; 26] {
let mut out = [0u8; 26];
out[..PACKET_FRAME_PAYLOAD].copy_from_slice(&self.data);
out[25] = (u8::from(self.eof) << 7) | ((self.counter & 0x1F) << 2);
out
}
#[must_use]
pub fn from_content(content: &[u8; 26]) -> Self {
let mut data = [0u8; PACKET_FRAME_PAYLOAD];
data.copy_from_slice(&content[..PACKET_FRAME_PAYLOAD]);
Self {
data,
eof: content[25] & 0x80 != 0,
counter: (content[25] >> 2) & 0x1F,
}
}
}
#[must_use]
pub fn packet_frame_encode(frame: &PacketFrame) -> [u8; FRAME_BYTES] {
let content = frame.to_content();
let mut coded = [0u8; 53]; let n = convolutional_encode(&content, 206, &mut coded);
debug_assert_eq!(n, 420);
let mut punctured = [0u8; FRAME_BYTES];
let kept = puncture(&coded, n, &PUNCTURE_P3, &mut punctured);
debug_assert_eq!(kept, FRAME_BITS);
let mut out = interleave(&punctured);
randomize(&mut out);
out
}
#[must_use]
pub fn packet_frame_decode(frame: &[u8; FRAME_BYTES]) -> (PacketFrame, u32) {
let mut f = *frame;
randomize(&mut f);
let deint = deinterleave(&f);
let mut bits = [0u8; 420];
let mut known = [false; 420];
depuncture(&deint, FRAME_BITS, &PUNCTURE_P3, 420, &mut bits, &mut known);
let mut content = [0u8; 26];
let metric = viterbi_decode(&bits, &known, 206, &mut content);
(PacketFrame::from_content(&content), metric)
}
#[must_use]
pub fn lsf_encode(lsf: &Lsf) -> [u8; FRAME_BYTES] {
let bytes = lsf.to_bytes();
let mut coded = [0u8; 61]; let n = convolutional_encode(&bytes, 240, &mut coded);
debug_assert_eq!(n, 488);
let mut punctured = [0u8; FRAME_BYTES];
let kept = puncture(&coded, n, &PUNCTURE_P1, &mut punctured);
debug_assert_eq!(kept, FRAME_BITS);
let mut out = interleave(&punctured);
randomize(&mut out);
out
}
pub fn lsf_decode(frame: &[u8; FRAME_BYTES]) -> Result<Lsf, M17Error> {
let mut f = *frame;
randomize(&mut f);
let deint = deinterleave(&f);
let mut bits = [0u8; 488];
let mut known = [false; 488];
depuncture(&deint, FRAME_BITS, &PUNCTURE_P1, 488, &mut bits, &mut known);
let mut content = [0u8; LSF_BYTES];
let _metric = viterbi_decode(&bits, &known, 240, &mut content);
Lsf::from_bytes(&content)
}
pub const PREAMBLE_SYMBOLS: usize = FRAME_SYMBOLS;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TxSection {
Preamble,
Frame,
Eot,
Flush,
Done,
}
#[derive(Debug, Clone)]
pub struct M17PacketTx<'a> {
modulator: M17Modulator,
lsf: Lsf,
payload: &'a [u8],
crc: u16,
section: TxSection,
frame_bits: [u8; FRAME_BYTES],
sync: u16,
symbol: usize,
next_frame: usize,
frames_total: usize,
}
impl<'a> M17PacketTx<'a> {
pub fn new(sample_rate: SampleRate, lsf: Lsf, payload: &'a [u8]) -> Result<Self, M17Error> {
if payload.len() > MAX_PACKET_PAYLOAD {
return Err(M17Error::PayloadTooLong { len: payload.len() });
}
let total = payload.len() + 2;
let frames_total = total.div_ceil(PACKET_FRAME_PAYLOAD);
Ok(Self {
modulator: M17Modulator::new(sample_rate)?,
lsf,
payload,
crc: crc16(payload),
section: TxSection::Preamble,
frame_bits: [0; FRAME_BYTES],
sync: SYNC_LSF,
symbol: 0,
next_frame: usize::MAX,
frames_total,
})
}
fn stream_byte(&self, i: usize) -> u8 {
if i < self.payload.len() {
self.payload[i]
} else if i == self.payload.len() {
(self.crc >> 8) as u8
} else {
(self.crc & 0xFF) as u8
}
}
fn load_next_frame(&mut self) -> bool {
if self.next_frame == usize::MAX {
self.frame_bits = lsf_encode(&self.lsf);
self.sync = SYNC_LSF;
self.next_frame = 0;
return true;
}
let k = self.next_frame;
if k >= self.frames_total {
return false;
}
let total = self.payload.len() + 2;
let start = k * PACKET_FRAME_PAYLOAD;
let take = (total - start).min(PACKET_FRAME_PAYLOAD);
let mut data = [0u8; PACKET_FRAME_PAYLOAD];
for (j, d) in data.iter_mut().enumerate().take(take) {
*d = self.stream_byte(start + j);
}
let eof = k + 1 == self.frames_total;
let frame = PacketFrame {
data,
eof,
counter: if eof { take as u8 } else { (k & 0x1F) as u8 },
};
self.frame_bits = packet_frame_encode(&frame);
self.sync = SYNC_PACKET;
self.next_frame = k + 1;
true
}
fn feed_next_symbol(&mut self) -> bool {
loop {
match self.section {
TxSection::Preamble => {
if self.symbol < PREAMBLE_SYMBOLS {
let s = if self.symbol.is_multiple_of(2) { 3 } else { -3 };
self.modulator.feed(s);
self.symbol += 1;
return true;
}
if self.load_next_frame() {
self.section = TxSection::Frame;
self.symbol = 0;
} else {
self.section = TxSection::Eot;
self.symbol = 0;
}
}
TxSection::Frame => {
if self.symbol < FRAME_SYMBOLS {
let s = if self.symbol < 8 {
sync_symbols(self.sync)[self.symbol]
} else {
let bit_idx = (self.symbol - 8) * 2;
let dibit = (get_bit(&self.frame_bits, bit_idx) << 1)
| get_bit(&self.frame_bits, bit_idx + 1);
dibit_to_symbol(dibit)
};
self.modulator.feed(s);
self.symbol += 1;
return true;
}
if self.load_next_frame() {
self.symbol = 0;
} else {
self.section = TxSection::Eot;
self.symbol = 0;
}
}
TxSection::Eot => {
if self.symbol < FRAME_SYMBOLS {
let i = self.symbol % 8;
let dibit = ((EOT_MARKER >> (14 - 2 * i)) & 0b11) as u8;
self.modulator.feed(dibit_to_symbol(dibit));
self.symbol += 1;
return true;
}
self.section = TxSection::Flush;
self.symbol = 0;
}
TxSection::Flush => {
if self.symbol < RRC_SPAN_SYMBOLS + 1 {
self.modulator.feed(0);
self.symbol += 1;
return true;
}
self.section = TxSection::Done;
}
TxSection::Done => return false,
}
}
}
pub fn next_i16(&mut self) -> Option<i16> {
loop {
if let Some(s) = self.modulator.next_i16() {
return Some(s);
}
if !self.feed_next_symbol() {
return None;
}
}
}
}
impl Iterator for M17PacketTx<'_> {
type Item = i16;
fn next(&mut self) -> Option<i16> {
self.next_i16()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum M17FrameEvent {
Lsf(Lsf),
PacketFrame(PacketFrame),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RxState {
Hunt,
Confirm,
Collect,
}
#[derive(Debug, Clone)]
pub struct M17Receiver {
taps: [i32; MAX_TAPS],
ntaps: usize,
sps: usize,
delay: [i16; MAX_TAPS],
dpos: usize,
hist: [i32; MAX_TAPS],
hpos: usize,
state: RxState,
best_corr: i64,
best_sync: u16,
best_age: usize,
confirm_left: usize,
unit: i32,
countdown: usize,
expect_sync: bool,
sync_acc: u16,
sync_count: u8,
frame_bits: [u8; FRAME_BYTES],
nsyms: usize,
frame_sync: u16,
}
impl M17Receiver {
pub fn new(sample_rate: SampleRate) -> Result<Self, M17Error> {
let sps = checked_sps(sample_rate)?;
let mut f = [0.0f64; MAX_TAPS];
let ntaps = design_rrc(sps, &mut f);
let mut taps = [0i32; MAX_TAPS];
for (q, &h) in taps.iter_mut().zip(f.iter()).take(ntaps) {
*q = (h * 8_192.0 + if h >= 0.0 { 0.5 } else { -0.5 }) as i32;
}
Ok(Self {
taps,
ntaps,
sps,
delay: [0; MAX_TAPS],
dpos: 0,
hist: [0; MAX_TAPS],
hpos: 0,
state: RxState::Hunt,
best_corr: 0,
best_sync: 0,
best_age: 0,
confirm_left: 0,
unit: 0,
countdown: 0,
expect_sync: false,
sync_acc: 0,
sync_count: 0,
frame_bits: [0; FRAME_BYTES],
nsyms: 0,
frame_sync: 0,
})
}
#[inline]
fn hist_at(&self, back: usize) -> i32 {
let len = self.hist.len();
self.hist[(self.hpos + len - 1 - back) % len]
}
fn sync_correlate(&self, sync: u16) -> (i64, bool) {
let syms = sync_symbols(sync);
let mut corr: i64 = 0;
let mut min_mag = i64::MAX;
let mut max_mag = 0i64;
let mut signs_ok = true;
for (k, &s) in syms.iter().enumerate() {
let y = i64::from(self.hist_at((7 - k) * self.sps));
corr += i64::from(s) * y;
let mag = y.abs();
min_mag = min_mag.min(mag);
max_mag = max_mag.max(mag);
if (s > 0) != (y > 0) {
signs_ok = false;
}
}
let plausible = signs_ok && min_mag * 2 >= max_mag && max_mag > 500;
(corr, plausible)
}
pub fn push_i16(&mut self, sample: i16) -> Option<M17FrameEvent> {
self.delay[self.dpos] = sample;
self.dpos = (self.dpos + 1) % self.ntaps;
let mut acc: i64 = 0;
for i in 0..self.ntaps {
let x = self.delay[(self.dpos + i) % self.ntaps];
acc += i64::from(x) * i64::from(self.taps[self.ntaps - 1 - i]);
}
let y = (acc >> 13).clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32;
let hlen = self.hist.len();
self.hist[self.hpos] = y;
self.hpos = (self.hpos + 1) % hlen;
match self.state {
RxState::Hunt | RxState::Confirm => {
let (c_lsf, ok_lsf) = self.sync_correlate(SYNC_LSF);
let (c_pkt, ok_pkt) = self.sync_correlate(SYNC_PACKET);
let (corr, sync, strong) = if ok_lsf && (!ok_pkt || c_lsf >= c_pkt) {
(c_lsf, SYNC_LSF, true)
} else if ok_pkt {
(c_pkt, SYNC_PACKET, true)
} else {
(0, SYNC_LSF, false)
};
match self.state {
RxState::Hunt if strong => {
self.state = RxState::Confirm;
self.best_corr = corr;
self.best_sync = sync;
self.best_age = 0;
self.confirm_left = self.sps;
}
RxState::Confirm => {
self.best_age += 1;
if strong && corr > self.best_corr {
self.best_corr = corr;
self.best_sync = sync;
self.best_age = 0;
}
self.confirm_left -= 1;
if self.confirm_left == 0 {
self.unit = (self.best_corr / (8 * 9)).max(1) as i32;
self.countdown = self.sps.saturating_sub(self.best_age).max(1);
self.frame_sync = self.best_sync;
self.frame_bits = [0; FRAME_BYTES];
self.nsyms = 0;
self.expect_sync = false;
self.state = RxState::Collect;
}
}
_ => {}
}
None
}
RxState::Collect => {
self.countdown -= 1;
if self.countdown > 0 {
return None;
}
self.countdown = self.sps;
let t2 = 2 * self.unit;
let symbol: i8 = if y > t2 {
3
} else if y > 0 {
1
} else if y > -t2 {
-1
} else {
-3
};
let dibit = symbol_to_dibit(symbol);
if self.expect_sync {
self.sync_acc = (self.sync_acc << 2) | u16::from(dibit);
self.sync_count += 1;
if self.sync_count < 8 {
return None;
}
self.expect_sync = false;
self.sync_count = 0;
let d_lsf = (self.sync_acc ^ SYNC_LSF).count_ones();
let d_pkt = (self.sync_acc ^ SYNC_PACKET).count_ones();
if d_lsf <= 3 && d_lsf <= d_pkt {
self.frame_sync = SYNC_LSF;
} else if d_pkt <= 3 {
self.frame_sync = SYNC_PACKET;
} else {
self.state = RxState::Hunt;
return None;
}
self.frame_bits = [0; FRAME_BYTES];
self.nsyms = 0;
return None;
}
let idx = self.nsyms * 2;
set_bit(&mut self.frame_bits, idx, (dibit >> 1) & 1);
set_bit(&mut self.frame_bits, idx + 1, dibit & 1);
self.nsyms += 1;
if self.nsyms < FRAME_SYMBOLS - 8 {
return None;
}
self.expect_sync = true;
self.sync_acc = 0;
let bits = self.frame_bits;
if self.frame_sync == SYNC_LSF {
lsf_decode(&bits).ok().map(M17FrameEvent::Lsf)
} else {
let (frame, _metric) = packet_frame_decode(&bits);
Some(M17FrameEvent::PacketFrame(frame))
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct PacketAssembler {
buf: [u8; 33 * PACKET_FRAME_PAYLOAD],
len: usize,
frames: usize,
active: bool,
lsf: Option<Lsf>,
}
impl Default for PacketAssembler {
fn default() -> Self {
Self::new()
}
}
impl PacketAssembler {
#[must_use]
pub const fn new() -> Self {
Self {
buf: [0; 825],
len: 0,
frames: 0,
active: false,
lsf: None,
}
}
pub fn start(&mut self, lsf: Lsf) {
self.len = 0;
self.frames = 0;
self.active = true;
self.lsf = Some(lsf);
}
#[must_use]
pub const fn lsf(&self) -> Option<Lsf> {
self.lsf
}
pub fn feed(&mut self, frame: &PacketFrame) -> Option<&[u8]> {
if !self.active {
return None;
}
if frame.eof {
let take = usize::from(frame.counter).clamp(1, PACKET_FRAME_PAYLOAD);
if self.len + take > self.buf.len() {
self.active = false;
return None;
}
self.buf[self.len..self.len + take].copy_from_slice(&frame.data[..take]);
let total = self.len + take;
self.active = false;
if total < 2 {
return None;
}
let payload_len = total - 2;
let want = u16::from_be_bytes([self.buf[payload_len], self.buf[payload_len + 1]]);
if crc16(&self.buf[..payload_len]) == want {
Some(&self.buf[..payload_len])
} else {
None
}
} else {
if usize::from(frame.counter) != self.frames % 32
|| self.len + PACKET_FRAME_PAYLOAD >= self.buf.len()
{
self.active = false;
return None;
}
self.buf[self.len..self.len + PACKET_FRAME_PAYLOAD].copy_from_slice(&frame.data);
self.len += PACKET_FRAME_PAYLOAD;
self.frames += 1;
None
}
}
}