use crate::error::{DecodeError, DecodeResult};
use crate::spvd_decode::{DecodedValue, FieldType, PvdDecoder, StructureDesc};
#[derive(Debug, Clone)]
pub struct MonitorUpdate {
pub value: DecodedValue,
pub changed: Vec<u8>,
pub overrun: Vec<u8>,
pub consumed: usize,
pub paths: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MonitorLayout {
SpecOrder,
OverrunBeforeData,
ChangedOnly,
}
impl MonitorUpdate {
pub fn has_overrun(&self) -> bool {
self.overrun.iter().any(|b| *b != 0)
}
pub fn overrun_fields(&self, desc: &StructureDesc) -> Vec<String> {
select_paths(&bit_paths(desc), &self.overrun)
}
pub fn changed_paths(&self) -> Vec<String> {
select_paths(&self.paths, &self.changed)
}
pub fn overrun_paths(&self) -> Vec<String> {
select_paths(&self.paths, &self.overrun)
}
}
pub(crate) fn bit_paths(desc: &StructureDesc) -> Vec<String> {
let mut paths = vec!["<whole structure>".to_string()];
flatten_field_paths(desc, "", &mut paths);
paths
}
fn select_paths(paths: &[String], bits: &[u8]) -> Vec<String> {
paths
.iter()
.enumerate()
.filter(|(bit, _)| {
let byte = bit / 8;
byte < bits.len() && (bits[byte] & (1 << (bit % 8))) != 0
})
.map(|(_, path)| path.clone())
.collect()
}
fn flatten_field_paths(desc: &StructureDesc, prefix: &str, out: &mut Vec<String>) {
for field in &desc.fields {
let path = if prefix.is_empty() {
field.name.clone()
} else {
format!("{prefix}.{}", field.name)
};
out.push(path.clone());
if let FieldType::Structure(nested) = &field.field_type {
flatten_field_paths(nested, &path, out);
}
}
}
impl PvdDecoder {
pub fn decode_monitor_update(
&self,
data: &[u8],
desc: &StructureDesc,
) -> DecodeResult<MonitorUpdate> {
let (changed, mut offset) = self.read_bitset(data, 0)?;
let (value, consumed) =
self.decode_structure_with_bitset_body(&data[offset..], desc, &changed)?;
offset += consumed;
let (overrun, next) = self.read_bitset(data, offset)?;
Ok(MonitorUpdate {
value,
changed,
overrun,
consumed: next,
paths: bit_paths(desc),
})
}
pub fn decode_monitor_update_lenient(
&self,
data: &[u8],
desc: &StructureDesc,
) -> DecodeResult<(MonitorUpdate, MonitorLayout)> {
let candidates = [
(
MonitorLayout::SpecOrder,
self.decode_monitor_update(data, desc),
),
(
MonitorLayout::OverrunBeforeData,
self.decode_overrun_before_data(data, desc),
),
(
MonitorLayout::ChangedOnly,
self.decode_changed_only(data, desc),
),
];
let mut best: Option<(MonitorUpdate, MonitorLayout, i32)> = None;
let mut last_err = DecodeError::Malformed("no monitor layout matched");
for (layout, result) in candidates {
match result {
Ok(update) => {
let score = score_decoded(&update.value);
let better = match &best {
None => true,
Some((prev, _, prev_score)) => {
score > *prev_score
|| (score == *prev_score && update.consumed > prev.consumed)
}
};
if better {
best = Some((update, layout, score));
}
}
Err(e) => last_err = e,
}
}
best.map(|(u, l, _)| (u, l)).ok_or(last_err)
}
fn decode_overrun_before_data(
&self,
data: &[u8],
desc: &StructureDesc,
) -> DecodeResult<MonitorUpdate> {
let (changed, offset) = self.read_bitset(data, 0)?;
let (overrun, mut offset) = self.read_bitset(data, offset)?;
let (value, consumed) =
self.decode_structure_with_bitset_body(&data[offset..], desc, &changed)?;
offset += consumed;
Ok(MonitorUpdate {
value,
changed,
overrun,
consumed: offset,
paths: bit_paths(desc),
})
}
fn decode_changed_only(
&self,
data: &[u8],
desc: &StructureDesc,
) -> DecodeResult<MonitorUpdate> {
let (changed, mut offset) = self.read_bitset(data, 0)?;
let (value, consumed) =
self.decode_structure_with_bitset_body(&data[offset..], desc, &changed)?;
offset += consumed;
Ok(MonitorUpdate {
value,
changed,
overrun: Vec::new(),
consumed: offset,
paths: bit_paths(desc),
})
}
fn read_bitset(&self, data: &[u8], offset: usize) -> DecodeResult<(Vec<u8>, usize)> {
if offset > data.len() {
return Err(DecodeError::Truncated {
needed: offset,
available: data.len(),
});
}
let (size, consumed) = self.decode_size(&data[offset..])?;
let start = offset + consumed;
let end = start + size;
if end > data.len() {
return Err(DecodeError::Truncated {
needed: end,
available: data.len(),
});
}
Ok((data[start..end].to_vec(), end))
}
}
fn score_decoded(value: &DecodedValue) -> i32 {
let DecodedValue::Structure(fields) = value else {
return -1;
};
let mut score = fields.len() as i32;
let mut has_value = false;
let mut has_alarm = false;
let mut has_ts = false;
for (name, val) in fields {
match name.as_str() {
"value" => {
has_value = true;
score += 4;
match val {
DecodedValue::Array(items) => {
if items.is_empty() {
score -= 2;
} else {
score += 6 + (items.len().min(8) as i32);
}
}
DecodedValue::Structure(_) => score += 1,
_ => score += 2,
}
}
"alarm" => {
has_alarm = true;
score += 2;
}
"timeStamp" => {
has_ts = true;
score += 2;
if let DecodedValue::Structure(ts_fields) = val {
if let Some(secs) = ts_fields.iter().find_map(|(n, v)| {
if n == "secondsPastEpoch" {
if let DecodedValue::Int64(s) = v {
return Some(*s);
}
}
None
}) {
if (0..=4_000_000_000i64).contains(&secs) {
score += 2;
} else if secs.abs() > 10_000_000_000i64 {
score -= 2;
}
}
}
}
"display" | "control" => {
score += 1;
}
_ => {}
}
}
if !has_value {
score -= 2;
}
if !has_alarm {
score -= 1;
}
if !has_ts {
score -= 1;
}
score
}
#[cfg(test)]
mod tests {
use super::*;
use crate::spvd_decode::{FieldDesc, FieldType, PvdDecoder, StructureDesc, TypeCode};
fn nt_scalar_desc() -> StructureDesc {
let mut alarm = StructureDesc::new();
alarm.fields.push(FieldDesc {
name: "severity".to_string(),
field_type: FieldType::Scalar(TypeCode::Int32),
});
let mut desc = StructureDesc::new();
desc.fields.push(FieldDesc {
name: "value".to_string(),
field_type: FieldType::Scalar(TypeCode::Int32),
});
desc.fields.push(FieldDesc {
name: "alarm".to_string(),
field_type: FieldType::Structure(alarm),
});
desc
}
fn spec_order_body(changed: u8, value: i32, overrun: u8) -> Vec<u8> {
let mut b = vec![1, changed];
b.extend_from_slice(&value.to_le_bytes());
b.extend_from_slice(&[1, overrun]);
b
}
#[test]
fn decodes_spec_order_and_reports_consumed() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let body = spec_order_body(0b0000_0010, 42, 0);
let update = decoder.decode_monitor_update(&body, &desc).unwrap();
assert_eq!(update.changed, vec![0b0000_0010]);
assert_eq!(update.overrun, vec![0]);
assert!(!update.has_overrun());
assert_eq!(update.consumed, body.len());
let DecodedValue::Structure(fields) = &update.value else {
panic!("expected a structure");
};
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].0, "value");
}
#[test]
fn overrun_bits_resolve_to_field_paths() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let body = spec_order_body(0b0000_0010, 42, 0b0000_1010);
let update = decoder.decode_monitor_update(&body, &desc).unwrap();
assert!(update.has_overrun());
assert_eq!(
update.overrun_fields(&desc),
vec!["value", "alarm.severity"]
);
}
#[test]
fn bit_zero_overrun_reports_the_whole_structure() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let body = spec_order_body(0b0000_0010, 42, 0b0000_0001);
let update = decoder.decode_monitor_update(&body, &desc).unwrap();
assert_eq!(update.overrun_fields(&desc), vec!["<whole structure>"]);
}
#[test]
fn flatten_field_paths_agrees_with_count_structure_fields() {
let mut leaf = StructureDesc::new();
leaf.fields.push(FieldDesc {
name: "deep".to_string(),
field_type: FieldType::Scalar(TypeCode::Int32),
});
let mut mid = StructureDesc::new();
mid.fields.push(FieldDesc {
name: "a".to_string(),
field_type: FieldType::Scalar(TypeCode::Int32),
});
mid.fields.push(FieldDesc {
name: "leaf".to_string(),
field_type: FieldType::Structure(leaf),
});
mid.fields.push(FieldDesc {
name: "b".to_string(),
field_type: FieldType::Scalar(TypeCode::Int32),
});
let mut root = StructureDesc::new();
root.fields.push(FieldDesc {
name: "value".to_string(),
field_type: FieldType::Scalar(TypeCode::Int32),
});
root.fields.push(FieldDesc {
name: "mid".to_string(),
field_type: FieldType::Structure(mid),
});
root.fields.push(FieldDesc {
name: "tail".to_string(),
field_type: FieldType::Scalar(TypeCode::Int32),
});
let mut paths = Vec::new();
flatten_field_paths(&root, "", &mut paths);
assert_eq!(
paths,
vec![
"value",
"mid",
"mid.a",
"mid.leaf",
"mid.leaf.deep",
"mid.b",
"tail",
]
);
assert_eq!(
paths.len(),
crate::spvd_decode::count_structure_fields(&root),
"one path per bit, in the same order the bits are numbered"
);
let mut overrun = vec![0u8; 2];
let bit = 1 + 4; overrun[bit / 8] |= 1 << (bit % 8);
let update = MonitorUpdate {
value: DecodedValue::Structure(Vec::new()),
changed: Vec::new(),
overrun,
consumed: 0,
paths: bit_paths(&root),
};
assert_eq!(update.overrun_fields(&root), vec!["mid.leaf.deep"]);
assert_eq!(update.overrun_paths(), vec!["mid.leaf.deep"]);
}
#[test]
fn monitor_update_reports_overrun_paths() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let body = spec_order_body(0b0000_0010, 42, 0b0000_1000);
let update = decoder.decode_monitor_update(&body, &desc).unwrap();
assert_eq!(update.changed_paths(), vec!["value"]);
assert_eq!(update.overrun_paths(), vec!["alarm.severity"]);
assert!(update.has_overrun());
}
#[test]
fn missing_overrun_bitset_is_truncated_not_silently_accepted() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let mut body = vec![1, 0b0000_0010];
body.extend_from_slice(&42i32.to_le_bytes());
assert!(matches!(
decoder.decode_monitor_update(&body, &desc).unwrap_err(),
DecodeError::Truncated { .. }
));
}
#[test]
fn lenient_identifies_the_spec_layout() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let body = spec_order_body(0b0000_0010, 42, 0);
let (_, layout) = decoder.decode_monitor_update_lenient(&body, &desc).unwrap();
assert_eq!(layout, MonitorLayout::SpecOrder);
}
#[test]
fn lenient_recovers_the_overrun_before_data_layout() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let mut body = vec![1, 0b0000_0010, 1, 0];
body.extend_from_slice(&42i32.to_le_bytes());
let strict = decoder.decode_monitor_update(&body, &desc);
let strict_ok = strict.map(|u| u.value).ok();
assert_ne!(
strict_ok.as_ref().and_then(scalar_value_of),
Some(42),
"strict must not accidentally decode the non-spec layout"
);
let (update, layout) = decoder.decode_monitor_update_lenient(&body, &desc).unwrap();
assert_eq!(layout, MonitorLayout::OverrunBeforeData);
assert_eq!(scalar_value_of(&update.value), Some(42));
}
fn scalar_value_of(v: &DecodedValue) -> Option<i32> {
let DecodedValue::Structure(fields) = v else {
return None;
};
fields
.iter()
.find_map(|(name, val)| match (name.as_str(), val) {
("value", DecodedValue::Int32(n)) => Some(*n),
_ => None,
})
}
#[test]
fn lenient_falls_back_to_changed_only() {
let decoder = PvdDecoder::new(false);
let desc = nt_scalar_desc();
let mut body = vec![1, 0b0000_0010];
body.extend_from_slice(&42i32.to_le_bytes());
let (update, layout) = decoder.decode_monitor_update_lenient(&body, &desc).unwrap();
assert_eq!(layout, MonitorLayout::ChangedOnly);
assert!(update.overrun.is_empty());
}
#[test]
fn round_trips_an_encoded_delta() {
use crate::spvd_encode::{compute_changed_bits, encode_nt_payload_delta, nt_payload_desc};
use spvirit_types::{NtPayload, NtScalar, ScalarValue};
let prev = NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(1.0)));
let next = NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(3.5)));
let desc = nt_payload_desc(&next);
let (bitset, values) =
encode_nt_payload_delta(&prev, &next, &desc, false).expect("value changed");
let mut body = bitset.clone();
body.extend_from_slice(&values);
body.extend_from_slice(&[0u8]);
let decoder = PvdDecoder::new(false);
let update = decoder.decode_monitor_update(&body, &desc).unwrap();
assert_eq!(update.consumed, body.len());
assert!(!update.has_overrun());
let bits =
compute_changed_bits(&projection(&prev, &desc), &projection(&next, &desc), &desc)
.expect("value changed");
let mut expected = vec![0u8; bits.len().div_ceil(8)];
for (i, b) in bits.iter().enumerate() {
if *b {
expected[i / 8] |= 1 << (i % 8);
}
}
assert_eq!(update.changed, expected);
assert!(bits[1], "bit 1 is 'value', which is what changed");
let DecodedValue::Structure(fields) = &update.value else {
panic!("expected a structure");
};
let value = fields
.iter()
.find(|(n, _)| n == "value")
.map(|(_, v)| v)
.expect("value field present");
match value {
DecodedValue::Float64(v) => assert!((v - 3.5).abs() < 1e-9),
other => panic!("unexpected value {other:?}"),
}
}
fn projection(payload: &spvirit_types::NtPayload, desc: &StructureDesc) -> DecodedValue {
let bytes = crate::spvd_encode::encode_nt_payload_values_for_desc(payload, desc, false);
PvdDecoder::new(false)
.decode_structure(&bytes, desc)
.expect("projection")
.0
}
}