#![cfg(any(
feature = "ubx_proto27",
feature = "ubx_proto31",
feature = "ubx_proto33",
))]
use byteorder::{LittleEndian, WriteBytesExt};
use proptest::prelude::*;
use ublox::{constants::UBX_SYNC_CHAR_1, constants::UBX_SYNC_CHAR_2, ParserBuilder, UbxPacket};
#[derive(Debug, Clone)]
pub struct NavPlPayload {
pub version: u8, pub tmir_coeff: u8, pub tmir_exp: i8, pub pl_pos_valid: u8, pub pl_pos_frame: u8, pub pl_vel_valid: u8, pub pl_vel_frame: u8, pub pl_time_valid: u8, pub pl_pos_invalidity_reason: u8, pub pl_vel_invalidity_reason: u8, pub pl_time_invalidity_reason: u8, pub reserved0: u8, pub itow: u32, pub pl_pos1: u32, pub pl_pos2: u32, pub pl_pos3: u32, pub pl_vel1: u32, pub pl_vel2: u32, pub pl_vel3: u32, pub pl_pos_horiz_orient: u16, pub pl_vel_horiz_orient: u16, pub pl_time: u32, pub reserved1: [u8; 4], }
impl NavPlPayload {
pub fn to_bytes(&self) -> Vec<u8> {
let mut wtr = Vec::with_capacity(52);
wtr.write_u8(self.version).unwrap();
wtr.write_u8(self.tmir_coeff).unwrap();
wtr.write_i8(self.tmir_exp).unwrap();
wtr.write_u8(self.pl_pos_valid).unwrap();
wtr.write_u8(self.pl_pos_frame).unwrap();
wtr.write_u8(self.pl_vel_valid).unwrap();
wtr.write_u8(self.pl_vel_frame).unwrap();
wtr.write_u8(self.pl_time_valid).unwrap();
wtr.write_u8(self.pl_pos_invalidity_reason).unwrap();
wtr.write_u8(self.pl_vel_invalidity_reason).unwrap();
wtr.write_u8(self.pl_time_invalidity_reason).unwrap();
wtr.write_u8(self.reserved0).unwrap();
wtr.write_u32::<LittleEndian>(self.itow).unwrap();
wtr.write_u32::<LittleEndian>(self.pl_pos1).unwrap();
wtr.write_u32::<LittleEndian>(self.pl_pos2).unwrap();
wtr.write_u32::<LittleEndian>(self.pl_pos3).unwrap();
wtr.write_u32::<LittleEndian>(self.pl_vel1).unwrap();
wtr.write_u32::<LittleEndian>(self.pl_vel2).unwrap();
wtr.write_u32::<LittleEndian>(self.pl_vel3).unwrap();
wtr.write_u16::<LittleEndian>(self.pl_pos_horiz_orient)
.unwrap();
wtr.write_u16::<LittleEndian>(self.pl_vel_horiz_orient)
.unwrap();
wtr.write_u32::<LittleEndian>(self.pl_time).unwrap();
wtr.extend_from_slice(&self.reserved1);
wtr
}
}
fn calculate_checksum(data: &[u8]) -> (u8, u8) {
let mut ck_a: u8 = 0;
let mut ck_b: u8 = 0;
for byte in data {
ck_a = ck_a.wrapping_add(*byte);
ck_b = ck_b.wrapping_add(ck_a);
}
(ck_a, ck_b)
}
fn nav_pl_payload_strategy() -> impl Strategy<Value = NavPlPayload> {
let header = (
Just(0x01u8), any::<u8>(), any::<i8>(), 0u8..=1u8, 0u8..=3u8, 0u8..=1u8, 0u8..=3u8, 0u8..=1u8, );
let invalidity = (
any::<u8>(), any::<u8>(), any::<u8>(), Just(0u8), );
let timing_and_pos = (
any::<u32>(), any::<u32>(), any::<u32>(), any::<u32>(), );
let velocity = (
any::<u32>(), any::<u32>(), any::<u32>(), );
let orient_and_time = (
any::<u16>(), any::<u16>(), any::<u32>(), Just([0u8; 4]), );
(
header,
invalidity,
timing_and_pos,
velocity,
orient_and_time,
)
.prop_map(
|(
(
version,
tmir_coeff,
tmir_exp,
pl_pos_valid,
pl_pos_frame,
pl_vel_valid,
pl_vel_frame,
pl_time_valid,
),
(
pl_pos_invalidity_reason,
pl_vel_invalidity_reason,
pl_time_invalidity_reason,
reserved0,
),
(itow, pl_pos1, pl_pos2, pl_pos3),
(pl_vel1, pl_vel2, pl_vel3),
(pl_pos_horiz_orient, pl_vel_horiz_orient, pl_time, reserved1),
)| {
NavPlPayload {
version,
tmir_coeff,
tmir_exp,
pl_pos_valid,
pl_pos_frame,
pl_vel_valid,
pl_vel_frame,
pl_time_valid,
pl_pos_invalidity_reason,
pl_vel_invalidity_reason,
pl_time_invalidity_reason,
reserved0,
itow,
pl_pos1,
pl_pos2,
pl_pos3,
pl_vel1,
pl_vel2,
pl_vel3,
pl_pos_horiz_orient,
pl_vel_horiz_orient,
pl_time,
reserved1,
}
},
)
}
pub fn ubx_nav_pl_frame_strategy() -> impl Strategy<Value = (NavPlPayload, Vec<u8>)> {
nav_pl_payload_strategy().prop_map(|payload_struct| {
let payload = payload_struct.to_bytes();
let class_id = 0x01;
let message_id = 0x62;
let length = payload.len() as u16;
let mut frame_core = Vec::with_capacity(4 + payload.len());
frame_core.push(class_id);
frame_core.push(message_id);
frame_core.write_u16::<LittleEndian>(length).unwrap();
frame_core.extend_from_slice(&payload);
let (ck_a, ck_b) = calculate_checksum(&frame_core);
let mut final_frame = Vec::with_capacity(8 + payload.len());
final_frame.push(UBX_SYNC_CHAR_1);
final_frame.push(UBX_SYNC_CHAR_2);
final_frame.extend_from_slice(&frame_core);
final_frame.push(ck_a);
final_frame.push(ck_b);
(payload_struct, final_frame)
})
}
#[cfg(feature = "ubx_proto27")]
proptest! {
#[test]
fn test_parser_proto27_with_generated_nav_pl_frames((expected, frame) in ubx_nav_pl_frame_strategy()) {
use ublox::proto27::{PacketRef, Proto27};
let mut parser = ParserBuilder::new().with_protocol::<Proto27>().with_fixed_buffer::<2048>();
let mut it = parser.consume_ubx(&frame);
let Some(Ok(UbxPacket::Proto27(PacketRef::NavPl(p)))) = it.next() else {
panic!("Parser failed to parse a NAV-PL valid packet");
};
prop_assert_eq!(p.version(), expected.version);
prop_assert_eq!(p.tmir_coeff(), expected.tmir_coeff);
prop_assert_eq!(p.tmir_exp(), expected.tmir_exp);
prop_assert_eq!(p.pl_pos_valid_raw(), expected.pl_pos_valid);
prop_assert_eq!(p.pl_pos_frame_raw(), expected.pl_pos_frame);
prop_assert_eq!(p.pl_vel_valid_raw(), expected.pl_vel_valid);
prop_assert_eq!(p.pl_vel_frame_raw(), expected.pl_vel_frame);
prop_assert_eq!(p.pl_time_valid_raw(), expected.pl_time_valid);
prop_assert_eq!(p.pl_pos_invalidity_reason_raw(), expected.pl_pos_invalidity_reason);
prop_assert_eq!(p.pl_vel_invalidity_reason_raw(), expected.pl_vel_invalidity_reason);
prop_assert_eq!(p.pl_time_invalidity_reason_raw(), expected.pl_time_invalidity_reason);
prop_assert_eq!(p.itow(), expected.itow);
prop_assert!((p.pl_pos1() - (expected.pl_pos1 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_pos2() - (expected.pl_pos2 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_pos3() - (expected.pl_pos3 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel1() - (expected.pl_vel1 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel2() - (expected.pl_vel2 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel3() - (expected.pl_vel3 as f64 * 0.001)).abs() < 1e-9);
prop_assert_eq!(p.pl_pos_horiz_orient_raw(), expected.pl_pos_horiz_orient);
prop_assert_eq!(p.pl_vel_horiz_orient_raw(), expected.pl_vel_horiz_orient);
prop_assert_eq!(p.pl_time(), expected.pl_time);
}
}
#[cfg(feature = "ubx_proto31")]
proptest! {
#[test]
fn test_parser_proto31_with_generated_nav_pl_frames((expected, frame) in ubx_nav_pl_frame_strategy()) {
use ublox::proto31::{PacketRef, Proto31};
let mut parser = ParserBuilder::new().with_protocol::<Proto31>().with_fixed_buffer::<2048>();
let mut it = parser.consume_ubx(&frame);
let Some(Ok(UbxPacket::Proto31(PacketRef::NavPl(p)))) = it.next() else {
panic!("Parser failed to parse a NAV-PL valid packet");
};
prop_assert_eq!(p.version(), expected.version);
prop_assert_eq!(p.tmir_coeff(), expected.tmir_coeff);
prop_assert_eq!(p.tmir_exp(), expected.tmir_exp);
prop_assert_eq!(p.pl_pos_valid_raw(), expected.pl_pos_valid);
prop_assert_eq!(p.pl_pos_frame_raw(), expected.pl_pos_frame);
prop_assert_eq!(p.pl_vel_valid_raw(), expected.pl_vel_valid);
prop_assert_eq!(p.pl_vel_frame_raw(), expected.pl_vel_frame);
prop_assert_eq!(p.pl_time_valid_raw(), expected.pl_time_valid);
prop_assert_eq!(p.pl_pos_invalidity_reason_raw(), expected.pl_pos_invalidity_reason);
prop_assert_eq!(p.pl_vel_invalidity_reason_raw(), expected.pl_vel_invalidity_reason);
prop_assert_eq!(p.pl_time_invalidity_reason_raw(), expected.pl_time_invalidity_reason);
prop_assert_eq!(p.itow(), expected.itow);
prop_assert!((p.pl_pos1() - (expected.pl_pos1 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_pos2() - (expected.pl_pos2 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_pos3() - (expected.pl_pos3 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel1() - (expected.pl_vel1 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel2() - (expected.pl_vel2 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel3() - (expected.pl_vel3 as f64 * 0.001)).abs() < 1e-9);
prop_assert_eq!(p.pl_pos_horiz_orient_raw(), expected.pl_pos_horiz_orient);
prop_assert_eq!(p.pl_vel_horiz_orient_raw(), expected.pl_vel_horiz_orient);
prop_assert_eq!(p.pl_time(), expected.pl_time);
}
}
#[cfg(feature = "ubx_proto33")]
proptest! {
#[test]
fn test_parser_proto33_with_generated_nav_pl_frames((expected, frame) in ubx_nav_pl_frame_strategy()) {
use ublox::proto33::{PacketRef, Proto33};
let mut parser = ParserBuilder::new().with_protocol::<Proto33>().with_fixed_buffer::<2048>();
let mut it = parser.consume_ubx(&frame);
let Some(Ok(UbxPacket::Proto33(PacketRef::NavPl(p)))) = it.next() else {
panic!("Parser failed to parse a NAV-PL valid packet");
};
prop_assert_eq!(p.version(), expected.version);
prop_assert_eq!(p.tmir_coeff(), expected.tmir_coeff);
prop_assert_eq!(p.tmir_exp(), expected.tmir_exp);
prop_assert_eq!(p.pl_pos_valid_raw(), expected.pl_pos_valid);
prop_assert_eq!(p.pl_pos_frame_raw(), expected.pl_pos_frame);
prop_assert_eq!(p.pl_vel_valid_raw(), expected.pl_vel_valid);
prop_assert_eq!(p.pl_vel_frame_raw(), expected.pl_vel_frame);
prop_assert_eq!(p.pl_time_valid_raw(), expected.pl_time_valid);
prop_assert_eq!(p.pl_pos_invalidity_reason_raw(), expected.pl_pos_invalidity_reason);
prop_assert_eq!(p.pl_vel_invalidity_reason_raw(), expected.pl_vel_invalidity_reason);
prop_assert_eq!(p.pl_time_invalidity_reason_raw(), expected.pl_time_invalidity_reason);
prop_assert_eq!(p.itow(), expected.itow);
prop_assert!((p.pl_pos1() - (expected.pl_pos1 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_pos2() - (expected.pl_pos2 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_pos3() - (expected.pl_pos3 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel1() - (expected.pl_vel1 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel2() - (expected.pl_vel2 as f64 * 0.001)).abs() < 1e-9);
prop_assert!((p.pl_vel3() - (expected.pl_vel3 as f64 * 0.001)).abs() < 1e-9);
prop_assert_eq!(p.pl_pos_horiz_orient_raw(), expected.pl_pos_horiz_orient);
prop_assert_eq!(p.pl_vel_horiz_orient_raw(), expected.pl_vel_horiz_orient);
prop_assert_eq!(p.pl_time(), expected.pl_time);
}
}