use super::types::*;
use tinyklv::dec::binary as decb;
use tinyklv::enc::binary as encb;
use tinyklv::prelude::*;
#[derive(Klv, Debug, PartialEq)]
#[klv(
stream = &[u8],
key(dec = decb::u8, enc = encb::u8),
len(dec = decb::u8_as_usize, enc = encb::u8_from_usize),
trait_fallback,
)]
struct SimplePosition {
#[klv(key = 0x01)]
coordinate: Coordinate,
#[klv(key = 0x02)]
color: Color,
}
#[derive(Klv, Debug, PartialEq)]
#[klv(
stream = &[u8],
key(dec = decb::u8, enc = encb::u8),
len(dec = decb::u8_as_usize, enc = encb::u8_from_usize),
trait_fallback,
)]
struct PartialReading {
#[klv(key = 0x01)]
color: Option<Color>,
#[klv(key = 0x02)]
velocity: Option<Velocity>,
#[klv(key = 0x03)]
timestamp: Option<Timestamp>,
}
#[derive(Klv, Debug, PartialEq)]
#[klv(
stream = &[u8],
sentinel = b"\x57\x41",
key(dec = decb::u8, enc = encb::u8),
len(dec = decb::u8_as_usize, enc = encb::u8_from_usize),
trait_fallback,
)]
struct Waypoint {
#[klv(key = 0x01)]
coordinate: Coordinate,
#[klv(key = 0x02)]
timestamp: Timestamp,
#[klv(key = 0x03)]
priority: Priority,
}
#[derive(Klv, Debug, PartialEq)]
#[klv(
stream = &[u8],
sentinel = b"\x41\x4C",
key(dec = decb::u8, enc = encb::u8),
len(dec = decb::u8_as_usize, enc = encb::u8_from_usize),
trait_fallback,
)]
struct Alert {
#[klv(key = 0x01)]
color: Color,
#[klv(key = 0x02)]
flags: StatusFlags,
}
fn encode_coordinate_tlv(key: u8, coord: &Coordinate) -> Vec<u8> {
let val = Coordinate::encode_value(coord);
let mut out = vec![key, val.len() as u8];
out.extend(val);
out
}
fn encode_color_tlv(key: u8, color: &Color) -> Vec<u8> {
let val = Color::encode_value(color);
let mut out = vec![key, val.len() as u8];
out.extend(val);
out
}
fn encode_timestamp_tlv(key: u8, ts: &Timestamp) -> Vec<u8> {
let val = Timestamp::encode_value(ts);
let mut out = vec![key, val.len() as u8];
out.extend(val);
out
}
#[test]
fn unknown_keys_between_valid() {
let coord = Coordinate {
lat: 48.8566,
lon: 2.3522,
};
let color = Color::Green;
let mut stream: Vec<u8> = Vec::new();
stream.extend(encode_coordinate_tlv(0x01, &coord));
stream.extend_from_slice(&[0xAA, 0x03, 0xDE, 0xAD, 0xFF]);
stream.extend_from_slice(&[0xBB, 0x02, 0xCA, 0xFE]);
stream.extend(encode_color_tlv(0x02, &color));
let result = SimplePosition::decode_value(&mut stream.as_slice()).unwrap();
assert_eq!(
result.coordinate, coord,
"coordinate should decode despite unknown keys"
);
assert_eq!(
result.color, color,
"color should decode despite unknown keys"
);
}
#[test]
fn corrupt_length_surfaces_as_recoverable_needmore() {
let color = Color::Blue;
let mut stream: Vec<u8> = Vec::new();
stream.extend(encode_color_tlv(0x01, &color));
stream.extend_from_slice(&[0x02, 100, 0x00, 0x01]);
let mut cursor: &[u8] = stream.as_slice();
let p = match PartialReading::decode_partial(&mut cursor) {
Ok(tinyklv::Packet::NeedMore(p)) => p,
other => panic!("expected NeedMore (recoverable truncation), got: {other:?}"),
};
assert_eq!(p.color, Some(Color::Blue));
assert!(p.velocity.is_none());
assert!(p.timestamp.is_none());
}
#[test]
fn corrupt_value_recoverable() {
let color = Color::Alpha;
let ts = Timestamp {
seconds: 1_000_000,
nanos: 250,
};
let mut stream: Vec<u8> = Vec::new();
stream.extend(encode_color_tlv(0x01, &color));
stream.extend_from_slice(&[0x02, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
stream.extend(encode_timestamp_tlv(0x03, &ts));
let result = PartialReading::decode_value(&mut stream.as_slice()).unwrap();
assert_eq!(result.color, Some(color), "color should have decoded");
assert_eq!(
result.velocity, None,
"velocity decode failed -> None kept via .ok()"
);
assert_eq!(
result.timestamp,
Some(ts),
"timestamp should decode after corrupt velocity"
);
}
#[test]
fn auto_generate_10_packets() {
let waypoints: Vec<Waypoint> = (0..10)
.map(|i| Waypoint {
coordinate: Coordinate {
lat: 10.0 * i as f64,
lon: -5.0 * i as f64,
},
timestamp: Timestamp {
seconds: 1_700_000_000 + i as u32 * 60,
nanos: i as u16 * 100,
},
priority: match i % 4 {
0 => Priority::Low,
1 => Priority::Medium,
2 => Priority::High,
_ => Priority::Critical,
},
})
.collect();
let stream: Vec<u8> = waypoints.iter().flat_map(|w| w.encode_frame()).collect();
let mut slice = stream.as_slice();
let mut decoded: Vec<Waypoint> = Vec::new();
for _ in 0..10 {
decoded.push(Waypoint::decode_frame(&mut slice).unwrap());
}
assert_eq!(decoded.len(), 10);
for (i, (got, expected)) in decoded.iter().zip(waypoints.iter()).enumerate() {
assert_eq!(got, expected, "waypoint[{i}] mismatch");
}
}
#[test]
fn auto_generate_mixed_types() {
let waypoints: Vec<Waypoint> = vec![
Waypoint {
coordinate: Coordinate {
lat: 37.7749,
lon: -122.4194,
},
timestamp: Timestamp {
seconds: 1_000,
nanos: 0,
},
priority: Priority::High,
},
Waypoint {
coordinate: Coordinate {
lat: 40.7128,
lon: -74.0060,
},
timestamp: Timestamp {
seconds: 2_000,
nanos: 500,
},
priority: Priority::Low,
},
Waypoint {
coordinate: Coordinate {
lat: 51.5074,
lon: -0.1278,
},
timestamp: Timestamp {
seconds: 3_000,
nanos: 999,
},
priority: Priority::Critical,
},
];
let alerts: Vec<Alert> = vec![
Alert {
color: Color::Red,
flags: StatusFlags {
active: true,
armed: false,
locked: false,
mode: 0x01,
},
},
Alert {
color: Color::Blue,
flags: StatusFlags {
active: false,
armed: true,
locked: true,
mode: 0x0A,
},
},
Alert {
color: Color::Green,
flags: StatusFlags {
active: true,
armed: true,
locked: false,
mode: 0x1F,
},
},
];
let mut stream: Vec<u8> = Vec::new();
for i in 0..3 {
stream.extend(waypoints[i].encode_frame());
stream.extend(alerts[i].encode_frame());
}
let mut wp_slice = stream.as_slice();
let got_wp: Vec<Waypoint> = (0..3)
.map(|_| Waypoint::decode_frame(&mut wp_slice).unwrap())
.collect();
assert_eq!(got_wp, waypoints);
let mut al_slice = stream.as_slice();
let got_al: Vec<Alert> = (0..3)
.map(|_| Alert::decode_frame(&mut al_slice).unwrap())
.collect();
assert_eq!(got_al, alerts);
}