use crate::error::{SbfError, SbfResult};
use crate::header::SbfHeader;
use super::block_ids;
use super::SbfBlockParse;
#[derive(Debug, Clone)]
pub struct Meas3RangesBlock {
tow_ms: u32,
wnc: u16,
pub common_flags: u8,
pub cum_clk_jumps: u8,
pub constellations: u16,
pub misc: u8,
pub reserved: u8,
pub data: Vec<u8>,
}
impl Meas3RangesBlock {
pub fn tow_seconds(&self) -> f64 {
self.tow_ms as f64 * 0.001
}
pub fn tow_ms(&self) -> u32 {
self.tow_ms
}
pub fn wnc(&self) -> u16 {
self.wnc
}
pub fn antenna_id(&self) -> u8 {
self.misc & 0x07
}
pub fn reference_epoch_interval_ms(&self) -> u32 {
match self.misc >> 4 {
0 => 1,
1 => 500,
2 => 1000,
3 => 2000,
4 => 5000,
5 => 10_000,
6 => 15_000,
7 => 30_000,
8 => 60_000,
9 => 120_000,
_ => 1,
}
}
pub fn is_reference_epoch(&self) -> bool {
self.tow_ms.checked_rem(self.reference_epoch_interval_ms()) == Some(0)
}
pub fn reference_epoch_contains_pr_rate(&self) -> bool {
(self.misc & 0x08) != 0
}
pub fn has_scrambled_measurements(&self) -> bool {
(self.common_flags & 0x80) != 0
}
}
impl SbfBlockParse for Meas3RangesBlock {
const BLOCK_ID: u16 = block_ids::MEAS3_RANGES;
fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
let full_len = header.length as usize;
if data.len() < full_len.saturating_sub(2) {
return Err(SbfError::IncompleteBlock {
needed: full_len,
have: data.len() + 2,
});
}
if data.len() < 18 {
return Err(SbfError::ParseError("Meas3Ranges too short".into()));
}
let common_flags = data[12];
let cum_clk_jumps = data[13];
let constellations = u16::from_le_bytes([data[14], data[15]]);
let misc = data[16];
let reserved = data[17];
let payload = data[18..].to_vec();
Ok(Self {
tow_ms: header.tow_ms,
wnc: header.wnc,
common_flags,
cum_clk_jumps,
constellations,
misc,
reserved,
data: payload,
})
}
}
macro_rules! impl_meas3_ext {
(
$name:ident,
$block_id:expr,
$doc:literal
) => {
#[doc = $doc]
#[derive(Debug, Clone)]
pub struct $name {
tow_ms: u32,
wnc: u16,
pub flags: u8,
pub data: Vec<u8>,
}
impl $name {
pub fn tow_seconds(&self) -> f64 {
self.tow_ms as f64 * 0.001
}
pub fn tow_ms(&self) -> u32 {
self.tow_ms
}
pub fn wnc(&self) -> u16 {
self.wnc
}
pub fn antenna_id(&self) -> u8 {
(self.flags & 0x07) as u8
}
}
impl SbfBlockParse for $name {
const BLOCK_ID: u16 = $block_id;
fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
let full_len = header.length as usize;
if data.len() < full_len.saturating_sub(2) {
return Err(SbfError::IncompleteBlock {
needed: full_len,
have: data.len() + 2,
});
}
if data.len() < 13 {
return Err(SbfError::ParseError(
concat!(stringify!($name), " too short").into(),
));
}
let flags = data[12];
let payload = data[13..].to_vec();
Ok(Self {
tow_ms: header.tow_ms,
wnc: header.wnc,
flags,
data: payload,
})
}
}
};
}
impl_meas3_ext!(
Meas3Cn0HiResBlock,
block_ids::MEAS3_CN0_HI_RES,
"Meas3CN0HiRes — fractional C/N0 extension (paired with Meas3Ranges)."
);
impl_meas3_ext!(
Meas3DopplerBlock,
block_ids::MEAS3_DOPPLER,
"Meas3Doppler — Doppler extension (paired with Meas3Ranges)."
);
impl_meas3_ext!(
Meas3PpBlock,
block_ids::MEAS3_PP,
"Meas3PP — post-processing flags extension (paired with Meas3Ranges)."
);
impl_meas3_ext!(
Meas3MpBlock,
block_ids::MEAS3_MP,
"Meas3MP — multipath correction extension (paired with Meas3Ranges)."
);
#[cfg(test)]
mod tests {
use super::*;
use crate::blocks::SbfBlock;
fn build_block(block_id: u16, block_rev: u8, body: &[u8]) -> Vec<u8> {
let block_data_len = 12 + body.len();
let mut total_len = (2 + block_data_len) as u16;
while (total_len as usize & 0x03) != 0 {
total_len += 1;
}
let mut data = vec![0u8; total_len as usize];
data[0] = 0x24;
data[1] = 0x40;
let id_rev = block_id | ((block_rev as u16 & 0x07) << 13);
data[4..6].copy_from_slice(&id_rev.to_le_bytes());
data[6..8].copy_from_slice(&total_len.to_le_bytes());
data[8..12].copy_from_slice(&123_456u32.to_le_bytes());
data[12..14].copy_from_slice(&2150u16.to_le_bytes());
data[14..14 + body.len()].copy_from_slice(body);
data
}
#[test]
fn meas3_ranges_parses_header_and_payload() {
let body = [
0u8, 0, 0, 0, 0x03, 0xAA, 0x01, 0x02, 0x03, 0x04, ];
let raw = build_block(block_ids::MEAS3_RANGES, 0, &body);
let (block, consumed) = SbfBlock::parse(&raw).unwrap();
assert_eq!(consumed, raw.len());
match block {
SbfBlock::Meas3Ranges(m) => {
assert_eq!(m.tow_ms(), 123_456);
assert_eq!(m.wnc(), 2150);
assert_eq!(m.common_flags, 0);
assert_eq!(m.cum_clk_jumps, 0);
assert_eq!(m.constellations, 0);
assert_eq!(m.misc, 0x03);
assert_eq!(m.reserved, 0xAA);
assert_eq!(m.antenna_id(), 0x03);
assert_eq!(m.data, vec![0x01, 0x02, 0x03, 0x04]);
}
other => panic!("expected Meas3Ranges, got {:?}", other),
}
}
#[test]
fn meas3_cn0_hi_res_parses_flags_and_bytes() {
let mut ext = vec![0u8; 1 + 5];
ext[0] = 0x05;
ext[1..].copy_from_slice(&[0xAAu8, 10, 20, 30, 40]);
let raw = build_block(block_ids::MEAS3_CN0_HI_RES, 1, &ext);
let (block, _) = SbfBlock::parse(&raw).unwrap();
match block {
SbfBlock::Meas3Cn0HiRes(m) => {
assert_eq!(m.flags, 0x05);
assert_eq!(m.antenna_id(), 0x05);
assert_eq!(m.data, vec![0xAA, 10, 20, 30, 40]);
}
other => panic!("expected Meas3Cn0HiRes, got {:?}", other),
}
}
#[test]
fn meas3_doppler_pp_mp_parse() {
for (id, label) in [
(block_ids::MEAS3_DOPPLER, "doppler"),
(block_ids::MEAS3_PP, "pp"),
(block_ids::MEAS3_MP, "mp"),
] {
let mut ext = vec![0u8; 1 + 5];
ext[0] = 0x02;
ext[1..].copy_from_slice(&[0xCCu8, 1, 2, 3, 4]);
let raw = build_block(id, 0, &ext);
let (block, _) = SbfBlock::parse(&raw).unwrap();
match (label, block) {
("doppler", SbfBlock::Meas3Doppler(m)) => {
assert_eq!(m.antenna_id(), 2);
assert_eq!(m.data, vec![0xCC, 1, 2, 3, 4]);
}
("pp", SbfBlock::Meas3Pp(m)) => {
assert_eq!(m.data, vec![0xCC, 1, 2, 3, 4]);
}
("mp", SbfBlock::Meas3Mp(m)) => {
assert_eq!(m.data, vec![0xCC, 1, 2, 3, 4]);
}
(_, other) => panic!("{}: unexpected {:?}", label, other),
}
}
}
}