Skip to main content

spvirit_codec/
monitor.rs

1//! Monitor delta decoding: changed bitset, value, overrun bitset.
2//!
3//! The pvAccess specification puts a MONITOR update's changed bitset first,
4//! then the delta data, then the overrun bitset. That order is what
5//! [`PvdDecoder::decode_monitor_update`] implements, and it is what every live
6//! connection in this workspace uses.
7//!
8//! Some implementations disagree about where the overrun bitset goes (or omit
9//! it entirely). [`PvdDecoder::decode_monitor_update_lenient`] tries every
10//! known layout and reports which one it matched; it exists for mid-stream
11//! packet captures, where the introspection was never seen and the peer's
12//! layout has to be inferred.
13
14use crate::error::{DecodeError, DecodeResult};
15use crate::spvd_decode::{DecodedValue, FieldType, PvdDecoder, StructureDesc};
16
17/// A decoded MONITOR update: the delta value plus both bitsets.
18#[derive(Debug, Clone)]
19pub struct MonitorUpdate {
20    pub value: DecodedValue,
21    /// Raw changed bitset. Bit 0 is the whole structure; field bits start at 1.
22    pub changed: Vec<u8>,
23    /// Raw overrun bitset, same numbering. A set bit means the server dropped
24    /// at least one update for that field before this one.
25    pub overrun: Vec<u8>,
26    /// Bytes consumed from the body.
27    pub consumed: usize,
28    /// Bit-indexed field paths for this update's introspection: index 0 is
29    /// `"<whole structure>"`, index *n* is the field addressed by bit *n*.
30    ///
31    /// Captured at decode time so a callback can name the set bits without
32    /// also being handed the [`StructureDesc`].
33    pub paths: Vec<String>,
34}
35
36/// Which wire layout a lenient decode matched.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum MonitorLayout {
39    /// changed bitset, data, overrun bitset. The pvAccess specification order.
40    SpecOrder,
41    /// changed bitset, overrun bitset, data.
42    OverrunBeforeData,
43    /// changed bitset, data, with no overrun bitset.
44    ChangedOnly,
45}
46
47impl MonitorUpdate {
48    /// True if any overrun bit is set.
49    pub fn has_overrun(&self) -> bool {
50        self.overrun.iter().any(|b| *b != 0)
51    }
52
53    /// Dotted paths of the fields whose overrun bits are set.
54    ///
55    /// Bit 0 yields the literal `"<whole structure>"`. Takes the descriptor
56    /// explicitly; [`MonitorUpdate::overrun_paths`] uses the paths the decoder
57    /// already captured.
58    pub fn overrun_fields(&self, desc: &StructureDesc) -> Vec<String> {
59        select_paths(&bit_paths(desc), &self.overrun)
60    }
61
62    /// Dotted paths of the fields marked changed in this update.
63    ///
64    /// Bit 0 yields the literal `"<whole structure>"`.
65    pub fn changed_paths(&self) -> Vec<String> {
66        select_paths(&self.paths, &self.changed)
67    }
68
69    /// Dotted paths of the fields whose overrun bits are set — the server
70    /// dropped at least one earlier update for each of them.
71    pub fn overrun_paths(&self) -> Vec<String> {
72        select_paths(&self.paths, &self.overrun)
73    }
74}
75
76/// Bit-indexed paths for a descriptor: bit 0 is the whole structure, field
77/// bits follow in `flatten_field_paths` order.
78pub(crate) fn bit_paths(desc: &StructureDesc) -> Vec<String> {
79    let mut paths = vec!["<whole structure>".to_string()];
80    flatten_field_paths(desc, "", &mut paths);
81    paths
82}
83
84/// The subset of `paths` whose bit is set in `bits`.
85fn select_paths(paths: &[String], bits: &[u8]) -> Vec<String> {
86    paths
87        .iter()
88        .enumerate()
89        .filter(|(bit, _)| {
90            let byte = bit / 8;
91            byte < bits.len() && (bits[byte] & (1 << (bit % 8))) != 0
92        })
93        .map(|(_, path)| path.clone())
94        .collect()
95}
96
97/// Depth-first, self-then-nested. Must stay in step with
98/// `count_structure_fields` in `spvd_decode.rs`, which numbers the bits.
99fn flatten_field_paths(desc: &StructureDesc, prefix: &str, out: &mut Vec<String>) {
100    for field in &desc.fields {
101        let path = if prefix.is_empty() {
102            field.name.clone()
103        } else {
104            format!("{prefix}.{}", field.name)
105        };
106        out.push(path.clone());
107        if let FieldType::Structure(nested) = &field.field_type {
108            flatten_field_paths(nested, &path, out);
109        }
110    }
111}
112
113impl PvdDecoder {
114    /// Decode a MONITOR update in specification order: changed bitset, then
115    /// the delta data, then the overrun bitset.
116    pub fn decode_monitor_update(
117        &self,
118        data: &[u8],
119        desc: &StructureDesc,
120    ) -> DecodeResult<MonitorUpdate> {
121        let (changed, mut offset) = self.read_bitset(data, 0)?;
122        let (value, consumed) =
123            self.decode_structure_with_bitset_body(&data[offset..], desc, &changed)?;
124        offset += consumed;
125        let (overrun, next) = self.read_bitset(data, offset)?;
126        Ok(MonitorUpdate {
127            value,
128            changed,
129            overrun,
130            consumed: next,
131            paths: bit_paths(desc),
132        })
133    }
134
135    /// Try all three known layouts and report which one won.
136    ///
137    /// For mid-stream packet captures, where the introspection was missed and
138    /// the peer's layout is unknown. Live connections should use
139    /// [`PvdDecoder::decode_monitor_update`].
140    pub fn decode_monitor_update_lenient(
141        &self,
142        data: &[u8],
143        desc: &StructureDesc,
144    ) -> DecodeResult<(MonitorUpdate, MonitorLayout)> {
145        let candidates = [
146            (
147                MonitorLayout::SpecOrder,
148                self.decode_monitor_update(data, desc),
149            ),
150            (
151                MonitorLayout::OverrunBeforeData,
152                self.decode_overrun_before_data(data, desc),
153            ),
154            (
155                MonitorLayout::ChangedOnly,
156                self.decode_changed_only(data, desc),
157            ),
158        ];
159
160        let mut best: Option<(MonitorUpdate, MonitorLayout, i32)> = None;
161        let mut last_err = DecodeError::Malformed("no monitor layout matched");
162        for (layout, result) in candidates {
163            match result {
164                Ok(update) => {
165                    let score = score_decoded(&update.value);
166                    let better = match &best {
167                        None => true,
168                        Some((prev, _, prev_score)) => {
169                            score > *prev_score
170                                || (score == *prev_score && update.consumed > prev.consumed)
171                        }
172                    };
173                    if better {
174                        best = Some((update, layout, score));
175                    }
176                }
177                Err(e) => last_err = e,
178            }
179        }
180
181        best.map(|(u, l, _)| (u, l)).ok_or(last_err)
182    }
183
184    /// changed bitset, overrun bitset, data.
185    fn decode_overrun_before_data(
186        &self,
187        data: &[u8],
188        desc: &StructureDesc,
189    ) -> DecodeResult<MonitorUpdate> {
190        let (changed, offset) = self.read_bitset(data, 0)?;
191        let (overrun, mut offset) = self.read_bitset(data, offset)?;
192        let (value, consumed) =
193            self.decode_structure_with_bitset_body(&data[offset..], desc, &changed)?;
194        offset += consumed;
195        Ok(MonitorUpdate {
196            value,
197            changed,
198            overrun,
199            consumed: offset,
200            paths: bit_paths(desc),
201        })
202    }
203
204    /// changed bitset, data, nothing else.
205    fn decode_changed_only(
206        &self,
207        data: &[u8],
208        desc: &StructureDesc,
209    ) -> DecodeResult<MonitorUpdate> {
210        let (changed, mut offset) = self.read_bitset(data, 0)?;
211        let (value, consumed) =
212            self.decode_structure_with_bitset_body(&data[offset..], desc, &changed)?;
213        offset += consumed;
214        Ok(MonitorUpdate {
215            value,
216            changed,
217            overrun: Vec::new(),
218            consumed: offset,
219            paths: bit_paths(desc),
220        })
221    }
222
223    /// Read a size-prefixed bitset starting at `offset`; returns it and the
224    /// offset just past it.
225    fn read_bitset(&self, data: &[u8], offset: usize) -> DecodeResult<(Vec<u8>, usize)> {
226        if offset > data.len() {
227            return Err(DecodeError::Truncated {
228                needed: offset,
229                available: data.len(),
230            });
231        }
232        let (size, consumed) = self.decode_size(&data[offset..])?;
233        let start = offset + consumed;
234        let end = start + size;
235        if end > data.len() {
236            return Err(DecodeError::Truncated {
237                needed: end,
238                available: data.len(),
239            });
240        }
241        Ok((data[start..end].to_vec(), end))
242    }
243}
244
245/// Plausibility score for a candidate decode. Higher is better.
246///
247/// Moved verbatim from `epics_decode.rs`, where it drove the old
248/// three-way "try everything" monitor decode. Only
249/// [`PvdDecoder::decode_monitor_update_lenient`] uses it now.
250fn score_decoded(value: &DecodedValue) -> i32 {
251    let DecodedValue::Structure(fields) = value else {
252        return -1;
253    };
254
255    let mut score = fields.len() as i32;
256
257    let mut has_value = false;
258    let mut has_alarm = false;
259    let mut has_ts = false;
260
261    for (name, val) in fields {
262        match name.as_str() {
263            "value" => {
264                has_value = true;
265                score += 4;
266                match val {
267                    DecodedValue::Array(items) => {
268                        if items.is_empty() {
269                            score -= 2;
270                        } else {
271                            score += 6 + (items.len().min(8) as i32);
272                        }
273                    }
274                    DecodedValue::Structure(_) => score += 1,
275                    _ => score += 2,
276                }
277            }
278            "alarm" => {
279                has_alarm = true;
280                score += 2;
281            }
282            "timeStamp" => {
283                has_ts = true;
284                score += 2;
285                if let DecodedValue::Structure(ts_fields) = val {
286                    if let Some(secs) = ts_fields.iter().find_map(|(n, v)| {
287                        if n == "secondsPastEpoch" {
288                            if let DecodedValue::Int64(s) = v {
289                                return Some(*s);
290                            }
291                        }
292                        None
293                    }) {
294                        if (0..=4_000_000_000i64).contains(&secs) {
295                            score += 2;
296                        } else if secs.abs() > 10_000_000_000i64 {
297                            score -= 2;
298                        }
299                    }
300                }
301            }
302            "display" | "control" => {
303                score += 1;
304            }
305            _ => {}
306        }
307    }
308
309    if !has_value {
310        score -= 2;
311    }
312    if !has_alarm {
313        score -= 1;
314    }
315    if !has_ts {
316        score -= 1;
317    }
318
319    score
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325    use crate::spvd_decode::{FieldDesc, FieldType, PvdDecoder, StructureDesc, TypeCode};
326
327    fn nt_scalar_desc() -> StructureDesc {
328        let mut alarm = StructureDesc::new();
329        alarm.fields.push(FieldDesc {
330            name: "severity".to_string(),
331            field_type: FieldType::Scalar(TypeCode::Int32),
332        });
333
334        let mut desc = StructureDesc::new();
335        desc.fields.push(FieldDesc {
336            name: "value".to_string(),
337            field_type: FieldType::Scalar(TypeCode::Int32),
338        });
339        desc.fields.push(FieldDesc {
340            name: "alarm".to_string(),
341            field_type: FieldType::Structure(alarm),
342        });
343        desc
344    }
345
346    /// changed bitset (1 byte), value data, overrun bitset (1 byte).
347    fn spec_order_body(changed: u8, value: i32, overrun: u8) -> Vec<u8> {
348        let mut b = vec![1, changed];
349        b.extend_from_slice(&value.to_le_bytes());
350        b.extend_from_slice(&[1, overrun]);
351        b
352    }
353
354    #[test]
355    fn decodes_spec_order_and_reports_consumed() {
356        let decoder = PvdDecoder::new(false);
357        let desc = nt_scalar_desc();
358        // bit 1 = "value".
359        let body = spec_order_body(0b0000_0010, 42, 0);
360        let update = decoder.decode_monitor_update(&body, &desc).unwrap();
361
362        assert_eq!(update.changed, vec![0b0000_0010]);
363        assert_eq!(update.overrun, vec![0]);
364        assert!(!update.has_overrun());
365        assert_eq!(update.consumed, body.len());
366        let DecodedValue::Structure(fields) = &update.value else {
367            panic!("expected a structure");
368        };
369        assert_eq!(fields.len(), 1);
370        assert_eq!(fields[0].0, "value");
371    }
372
373    #[test]
374    fn overrun_bits_resolve_to_field_paths() {
375        let decoder = PvdDecoder::new(false);
376        let desc = nt_scalar_desc();
377        // Overrun on bit 1 ("value") and bit 3 ("alarm.severity").
378        let body = spec_order_body(0b0000_0010, 42, 0b0000_1010);
379        let update = decoder.decode_monitor_update(&body, &desc).unwrap();
380
381        assert!(update.has_overrun());
382        assert_eq!(
383            update.overrun_fields(&desc),
384            vec!["value", "alarm.severity"]
385        );
386    }
387
388    #[test]
389    fn bit_zero_overrun_reports_the_whole_structure() {
390        let decoder = PvdDecoder::new(false);
391        let desc = nt_scalar_desc();
392        let body = spec_order_body(0b0000_0010, 42, 0b0000_0001);
393        let update = decoder.decode_monitor_update(&body, &desc).unwrap();
394        assert_eq!(update.overrun_fields(&desc), vec!["<whole structure>"]);
395    }
396
397    /// The bit numbering `overrun_fields` walks must be the same numbering
398    /// `decode_structure_with_bitset_body` uses, which is
399    /// `count_structure_fields`' self-then-nested depth-first order. If the
400    /// two ever diverge, overrun bits map to the wrong field names.
401    #[test]
402    fn flatten_field_paths_agrees_with_count_structure_fields() {
403        let mut leaf = StructureDesc::new();
404        leaf.fields.push(FieldDesc {
405            name: "deep".to_string(),
406            field_type: FieldType::Scalar(TypeCode::Int32),
407        });
408
409        let mut mid = StructureDesc::new();
410        mid.fields.push(FieldDesc {
411            name: "a".to_string(),
412            field_type: FieldType::Scalar(TypeCode::Int32),
413        });
414        mid.fields.push(FieldDesc {
415            name: "leaf".to_string(),
416            field_type: FieldType::Structure(leaf),
417        });
418        mid.fields.push(FieldDesc {
419            name: "b".to_string(),
420            field_type: FieldType::Scalar(TypeCode::Int32),
421        });
422
423        let mut root = StructureDesc::new();
424        root.fields.push(FieldDesc {
425            name: "value".to_string(),
426            field_type: FieldType::Scalar(TypeCode::Int32),
427        });
428        root.fields.push(FieldDesc {
429            name: "mid".to_string(),
430            field_type: FieldType::Structure(mid),
431        });
432        root.fields.push(FieldDesc {
433            name: "tail".to_string(),
434            field_type: FieldType::Scalar(TypeCode::Int32),
435        });
436
437        let mut paths = Vec::new();
438        flatten_field_paths(&root, "", &mut paths);
439
440        assert_eq!(
441            paths,
442            vec![
443                "value",
444                "mid",
445                "mid.a",
446                "mid.leaf",
447                "mid.leaf.deep",
448                "mid.b",
449                "tail",
450            ]
451        );
452        assert_eq!(
453            paths.len(),
454            crate::spvd_decode::count_structure_fields(&root),
455            "one path per bit, in the same order the bits are numbered"
456        );
457
458        // And the paths line up with the bits when read through an update:
459        // bit 0 is the whole structure, so field bits start at 1.
460        let mut overrun = vec![0u8; 2];
461        let bit = 1 + 4; // "mid.leaf.deep"
462        overrun[bit / 8] |= 1 << (bit % 8);
463        let update = MonitorUpdate {
464            value: DecodedValue::Structure(Vec::new()),
465            changed: Vec::new(),
466            overrun,
467            consumed: 0,
468            paths: bit_paths(&root),
469        };
470        assert_eq!(update.overrun_fields(&root), vec!["mid.leaf.deep"]);
471        assert_eq!(update.overrun_paths(), vec!["mid.leaf.deep"]);
472    }
473
474    /// The shape the client monitor callback relies on: a decoded update can
475    /// name its own changed and overrun bits without the caller holding the
476    /// descriptor.
477    #[test]
478    fn monitor_update_reports_overrun_paths() {
479        let decoder = PvdDecoder::new(false);
480        let desc = nt_scalar_desc();
481        // changed = bit 1 ("value"), overrun = bit 3 ("alarm.severity").
482        let body = spec_order_body(0b0000_0010, 42, 0b0000_1000);
483        let update = decoder.decode_monitor_update(&body, &desc).unwrap();
484
485        assert_eq!(update.changed_paths(), vec!["value"]);
486        assert_eq!(update.overrun_paths(), vec!["alarm.severity"]);
487        assert!(update.has_overrun());
488    }
489
490    #[test]
491    fn missing_overrun_bitset_is_truncated_not_silently_accepted() {
492        let decoder = PvdDecoder::new(false);
493        let desc = nt_scalar_desc();
494        // changed bitset and value, then nothing.
495        let mut body = vec![1, 0b0000_0010];
496        body.extend_from_slice(&42i32.to_le_bytes());
497        assert!(matches!(
498            decoder.decode_monitor_update(&body, &desc).unwrap_err(),
499            DecodeError::Truncated { .. }
500        ));
501    }
502
503    #[test]
504    fn lenient_identifies_the_spec_layout() {
505        let decoder = PvdDecoder::new(false);
506        let desc = nt_scalar_desc();
507        let body = spec_order_body(0b0000_0010, 42, 0);
508        let (_, layout) = decoder.decode_monitor_update_lenient(&body, &desc).unwrap();
509        assert_eq!(layout, MonitorLayout::SpecOrder);
510    }
511
512    #[test]
513    fn lenient_recovers_the_overrun_before_data_layout() {
514        let decoder = PvdDecoder::new(false);
515        let desc = nt_scalar_desc();
516        // changed bitset, overrun bitset, then the value.
517        let mut body = vec![1, 0b0000_0010, 1, 0];
518        body.extend_from_slice(&42i32.to_le_bytes());
519
520        // Strict must not accept it: it would read the value from the
521        // overrun bitset's bytes.
522        let strict = decoder.decode_monitor_update(&body, &desc);
523        let strict_ok = strict.map(|u| u.value).ok();
524        assert_ne!(
525            strict_ok.as_ref().and_then(scalar_value_of),
526            Some(42),
527            "strict must not accidentally decode the non-spec layout"
528        );
529
530        let (update, layout) = decoder.decode_monitor_update_lenient(&body, &desc).unwrap();
531        assert_eq!(layout, MonitorLayout::OverrunBeforeData);
532        assert_eq!(scalar_value_of(&update.value), Some(42));
533    }
534
535    fn scalar_value_of(v: &DecodedValue) -> Option<i32> {
536        let DecodedValue::Structure(fields) = v else {
537            return None;
538        };
539        fields
540            .iter()
541            .find_map(|(name, val)| match (name.as_str(), val) {
542                ("value", DecodedValue::Int32(n)) => Some(*n),
543                _ => None,
544            })
545    }
546
547    #[test]
548    fn lenient_falls_back_to_changed_only() {
549        let decoder = PvdDecoder::new(false);
550        let desc = nt_scalar_desc();
551        // changed bitset and value, no overrun bitset at all.
552        let mut body = vec![1, 0b0000_0010];
553        body.extend_from_slice(&42i32.to_le_bytes());
554        let (update, layout) = decoder.decode_monitor_update_lenient(&body, &desc).unwrap();
555        assert_eq!(layout, MonitorLayout::ChangedOnly);
556        assert!(update.overrun.is_empty());
557    }
558
559    #[test]
560    fn round_trips_an_encoded_delta() {
561        use crate::spvd_encode::{compute_changed_bits, encode_nt_payload_delta, nt_payload_desc};
562        use spvirit_types::{NtPayload, NtScalar, ScalarValue};
563
564        let prev = NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(1.0)));
565        let next = NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(3.5)));
566        let desc = nt_payload_desc(&next);
567
568        let (bitset, values) =
569            encode_nt_payload_delta(&prev, &next, &desc, false).expect("value changed");
570
571        // Spec order: changed bitset, data, then an empty overrun bitset.
572        let mut body = bitset.clone();
573        body.extend_from_slice(&values);
574        body.extend_from_slice(&[0u8]); // zero-length overrun bitset
575
576        let decoder = PvdDecoder::new(false);
577        let update = decoder.decode_monitor_update(&body, &desc).unwrap();
578
579        assert_eq!(update.consumed, body.len());
580        assert!(!update.has_overrun());
581
582        // The changed bitset the decoder reports is exactly what
583        // compute_changed_bits produced for this pair.
584        let bits =
585            compute_changed_bits(&projection(&prev, &desc), &projection(&next, &desc), &desc)
586                .expect("value changed");
587        let mut expected = vec![0u8; bits.len().div_ceil(8)];
588        for (i, b) in bits.iter().enumerate() {
589            if *b {
590                expected[i / 8] |= 1 << (i % 8);
591            }
592        }
593        assert_eq!(update.changed, expected);
594        assert!(bits[1], "bit 1 is 'value', which is what changed");
595
596        // And the value round-trips.
597        let DecodedValue::Structure(fields) = &update.value else {
598            panic!("expected a structure");
599        };
600        let value = fields
601            .iter()
602            .find(|(n, _)| n == "value")
603            .map(|(_, v)| v)
604            .expect("value field present");
605        match value {
606            DecodedValue::Float64(v) => assert!((v - 3.5).abs() < 1e-9),
607            other => panic!("unexpected value {other:?}"),
608        }
609    }
610
611    /// `compute_changed_bits` compares two `DecodedValue`s; `encode_nt_payload_delta`
612    /// projects each payload onto the descriptor first. Reproduce that
613    /// projection by encoding and decoding against the same descriptor.
614    fn projection(payload: &spvirit_types::NtPayload, desc: &StructureDesc) -> DecodedValue {
615        let bytes = crate::spvd_encode::encode_nt_payload_values_for_desc(payload, desc, false);
616        PvdDecoder::new(false)
617            .decode_structure(&bytes, desc)
618            .expect("projection")
619            .0
620    }
621}