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
9/// PVD type codes from the specification
10#[repr(u8)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum TypeCode {
13    Null = 0xFF,
14    Boolean = 0x00,
15    Int8 = 0x20,
16    Int16 = 0x21,
17    Int32 = 0x22,
18    Int64 = 0x23,
19    UInt8 = 0x24,
20    UInt16 = 0x25,
21    UInt32 = 0x26,
22    UInt64 = 0x27,
23    Float32 = 0x42,
24    Float64 = 0x43,
25    String = 0x60,
26    // Bounded string has 0x83 prefix followed by size
27    Variant = 0xFE, // Union with no fixed type (0xFF is Null)
28}
29
30impl TypeCode {
31    pub fn from_byte(b: u8) -> Option<Self> {
32        // Clear scalar-array mode bits (variable/bounded/fixed array)
33        let base = b & 0xE7;
34        match base {
35            0x00 => Some(TypeCode::Boolean),
36            0x20 => Some(TypeCode::Int8),
37            0x21 => Some(TypeCode::Int16),
38            0x22 => Some(TypeCode::Int32),
39            0x23 => Some(TypeCode::Int64),
40            0x24 => Some(TypeCode::UInt8),
41            0x25 => Some(TypeCode::UInt16),
42            0x26 => Some(TypeCode::UInt32),
43            0x27 => Some(TypeCode::UInt64),
44            0x42 => Some(TypeCode::Float32),
45            0x43 => Some(TypeCode::Float64),
46            0x60 => Some(TypeCode::String),
47            _ => None,
48        }
49    }
50
51    pub fn size(&self) -> Option<usize> {
52        match self {
53            TypeCode::Boolean | TypeCode::Int8 | TypeCode::UInt8 => Some(1),
54            TypeCode::Int16 | TypeCode::UInt16 => Some(2),
55            TypeCode::Int32 | TypeCode::UInt32 | TypeCode::Float32 => Some(4),
56            TypeCode::Int64 | TypeCode::UInt64 | TypeCode::Float64 => Some(8),
57            TypeCode::String | TypeCode::Null | TypeCode::Variant => None,
58        }
59    }
60}
61
62/// Field type description
63#[derive(Debug, Clone)]
64pub enum FieldType {
65    Scalar(TypeCode),
66    ScalarArray(TypeCode),
67    String,
68    StringArray,
69    Structure(StructureDesc),
70    StructureArray(StructureDesc),
71    Union(Vec<FieldDesc>),
72    UnionArray(Vec<FieldDesc>),
73    Variant,
74    VariantArray,
75    BoundedString(u32),
76}
77
78impl FieldType {
79    pub fn type_name(&self) -> &'static str {
80        match self {
81            FieldType::Scalar(tc) => match tc {
82                TypeCode::Boolean => "boolean",
83                TypeCode::Int8 => "byte",
84                TypeCode::Int16 => "short",
85                TypeCode::Int32 => "int",
86                TypeCode::Int64 => "long",
87                TypeCode::UInt8 => "ubyte",
88                TypeCode::UInt16 => "ushort",
89                TypeCode::UInt32 => "uint",
90                TypeCode::UInt64 => "ulong",
91                TypeCode::Float32 => "float",
92                TypeCode::Float64 => "double",
93                TypeCode::String => "string",
94                _ => "unknown",
95            },
96            FieldType::ScalarArray(tc) => match tc {
97                TypeCode::Float64 => "double[]",
98                TypeCode::Float32 => "float[]",
99                TypeCode::Int64 => "long[]",
100                TypeCode::Int32 => "int[]",
101                _ => "array",
102            },
103            FieldType::String => "string",
104            FieldType::StringArray => "string[]",
105            FieldType::Structure(_) => "structure",
106            FieldType::StructureArray(_) => "structure[]",
107            FieldType::Union(_) => "union",
108            FieldType::UnionArray(_) => "union[]",
109            FieldType::Variant => "any",
110            FieldType::VariantArray => "any[]",
111            FieldType::BoundedString(_) => "string",
112        }
113    }
114}
115
116/// Field description (name + type)
117#[derive(Debug, Clone)]
118pub struct FieldDesc {
119    pub name: String,
120    pub field_type: FieldType,
121}
122
123/// Structure description with optional ID
124#[derive(Debug, Clone)]
125pub struct StructureDesc {
126    pub struct_id: Option<String>,
127    pub fields: Vec<FieldDesc>,
128}
129
130impl StructureDesc {
131    pub fn new() -> Self {
132        Self {
133            struct_id: None,
134            fields: Vec::new(),
135        }
136    }
137}
138
139impl Default for StructureDesc {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145/// Decoded value
146#[derive(Debug, Clone)]
147pub enum DecodedValue {
148    Null,
149    Boolean(bool),
150    Int8(i8),
151    Int16(i16),
152    Int32(i32),
153    Int64(i64),
154    UInt8(u8),
155    UInt16(u16),
156    UInt32(u32),
157    UInt64(u64),
158    Float32(f32),
159    Float64(f64),
160    String(String),
161    Array(Vec<DecodedValue>),
162    Structure(Vec<(String, DecodedValue)>),
163    Raw(Vec<u8>),
164}
165
166impl fmt::Display for DecodedValue {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        match self {
169            DecodedValue::Null => write!(f, "null"),
170            DecodedValue::Boolean(v) => write!(f, "{}", v),
171            DecodedValue::Int8(v) => write!(f, "{}", v),
172            DecodedValue::Int16(v) => write!(f, "{}", v),
173            DecodedValue::Int32(v) => write!(f, "{}", v),
174            DecodedValue::Int64(v) => write!(f, "{}", v),
175            DecodedValue::UInt8(v) => write!(f, "{}", v),
176            DecodedValue::UInt16(v) => write!(f, "{}", v),
177            DecodedValue::UInt32(v) => write!(f, "{}", v),
178            DecodedValue::UInt64(v) => write!(f, "{}", v),
179            DecodedValue::Float32(v) => write!(f, "{:.6}", v),
180            DecodedValue::Float64(v) => write!(f, "{:.6}", v),
181            DecodedValue::String(v) => write!(f, "\"{}\"", v),
182            DecodedValue::Array(arr) => {
183                write!(f, "[")?;
184                for (i, v) in arr.iter().enumerate() {
185                    if i > 0 {
186                        write!(f, ", ")?;
187                    }
188                    if i >= 5 {
189                        write!(f, "... ({} total)", arr.len())?;
190                        break;
191                    }
192                    write!(f, "{}", v)?;
193                }
194                write!(f, "]")
195            }
196            DecodedValue::Structure(fields) => {
197                write!(f, "{{")?;
198                for (i, (name, val)) in fields.iter().enumerate() {
199                    if i > 0 {
200                        write!(f, ", ")?;
201                    }
202                    write!(f, "{}={}", name, val)?;
203                }
204                write!(f, "}}")
205            }
206            DecodedValue::Raw(data) => {
207                if data.len() <= 8 {
208                    write!(f, "<{} bytes: {}>", data.len(), hex::encode(data))
209                } else {
210                    write!(f, "<{} bytes>", data.len())
211                }
212            }
213        }
214    }
215}
216
217/// PVD Decoder state
218pub struct PvdDecoder {
219    is_be: bool,
220}
221
222impl PvdDecoder {
223    pub fn new(is_be: bool) -> Self {
224        Self { is_be }
225    }
226
227    /// Decode a size value (PVA variable-length encoding)
228    pub fn decode_size(&self, data: &[u8]) -> Option<(usize, usize)> {
229        if data.is_empty() {
230            return None;
231        }
232        let first = data[0];
233        if first == 0xFF {
234            // Special: -1 (null)
235            return Some((0, 1)); // Treat as 0 for simplicity
236        }
237        if first < 254 {
238            return Some((first as usize, 1));
239        }
240        if first == 254 {
241            // 4-byte size follows
242            if data.len() < 5 {
243                return None;
244            }
245            let size = if self.is_be {
246                u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as usize
247            } else {
248                u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as usize
249            };
250            return Some((size, 5));
251        }
252        // first == 255 is null marker, handled above.
253        None
254    }
255
256    /// Decode a string
257    pub fn decode_string(&self, data: &[u8]) -> Option<(String, usize)> {
258        let (size, size_bytes) = self.decode_size(data)?;
259        if size == 0 {
260            return Some((String::new(), size_bytes));
261        }
262        if data.len() < size_bytes + size {
263            return None;
264        }
265        let s = std::str::from_utf8(&data[size_bytes..size_bytes + size]).ok()?;
266        Some((s.to_string(), size_bytes + size))
267    }
268
269    /// Parse field description from introspection data
270    pub fn parse_field_desc(&self, data: &[u8]) -> Option<(FieldDesc, usize)> {
271        if data.is_empty() {
272            return None;
273        }
274
275        let mut offset = 0;
276
277        // Parse field name
278        let (name, consumed) = self.decode_string(&data[offset..])?;
279        offset += consumed;
280
281        if offset >= data.len() {
282            return None;
283        }
284
285        // Parse type descriptor
286        let (field_type, type_consumed) = self.parse_type_desc(&data[offset..])?;
287        offset += type_consumed;
288
289        Some((FieldDesc { name, field_type }, offset))
290    }
291
292    /// Parse type descriptor
293    fn parse_type_desc(&self, data: &[u8]) -> Option<(FieldType, usize)> {
294        if data.is_empty() {
295            return None;
296        }
297
298        let type_byte = data[0];
299        let mut offset = 1;
300
301        // Check for NULL type
302        if type_byte == 0xFF {
303            return Some((FieldType::Variant, 1));
304        }
305
306        // Full-with-id from IntrospectionRegistry:
307        // 0xFD + int16 key + type descriptor payload.
308        if type_byte == 0xFD {
309            if data.len() < 4 {
310                return None;
311            }
312            // Preferred parsing path: skip tag + int16 key.
313            if let Some((field_type, consumed)) = self.parse_type_desc(&data[3..]) {
314                return Some((field_type, 3 + consumed));
315            }
316            // Legacy fallback for older non-keyed streams.
317            if let Some((field_type, consumed)) = self.parse_type_desc(&data[1..]) {
318                return Some((field_type, 1 + consumed));
319            }
320            return None;
321        }
322
323        // Only-id from IntrospectionRegistry:
324        // 0xFE + int16 key, requires connection-level registry state.
325        if type_byte == 0xFE {
326            debug!("Type descriptor uses ONLY_ID (0xFE) without registry context");
327            return None;
328        }
329
330        // Check for structure (0x80) or structure array (0x88)
331        if type_byte == 0x80 || type_byte == 0x88 {
332            let is_array = (type_byte & 0x08) != 0;
333            if is_array {
334                // Skip the inner structure element tag (0x80)
335                if offset >= data.len() || data[offset] != 0x80 {
336                    return None;
337                }
338                offset += 1;
339            }
340            let (struct_desc, consumed) = self.parse_structure_desc(&data[offset..])?;
341            offset += consumed;
342            if is_array {
343                return Some((FieldType::StructureArray(struct_desc), offset));
344            } else {
345                return Some((FieldType::Structure(struct_desc), offset));
346            }
347        }
348
349        // Check for union (0x81) or union array (0x89)
350        if type_byte == 0x81 || type_byte == 0x89 {
351            let is_array = (type_byte & 0x08) != 0;
352            if is_array {
353                // Skip the inner union element tag (0x81)
354                if offset >= data.len() || data[offset] != 0x81 {
355                    return None;
356                }
357                offset += 1;
358            }
359            // Parse union fields (same as structure)
360            let (struct_desc, consumed) = self.parse_structure_desc(&data[offset..])?;
361            offset += consumed;
362            if is_array {
363                return Some((FieldType::UnionArray(struct_desc.fields), offset));
364            } else {
365                return Some((FieldType::Union(struct_desc.fields), offset));
366            }
367        }
368
369        // Check for variant/any (0x82) or variant array (0x8A)
370        if type_byte == 0x82 {
371            return Some((FieldType::Variant, 1));
372        }
373        if type_byte == 0x8A {
374            return Some((FieldType::VariantArray, 1));
375        }
376
377        // Check for bounded string (0x83, legacy 0x86 accepted for compatibility)
378        if type_byte == 0x83 || type_byte == 0x86 {
379            let (bound, consumed) = self.decode_size(&data[offset..])?;
380            offset += consumed;
381            return Some((FieldType::BoundedString(bound as u32), offset));
382        }
383
384        // Scalar / scalar-array with mode bits:
385        // 0x00=not-array, 0x08=variable, 0x10=bounded, 0x18=fixed
386        let scalar_or_array = type_byte & 0x18;
387        let is_array = scalar_or_array != 0;
388        if is_array && scalar_or_array != 0x08 {
389            // Consume bounded/fixed max length for alignment, even if we don't model it.
390            let (_bound, consumed) = self.decode_size(&data[offset..])?;
391            offset += consumed;
392        }
393        let base_type = type_byte & 0xE7;
394
395        // String type
396        if base_type == 0x60 {
397            if is_array {
398                return Some((FieldType::StringArray, offset));
399            } else {
400                return Some((FieldType::String, offset));
401            }
402        }
403
404        // Numeric types
405        if let Some(tc) = TypeCode::from_byte(base_type) {
406            if is_array {
407                return Some((FieldType::ScalarArray(tc), offset));
408            } else {
409                return Some((FieldType::Scalar(tc), offset));
410            }
411        }
412
413        debug!("Unknown type byte: 0x{:02x}", type_byte);
414        None
415    }
416
417    /// Parse structure description
418    fn parse_structure_desc(&self, data: &[u8]) -> Option<(StructureDesc, usize)> {
419        let mut offset = 0;
420
421        // Parse optional struct ID
422        let (struct_id, consumed) = self.decode_string(&data[offset..])?;
423        offset += consumed;
424
425        let struct_id = if struct_id.is_empty() {
426            None
427        } else {
428            Some(struct_id)
429        };
430
431        // Parse field count
432        let (field_count, consumed) = self.decode_size(&data[offset..])?;
433        offset += consumed;
434
435        let mut fields = Vec::with_capacity(field_count);
436
437        for _ in 0..field_count {
438            if offset >= data.len() {
439                break;
440            }
441            if let Some((field, consumed)) = self.parse_field_desc(&data[offset..]) {
442                offset += consumed;
443                fields.push(field);
444            } else {
445                break;
446            }
447        }
448
449        Some((StructureDesc { struct_id, fields }, offset))
450    }
451
452    /// Parse the full type introspection from INIT response
453    pub fn parse_introspection(&self, data: &[u8]) -> Option<StructureDesc> {
454        self.parse_introspection_with_len(data)
455            .map(|(desc, _)| desc)
456    }
457
458    /// Parse full type introspection and return consumed bytes.
459    pub fn parse_introspection_with_len(&self, data: &[u8]) -> Option<(StructureDesc, usize)> {
460        if data.is_empty() {
461            return None;
462        }
463
464        // The introspection starts with a type byte
465        let type_byte = data[0];
466
467        // Should be a structure (0x80)
468        if type_byte == 0x80 {
469            let (desc, consumed) = self.parse_structure_desc(&data[1..])?;
470            return Some((desc, 1 + consumed));
471        }
472
473        // Full-with-id from IntrospectionRegistry:
474        // 0xFD + int16 key + field type descriptor payload.
475        if type_byte == 0xFD {
476            if data.len() < 4 {
477                return None;
478            }
479            // Preferred parsing path: skip tag + int16 key.
480            if let Some((desc, consumed)) = self.parse_introspection_with_len(&data[3..]) {
481                return Some((desc, 3 + consumed));
482            }
483            // Legacy fallback for older non-keyed streams.
484            if let Some((desc, consumed)) = self.parse_introspection_with_len(&data[1..]) {
485                return Some((desc, 1 + consumed));
486            }
487            return None;
488        }
489
490        // Only-id from IntrospectionRegistry:
491        // 0xFE + int16 key, requires connection-level registry state.
492        if type_byte == 0xFE {
493            debug!(
494                "Introspection uses ONLY_ID (0xFE), but no registry is available in this decoder"
495            );
496            return None;
497        }
498
499        debug!("Unexpected introspection type byte: 0x{:02x}", type_byte);
500        None
501    }
502
503    /// Decode a scalar value
504    fn decode_scalar(&self, data: &[u8], tc: TypeCode) -> Option<(DecodedValue, usize)> {
505        let size = tc.size()?;
506        if data.len() < size {
507            return None;
508        }
509
510        let value = match tc {
511            TypeCode::Boolean => DecodedValue::Boolean(data[0] != 0),
512            TypeCode::Int8 => DecodedValue::Int8(data[0] as i8),
513            TypeCode::UInt8 => DecodedValue::UInt8(data[0]),
514            TypeCode::Int16 => {
515                let v = if self.is_be {
516                    i16::from_be_bytes([data[0], data[1]])
517                } else {
518                    i16::from_le_bytes([data[0], data[1]])
519                };
520                DecodedValue::Int16(v)
521            }
522            TypeCode::UInt16 => {
523                let v = if self.is_be {
524                    u16::from_be_bytes([data[0], data[1]])
525                } else {
526                    u16::from_le_bytes([data[0], data[1]])
527                };
528                DecodedValue::UInt16(v)
529            }
530            TypeCode::Int32 => {
531                let v = if self.is_be {
532                    i32::from_be_bytes(data[0..4].try_into().unwrap())
533                } else {
534                    i32::from_le_bytes(data[0..4].try_into().unwrap())
535                };
536                DecodedValue::Int32(v)
537            }
538            TypeCode::UInt32 => {
539                let v = if self.is_be {
540                    u32::from_be_bytes(data[0..4].try_into().unwrap())
541                } else {
542                    u32::from_le_bytes(data[0..4].try_into().unwrap())
543                };
544                DecodedValue::UInt32(v)
545            }
546            TypeCode::Int64 => {
547                let v = if self.is_be {
548                    i64::from_be_bytes(data[0..8].try_into().unwrap())
549                } else {
550                    i64::from_le_bytes(data[0..8].try_into().unwrap())
551                };
552                DecodedValue::Int64(v)
553            }
554            TypeCode::UInt64 => {
555                let v = if self.is_be {
556                    u64::from_be_bytes(data[0..8].try_into().unwrap())
557                } else {
558                    u64::from_le_bytes(data[0..8].try_into().unwrap())
559                };
560                DecodedValue::UInt64(v)
561            }
562            TypeCode::Float32 => {
563                let v = if self.is_be {
564                    f32::from_be_bytes(data[0..4].try_into().unwrap())
565                } else {
566                    f32::from_le_bytes(data[0..4].try_into().unwrap())
567                };
568                DecodedValue::Float32(v)
569            }
570            TypeCode::Float64 => {
571                let v = if self.is_be {
572                    f64::from_be_bytes(data[0..8].try_into().unwrap())
573                } else {
574                    f64::from_le_bytes(data[0..8].try_into().unwrap())
575                };
576                DecodedValue::Float64(v)
577            }
578            _ => return None,
579        };
580
581        Some((value, size))
582    }
583
584    /// Decode value according to field type
585    pub fn decode_value(
586        &self,
587        data: &[u8],
588        field_type: &FieldType,
589    ) -> Option<(DecodedValue, usize)> {
590        match field_type {
591            FieldType::Scalar(tc) => self.decode_scalar(data, *tc),
592            FieldType::String | FieldType::BoundedString(_) => {
593                let (s, consumed) = self.decode_string(data)?;
594                Some((DecodedValue::String(s), consumed))
595            }
596            FieldType::ScalarArray(tc) => {
597                let (count, size_consumed) = self.decode_size(data)?;
598                let mut offset = size_consumed;
599                let limit = count.min(4_000_000);
600                let mut values = Vec::with_capacity(limit);
601                let elem_size = tc.size().unwrap_or(1);
602                for _ in 0..limit {
603                    if let Some((val, consumed)) = self.decode_scalar(&data[offset..], *tc) {
604                        values.push(val);
605                        offset += consumed;
606                    } else {
607                        break;
608                    }
609                }
610                // Skip past any remaining elements we didn't store, so the
611                // stream stays aligned for the next field.
612                let remaining = count.saturating_sub(limit);
613                offset += remaining * elem_size;
614                Some((DecodedValue::Array(values), offset))
615            }
616            FieldType::StringArray => {
617                let (count, size_consumed) = self.decode_size(data)?;
618                let mut offset = size_consumed;
619                let max_items = count.min(4096);
620                let mut values = Vec::with_capacity(max_items);
621                for _ in 0..max_items {
622                    if let Some((s, consumed)) = self.decode_string(&data[offset..]) {
623                        values.push(DecodedValue::String(s));
624                        offset += consumed;
625                    } else {
626                        break;
627                    }
628                }
629                Some((DecodedValue::Array(values), offset))
630            }
631            FieldType::Structure(desc) => self.decode_structure(data, desc),
632            FieldType::StructureArray(desc) => {
633                let (count, size_consumed) = self.decode_size(data)?;
634                let mut offset = size_consumed;
635                let mut values = Vec::with_capacity(count.min(256));
636                for _ in 0..count.min(256) {
637                    // Read per-element null indicator (0 = null, non-zero = present)
638                    if offset >= data.len() {
639                        return None;
640                    }
641                    let null_indicator = data[offset];
642                    offset += 1;
643                    if null_indicator == 0 {
644                        // null element – push empty structure placeholder
645                        values.push(DecodedValue::Structure(Vec::new()));
646                        continue;
647                    }
648                    let (item, consumed) = self.decode_structure(&data[offset..], desc)?;
649                    values.push(item);
650                    offset += consumed;
651                }
652                Some((DecodedValue::Array(values), offset))
653            }
654            FieldType::Union(fields) => {
655                let (selector, consumed) = self.decode_size(data)?;
656                let field = fields.get(selector)?;
657                let (value, val_consumed) =
658                    self.decode_value(&data[consumed..], &field.field_type)?;
659                Some((
660                    DecodedValue::Structure(vec![(field.name.clone(), value)]),
661                    consumed + val_consumed,
662                ))
663            }
664            FieldType::UnionArray(fields) => {
665                let (count, size_consumed) = self.decode_size(data)?;
666                let mut offset = size_consumed;
667                let mut values = Vec::with_capacity(count.min(128));
668                for _ in 0..count.min(128) {
669                    let (selector, consumed) = self.decode_size(&data[offset..])?;
670                    offset += consumed;
671                    let field = fields.get(selector)?;
672                    let (value, val_consumed) =
673                        self.decode_value(&data[offset..], &field.field_type)?;
674                    offset += val_consumed;
675                    values.push(DecodedValue::Structure(vec![(field.name.clone(), value)]));
676                }
677                Some((DecodedValue::Array(values), offset))
678            }
679            FieldType::Variant => {
680                if data.is_empty() {
681                    return None;
682                }
683                if data[0] == 0xFF {
684                    return Some((DecodedValue::Null, 1));
685                }
686                let (variant_type, type_consumed) = self.parse_type_desc(data)?;
687                let (variant_value, value_consumed) =
688                    self.decode_value(&data[type_consumed..], &variant_type)?;
689                Some((variant_value, type_consumed + value_consumed))
690            }
691            FieldType::VariantArray => {
692                let (count, size_consumed) = self.decode_size(data)?;
693                let mut offset = size_consumed;
694                let mut values = Vec::with_capacity(count.min(128));
695                for _ in 0..count.min(128) {
696                    let (v, consumed) = self.decode_value(&data[offset..], &FieldType::Variant)?;
697                    values.push(v);
698                    offset += consumed;
699                }
700                Some((DecodedValue::Array(values), offset))
701            }
702        }
703    }
704
705    /// Decode a structure value using the field descriptions
706    pub fn decode_structure(
707        &self,
708        data: &[u8],
709        desc: &StructureDesc,
710    ) -> Option<(DecodedValue, usize)> {
711        let mut offset = 0;
712        let mut fields: Vec<(String, DecodedValue)> = Vec::new();
713
714        for field in &desc.fields {
715            if offset >= data.len() {
716                break;
717            }
718            if let Some((value, consumed)) = self.decode_value(&data[offset..], &field.field_type) {
719                fields.push((field.name.clone(), value));
720                offset += consumed;
721            } else {
722                // Can't decode this field, stop
723                break;
724            }
725        }
726
727        Some((DecodedValue::Structure(fields), offset))
728    }
729
730    /// Decode a structure with a bitset indicating which fields are present
731    /// This is used for delta updates in MONITOR
732    pub fn decode_structure_with_bitset(
733        &self,
734        data: &[u8],
735        desc: &StructureDesc,
736    ) -> Option<(DecodedValue, usize)> {
737        if data.is_empty() {
738            return None;
739        }
740
741        let mut offset = 0;
742
743        // Parse the bitset - PVA uses size-encoded bitset
744        let (bitset_size, size_consumed) = self.decode_size(data)?;
745        offset += size_consumed;
746
747        if bitset_size == 0 || offset + bitset_size > data.len() {
748            return Some((DecodedValue::Structure(vec![]), offset));
749        }
750
751        let bitset = &data[offset..offset + bitset_size];
752        offset += bitset_size;
753
754        let (value, consumed) =
755            self.decode_structure_with_bitset_body(&data[offset..], desc, bitset)?;
756        Some((value, offset + consumed))
757    }
758
759    /// Decode a structure with changed and overrun bitsets (MONITOR updates)
760    pub fn decode_structure_with_bitset_and_overrun(
761        &self,
762        data: &[u8],
763        desc: &StructureDesc,
764    ) -> Option<(DecodedValue, usize)> {
765        if data.is_empty() {
766            return None;
767        }
768        let mut offset = 0usize;
769        let (changed_size, consumed1) = self.decode_size(&data[offset..])?;
770        offset += consumed1;
771        if offset + changed_size > data.len() {
772            return None;
773        }
774        let changed = &data[offset..offset + changed_size];
775        offset += changed_size;
776
777        let (overrun_size, consumed2) = self.decode_size(&data[offset..])?;
778        offset += consumed2;
779        if offset + overrun_size > data.len() {
780            return None;
781        }
782        offset += overrun_size;
783
784        let (value, consumed) =
785            self.decode_structure_with_bitset_body(&data[offset..], desc, changed)?;
786        Some((value, offset + consumed))
787    }
788
789    /// Decode a structure with changed bitset, data, then overrun bitset (spec order)
790    pub fn decode_structure_with_bitset_then_overrun(
791        &self,
792        data: &[u8],
793        desc: &StructureDesc,
794    ) -> Option<(DecodedValue, usize)> {
795        if data.is_empty() {
796            return None;
797        }
798        let mut offset = 0usize;
799        let (changed_size, consumed1) = self.decode_size(&data[offset..])?;
800        offset += consumed1;
801        if offset + changed_size > data.len() {
802            return None;
803        }
804        let changed = &data[offset..offset + changed_size];
805        offset += changed_size;
806
807        let (value, consumed) =
808            self.decode_structure_with_bitset_body(&data[offset..], desc, changed)?;
809        offset += consumed;
810
811        let (overrun_size, consumed2) = self.decode_size(&data[offset..])?;
812        offset += consumed2;
813        if offset + overrun_size > data.len() {
814            return None;
815        }
816        offset += overrun_size;
817
818        Some((value, offset))
819    }
820
821    fn decode_structure_with_bitset_body(
822        &self,
823        data: &[u8],
824        desc: &StructureDesc,
825        bitset: &[u8],
826    ) -> Option<(DecodedValue, usize)> {
827        // Bit 0 is for the whole structure, field bits start at bit 1
828        debug!(
829            "Bitset: {:02x?} (size={}), total_fields={}",
830            bitset,
831            bitset.len(),
832            count_structure_fields(desc)
833        );
834        debug!(
835            "Structure fields: {:?}",
836            desc.fields.iter().map(|f| &f.name).collect::<Vec<_>>()
837        );
838
839        // Special case: bitset contains only bit0 (whole structure) and no field bits.
840        let mut has_field_bits = false;
841        if !bitset.is_empty() {
842            for (i, b) in bitset.iter().enumerate() {
843                let mask = if i == 0 { *b & !0x01 } else { *b };
844                if mask != 0 {
845                    has_field_bits = true;
846                    break;
847                }
848            }
849        }
850        if !has_field_bits && !bitset.is_empty() && (bitset[0] & 0x01) != 0 {
851            if let Some((value, consumed)) = self.decode_structure(data, desc) {
852                return Some((value, consumed));
853            }
854        }
855
856        let mut fields: Vec<(String, DecodedValue)> = Vec::new();
857        let mut offset = 0usize;
858
859        fn decode_with_bitset_recursive(
860            decoder: &PvdDecoder,
861            data: &[u8],
862            offset: &mut usize,
863            desc: &StructureDesc,
864            bitset: &[u8],
865            bit_offset: &mut usize,
866            fields: &mut Vec<(String, DecodedValue)>,
867        ) -> bool {
868            for field in &desc.fields {
869                let byte_idx = *bit_offset / 8;
870                let bit_idx = *bit_offset % 8;
871                let current_bit = *bit_offset;
872                *bit_offset += 1;
873
874                let field_present = if byte_idx < bitset.len() {
875                    (bitset[byte_idx] & (1 << bit_idx)) != 0
876                } else {
877                    false
878                };
879
880                debug!(
881                    "Field '{}' at bit {}: present={}",
882                    field.name, current_bit, field_present
883                );
884
885                if let FieldType::Structure(nested_desc) = &field.field_type {
886                    let child_start_bit = *bit_offset;
887                    let child_field_count = count_structure_fields(nested_desc);
888
889                    let mut any_child_bits_set = false;
890                    for i in 0..child_field_count {
891                        let check_byte = (child_start_bit + i) / 8;
892                        let check_bit = (child_start_bit + i) % 8;
893                        if check_byte < bitset.len() && (bitset[check_byte] & (1 << check_bit)) != 0
894                        {
895                            any_child_bits_set = true;
896                            break;
897                        }
898                    }
899
900                    debug!(
901                        "Nested structure '{}': parent_present={}, child_start_bit={}, child_count={}, any_child_bits_set={}",
902                        field.name, field_present, child_start_bit, child_field_count, any_child_bits_set
903                    );
904
905                    if field_present && !any_child_bits_set {
906                        *bit_offset += child_field_count;
907                        if *offset < data.len() {
908                            if let Some((value, consumed)) =
909                                decoder.decode_structure(&data[*offset..], nested_desc)
910                            {
911                                debug!(
912                                    "Decoded full nested structure '{}', consumed {} bytes",
913                                    field.name, consumed
914                                );
915                                fields.push((field.name.clone(), value));
916                                *offset += consumed;
917                            } else {
918                                debug!("Failed to decode full nested structure '{}'", field.name);
919                                return false;
920                            }
921                        }
922                    } else if any_child_bits_set {
923                        let mut nested_fields: Vec<(String, DecodedValue)> = Vec::new();
924                        if !decode_with_bitset_recursive(
925                            decoder,
926                            data,
927                            offset,
928                            nested_desc,
929                            bitset,
930                            bit_offset,
931                            &mut nested_fields,
932                        ) {
933                            return false;
934                        }
935                        debug!(
936                            "Nested structure '{}' decoded {} fields",
937                            field.name,
938                            nested_fields.len()
939                        );
940                        if !nested_fields.is_empty() {
941                            fields
942                                .push((field.name.clone(), DecodedValue::Structure(nested_fields)));
943                        }
944                    } else {
945                        *bit_offset += child_field_count;
946                    }
947                } else if field_present {
948                    if *offset >= data.len() {
949                        debug!(
950                            "Data exhausted at offset {} for field '{}'",
951                            *offset, field.name
952                        );
953                        return false;
954                    }
955                    if let Some((value, consumed)) =
956                        decoder.decode_value(&data[*offset..], &field.field_type)
957                    {
958                        fields.push((field.name.clone(), value));
959                        *offset += consumed;
960                    } else {
961                        return false;
962                    }
963                }
964            }
965            true
966        }
967
968        let mut bit_offset = 1;
969        decode_with_bitset_recursive(
970            self,
971            data,
972            &mut offset,
973            desc,
974            bitset,
975            &mut bit_offset,
976            &mut fields,
977        );
978        Some((DecodedValue::Structure(fields), offset))
979    }
980}
981
982/// Count total fields in a structure (including nested)
983fn count_structure_fields(desc: &StructureDesc) -> usize {
984    let mut count = 0;
985    for field in &desc.fields {
986        count += 1;
987        if let FieldType::Structure(nested) = &field.field_type {
988            count += count_structure_fields(nested);
989        }
990    }
991    count
992}
993
994/// Extract a sub-field from a StructureDesc by dot-separated path.
995/// Returns the sub-field as an owned StructureDesc. For leaf (non-structure)
996/// fields, returns a single-field StructureDesc wrapping the matched field.
997/// Returns the full desc if path is empty.
998pub fn extract_subfield_desc(desc: &StructureDesc, path: &str) -> Option<StructureDesc> {
999    if path.is_empty() {
1000        return Some(desc.clone());
1001    }
1002    let mut parts = path.splitn(2, '.');
1003    let head = parts.next()?;
1004    let tail = parts.next().unwrap_or("");
1005    for field in &desc.fields {
1006        if field.name == head {
1007            match &field.field_type {
1008                FieldType::Structure(nested) | FieldType::StructureArray(nested) => {
1009                    return extract_subfield_desc(nested, tail);
1010                }
1011                _ => {
1012                    if tail.is_empty() {
1013                        return Some(StructureDesc {
1014                            struct_id: None,
1015                            fields: vec![field.clone()],
1016                        });
1017                    }
1018                    return None;
1019                }
1020            }
1021        }
1022    }
1023    None
1024}
1025
1026/// Format a structure description for display
1027pub fn format_structure_desc(desc: &StructureDesc) -> String {
1028    let mut parts = Vec::new();
1029    if let Some(ref id) = desc.struct_id {
1030        parts.push(id.clone());
1031    }
1032    for field in &desc.fields {
1033        parts.push(format!("{}:{}", field.name, field.field_type.type_name()));
1034    }
1035    parts.join(", ")
1036}
1037
1038pub fn format_structure_tree(desc: &StructureDesc) -> String {
1039    fn push_fields(out: &mut Vec<String>, fields: &[FieldDesc], indent: usize) {
1040        let prefix = "  ".repeat(indent);
1041        for field in fields {
1042            match &field.field_type {
1043                FieldType::Structure(nested) => {
1044                    out.push(format!("{}{}: structure", prefix, field.name));
1045                    push_fields(out, &nested.fields, indent + 1);
1046                }
1047                FieldType::StructureArray(nested) => {
1048                    out.push(format!("{}{}: structure[]", prefix, field.name));
1049                    push_fields(out, &nested.fields, indent + 1);
1050                }
1051                FieldType::Union(variants) => {
1052                    out.push(format!("{}{}: union", prefix, field.name));
1053                    push_fields(out, variants, indent + 1);
1054                }
1055                FieldType::UnionArray(variants) => {
1056                    out.push(format!("{}{}: union[]", prefix, field.name));
1057                    push_fields(out, variants, indent + 1);
1058                }
1059                FieldType::BoundedString(bound) => {
1060                    out.push(format!("{}{}: string<={}", prefix, field.name, bound));
1061                }
1062                _ => {
1063                    out.push(format!(
1064                        "{}{}: {}",
1065                        prefix,
1066                        field.name,
1067                        field.field_type.type_name()
1068                    ));
1069                }
1070            }
1071        }
1072    }
1073
1074    let mut lines = Vec::new();
1075    if let Some(id) = &desc.struct_id {
1076        lines.push(format!("struct {}", id));
1077    } else {
1078        lines.push("struct <anonymous>".to_string());
1079    }
1080    push_fields(&mut lines, &desc.fields, 0);
1081    lines.join("\n")
1082}
1083
1084/// Extract the "value" field from a decoded NTScalar structure
1085pub fn extract_nt_scalar_value(decoded: &DecodedValue) -> Option<&DecodedValue> {
1086    if let DecodedValue::Structure(fields) = decoded {
1087        for (name, value) in fields {
1088            if name == "value" {
1089                return Some(value);
1090            }
1091        }
1092    }
1093    None
1094}
1095
1096/// Compact display of decoded value for logging - shows only updated fields concisely
1097pub fn format_compact_value(decoded: &DecodedValue) -> String {
1098    match decoded {
1099        DecodedValue::Structure(fields) => {
1100            if fields.is_empty() {
1101                return "{}".to_string();
1102            }
1103
1104            let mut parts = Vec::new();
1105
1106            for (name, val) in fields {
1107                let formatted = format_field_value_compact(name, val);
1108                if !formatted.is_empty() {
1109                    parts.push(formatted);
1110                }
1111            }
1112
1113            parts.join(", ")
1114        }
1115        _ => format!("{}", decoded),
1116    }
1117}
1118
1119/// Format a single field value compactly - shows key info for known structures
1120fn format_field_value_compact(name: &str, val: &DecodedValue) -> String {
1121    match val {
1122        DecodedValue::Structure(fields) => {
1123            // For known EPICS NTScalar structures, show only key fields
1124            match name {
1125                "alarm" => {
1126                    // Show severity and message if non-zero/non-empty
1127                    let severity = fields.iter().find(|(n, _)| n == "severity");
1128                    let message = fields.iter().find(|(n, _)| n == "message");
1129                    let mut parts = Vec::new();
1130                    if let Some((_, DecodedValue::Int32(s))) = severity {
1131                        if *s != 0 {
1132                            parts.push(format!("sev={}", s));
1133                        }
1134                    }
1135                    if let Some((_, DecodedValue::String(m))) = message {
1136                        if !m.is_empty() {
1137                            parts.push(format!("\"{}\"", m));
1138                        }
1139                    }
1140                    if parts.is_empty() {
1141                        String::new() // Don't show alarm if it's OK
1142                    } else {
1143                        format!("alarm={{{}}}", parts.join(", "))
1144                    }
1145                }
1146                "timeStamp" => {
1147                    // Show just seconds or skip entirely for brevity
1148                    let secs = fields.iter().find(|(n, _)| n == "secondsPastEpoch");
1149                    if let Some((_, DecodedValue::Int64(s))) = secs {
1150                        format!("ts={}", s)
1151                    } else {
1152                        String::new()
1153                    }
1154                }
1155                "display" | "control" | "valueAlarm" => {
1156                    // Skip verbose metadata structures in compact view
1157                    String::new()
1158                }
1159                _ => {
1160                    // For other structures, show all fields
1161                    let nested: Vec<String> = fields
1162                        .iter()
1163                        .map(|(n, v)| format!("{}={}", n, format_scalar_value(v)))
1164                        .collect();
1165
1166                    if nested.is_empty() {
1167                        String::new()
1168                    } else {
1169                        format!("{}={{{}}}", name, nested.join(", "))
1170                    }
1171                }
1172            }
1173        }
1174        _ => {
1175            format!("{}={}", name, format_scalar_value(val))
1176        }
1177    }
1178}
1179
1180/// Format a scalar value concisely
1181fn format_scalar_value(val: &DecodedValue) -> String {
1182    match val {
1183        DecodedValue::Null => "null".to_string(),
1184        DecodedValue::Boolean(v) => format!("{}", v),
1185        DecodedValue::Int8(v) => format!("{}", v),
1186        DecodedValue::Int16(v) => format!("{}", v),
1187        DecodedValue::Int32(v) => format!("{}", v),
1188        DecodedValue::Int64(v) => format!("{}", v),
1189        DecodedValue::UInt8(v) => format!("{}", v),
1190        DecodedValue::UInt16(v) => format!("{}", v),
1191        DecodedValue::UInt32(v) => format!("{}", v),
1192        DecodedValue::UInt64(v) => format!("{}", v),
1193        DecodedValue::Float32(v) => format!("{:.4}", v),
1194        DecodedValue::Float64(v) => format!("{:.6}", v),
1195        DecodedValue::String(v) => format!("\"{}\"", v),
1196        DecodedValue::Array(arr) => {
1197            if arr.is_empty() {
1198                "[]".to_string()
1199            } else if arr.len() <= 3 {
1200                let items: Vec<String> = arr.iter().map(|v| format_scalar_value(v)).collect();
1201                format!("[{}]", items.join(", "))
1202            } else {
1203                format!("[{} items]", arr.len())
1204            }
1205        }
1206        DecodedValue::Structure(fields) => {
1207            let nested: Vec<String> = fields
1208                .iter()
1209                .map(|(n, v)| format!("{}={}", n, format_scalar_value(v)))
1210                .collect();
1211            format!("{{{}}}", nested.join(", "))
1212        }
1213        DecodedValue::Raw(data) => {
1214            if data.len() <= 4 {
1215                format!("<{}>", hex::encode(data))
1216            } else {
1217                format!("<{}B>", data.len())
1218            }
1219        }
1220    }
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225    use super::*;
1226
1227    #[test]
1228    fn test_decode_size() {
1229        let decoder = PvdDecoder::new(false);
1230
1231        // Small size (single byte)
1232        assert_eq!(decoder.decode_size(&[5]), Some((5, 1)));
1233        assert_eq!(decoder.decode_size(&[253]), Some((253, 1)));
1234
1235        // Medium/large size (5 bytes, 254 prefix + uint32)
1236        assert_eq!(
1237            decoder.decode_size(&[254, 0x00, 0x01, 0x00, 0x00]),
1238            Some((256, 5))
1239        );
1240    }
1241
1242    #[test]
1243    fn test_parse_introspection_full_with_id() {
1244        let decoder = PvdDecoder::new(false);
1245        let data = vec![
1246            0xFD, // FULL_WITH_ID
1247            0x06, 0x00, // registry key (little-endian)
1248            0x80, // structure type follows
1249            0x00, // empty struct id
1250            0x01, // one field
1251            0x05, b'v', b'a', b'l', b'u', b'e', // field name
1252            0x43, // float64
1253        ];
1254        let desc = decoder
1255            .parse_introspection(&data)
1256            .expect("parsed introspection");
1257        assert_eq!(desc.fields.len(), 1);
1258        assert_eq!(desc.fields[0].name, "value");
1259        match desc.fields[0].field_type {
1260            FieldType::Scalar(TypeCode::Float64) => {}
1261            _ => panic!("expected float64 value field"),
1262        }
1263    }
1264
1265    #[test]
1266    fn test_decode_string() {
1267        let decoder = PvdDecoder::new(false);
1268
1269        // Empty string
1270        assert_eq!(decoder.decode_string(&[0]), Some((String::new(), 1)));
1271
1272        // "hello"
1273        let data = [5, b'h', b'e', b'l', b'l', b'o'];
1274        assert_eq!(decoder.decode_string(&data), Some(("hello".to_string(), 6)));
1275    }
1276
1277    #[test]
1278    fn decode_variant_accepts_full_with_id_type_tag() {
1279        let decoder = PvdDecoder::new(false);
1280        // Variant payload: 0xFD + int16 key + string type + "ok"
1281        let data = [0xFD, 0x02, 0x00, 0x60, 0x02, b'o', b'k'];
1282        let (value, consumed) = decoder
1283            .decode_value(&data, &FieldType::Variant)
1284            .expect("decode variant");
1285        assert_eq!(consumed, data.len());
1286        assert!(matches!(value, DecodedValue::String(ref s) if s == "ok"));
1287    }
1288
1289    #[test]
1290    fn test_decode_bitset_whole_structure() {
1291        let decoder = PvdDecoder::new(false);
1292        let desc = StructureDesc {
1293            struct_id: None,
1294            fields: vec![FieldDesc {
1295                name: "value".to_string(),
1296                field_type: FieldType::Scalar(TypeCode::Float64),
1297            }],
1298        };
1299        // bitset_size=1, bitset=0x01 (whole structure), then float64 value.
1300        let mut data = Vec::new();
1301        data.push(0x01);
1302        data.push(0x01);
1303        data.extend_from_slice(&1.25f64.to_le_bytes());
1304
1305        let (decoded, _consumed) = decoder
1306            .decode_structure_with_bitset(&data, &desc)
1307            .expect("decoded");
1308        if let DecodedValue::Structure(fields) = decoded {
1309            assert_eq!(fields.len(), 1);
1310            assert_eq!(fields[0].0, "value");
1311        } else {
1312            panic!("expected structure");
1313        }
1314    }
1315
1316    #[test]
1317    fn format_structure_tree_includes_nested_fields() {
1318        let desc = StructureDesc {
1319            struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
1320            fields: vec![
1321                FieldDesc {
1322                    name: "value".to_string(),
1323                    field_type: FieldType::Scalar(TypeCode::Float64),
1324                },
1325                FieldDesc {
1326                    name: "alarm".to_string(),
1327                    field_type: FieldType::Structure(StructureDesc {
1328                        struct_id: None,
1329                        fields: vec![
1330                            FieldDesc {
1331                                name: "severity".to_string(),
1332                                field_type: FieldType::Scalar(TypeCode::Int32),
1333                            },
1334                            FieldDesc {
1335                                name: "message".to_string(),
1336                                field_type: FieldType::String,
1337                            },
1338                        ],
1339                    }),
1340                },
1341            ],
1342        };
1343
1344        let rendered = format_structure_tree(&desc);
1345        assert!(rendered.contains("struct epics:nt/NTScalar:1.0"));
1346        assert!(rendered.contains("value: double"));
1347        assert!(rendered.contains("alarm: structure"));
1348        assert!(rendered.contains("severity: int"));
1349        assert!(rendered.contains("message: string"));
1350    }
1351
1352    #[test]
1353    fn decode_string_array_not_capped_at_100_items() {
1354        fn encode_size(size: usize) -> Vec<u8> {
1355            if size == 0 {
1356                return vec![0x00];
1357            }
1358            if size < 254 {
1359                return vec![size as u8];
1360            }
1361            let mut out = vec![0xFE];
1362            out.extend_from_slice(&(size as u32).to_le_bytes());
1363            out
1364        }
1365
1366        let item_count = 150usize;
1367        let mut raw = encode_size(item_count);
1368        for idx in 0..item_count {
1369            let s = format!("PV:{}", idx);
1370            raw.extend_from_slice(&encode_size(s.len()));
1371            raw.extend_from_slice(s.as_bytes());
1372        }
1373
1374        let decoder = PvdDecoder::new(false);
1375        let (decoded, _consumed) = decoder
1376            .decode_value(&raw, &FieldType::StringArray)
1377            .expect("decoded");
1378
1379        let DecodedValue::Array(items) = decoded else {
1380            panic!("expected decoded array");
1381        };
1382        assert_eq!(items.len(), item_count);
1383    }
1384}