Skip to main content

spvirit_codec/
spvd_decode.rs

1//! PVD (pvData) Type Introspection and Value Decoding
2//!
3//! Implements parsing of PVAccess field descriptions and value decoding
4//! according to the pvData serialization specification.
5
6use std::fmt;
7use tracing::debug;
8
9use crate::error::{DecodeError, DecodeResult};
10
11/// Re-export the free-standing `decode_string` from `epics_decode` for
12/// discoverability alongside the other decode helpers in this module.
13pub use crate::epics_decode::decode_string;
14
15/// Ceilings on decoded array lengths, per array kind.
16///
17/// These are a backstop against corrupt or hostile element counts. Exceeding
18/// one is [`DecodeError::ArrayTooLarge`] — the decoder never returns a
19/// silently shortened array.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct DecodeLimits {
22    pub max_scalar_array: usize,
23    pub max_string_array: usize,
24    pub max_struct_array: usize,
25    pub max_union_array: usize,
26    pub max_variant_array: usize,
27}
28
29impl Default for DecodeLimits {
30    fn default() -> Self {
31        Self {
32            max_scalar_array: 4_000_000,
33            max_string_array: 65_536,
34            max_struct_array: 65_536,
35            max_union_array: 65_536,
36            max_variant_array: 65_536,
37        }
38    }
39}
40
41/// PVD type codes from the specification
42#[repr(u8)]
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum TypeCode {
45    Null = 0xFF,
46    Boolean = 0x00,
47    Int8 = 0x20,
48    Int16 = 0x21,
49    Int32 = 0x22,
50    Int64 = 0x23,
51    UInt8 = 0x24,
52    UInt16 = 0x25,
53    UInt32 = 0x26,
54    UInt64 = 0x27,
55    Float32 = 0x42,
56    Float64 = 0x43,
57    String = 0x60,
58    // Bounded string has 0x83 prefix followed by size
59    Variant = 0xFE, // Union with no fixed type (0xFF is Null)
60}
61
62impl TypeCode {
63    pub fn from_byte(b: u8) -> Option<Self> {
64        // Clear scalar-array mode bits (variable/bounded/fixed array)
65        let base = b & 0xE7;
66        match base {
67            0x00 => Some(TypeCode::Boolean),
68            0x20 => Some(TypeCode::Int8),
69            0x21 => Some(TypeCode::Int16),
70            0x22 => Some(TypeCode::Int32),
71            0x23 => Some(TypeCode::Int64),
72            0x24 => Some(TypeCode::UInt8),
73            0x25 => Some(TypeCode::UInt16),
74            0x26 => Some(TypeCode::UInt32),
75            0x27 => Some(TypeCode::UInt64),
76            0x42 => Some(TypeCode::Float32),
77            0x43 => Some(TypeCode::Float64),
78            0x60 => Some(TypeCode::String),
79            _ => None,
80        }
81    }
82
83    pub fn size(&self) -> Option<usize> {
84        match self {
85            TypeCode::Boolean | TypeCode::Int8 | TypeCode::UInt8 => Some(1),
86            TypeCode::Int16 | TypeCode::UInt16 => Some(2),
87            TypeCode::Int32 | TypeCode::UInt32 | TypeCode::Float32 => Some(4),
88            TypeCode::Int64 | TypeCode::UInt64 | TypeCode::Float64 => Some(8),
89            TypeCode::String | TypeCode::Null | TypeCode::Variant => None,
90        }
91    }
92}
93
94/// Field type description
95#[derive(Debug, Clone, PartialEq)]
96pub enum FieldType {
97    Scalar(TypeCode),
98    ScalarArray(TypeCode),
99    String,
100    StringArray,
101    Structure(StructureDesc),
102    StructureArray(StructureDesc),
103    Union(Vec<FieldDesc>),
104    UnionArray(Vec<FieldDesc>),
105    Variant,
106    VariantArray,
107    BoundedString(u32),
108}
109
110impl FieldType {
111    pub fn type_name(&self) -> &'static str {
112        match self {
113            FieldType::Scalar(tc) => match tc {
114                TypeCode::Boolean => "boolean",
115                TypeCode::Int8 => "byte",
116                TypeCode::Int16 => "short",
117                TypeCode::Int32 => "int",
118                TypeCode::Int64 => "long",
119                TypeCode::UInt8 => "ubyte",
120                TypeCode::UInt16 => "ushort",
121                TypeCode::UInt32 => "uint",
122                TypeCode::UInt64 => "ulong",
123                TypeCode::Float32 => "float",
124                TypeCode::Float64 => "double",
125                TypeCode::String => "string",
126                _ => "unknown",
127            },
128            FieldType::ScalarArray(tc) => match tc {
129                TypeCode::Float64 => "double[]",
130                TypeCode::Float32 => "float[]",
131                TypeCode::Int64 => "long[]",
132                TypeCode::Int32 => "int[]",
133                _ => "array",
134            },
135            FieldType::String => "string",
136            FieldType::StringArray => "string[]",
137            FieldType::Structure(_) => "structure",
138            FieldType::StructureArray(_) => "structure[]",
139            FieldType::Union(_) => "union",
140            FieldType::UnionArray(_) => "union[]",
141            FieldType::Variant => "any",
142            FieldType::VariantArray => "any[]",
143            FieldType::BoundedString(_) => "string",
144        }
145    }
146
147    /// Bytes this type owns on the heap, walked recursively.
148    ///
149    /// Only the nesting variants own anything; scalars are pure discriminant.
150    pub fn heap_size(&self) -> usize {
151        match self {
152            FieldType::Structure(s) | FieldType::StructureArray(s) => s.heap_size(),
153            FieldType::Union(f) | FieldType::UnionArray(f) => {
154                f.capacity() * std::mem::size_of::<FieldDesc>()
155                    + f.iter().map(FieldDesc::heap_size).sum::<usize>()
156            }
157            _ => 0,
158        }
159    }
160}
161
162/// Field description (name + type)
163#[derive(Debug, Clone, PartialEq)]
164pub struct FieldDesc {
165    pub name: String,
166    pub field_type: FieldType,
167}
168
169impl FieldDesc {
170    /// Bytes this field owns on the heap, including any nested structure.
171    pub fn heap_size(&self) -> usize {
172        self.name.capacity() + self.field_type.heap_size()
173    }
174}
175
176/// Structure description with optional ID
177#[derive(Debug, Clone, PartialEq)]
178pub struct StructureDesc {
179    pub struct_id: Option<String>,
180    pub fields: Vec<FieldDesc>,
181}
182
183impl StructureDesc {
184    pub fn new() -> Self {
185        Self {
186            struct_id: None,
187            fields: Vec::new(),
188        }
189    }
190
191    /// Look up a field by name.
192    pub fn field(&self, name: &str) -> Option<&FieldDesc> {
193        self.fields.iter().find(|f| f.name == name)
194    }
195
196    /// Bytes this description owns on the heap, walked recursively.
197    ///
198    /// Introspection is the one term in the state tracker's memory estimate
199    /// that is both large and expensive to measure: an NTScalar carries
200    /// thirty-odd nested `FieldDesc` nodes, each with its own name. Callers
201    /// are expected to cache this at assignment rather than re-walk the tree
202    /// on every accounting pass.
203    pub fn heap_size(&self) -> usize {
204        let id = self.struct_id.as_ref().map_or(0, |s| s.capacity());
205        let fields = self.fields.capacity() * std::mem::size_of::<FieldDesc>()
206            + self.fields.iter().map(FieldDesc::heap_size).sum::<usize>();
207        id + fields
208    }
209}
210
211impl Default for StructureDesc {
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217impl fmt::Display for StructureDesc {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        fn write_indent(f: &mut fmt::Formatter<'_>, depth: usize) -> fmt::Result {
220            for _ in 0..depth {
221                write!(f, "    ")?;
222            }
223            Ok(())
224        }
225
226        fn write_field_type(
227            f: &mut fmt::Formatter<'_>,
228            ft: &FieldType,
229            depth: usize,
230        ) -> fmt::Result {
231            match ft {
232                FieldType::Structure(desc) => write_structure(f, desc, depth),
233                FieldType::StructureArray(desc) => {
234                    write_structure(f, desc, depth)?;
235                    write!(f, "[]")
236                }
237                FieldType::Union(fields) => {
238                    writeln!(f, "union")?;
239                    for field in fields {
240                        write_indent(f, depth + 1)?;
241                        write!(f, "{} ", field.name)?;
242                        write_field_type(f, &field.field_type, depth + 1)?;
243                        writeln!(f)?;
244                    }
245                    Ok(())
246                }
247                FieldType::UnionArray(fields) => {
248                    writeln!(f, "union[]")?;
249                    for field in fields {
250                        write_indent(f, depth + 1)?;
251                        write!(f, "{} ", field.name)?;
252                        write_field_type(f, &field.field_type, depth + 1)?;
253                        writeln!(f)?;
254                    }
255                    Ok(())
256                }
257                other => write!(f, "{}", other.type_name()),
258            }
259        }
260
261        fn write_structure(
262            f: &mut fmt::Formatter<'_>,
263            desc: &StructureDesc,
264            depth: usize,
265        ) -> fmt::Result {
266            if let Some(id) = &desc.struct_id {
267                write!(f, "structure «{}»", id)?;
268            } else {
269                write!(f, "structure")?;
270            }
271            if desc.fields.is_empty() {
272                return Ok(());
273            }
274            writeln!(f)?;
275            for field in &desc.fields {
276                write_indent(f, depth + 1)?;
277                write!(f, "{} ", field.name)?;
278                write_field_type(f, &field.field_type, depth + 1)?;
279                writeln!(f)?;
280            }
281            Ok(())
282        }
283
284        write_structure(f, self, 0)
285    }
286}
287
288/// Decoded value
289#[derive(Debug, Clone)]
290pub enum DecodedValue {
291    Null,
292    Boolean(bool),
293    Int8(i8),
294    Int16(i16),
295    Int32(i32),
296    Int64(i64),
297    UInt8(u8),
298    UInt16(u16),
299    UInt32(u32),
300    UInt64(u64),
301    Float32(f32),
302    Float64(f64),
303    String(String),
304    Array(Vec<DecodedValue>),
305    Structure(Vec<(String, DecodedValue)>),
306    Raw(Vec<u8>),
307}
308
309impl fmt::Display for DecodedValue {
310    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311        match self {
312            DecodedValue::Null => write!(f, "null"),
313            DecodedValue::Boolean(v) => write!(f, "{}", v),
314            DecodedValue::Int8(v) => write!(f, "{}", v),
315            DecodedValue::Int16(v) => write!(f, "{}", v),
316            DecodedValue::Int32(v) => write!(f, "{}", v),
317            DecodedValue::Int64(v) => write!(f, "{}", v),
318            DecodedValue::UInt8(v) => write!(f, "{}", v),
319            DecodedValue::UInt16(v) => write!(f, "{}", v),
320            DecodedValue::UInt32(v) => write!(f, "{}", v),
321            DecodedValue::UInt64(v) => write!(f, "{}", v),
322            DecodedValue::Float32(v) => write!(f, "{:.6}", v),
323            DecodedValue::Float64(v) => write!(f, "{:.6}", v),
324            DecodedValue::String(v) => write!(f, "\"{}\"", v),
325            DecodedValue::Array(arr) => {
326                write!(f, "[")?;
327                for (i, v) in arr.iter().enumerate() {
328                    if i > 0 {
329                        write!(f, ", ")?;
330                    }
331                    write!(f, "{}", v)?;
332                }
333                write!(f, "]")
334            }
335            DecodedValue::Structure(fields) => {
336                write!(f, "{{")?;
337                for (i, (name, val)) in fields.iter().enumerate() {
338                    if i > 0 {
339                        write!(f, ", ")?;
340                    }
341                    write!(f, "{}={}", name, val)?;
342                }
343                write!(f, "}}")
344            }
345            DecodedValue::Raw(data) => {
346                if data.len() <= 8 {
347                    write!(f, "<{} bytes: {}>", data.len(), hex::encode(data))
348                } else {
349                    write!(f, "<{} bytes>", data.len())
350                }
351            }
352        }
353    }
354}
355
356/// PVD Decoder state
357pub struct PvdDecoder {
358    is_be: bool,
359    limits: DecodeLimits,
360    /// IntrospectionRegistry: maps int16 keys to previously seen FieldTypes.
361    /// Populated when parsing `0xFD` (full-with-id) entries, looked up on `0xFE` (only-id).
362    registry: std::cell::RefCell<std::collections::HashMap<u16, FieldType>>,
363}
364
365impl PvdDecoder {
366    pub fn new(is_be: bool) -> Self {
367        Self::with_limits(is_be, DecodeLimits::default())
368    }
369
370    /// Build a decoder with non-default array-length ceilings.
371    pub fn with_limits(is_be: bool, limits: DecodeLimits) -> Self {
372        Self {
373            is_be,
374            limits,
375            registry: std::cell::RefCell::new(std::collections::HashMap::new()),
376        }
377    }
378
379    /// The array-length ceilings this decoder was built with.
380    pub fn limits(&self) -> &DecodeLimits {
381        &self.limits
382    }
383
384    /// Decode a size value (PVA variable-length encoding)
385    pub fn decode_size(&self, data: &[u8]) -> DecodeResult<(usize, usize)> {
386        if data.is_empty() {
387            return Err(DecodeError::Truncated {
388                needed: 1,
389                available: 0,
390            });
391        }
392        let first = data[0];
393        if first == 0xFF {
394            // Special: -1 (null)
395            return Ok((0, 1)); // Treat as 0 for simplicity
396        }
397        if first < 254 {
398            return Ok((first as usize, 1));
399        }
400        if first == 254 {
401            // 4-byte size follows
402            if data.len() < 5 {
403                return Err(DecodeError::Truncated {
404                    needed: 5,
405                    available: data.len(),
406                });
407            }
408            let size = if self.is_be {
409                u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as usize
410            } else {
411                u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as usize
412            };
413            return Ok((size, 5));
414        }
415        // first == 255 is null marker, handled above.
416        Err(DecodeError::Malformed("invalid size prefix 255"))
417    }
418
419    /// Decode a string
420    pub fn decode_string(&self, data: &[u8]) -> DecodeResult<(String, usize)> {
421        let (size, size_bytes) = self.decode_size(data)?;
422        if size == 0 {
423            return Ok((String::new(), size_bytes));
424        }
425        if data.len() < size_bytes + size {
426            return Err(DecodeError::Truncated {
427                needed: size_bytes + size,
428                available: data.len(),
429            });
430        }
431        let s = std::str::from_utf8(&data[size_bytes..size_bytes + size])
432            .map_err(|_| DecodeError::Malformed("string is not valid UTF-8"))?;
433        Ok((s.to_string(), size_bytes + size))
434    }
435
436    /// Parse field description from introspection data
437    pub fn parse_field_desc(&self, data: &[u8]) -> DecodeResult<(FieldDesc, usize)> {
438        if data.is_empty() {
439            return Err(DecodeError::Truncated {
440                needed: 1,
441                available: 0,
442            });
443        }
444
445        let mut offset = 0;
446
447        // Parse field name
448        let (name, consumed) = self.decode_string(&data[offset..])?;
449        offset += consumed;
450
451        if offset >= data.len() {
452            return Err(DecodeError::Truncated {
453                needed: offset + 1,
454                available: data.len(),
455            });
456        }
457
458        // Parse type descriptor
459        let (field_type, type_consumed) = self.parse_type_desc(&data[offset..])?;
460        offset += type_consumed;
461
462        Ok((FieldDesc { name, field_type }, offset))
463    }
464
465    /// Parse type descriptor
466    fn parse_type_desc(&self, data: &[u8]) -> DecodeResult<(FieldType, usize)> {
467        if data.is_empty() {
468            return Err(DecodeError::Truncated {
469                needed: 1,
470                available: 0,
471            });
472        }
473
474        let type_byte = data[0];
475        let mut offset = 1;
476
477        // Check for NULL type
478        if type_byte == 0xFF {
479            return Ok((FieldType::Variant, 1));
480        }
481
482        // Full-with-id from IntrospectionRegistry:
483        // 0xFD + int16 key + type descriptor payload.
484        if type_byte == 0xFD {
485            if data.len() < 3 {
486                return Err(DecodeError::Truncated {
487                    needed: 3,
488                    available: data.len(),
489                });
490            }
491            let key = if self.is_be {
492                u16::from_be_bytes([data[1], data[2]])
493            } else {
494                u16::from_le_bytes([data[1], data[2]])
495            };
496            let (field_type, consumed) = self.parse_type_desc(&data[3..])?;
497            self.registry.borrow_mut().insert(key, field_type.clone());
498            return Ok((field_type, 3 + consumed));
499        }
500
501        // Only-id from IntrospectionRegistry:
502        // 0xFE + int16 key — reference to a previously seen type.
503        if type_byte == 0xFE {
504            if data.len() < 3 {
505                return Err(DecodeError::Truncated {
506                    needed: 3,
507                    available: data.len(),
508                });
509            }
510            let key = if self.is_be {
511                u16::from_be_bytes([data[1], data[2]])
512            } else {
513                u16::from_le_bytes([data[1], data[2]])
514            };
515            if let Some(ft) = self.registry.borrow().get(&key) {
516                return Ok((ft.clone(), 3));
517            }
518            debug!(
519                "Type descriptor ONLY_ID (0xFE) key={} not found in registry",
520                key
521            );
522            return Err(DecodeError::UnresolvedTypeId(key));
523        }
524
525        // Check for structure (0x80) or structure array (0x88)
526        if type_byte == 0x80 || type_byte == 0x88 {
527            let is_array = (type_byte & 0x08) != 0;
528            if is_array {
529                // Skip the inner structure element tag (0x80)
530                if offset >= data.len() {
531                    return Err(DecodeError::Truncated {
532                        needed: offset + 1,
533                        available: data.len(),
534                    });
535                }
536                if data[offset] != 0x80 {
537                    return Err(DecodeError::UnknownTypeTag(data[offset]));
538                }
539                offset += 1;
540            }
541            let (struct_desc, consumed) = self.parse_structure_desc(&data[offset..])?;
542            offset += consumed;
543            if is_array {
544                return Ok((FieldType::StructureArray(struct_desc), offset));
545            } else {
546                return Ok((FieldType::Structure(struct_desc), offset));
547            }
548        }
549
550        // Check for union (0x81) or union array (0x89)
551        if type_byte == 0x81 || type_byte == 0x89 {
552            let is_array = (type_byte & 0x08) != 0;
553            if is_array {
554                // Skip the inner union element tag (0x81)
555                if offset >= data.len() {
556                    return Err(DecodeError::Truncated {
557                        needed: offset + 1,
558                        available: data.len(),
559                    });
560                }
561                if data[offset] != 0x81 {
562                    return Err(DecodeError::UnknownTypeTag(data[offset]));
563                }
564                offset += 1;
565            }
566            // Parse union fields (same as structure)
567            let (struct_desc, consumed) = self.parse_structure_desc(&data[offset..])?;
568            offset += consumed;
569            if is_array {
570                return Ok((FieldType::UnionArray(struct_desc.fields), offset));
571            } else {
572                return Ok((FieldType::Union(struct_desc.fields), offset));
573            }
574        }
575
576        // Check for variant/any (0x82) or variant array (0x8A)
577        if type_byte == 0x82 {
578            return Ok((FieldType::Variant, 1));
579        }
580        if type_byte == 0x8A {
581            return Ok((FieldType::VariantArray, 1));
582        }
583
584        // Check for bounded string (0x83, legacy 0x86 accepted for compatibility)
585        if type_byte == 0x83 || type_byte == 0x86 {
586            let (bound, consumed) = self.decode_size(&data[offset..])?;
587            offset += consumed;
588            return Ok((FieldType::BoundedString(bound as u32), offset));
589        }
590
591        // Scalar / scalar-array with mode bits:
592        // 0x00=not-array, 0x08=variable, 0x10=bounded, 0x18=fixed
593        let scalar_or_array = type_byte & 0x18;
594        let is_array = scalar_or_array != 0;
595        if is_array && scalar_or_array != 0x08 {
596            // Consume bounded/fixed max length for alignment, even if we don't model it.
597            let (_bound, consumed) = self.decode_size(&data[offset..])?;
598            offset += consumed;
599        }
600        let base_type = type_byte & 0xE7;
601
602        // String type
603        if base_type == 0x60 {
604            if is_array {
605                return Ok((FieldType::StringArray, offset));
606            } else {
607                return Ok((FieldType::String, offset));
608            }
609        }
610
611        // Numeric types
612        if let Some(tc) = TypeCode::from_byte(base_type) {
613            if is_array {
614                return Ok((FieldType::ScalarArray(tc), offset));
615            } else {
616                return Ok((FieldType::Scalar(tc), offset));
617            }
618        }
619
620        debug!("Unknown type byte: 0x{:02x}", type_byte);
621        Err(DecodeError::UnknownTypeTag(type_byte))
622    }
623
624    /// Parse structure description
625    fn parse_structure_desc(&self, data: &[u8]) -> DecodeResult<(StructureDesc, usize)> {
626        let mut offset = 0;
627
628        // Parse optional struct ID
629        let (struct_id, consumed) = self.decode_string(&data[offset..])?;
630        offset += consumed;
631
632        let struct_id = if struct_id.is_empty() {
633            None
634        } else {
635            Some(struct_id)
636        };
637
638        // Parse field count
639        let (field_count, consumed) = self.decode_size(&data[offset..])?;
640        offset += consumed;
641
642        let mut fields = Vec::with_capacity(field_count);
643
644        for _ in 0..field_count {
645            if offset >= data.len() {
646                break;
647            }
648            if let Ok((field, consumed)) = self.parse_field_desc(&data[offset..]) {
649                offset += consumed;
650                fields.push(field);
651            } else {
652                break;
653            }
654        }
655
656        Ok((StructureDesc { struct_id, fields }, offset))
657    }
658
659    /// Parse the full type introspection from INIT response
660    pub fn parse_introspection(&self, data: &[u8]) -> DecodeResult<StructureDesc> {
661        self.parse_introspection_with_len(data)
662            .map(|(desc, _)| desc)
663    }
664
665    /// Parse full type introspection and return consumed bytes.
666    pub fn parse_introspection_with_len(&self, data: &[u8]) -> DecodeResult<(StructureDesc, usize)> {
667        if data.is_empty() {
668            return Err(DecodeError::Truncated {
669                needed: 1,
670                available: 0,
671            });
672        }
673
674        // The introspection starts with a type byte
675        let type_byte = data[0];
676
677        // Should be a structure (0x80)
678        if type_byte == 0x80 {
679            let (desc, consumed) = self.parse_structure_desc(&data[1..])?;
680            return Ok((desc, 1 + consumed));
681        }
682
683        // Full-with-id from IntrospectionRegistry:
684        // 0xFD + int16 key + field type descriptor payload.
685        if type_byte == 0xFD {
686            if data.len() < 3 {
687                return Err(DecodeError::Truncated {
688                    needed: 3,
689                    available: data.len(),
690                });
691            }
692            let key = if self.is_be {
693                u16::from_be_bytes([data[1], data[2]])
694            } else {
695                u16::from_le_bytes([data[1], data[2]])
696            };
697            let (desc, consumed) = self.parse_introspection_with_len(&data[3..])?;
698            // Register this structure type for later 0xFE references
699            self.registry
700                .borrow_mut()
701                .insert(key, FieldType::Structure(desc.clone()));
702            return Ok((desc, 3 + consumed));
703        }
704
705        // Only-id from IntrospectionRegistry:
706        // 0xFE + int16 key — reference to a previously seen type.
707        if type_byte == 0xFE {
708            if data.len() < 3 {
709                return Err(DecodeError::Truncated {
710                    needed: 3,
711                    available: data.len(),
712                });
713            }
714            let key = if self.is_be {
715                u16::from_be_bytes([data[1], data[2]])
716            } else {
717                u16::from_le_bytes([data[1], data[2]])
718            };
719            if let Some(ft) = self.registry.borrow().get(&key) {
720                if let FieldType::Structure(desc) = ft {
721                    return Ok((desc.clone(), 3));
722                }
723            }
724            debug!(
725                "Introspection ONLY_ID (0xFE) key={} not found in registry",
726                key
727            );
728            return Err(DecodeError::UnresolvedTypeId(key));
729        }
730
731        debug!("Unexpected introspection type byte: 0x{:02x}", type_byte);
732        Err(DecodeError::UnknownTypeTag(type_byte))
733    }
734
735    /// Decode a scalar value
736    fn decode_scalar(&self, data: &[u8], tc: TypeCode) -> DecodeResult<(DecodedValue, usize)> {
737        let size = tc
738            .size()
739            .ok_or(DecodeError::Malformed("type code has no fixed scalar size"))?;
740        if data.len() < size {
741            return Err(DecodeError::Truncated {
742                needed: size,
743                available: data.len(),
744            });
745        }
746
747        let value = match tc {
748            TypeCode::Boolean => DecodedValue::Boolean(data[0] != 0),
749            TypeCode::Int8 => DecodedValue::Int8(data[0] as i8),
750            TypeCode::UInt8 => DecodedValue::UInt8(data[0]),
751            TypeCode::Int16 => {
752                let v = if self.is_be {
753                    i16::from_be_bytes([data[0], data[1]])
754                } else {
755                    i16::from_le_bytes([data[0], data[1]])
756                };
757                DecodedValue::Int16(v)
758            }
759            TypeCode::UInt16 => {
760                let v = if self.is_be {
761                    u16::from_be_bytes([data[0], data[1]])
762                } else {
763                    u16::from_le_bytes([data[0], data[1]])
764                };
765                DecodedValue::UInt16(v)
766            }
767            TypeCode::Int32 => {
768                let v = if self.is_be {
769                    i32::from_be_bytes(data[0..4].try_into().unwrap())
770                } else {
771                    i32::from_le_bytes(data[0..4].try_into().unwrap())
772                };
773                DecodedValue::Int32(v)
774            }
775            TypeCode::UInt32 => {
776                let v = if self.is_be {
777                    u32::from_be_bytes(data[0..4].try_into().unwrap())
778                } else {
779                    u32::from_le_bytes(data[0..4].try_into().unwrap())
780                };
781                DecodedValue::UInt32(v)
782            }
783            TypeCode::Int64 => {
784                let v = if self.is_be {
785                    i64::from_be_bytes(data[0..8].try_into().unwrap())
786                } else {
787                    i64::from_le_bytes(data[0..8].try_into().unwrap())
788                };
789                DecodedValue::Int64(v)
790            }
791            TypeCode::UInt64 => {
792                let v = if self.is_be {
793                    u64::from_be_bytes(data[0..8].try_into().unwrap())
794                } else {
795                    u64::from_le_bytes(data[0..8].try_into().unwrap())
796                };
797                DecodedValue::UInt64(v)
798            }
799            TypeCode::Float32 => {
800                let v = if self.is_be {
801                    f32::from_be_bytes(data[0..4].try_into().unwrap())
802                } else {
803                    f32::from_le_bytes(data[0..4].try_into().unwrap())
804                };
805                DecodedValue::Float32(v)
806            }
807            TypeCode::Float64 => {
808                let v = if self.is_be {
809                    f64::from_be_bytes(data[0..8].try_into().unwrap())
810                } else {
811                    f64::from_le_bytes(data[0..8].try_into().unwrap())
812                };
813                DecodedValue::Float64(v)
814            }
815            _ => {
816                return Err(DecodeError::Malformed(
817                    "type code is not a decodable scalar",
818                ))
819            }
820        };
821
822        Ok((value, size))
823    }
824
825    /// Reject an array count that exceeds its configured limit, or that could
826    /// not possibly fit in the bytes that remain.
827    ///
828    /// `min_elem` is the smallest number of wire bytes one element can
829    /// occupy: the fixed width for scalars, 1 for strings, structures and
830    /// unions. The limit is checked first, so a count that violates both
831    /// yields `ArrayTooLarge`.
832    fn check_array_count(
833        &self,
834        count: usize,
835        min_elem: usize,
836        available: usize,
837        kind: &'static str,
838        limit: usize,
839    ) -> DecodeResult<()> {
840        if count > limit {
841            return Err(DecodeError::ArrayTooLarge { kind, count, limit });
842        }
843        let min_bytes = count.saturating_mul(min_elem);
844        if min_bytes > available {
845            return Err(DecodeError::CountExceedsBuffer {
846                count,
847                min_bytes,
848                available,
849            });
850        }
851        Ok(())
852    }
853
854    /// Decode value according to field type
855    pub fn decode_value(
856        &self,
857        data: &[u8],
858        field_type: &FieldType,
859    ) -> DecodeResult<(DecodedValue, usize)> {
860        match field_type {
861            FieldType::Scalar(tc) => self.decode_scalar(data, *tc),
862            FieldType::String | FieldType::BoundedString(_) => {
863                let (s, consumed) = self.decode_string(data)?;
864                Ok((DecodedValue::String(s), consumed))
865            }
866            FieldType::ScalarArray(tc) => {
867                let (count, size_consumed) = self.decode_size(data)?;
868                let mut offset = size_consumed;
869                let elem_size = tc.size().unwrap_or(1);
870                self.check_array_count(
871                    count,
872                    elem_size,
873                    data.len() - offset,
874                    "scalar array",
875                    self.limits.max_scalar_array,
876                )?;
877                let mut values = Vec::with_capacity(count);
878                for _ in 0..count {
879                    let (val, consumed) = self.decode_scalar(&data[offset..], *tc)?;
880                    values.push(val);
881                    offset += consumed;
882                }
883                Ok((DecodedValue::Array(values), offset))
884            }
885            FieldType::StringArray => {
886                let (count, size_consumed) = self.decode_size(data)?;
887                let mut offset = size_consumed;
888                self.check_array_count(
889                    count,
890                    1,
891                    data.len() - offset,
892                    "string array",
893                    self.limits.max_string_array,
894                )?;
895                let mut values = Vec::with_capacity(count);
896                for _ in 0..count {
897                    let (s, consumed) = self.decode_string(&data[offset..])?;
898                    values.push(DecodedValue::String(s));
899                    offset += consumed;
900                }
901                Ok((DecodedValue::Array(values), offset))
902            }
903            FieldType::Structure(desc) => self.decode_structure(data, desc),
904            FieldType::StructureArray(desc) => {
905                let (count, size_consumed) = self.decode_size(data)?;
906                let mut offset = size_consumed;
907                self.check_array_count(
908                    count,
909                    1,
910                    data.len() - offset,
911                    "structure array",
912                    self.limits.max_struct_array,
913                )?;
914                let mut values = Vec::with_capacity(count);
915                for _ in 0..count {
916                    // Read per-element null indicator (0 = null, non-zero = present)
917                    if offset >= data.len() {
918                        return Err(DecodeError::Truncated {
919                            needed: offset + 1,
920                            available: data.len(),
921                        });
922                    }
923                    let null_indicator = data[offset];
924                    offset += 1;
925                    if null_indicator == 0 {
926                        // null element – push empty structure placeholder
927                        values.push(DecodedValue::Structure(Vec::new()));
928                        continue;
929                    }
930                    let (item, consumed) = self.decode_structure(&data[offset..], desc)?;
931                    values.push(item);
932                    offset += consumed;
933                }
934                Ok((DecodedValue::Array(values), offset))
935            }
936            FieldType::Union(fields) => {
937                let (selector, consumed) = self.decode_size(data)?;
938                let field = fields.get(selector).ok_or(DecodeError::UnknownUnionSelector {
939                    selector,
940                    len: fields.len(),
941                })?;
942                let (value, val_consumed) =
943                    self.decode_value(&data[consumed..], &field.field_type)?;
944                Ok((
945                    DecodedValue::Structure(vec![(field.name.clone(), value)]),
946                    consumed + val_consumed,
947                ))
948            }
949            FieldType::UnionArray(fields) => {
950                let (count, size_consumed) = self.decode_size(data)?;
951                let mut offset = size_consumed;
952                self.check_array_count(
953                    count,
954                    1,
955                    data.len() - offset,
956                    "union array",
957                    self.limits.max_union_array,
958                )?;
959                let mut values = Vec::with_capacity(count);
960                for _ in 0..count {
961                    let (selector, consumed) = self.decode_size(&data[offset..])?;
962                    offset += consumed;
963                    let field =
964                        fields
965                            .get(selector)
966                            .ok_or(DecodeError::UnknownUnionSelector {
967                                selector,
968                                len: fields.len(),
969                            })?;
970                    let (value, val_consumed) =
971                        self.decode_value(&data[offset..], &field.field_type)?;
972                    offset += val_consumed;
973                    values.push(DecodedValue::Structure(vec![(field.name.clone(), value)]));
974                }
975                Ok((DecodedValue::Array(values), offset))
976            }
977            FieldType::Variant => {
978                if data.is_empty() {
979                    return Err(DecodeError::Truncated {
980                        needed: 1,
981                        available: 0,
982                    });
983                }
984                if data[0] == 0xFF {
985                    return Ok((DecodedValue::Null, 1));
986                }
987                let (variant_type, type_consumed) = self.parse_type_desc(data)?;
988                let (variant_value, value_consumed) =
989                    self.decode_value(&data[type_consumed..], &variant_type)?;
990                Ok((variant_value, type_consumed + value_consumed))
991            }
992            FieldType::VariantArray => {
993                let (count, size_consumed) = self.decode_size(data)?;
994                let mut offset = size_consumed;
995                self.check_array_count(
996                    count,
997                    1,
998                    data.len() - offset,
999                    "variant array",
1000                    self.limits.max_variant_array,
1001                )?;
1002                let mut values = Vec::with_capacity(count);
1003                for _ in 0..count {
1004                    let (v, consumed) = self.decode_value(&data[offset..], &FieldType::Variant)?;
1005                    values.push(v);
1006                    offset += consumed;
1007                }
1008                Ok((DecodedValue::Array(values), offset))
1009            }
1010        }
1011    }
1012
1013    /// Decode a structure value using the field descriptions
1014    pub fn decode_structure(
1015        &self,
1016        data: &[u8],
1017        desc: &StructureDesc,
1018    ) -> DecodeResult<(DecodedValue, usize)> {
1019        let mut offset = 0;
1020        let mut fields: Vec<(String, DecodedValue)> = Vec::new();
1021
1022        for field in &desc.fields {
1023            if offset >= data.len() {
1024                break;
1025            }
1026            if let Ok((value, consumed)) = self.decode_value(&data[offset..], &field.field_type) {
1027                fields.push((field.name.clone(), value));
1028                offset += consumed;
1029            } else {
1030                // Can't decode this field, stop
1031                break;
1032            }
1033        }
1034
1035        Ok((DecodedValue::Structure(fields), offset))
1036    }
1037
1038    /// Decode a structure with a bitset indicating which fields are present
1039    /// This is used for delta updates in MONITOR
1040    pub fn decode_structure_with_bitset(
1041        &self,
1042        data: &[u8],
1043        desc: &StructureDesc,
1044    ) -> DecodeResult<(DecodedValue, usize)> {
1045        if data.is_empty() {
1046            return Err(DecodeError::Truncated {
1047                needed: 1,
1048                available: 0,
1049            });
1050        }
1051
1052        let mut offset = 0;
1053
1054        // Parse the bitset - PVA uses size-encoded bitset
1055        let (bitset_size, size_consumed) = self.decode_size(data)?;
1056        offset += size_consumed;
1057
1058        if bitset_size == 0 || offset + bitset_size > data.len() {
1059            return Ok((DecodedValue::Structure(vec![]), offset));
1060        }
1061
1062        let bitset = &data[offset..offset + bitset_size];
1063        offset += bitset_size;
1064
1065        let (value, consumed) =
1066            self.decode_structure_with_bitset_body(&data[offset..], desc, bitset)?;
1067        Ok((value, offset + consumed))
1068    }
1069
1070    pub(crate) fn decode_structure_with_bitset_body(
1071        &self,
1072        data: &[u8],
1073        desc: &StructureDesc,
1074        bitset: &[u8],
1075    ) -> DecodeResult<(DecodedValue, usize)> {
1076        // Bit 0 is for the whole structure, field bits start at bit 1
1077        debug!(
1078            "Bitset: {:02x?} (size={}), total_fields={}",
1079            bitset,
1080            bitset.len(),
1081            count_structure_fields(desc)
1082        );
1083        debug!(
1084            "Structure fields: {:?}",
1085            desc.fields.iter().map(|f| &f.name).collect::<Vec<_>>()
1086        );
1087
1088        // Special case: bitset contains only bit0 (whole structure) and no field bits.
1089        let mut has_field_bits = false;
1090        if !bitset.is_empty() {
1091            for (i, b) in bitset.iter().enumerate() {
1092                let mask = if i == 0 { *b & !0x01 } else { *b };
1093                if mask != 0 {
1094                    has_field_bits = true;
1095                    break;
1096                }
1097            }
1098        }
1099        if !has_field_bits && !bitset.is_empty() && (bitset[0] & 0x01) != 0 {
1100            if let Ok((value, consumed)) = self.decode_structure(data, desc) {
1101                return Ok((value, consumed));
1102            }
1103        }
1104
1105        let mut fields: Vec<(String, DecodedValue)> = Vec::new();
1106        let mut offset = 0usize;
1107
1108        fn decode_with_bitset_recursive(
1109            decoder: &PvdDecoder,
1110            data: &[u8],
1111            offset: &mut usize,
1112            desc: &StructureDesc,
1113            bitset: &[u8],
1114            bit_offset: &mut usize,
1115            fields: &mut Vec<(String, DecodedValue)>,
1116        ) -> bool {
1117            for field in &desc.fields {
1118                let byte_idx = *bit_offset / 8;
1119                let bit_idx = *bit_offset % 8;
1120                let current_bit = *bit_offset;
1121                *bit_offset += 1;
1122
1123                let field_present = if byte_idx < bitset.len() {
1124                    (bitset[byte_idx] & (1 << bit_idx)) != 0
1125                } else {
1126                    false
1127                };
1128
1129                debug!(
1130                    "Field '{}' at bit {}: present={}",
1131                    field.name, current_bit, field_present
1132                );
1133
1134                if let FieldType::Structure(nested_desc) = &field.field_type {
1135                    let child_start_bit = *bit_offset;
1136                    let child_field_count = count_structure_fields(nested_desc);
1137
1138                    let mut any_child_bits_set = false;
1139                    for i in 0..child_field_count {
1140                        let check_byte = (child_start_bit + i) / 8;
1141                        let check_bit = (child_start_bit + i) % 8;
1142                        if check_byte < bitset.len() && (bitset[check_byte] & (1 << check_bit)) != 0
1143                        {
1144                            any_child_bits_set = true;
1145                            break;
1146                        }
1147                    }
1148
1149                    debug!(
1150                        "Nested structure '{}': parent_present={}, child_start_bit={}, child_count={}, any_child_bits_set={}",
1151                        field.name,
1152                        field_present,
1153                        child_start_bit,
1154                        child_field_count,
1155                        any_child_bits_set
1156                    );
1157
1158                    if field_present && !any_child_bits_set {
1159                        *bit_offset += child_field_count;
1160                        if *offset < data.len() {
1161                            if let Ok((value, consumed)) =
1162                                decoder.decode_structure(&data[*offset..], nested_desc)
1163                            {
1164                                debug!(
1165                                    "Decoded full nested structure '{}', consumed {} bytes",
1166                                    field.name, consumed
1167                                );
1168                                fields.push((field.name.clone(), value));
1169                                *offset += consumed;
1170                            } else {
1171                                debug!("Failed to decode full nested structure '{}'", field.name);
1172                                return false;
1173                            }
1174                        }
1175                    } else if any_child_bits_set {
1176                        let mut nested_fields: Vec<(String, DecodedValue)> = Vec::new();
1177                        if !decode_with_bitset_recursive(
1178                            decoder,
1179                            data,
1180                            offset,
1181                            nested_desc,
1182                            bitset,
1183                            bit_offset,
1184                            &mut nested_fields,
1185                        ) {
1186                            return false;
1187                        }
1188                        debug!(
1189                            "Nested structure '{}' decoded {} fields",
1190                            field.name,
1191                            nested_fields.len()
1192                        );
1193                        if !nested_fields.is_empty() {
1194                            fields
1195                                .push((field.name.clone(), DecodedValue::Structure(nested_fields)));
1196                        }
1197                    } else {
1198                        *bit_offset += child_field_count;
1199                    }
1200                } else if field_present {
1201                    if *offset >= data.len() {
1202                        debug!(
1203                            "Data exhausted at offset {} for field '{}'",
1204                            *offset, field.name
1205                        );
1206                        return false;
1207                    }
1208                    if let Ok((value, consumed)) =
1209                        decoder.decode_value(&data[*offset..], &field.field_type)
1210                    {
1211                        fields.push((field.name.clone(), value));
1212                        *offset += consumed;
1213                    } else {
1214                        return false;
1215                    }
1216                }
1217            }
1218            true
1219        }
1220
1221        let mut bit_offset = 1;
1222        decode_with_bitset_recursive(
1223            self,
1224            data,
1225            &mut offset,
1226            desc,
1227            bitset,
1228            &mut bit_offset,
1229            &mut fields,
1230        );
1231        Ok((DecodedValue::Structure(fields), offset))
1232    }
1233}
1234
1235/// Count total fields in a structure (including nested).
1236///
1237/// Self-then-nested, depth-first. This is the order the MONITOR bitset bits
1238/// are numbered in, so `monitor::flatten_field_paths` must walk it identically.
1239pub(crate) fn count_structure_fields(desc: &StructureDesc) -> usize {
1240    let mut count = 0;
1241    for field in &desc.fields {
1242        count += 1;
1243        if let FieldType::Structure(nested) = &field.field_type {
1244            count += count_structure_fields(nested);
1245        }
1246    }
1247    count
1248}
1249
1250/// Extract a sub-field from a StructureDesc by dot-separated path.
1251/// Returns the sub-field as an owned StructureDesc. For leaf (non-structure)
1252/// fields, returns a single-field StructureDesc wrapping the matched field.
1253/// Returns the full desc if path is empty.
1254pub fn extract_subfield_desc(desc: &StructureDesc, path: &str) -> Option<StructureDesc> {
1255    if path.is_empty() {
1256        return Some(desc.clone());
1257    }
1258    let mut parts = path.splitn(2, '.');
1259    let head = parts.next()?;
1260    let tail = parts.next().unwrap_or("");
1261    for field in &desc.fields {
1262        if field.name == head {
1263            match &field.field_type {
1264                FieldType::Structure(nested) | FieldType::StructureArray(nested) => {
1265                    return extract_subfield_desc(nested, tail);
1266                }
1267                _ => {
1268                    if tail.is_empty() {
1269                        return Some(StructureDesc {
1270                            struct_id: None,
1271                            fields: vec![field.clone()],
1272                        });
1273                    }
1274                    return None;
1275                }
1276            }
1277        }
1278    }
1279    None
1280}
1281
1282/// Format a structure description for display
1283pub fn format_structure_desc(desc: &StructureDesc) -> String {
1284    let mut parts = Vec::new();
1285    if let Some(ref id) = desc.struct_id {
1286        parts.push(id.clone());
1287    }
1288    for field in &desc.fields {
1289        parts.push(format!("{}:{}", field.name, field.field_type.type_name()));
1290    }
1291    parts.join(", ")
1292}
1293
1294pub fn format_structure_tree(desc: &StructureDesc) -> String {
1295    fn push_fields(out: &mut Vec<String>, fields: &[FieldDesc], indent: usize) {
1296        let prefix = "  ".repeat(indent);
1297        for field in fields {
1298            match &field.field_type {
1299                FieldType::Structure(nested) => {
1300                    out.push(format!("{}{}: structure", prefix, field.name));
1301                    push_fields(out, &nested.fields, indent + 1);
1302                }
1303                FieldType::StructureArray(nested) => {
1304                    out.push(format!("{}{}: structure[]", prefix, field.name));
1305                    push_fields(out, &nested.fields, indent + 1);
1306                }
1307                FieldType::Union(variants) => {
1308                    out.push(format!("{}{}: union", prefix, field.name));
1309                    push_fields(out, variants, indent + 1);
1310                }
1311                FieldType::UnionArray(variants) => {
1312                    out.push(format!("{}{}: union[]", prefix, field.name));
1313                    push_fields(out, variants, indent + 1);
1314                }
1315                FieldType::BoundedString(bound) => {
1316                    out.push(format!("{}{}: string<={}", prefix, field.name, bound));
1317                }
1318                _ => {
1319                    out.push(format!(
1320                        "{}{}: {}",
1321                        prefix,
1322                        field.name,
1323                        field.field_type.type_name()
1324                    ));
1325                }
1326            }
1327        }
1328    }
1329
1330    let mut lines = Vec::new();
1331    if let Some(id) = &desc.struct_id {
1332        lines.push(format!("struct {}", id));
1333    } else {
1334        lines.push("struct <anonymous>".to_string());
1335    }
1336    push_fields(&mut lines, &desc.fields, 0);
1337    lines.join("\n")
1338}
1339
1340/// Extract the "value" field from a decoded NTScalar structure
1341pub fn extract_nt_scalar_value(decoded: &DecodedValue) -> Option<&DecodedValue> {
1342    if let DecodedValue::Structure(fields) = decoded {
1343        for (name, value) in fields {
1344            if name == "value" {
1345                return Some(value);
1346            }
1347        }
1348    }
1349    None
1350}
1351
1352/// Compact display of decoded value for logging - shows only updated fields concisely
1353pub fn format_compact_value(decoded: &DecodedValue) -> String {
1354    match decoded {
1355        DecodedValue::Structure(fields) => {
1356            if fields.is_empty() {
1357                return "{}".to_string();
1358            }
1359
1360            let mut parts = Vec::new();
1361
1362            for (name, val) in fields {
1363                let formatted = format_field_value_compact(name, val);
1364                if !formatted.is_empty() {
1365                    parts.push(formatted);
1366                }
1367            }
1368
1369            parts.join(", ")
1370        }
1371        _ => format!("{}", decoded),
1372    }
1373}
1374
1375/// Format a single field value compactly - shows key info for known structures
1376fn format_field_value_compact(name: &str, val: &DecodedValue) -> String {
1377    match val {
1378        DecodedValue::Structure(fields) => {
1379            // For known EPICS NTScalar structures, show only key fields
1380            match name {
1381                "alarm" => {
1382                    // Show severity and message if non-zero/non-empty
1383                    let severity = fields.iter().find(|(n, _)| n == "severity");
1384                    let message = fields.iter().find(|(n, _)| n == "message");
1385                    let mut parts = Vec::new();
1386                    if let Some((_, DecodedValue::Int32(s))) = severity {
1387                        if *s != 0 {
1388                            parts.push(format!("sev={}", s));
1389                        }
1390                    }
1391                    if let Some((_, DecodedValue::String(m))) = message {
1392                        if !m.is_empty() {
1393                            parts.push(format!("\"{}\"", m));
1394                        }
1395                    }
1396                    if parts.is_empty() {
1397                        String::new() // Don't show alarm if it's OK
1398                    } else {
1399                        format!("alarm={{{}}}", parts.join(", "))
1400                    }
1401                }
1402                "timeStamp" => {
1403                    // Show just seconds or skip entirely for brevity
1404                    let secs = fields.iter().find(|(n, _)| n == "secondsPastEpoch");
1405                    if let Some((_, DecodedValue::Int64(s))) = secs {
1406                        format!("ts={}", s)
1407                    } else {
1408                        String::new()
1409                    }
1410                }
1411                "display" | "control" | "valueAlarm" => {
1412                    // Skip verbose metadata structures in compact view
1413                    String::new()
1414                }
1415                _ => {
1416                    // For other structures, show all fields
1417                    let nested: Vec<String> = fields
1418                        .iter()
1419                        .map(|(n, v)| format!("{}={}", n, format_scalar_value(v)))
1420                        .collect();
1421
1422                    if nested.is_empty() {
1423                        String::new()
1424                    } else {
1425                        format!("{}={{{}}}", name, nested.join(", "))
1426                    }
1427                }
1428            }
1429        }
1430        _ => {
1431            format!("{}={}", name, format_scalar_value(val))
1432        }
1433    }
1434}
1435
1436/// Format a scalar value concisely
1437fn format_scalar_value(val: &DecodedValue) -> String {
1438    match val {
1439        DecodedValue::Null => "null".to_string(),
1440        DecodedValue::Boolean(v) => format!("{}", v),
1441        DecodedValue::Int8(v) => format!("{}", v),
1442        DecodedValue::Int16(v) => format!("{}", v),
1443        DecodedValue::Int32(v) => format!("{}", v),
1444        DecodedValue::Int64(v) => format!("{}", v),
1445        DecodedValue::UInt8(v) => format!("{}", v),
1446        DecodedValue::UInt16(v) => format!("{}", v),
1447        DecodedValue::UInt32(v) => format!("{}", v),
1448        DecodedValue::UInt64(v) => format!("{}", v),
1449        DecodedValue::Float32(v) => format!("{:.4}", v),
1450        DecodedValue::Float64(v) => format!("{:.6}", v),
1451        DecodedValue::String(v) => format!("\"{}\"", v),
1452        DecodedValue::Array(arr) => {
1453            if arr.is_empty() {
1454                "[]".to_string()
1455            } else {
1456                let items: Vec<String> = arr.iter().map(|v| format_scalar_value(v)).collect();
1457                format!("[{}]", items.join(", "))
1458            }
1459        }
1460        DecodedValue::Structure(fields) => {
1461            let nested: Vec<String> = fields
1462                .iter()
1463                .map(|(n, v)| format!("{}={}", n, format_scalar_value(v)))
1464                .collect();
1465            format!("{{{}}}", nested.join(", "))
1466        }
1467        DecodedValue::Raw(data) => {
1468            if data.len() <= 4 {
1469                format!("<{}>", hex::encode(data))
1470            } else {
1471                format!("<{}B>", data.len())
1472            }
1473        }
1474    }
1475}
1476
1477#[cfg(test)]
1478mod tests {
1479    use super::*;
1480
1481    #[test]
1482    fn test_decode_size() {
1483        let decoder = PvdDecoder::new(false);
1484
1485        // Small size (single byte)
1486        assert_eq!(decoder.decode_size(&[5]), Ok((5, 1)));
1487        assert_eq!(decoder.decode_size(&[253]), Ok((253, 1)));
1488
1489        // Medium/large size (5 bytes, 254 prefix + uint32)
1490        assert_eq!(
1491            decoder.decode_size(&[254, 0x00, 0x01, 0x00, 0x00]),
1492            Ok((256, 5))
1493        );
1494    }
1495
1496    #[test]
1497    fn decode_size_reports_truncation_rather_than_none() {
1498        let decoder = PvdDecoder::new(false);
1499        // 254 announces a 4-byte length that is not present.
1500        let err = decoder.decode_size(&[254, 0x01]).unwrap_err();
1501        assert_eq!(
1502            err,
1503            DecodeError::Truncated {
1504                needed: 5,
1505                available: 2
1506            }
1507        );
1508    }
1509
1510    #[test]
1511    fn decode_size_on_empty_buffer_is_truncated() {
1512        let decoder = PvdDecoder::new(false);
1513        assert_eq!(
1514            decoder.decode_size(&[]).unwrap_err(),
1515            DecodeError::Truncated {
1516                needed: 1,
1517                available: 0
1518            }
1519        );
1520    }
1521
1522    #[test]
1523    fn unknown_type_tag_is_named() {
1524        let decoder = PvdDecoder::new(false);
1525        // Empty field name (0x00), then 0x40 — not a valid type descriptor
1526        // byte. (0x7F, as the plan suggested, is consumed as a 127-byte field
1527        // name and reports Truncated before reaching the type tag.)
1528        assert_eq!(
1529            decoder.parse_field_desc(&[0x00, 0x40]).unwrap_err(),
1530            DecodeError::UnknownTypeTag(0x40)
1531        );
1532    }
1533
1534    #[test]
1535    fn limits_are_configurable_and_readable() {
1536        let limits = DecodeLimits {
1537            max_string_array: 7,
1538            ..DecodeLimits::default()
1539        };
1540        let decoder = PvdDecoder::with_limits(false, limits);
1541        assert_eq!(decoder.limits().max_string_array, 7);
1542        assert_eq!(decoder.limits().max_scalar_array, 4_000_000);
1543    }
1544
1545    #[test]
1546    fn default_limits_match_the_documented_values() {
1547        let d = DecodeLimits::default();
1548        assert_eq!(d.max_scalar_array, 4_000_000);
1549        assert_eq!(d.max_string_array, 65_536);
1550        assert_eq!(d.max_struct_array, 65_536);
1551        assert_eq!(d.max_union_array, 65_536);
1552        assert_eq!(d.max_variant_array, 65_536);
1553    }
1554
1555    #[test]
1556    fn test_parse_introspection_full_with_id() {
1557        let decoder = PvdDecoder::new(false);
1558        let data = vec![
1559            0xFD, // FULL_WITH_ID
1560            0x06, 0x00, // registry key (little-endian)
1561            0x80, // structure type follows
1562            0x00, // empty struct id
1563            0x01, // one field
1564            0x05, b'v', b'a', b'l', b'u', b'e', // field name
1565            0x43, // float64
1566        ];
1567        let desc = decoder
1568            .parse_introspection(&data)
1569            .expect("parsed introspection");
1570        assert_eq!(desc.fields.len(), 1);
1571        assert_eq!(desc.fields[0].name, "value");
1572        match desc.fields[0].field_type {
1573            FieldType::Scalar(TypeCode::Float64) => {}
1574            _ => panic!("expected float64 value field"),
1575        }
1576    }
1577
1578    #[test]
1579    fn test_decode_string() {
1580        let decoder = PvdDecoder::new(false);
1581
1582        // Empty string
1583        assert_eq!(decoder.decode_string(&[0]), Ok((String::new(), 1)));
1584
1585        // "hello"
1586        let data = [5, b'h', b'e', b'l', b'l', b'o'];
1587        assert_eq!(decoder.decode_string(&data), Ok(("hello".to_string(), 6)));
1588    }
1589
1590    #[test]
1591    fn decode_variant_accepts_full_with_id_type_tag() {
1592        let decoder = PvdDecoder::new(false);
1593        // Variant payload: 0xFD + int16 key + string type + "ok"
1594        let data = [0xFD, 0x02, 0x00, 0x60, 0x02, b'o', b'k'];
1595        let (value, consumed) = decoder
1596            .decode_value(&data, &FieldType::Variant)
1597            .expect("decode variant");
1598        assert_eq!(consumed, data.len());
1599        assert!(matches!(value, DecodedValue::String(ref s) if s == "ok"));
1600    }
1601
1602    #[test]
1603    fn test_decode_bitset_whole_structure() {
1604        let decoder = PvdDecoder::new(false);
1605        let desc = StructureDesc {
1606            struct_id: None,
1607            fields: vec![FieldDesc {
1608                name: "value".to_string(),
1609                field_type: FieldType::Scalar(TypeCode::Float64),
1610            }],
1611        };
1612        // bitset_size=1, bitset=0x01 (whole structure), then float64 value.
1613        let mut data = Vec::new();
1614        data.push(0x01);
1615        data.push(0x01);
1616        data.extend_from_slice(&1.25f64.to_le_bytes());
1617
1618        let (decoded, _consumed) = decoder
1619            .decode_structure_with_bitset(&data, &desc)
1620            .expect("decoded");
1621        if let DecodedValue::Structure(fields) = decoded {
1622            assert_eq!(fields.len(), 1);
1623            assert_eq!(fields[0].0, "value");
1624        } else {
1625            panic!("expected structure");
1626        }
1627    }
1628
1629    #[test]
1630    fn format_structure_tree_includes_nested_fields() {
1631        let desc = StructureDesc {
1632            struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
1633            fields: vec![
1634                FieldDesc {
1635                    name: "value".to_string(),
1636                    field_type: FieldType::Scalar(TypeCode::Float64),
1637                },
1638                FieldDesc {
1639                    name: "alarm".to_string(),
1640                    field_type: FieldType::Structure(StructureDesc {
1641                        struct_id: None,
1642                        fields: vec![
1643                            FieldDesc {
1644                                name: "severity".to_string(),
1645                                field_type: FieldType::Scalar(TypeCode::Int32),
1646                            },
1647                            FieldDesc {
1648                                name: "message".to_string(),
1649                                field_type: FieldType::String,
1650                            },
1651                        ],
1652                    }),
1653                },
1654            ],
1655        };
1656
1657        let rendered = format_structure_tree(&desc);
1658        assert!(rendered.contains("struct epics:nt/NTScalar:1.0"));
1659        assert!(rendered.contains("value: double"));
1660        assert!(rendered.contains("alarm: structure"));
1661        assert!(rendered.contains("severity: int"));
1662        assert!(rendered.contains("message: string"));
1663    }
1664
1665    #[test]
1666    fn decode_string_array_not_capped_at_100_items() {
1667        fn encode_size(size: usize) -> Vec<u8> {
1668            if size == 0 {
1669                return vec![0x00];
1670            }
1671            if size < 254 {
1672                return vec![size as u8];
1673            }
1674            let mut out = vec![0xFE];
1675            out.extend_from_slice(&(size as u32).to_le_bytes());
1676            out
1677        }
1678
1679        let item_count = 150usize;
1680        let mut raw = encode_size(item_count);
1681        for idx in 0..item_count {
1682            let s = format!("PV:{}", idx);
1683            raw.extend_from_slice(&encode_size(s.len()));
1684            raw.extend_from_slice(s.as_bytes());
1685        }
1686
1687        let decoder = PvdDecoder::new(false);
1688        let (decoded, _consumed) = decoder
1689            .decode_value(&raw, &FieldType::StringArray)
1690            .expect("decoded");
1691
1692        let DecodedValue::Array(items) = decoded else {
1693            panic!("expected decoded array");
1694        };
1695        assert_eq!(items.len(), item_count);
1696    }
1697
1698    /// Encode a PVA size prefix for `n` using the 4-byte form.
1699    fn size_prefix(n: u32) -> Vec<u8> {
1700        let mut v = vec![254u8];
1701        v.extend_from_slice(&n.to_le_bytes());
1702        v
1703    }
1704
1705    #[test]
1706    fn oversized_string_array_errors_instead_of_truncating() {
1707        let limits = DecodeLimits {
1708            max_string_array: 4,
1709            ..DecodeLimits::default()
1710        };
1711        let decoder = PvdDecoder::with_limits(false, limits);
1712        let mut data = size_prefix(9);
1713        for _ in 0..9 {
1714            data.push(1); // one-byte string, length 1
1715            data.push(b'x');
1716        }
1717        assert_eq!(
1718            decoder
1719                .decode_value(&data, &FieldType::StringArray)
1720                .unwrap_err(),
1721            DecodeError::ArrayTooLarge {
1722                kind: "string array",
1723                count: 9,
1724                limit: 4
1725            }
1726        );
1727    }
1728
1729    #[test]
1730    fn count_larger_than_the_buffer_is_rejected_before_allocating() {
1731        let decoder = PvdDecoder::new(false);
1732        // Claims 4 billion int32s in a 5-byte buffer.
1733        let data = size_prefix(4_000_000_000);
1734        let err = decoder
1735            .decode_value(&data, &FieldType::ScalarArray(TypeCode::Int32))
1736            .unwrap_err();
1737        // The limit is checked first, so this is ArrayTooLarge, not
1738        // CountExceedsBuffer. See the Global Constraints.
1739        assert_eq!(
1740            err,
1741            DecodeError::ArrayTooLarge {
1742                kind: "scalar array",
1743                count: 4_000_000_000,
1744                limit: 4_000_000,
1745            }
1746        );
1747    }
1748
1749    #[test]
1750    fn count_within_the_limit_but_beyond_the_buffer_is_rejected() {
1751        let decoder = PvdDecoder::new(false);
1752        // 1000 int32s = 4000 bytes claimed, 8 supplied.
1753        let mut data = size_prefix(1000);
1754        data.extend_from_slice(&[0u8; 8]);
1755        assert_eq!(
1756            decoder
1757                .decode_value(&data, &FieldType::ScalarArray(TypeCode::Int32))
1758                .unwrap_err(),
1759            DecodeError::CountExceedsBuffer {
1760                count: 1000,
1761                min_bytes: 4000,
1762                available: 8
1763            }
1764        );
1765    }
1766
1767    #[test]
1768    fn oversized_struct_array_errors() {
1769        let limits = DecodeLimits {
1770            max_struct_array: 2,
1771            ..DecodeLimits::default()
1772        };
1773        let decoder = PvdDecoder::with_limits(false, limits);
1774        let mut inner = StructureDesc::new();
1775        inner.fields.push(FieldDesc {
1776            name: "a".to_string(),
1777            field_type: FieldType::Scalar(TypeCode::Int8),
1778        });
1779        let mut data = size_prefix(5);
1780        for _ in 0..5 {
1781            data.push(1); // present
1782            data.push(0); // the int8
1783        }
1784        assert_eq!(
1785            decoder
1786                .decode_value(&data, &FieldType::StructureArray(inner))
1787                .unwrap_err(),
1788            DecodeError::ArrayTooLarge {
1789                kind: "structure array",
1790                count: 5,
1791                limit: 2
1792            }
1793        );
1794    }
1795
1796    /// The regression this whole task exists for. Before the change, an
1797    /// over-cap string array returned a short Vec *and* left the offset
1798    /// short, so the following int32 decoded from the wrong bytes and the
1799    /// caller got a plausible, wrong answer.
1800    #[test]
1801    fn truncated_array_no_longer_desyncs_the_following_field() {
1802        let limits = DecodeLimits {
1803            max_string_array: 2,
1804            ..DecodeLimits::default()
1805        };
1806        let decoder = PvdDecoder::with_limits(false, limits);
1807
1808        let mut desc = StructureDesc::new();
1809        desc.fields.push(FieldDesc {
1810            name: "names".to_string(),
1811            field_type: FieldType::StringArray,
1812        });
1813        desc.fields.push(FieldDesc {
1814            name: "count".to_string(),
1815            field_type: FieldType::Scalar(TypeCode::Int32),
1816        });
1817
1818        let mut data = size_prefix(4);
1819        for _ in 0..4 {
1820            data.push(1);
1821            data.push(b'x');
1822        }
1823        data.extend_from_slice(&7i32.to_le_bytes());
1824
1825        // decode_structure stops at the first field it cannot decode and
1826        // returns what it has, so "count" must be absent rather than wrong.
1827        let (value, _) = decoder.decode_structure(&data, &desc).unwrap();
1828        let DecodedValue::Structure(fields) = value else {
1829            panic!("expected a structure");
1830        };
1831        assert!(
1832            fields.iter().all(|(name, _)| name != "count"),
1833            "a desynced 'count' must not be reported: got {fields:?}"
1834        );
1835
1836        // And with a limit that admits the array, the following field decodes
1837        // correctly — proving the payload above really does carry a readable
1838        // `count` that only the desync could have mangled.
1839        let loose = PvdDecoder::new(false);
1840        let (value, consumed) = loose.decode_structure(&data, &desc).unwrap();
1841        let DecodedValue::Structure(fields) = value else {
1842            panic!("expected a structure");
1843        };
1844        assert_eq!(consumed, data.len());
1845        assert_eq!(fields.len(), 2);
1846        assert_eq!(fields[1].0, "count");
1847        assert!(
1848            matches!(fields[1].1, DecodedValue::Int32(7)),
1849            "expected count == 7, got {:?}",
1850            fields[1].1
1851        );
1852    }
1853
1854    #[test]
1855    fn raising_the_limit_lets_the_same_payload_decode() {
1856        let mut data = size_prefix(5);
1857        for _ in 0..5 {
1858            data.push(1);
1859            data.push(b'x');
1860        }
1861        let strict = PvdDecoder::with_limits(
1862            false,
1863            DecodeLimits {
1864                max_string_array: 2,
1865                ..DecodeLimits::default()
1866            },
1867        );
1868        assert!(strict.decode_value(&data, &FieldType::StringArray).is_err());
1869
1870        let loose = PvdDecoder::new(false);
1871        let (value, _) = loose.decode_value(&data, &FieldType::StringArray).unwrap();
1872        let DecodedValue::Array(items) = value else {
1873            panic!("expected an array");
1874        };
1875        assert_eq!(items.len(), 5);
1876    }
1877}