use snafu::OptionExt as _;
use tracing::instrument;
use crate::types::{FromBytes, NotEnoughBytes, ParseError, ToBytes, information_elements::Lpc};
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub enum Kpa {
#[default]
Unused,
Thresh,
Filter,
LoLimit,
HiLimit,
Other(u8),
}
impl Kpa {
#[must_use]
pub const fn from_byte(byte: u8) -> Self {
match byte {
0 => Kpa::Unused,
1 => Kpa::Thresh,
2 => Kpa::Filter,
3 => Kpa::LoLimit,
4 => Kpa::HiLimit,
_ => Kpa::Other(byte),
}
}
#[must_use]
pub const fn to_byte(&self) -> u8 {
match self {
Kpa::Unused => 0,
Kpa::Thresh => 1,
Kpa::Filter => 2,
Kpa::LoLimit => 3,
Kpa::HiLimit => 4,
Kpa::Other(byte) => *byte,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct Qpm {
pub kpa: Kpa,
pub pop: bool,
pub lpc: Lpc,
}
impl Qpm {
#[must_use]
pub const fn from_byte(byte: u8) -> Self {
let kpa = Kpa::from_byte(byte & 0b0011_1111);
let lpc = Lpc::from_bool(byte & 0b0100_0000 != 0);
let pop = byte & 0b1000_0000 != 0;
Self { kpa, pop, lpc }
}
#[must_use]
pub const fn to_byte(&self) -> u8 {
let mut byte: u8 = 0;
byte |= self.kpa.to_byte() & 0b0011_1111;
byte |= (self.lpc as u8) << 6;
byte |= (self.pop as u8) << 7;
byte
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default, Copy)]
#[repr(u8)]
pub enum Qpa {
#[default]
Unused,
General,
Object,
Transmission,
Other(u8),
}
impl Qpa {
#[must_use]
pub const fn from_byte(byte: u8) -> Self {
match byte {
0 => Qpa::Unused,
1 => Qpa::General,
2 => Qpa::Object,
3 => Qpa::Transmission,
_ => Qpa::Other(byte),
}
}
#[must_use]
pub const fn to_byte(self) -> u8 {
match self {
Qpa::Unused => 0,
Qpa::General => 1,
Qpa::Object => 2,
Qpa::Transmission => 3,
Qpa::Other(byte) => byte,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct PMeNa1 {
pub nva: i16,
pub qpm: Qpm,
}
impl FromBytes for PMeNa1 {
#[instrument]
fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
let nva = i16::from_le_bytes(*bytes.first_chunk::<2>().context(NotEnoughBytes)?);
let qpm = Qpm::from_byte(*bytes.get(2).context(NotEnoughBytes)?);
Ok(Self { nva, qpm })
}
}
impl ToBytes for PMeNa1 {
#[instrument]
fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), ParseError> {
buffer.extend_from_slice(&self.nva.to_le_bytes());
buffer.push(self.qpm.to_byte());
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct PMeNb1 {
pub sva: i16,
pub qpm: Qpm,
}
impl FromBytes for PMeNb1 {
#[instrument]
fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
let sva = i16::from_le_bytes(*bytes.first_chunk::<2>().context(NotEnoughBytes)?);
let qpm = Qpm::from_byte(*bytes.get(2).context(NotEnoughBytes)?);
Ok(Self { sva, qpm })
}
}
impl ToBytes for PMeNb1 {
#[instrument]
fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), ParseError> {
buffer.extend_from_slice(&self.sva.to_le_bytes());
buffer.push(self.qpm.to_byte());
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PMeNc1 {
pub value: f32,
pub qpm: Qpm,
}
impl FromBytes for PMeNc1 {
#[instrument]
fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
let value = f32::from_le_bytes(*bytes.first_chunk::<4>().context(NotEnoughBytes)?);
let qpm = Qpm::from_byte(*bytes.get(4).context(NotEnoughBytes)?);
Ok(Self { value, qpm })
}
}
impl ToBytes for PMeNc1 {
#[instrument]
fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), ParseError> {
buffer.extend_from_slice(&self.value.to_le_bytes());
buffer.push(self.qpm.to_byte());
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct PAcNa1 {
pub qpa: Qpa,
}
impl FromBytes for PAcNa1 {
#[instrument]
fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
let qpa = Qpa::from_byte(*bytes.first().context(NotEnoughBytes)?);
Ok(Self { qpa })
}
}
impl ToBytes for PAcNa1 {
#[instrument]
fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), ParseError> {
buffer.push(self.qpa.to_byte());
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn qpm_round_trip(byte: u8) -> Qpm {
let q = Qpm::from_byte(byte);
assert_eq!(q.to_byte(), byte, "wire byte must match after round-trip");
q
}
#[test]
fn qpm_kpa_uses_lower_six_bits() {
let q = qpm_round_trip(0b0000_0100);
assert_eq!(q.kpa, Kpa::HiLimit);
assert!(!q.pop);
assert_eq!(q.lpc, Lpc::NoChange);
}
#[test]
fn qpm_lpc_is_bit_6() {
let q = qpm_round_trip(0b0100_0000);
assert_eq!(q.lpc, Lpc::Changed);
assert_eq!(q.kpa, Kpa::Unused);
assert!(!q.pop);
}
#[test]
fn qpm_pop_is_bit_7() {
let q = qpm_round_trip(0b1000_0000);
assert!(q.pop);
assert_eq!(q.lpc, Lpc::NoChange);
assert_eq!(q.kpa, Kpa::Unused);
}
#[test]
fn qpm_all_set() {
let q = qpm_round_trip(0xFF);
assert_eq!(q.kpa, Kpa::Other(63));
assert_eq!(q.lpc, Lpc::Changed);
assert!(q.pop);
}
#[test]
fn qpm_kpa_filter_round_trip() {
let q = qpm_round_trip(0b0000_0010);
assert_eq!(q.kpa, Kpa::Filter);
}
}