#[allow(unused_imports)]
use crate::{FecCodec, FrameLayout, MessageCodec, ModulationParams, Protocol};
use crate::ProtocolId;
#[derive(Clone, Copy, Debug)]
pub struct ProtocolMeta {
pub id: ProtocolId,
pub name: &'static str,
pub ntones: u32,
pub bits_per_symbol: u32,
pub nsps: u32,
pub symbol_dt: f32,
pub tone_spacing_hz: f32,
pub gfsk_bt: f32,
pub gfsk_hmod: f32,
pub n_data: u32,
pub n_sync: u32,
pub n_symbols: u32,
pub t_slot_s: f32,
pub fec_k: usize,
pub fec_n: usize,
pub payload_bits: u32,
}
#[allow(unused_macros)] macro_rules! protocol_meta {
($name:literal, $ty:ty) => {
ProtocolMeta {
id: <$ty as Protocol>::ID,
name: $name,
ntones: <$ty as ModulationParams>::NTONES,
bits_per_symbol: <$ty as ModulationParams>::BITS_PER_SYMBOL,
nsps: <$ty as ModulationParams>::NSPS,
symbol_dt: <$ty as ModulationParams>::SYMBOL_DT,
tone_spacing_hz: <$ty as ModulationParams>::TONE_SPACING_HZ,
gfsk_bt: <$ty as ModulationParams>::GFSK_BT,
gfsk_hmod: <$ty as ModulationParams>::GFSK_HMOD,
n_data: <$ty as FrameLayout>::N_DATA,
n_sync: <$ty as FrameLayout>::N_SYNC,
n_symbols: <$ty as FrameLayout>::N_SYMBOLS,
t_slot_s: <$ty as FrameLayout>::T_SLOT_S,
fec_k: <<$ty as Protocol>::Fec as FecCodec>::K,
fec_n: <<$ty as Protocol>::Fec as FecCodec>::N,
payload_bits: <<$ty as Protocol>::Msg as MessageCodec>::PAYLOAD_BITS,
}
};
}
pub static PROTOCOLS: &[ProtocolMeta] = &[
#[cfg(feature = "ft8")]
protocol_meta!("FT8", crate::Ft8),
#[cfg(feature = "ft4")]
protocol_meta!("FT4", crate::Ft4),
#[cfg(feature = "fst4")]
protocol_meta!("FST4-60A", crate::Fst4s60),
#[cfg(feature = "wspr")]
protocol_meta!("WSPR", crate::Wspr),
#[cfg(feature = "jt9")]
protocol_meta!("JT9", crate::Jt9),
#[cfg(feature = "jt65")]
protocol_meta!("JT65", crate::Jt65),
#[cfg(feature = "q65")]
protocol_meta!("Q65-30A", crate::q65::Q65a30),
#[cfg(feature = "q65")]
protocol_meta!("Q65-60A", crate::q65::Q65a60),
#[cfg(feature = "q65")]
protocol_meta!("Q65-60B", crate::q65::Q65b60),
#[cfg(feature = "q65")]
protocol_meta!("Q65-60C", crate::q65::Q65c60),
#[cfg(feature = "q65")]
protocol_meta!("Q65-60D", crate::q65::Q65d60),
#[cfg(feature = "q65")]
protocol_meta!("Q65-60E", crate::q65::Q65e60),
#[cfg(feature = "uvpacket")]
protocol_meta!("UvRobust", crate::UvRobust),
#[cfg(feature = "uvpacket")]
protocol_meta!("UvStandard", crate::UvStandard),
#[cfg(feature = "uvpacket")]
protocol_meta!("UvUltraRobust", crate::UvUltraRobust),
#[cfg(feature = "uvpacket")]
protocol_meta!("UvExpress", crate::UvExpress),
];
pub fn by_id(id: ProtocolId) -> impl Iterator<Item = &'static ProtocolMeta> {
PROTOCOLS.iter().filter(move |p| p.id == id)
}
pub fn by_name(name: &str) -> Option<&'static ProtocolMeta> {
PROTOCOLS.iter().find(|p| p.name == name)
}
pub fn for_protocol_id(id: ProtocolId) -> Option<&'static ProtocolMeta> {
by_id(id).next()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_is_non_empty_in_default_build() {
assert!(!PROTOCOLS.is_empty());
}
#[test]
fn names_are_unique() {
let mut names: Vec<&str> = PROTOCOLS.iter().map(|p| p.name).collect();
names.sort_unstable();
let dedup_len = {
let mut v = names.clone();
v.dedup();
v.len()
};
assert_eq!(
dedup_len,
names.len(),
"duplicate protocol names in registry: {names:?}"
);
}
#[test]
fn by_name_round_trips() {
for p in PROTOCOLS {
let q = by_name(p.name).expect("by_name should find every registered name");
assert!(
std::ptr::eq(p, q),
"by_name returned a different entry for {}",
p.name
);
}
}
#[test]
fn by_name_returns_none_for_unknown() {
assert!(by_name("NotAProtocol-9000").is_none());
}
#[test]
fn by_id_yields_at_least_one_entry_for_each_distinct_id() {
let mut ids: Vec<ProtocolId> = PROTOCOLS.iter().map(|p| p.id).collect();
ids.sort_unstable_by_key(|id| *id as u8);
ids.dedup();
for id in ids {
assert!(
by_id(id).next().is_some(),
"by_id({id:?}) found no entries despite the id appearing in the registry"
);
}
}
#[cfg(feature = "q65")]
#[test]
fn q65_id_yields_all_six_submodes() {
let q65_entries: Vec<&ProtocolMeta> = by_id(ProtocolId::Q65).collect();
assert_eq!(
q65_entries.len(),
6,
"expected six Q65 sub-modes in the registry, got {}: {:?}",
q65_entries.len(),
q65_entries.iter().map(|p| p.name).collect::<Vec<_>>()
);
let names: Vec<&str> = q65_entries.iter().map(|p| p.name).collect();
for expected in &[
"Q65-30A", "Q65-60A", "Q65-60B", "Q65-60C", "Q65-60D", "Q65-60E",
] {
assert!(
names.contains(expected),
"Q65 registry missing sub-mode {expected}; have {names:?}"
);
}
}
}