Skip to main content

spvirit_codec/
spvd_encode.rs

1//! PVD (pvData) Encoding Helpers
2//!
3//! Minimal encoder for NTScalar introspection and value updates.
4
5use std::time::{SystemTime, UNIX_EPOCH};
6
7use crate::spvd_decode::{FieldDesc, FieldType, StructureDesc, TypeCode};
8
9use spvirit_types::{
10    NdDimension, NtAlarm, NtAttribute, NtDisplay, NtEnum, NtNdArray, NtPayload, NtScalar,
11    NtScalarArray, NtTable, NtTableColumn, NtTimeStamp, PvValue, ScalarArrayValue, ScalarValue,
12};
13
14fn count_structure_fields(desc: &StructureDesc) -> usize {
15    let mut count = 0;
16    for field in &desc.fields {
17        count += 1;
18        if let FieldType::Structure(nested) = &field.field_type {
19            count += count_structure_fields(nested);
20        }
21    }
22    count
23}
24
25pub fn encode_size_pvd(size: usize, is_be: bool) -> Vec<u8> {
26    crate::encode_common::encode_size(size, is_be)
27}
28
29pub fn encode_string_pvd(value: &str, is_be: bool) -> Vec<u8> {
30    crate::encode_common::encode_string(value, is_be)
31}
32
33pub fn encode_structure_desc(desc: &StructureDesc, is_be: bool) -> Vec<u8> {
34    let mut out = Vec::new();
35    let struct_id = desc.struct_id.clone().unwrap_or_default();
36    out.extend_from_slice(&encode_string_pvd(&struct_id, is_be));
37    out.extend_from_slice(&encode_size_pvd(desc.fields.len(), is_be));
38    for field in &desc.fields {
39        out.extend_from_slice(&encode_field_desc(field, is_be));
40    }
41    out
42}
43
44fn encode_field_desc(field: &FieldDesc, is_be: bool) -> Vec<u8> {
45    let mut out = Vec::new();
46    out.extend_from_slice(&encode_string_pvd(&field.name, is_be));
47    out.extend_from_slice(&encode_type_desc(&field.field_type, is_be));
48    out
49}
50
51fn encode_type_desc(field_type: &FieldType, is_be: bool) -> Vec<u8> {
52    let mut out = Vec::new();
53    match field_type {
54        FieldType::Structure(desc) => {
55            out.push(0x80);
56            out.extend_from_slice(&encode_structure_desc(desc, is_be));
57        }
58        FieldType::StructureArray(desc) => {
59            out.push(0x88);
60            out.push(0x80); // inner structure element tag
61            out.extend_from_slice(&encode_structure_desc(desc, is_be));
62        }
63        FieldType::Union(fields) => {
64            out.push(0x81);
65            let desc = StructureDesc {
66                struct_id: None,
67                fields: fields.clone(),
68            };
69            out.extend_from_slice(&encode_structure_desc(&desc, is_be));
70        }
71        FieldType::UnionArray(fields) => {
72            out.push(0x89);
73            out.push(0x81); // inner union element tag
74            let desc = StructureDesc {
75                struct_id: None,
76                fields: fields.clone(),
77            };
78            out.extend_from_slice(&encode_structure_desc(&desc, is_be));
79        }
80        FieldType::Variant => out.push(0x82),
81        FieldType::VariantArray => out.push(0x8A),
82        FieldType::BoundedString(bound) => {
83            out.push(0x83);
84            out.extend_from_slice(&encode_size_pvd(*bound as usize, is_be));
85        }
86        FieldType::String => out.push(0x60),
87        FieldType::StringArray => out.push(0x68),
88        FieldType::Scalar(tc) => out.push(*tc as u8),
89        FieldType::ScalarArray(tc) => out.push((*tc as u8) | 0x08),
90    }
91    out
92}
93
94fn encode_scalar_value(value: &ScalarValue, is_be: bool) -> Vec<u8> {
95    match value {
96        ScalarValue::Bool(v) => vec![if *v { 1 } else { 0 }],
97        ScalarValue::I8(v) => vec![*v as u8],
98        ScalarValue::I16(v) => {
99            if is_be {
100                v.to_be_bytes().to_vec()
101            } else {
102                v.to_le_bytes().to_vec()
103            }
104        }
105        ScalarValue::I32(v) => {
106            if is_be {
107                v.to_be_bytes().to_vec()
108            } else {
109                v.to_le_bytes().to_vec()
110            }
111        }
112        ScalarValue::I64(v) => {
113            if is_be {
114                v.to_be_bytes().to_vec()
115            } else {
116                v.to_le_bytes().to_vec()
117            }
118        }
119        ScalarValue::U8(v) => vec![*v],
120        ScalarValue::U16(v) => {
121            if is_be {
122                v.to_be_bytes().to_vec()
123            } else {
124                v.to_le_bytes().to_vec()
125            }
126        }
127        ScalarValue::U32(v) => {
128            if is_be {
129                v.to_be_bytes().to_vec()
130            } else {
131                v.to_le_bytes().to_vec()
132            }
133        }
134        ScalarValue::U64(v) => {
135            if is_be {
136                v.to_be_bytes().to_vec()
137            } else {
138                v.to_le_bytes().to_vec()
139            }
140        }
141        ScalarValue::F32(v) => {
142            if is_be {
143                v.to_be_bytes().to_vec()
144            } else {
145                v.to_le_bytes().to_vec()
146            }
147        }
148        ScalarValue::F64(v) => {
149            if is_be {
150                v.to_be_bytes().to_vec()
151            } else {
152                v.to_le_bytes().to_vec()
153            }
154        }
155        ScalarValue::Str(v) => encode_string_pvd(v, is_be),
156    }
157}
158
159fn encode_alarm(nt: &NtScalar, is_be: bool) -> Vec<u8> {
160    let mut out = Vec::new();
161    out.extend_from_slice(&encode_i32(nt.alarm_severity, is_be));
162    out.extend_from_slice(&encode_i32(nt.alarm_status, is_be));
163    out.extend_from_slice(&encode_string_pvd(&nt.alarm_message, is_be));
164    out
165}
166
167fn encode_bool(value: bool) -> Vec<u8> {
168    vec![if value { 1 } else { 0 }]
169}
170
171fn encode_string_array(values: &[String], is_be: bool) -> Vec<u8> {
172    let mut out = Vec::new();
173    out.extend_from_slice(&encode_size_pvd(values.len(), is_be));
174    for v in values {
175        out.extend_from_slice(&encode_string_pvd(v, is_be));
176    }
177    out
178}
179
180fn encode_enum(index: i32, choices: &[String], is_be: bool) -> Vec<u8> {
181    let mut out = Vec::new();
182    out.extend_from_slice(&encode_i32(index, is_be));
183    out.extend_from_slice(&encode_string_array(choices, is_be));
184    out
185}
186
187fn encode_timestamp(_nt: &NtScalar, is_be: bool) -> Vec<u8> {
188    let mut out = Vec::new();
189    let now = SystemTime::now()
190        .duration_since(UNIX_EPOCH)
191        .unwrap_or_default();
192    let seconds_past_epoch = now.as_secs() as i64;
193    let nanos = now.subsec_nanos() as i32;
194
195    out.extend_from_slice(&encode_i64(seconds_past_epoch, is_be));
196    out.extend_from_slice(&encode_i32(nanos, is_be));
197    out.extend_from_slice(&encode_i32(0, is_be)); // userTag
198    out
199}
200
201fn encode_display(nt: &NtScalar, is_be: bool) -> Vec<u8> {
202    let mut out = Vec::new();
203    out.extend_from_slice(&encode_f64(nt.display_low, is_be));
204    out.extend_from_slice(&encode_f64(nt.display_high, is_be));
205    out.extend_from_slice(&encode_string_pvd(&nt.display_description, is_be));
206    out.extend_from_slice(&encode_string_pvd(&nt.units, is_be));
207    out.extend_from_slice(&encode_i32(nt.display_precision, is_be));
208    out.extend_from_slice(&encode_enum(
209        nt.display_form_index,
210        &nt.display_form_choices,
211        is_be,
212    ));
213    out
214}
215
216fn encode_control(nt: &NtScalar, is_be: bool) -> Vec<u8> {
217    let mut out = Vec::new();
218    out.extend_from_slice(&encode_f64(nt.control_low, is_be));
219    out.extend_from_slice(&encode_f64(nt.control_high, is_be));
220    out.extend_from_slice(&encode_f64(nt.control_min_step, is_be));
221    out
222}
223
224fn encode_value_alarm(nt: &NtScalar, is_be: bool) -> Vec<u8> {
225    let mut out = Vec::new();
226    out.extend_from_slice(&encode_bool(nt.value_alarm_active));
227    out.extend_from_slice(&encode_f64(nt.value_alarm_low_alarm_limit, is_be));
228    out.extend_from_slice(&encode_f64(nt.value_alarm_low_warning_limit, is_be));
229    out.extend_from_slice(&encode_f64(nt.value_alarm_high_warning_limit, is_be));
230    out.extend_from_slice(&encode_f64(nt.value_alarm_high_alarm_limit, is_be));
231    out.extend_from_slice(&encode_i32(nt.value_alarm_low_alarm_severity, is_be));
232    out.extend_from_slice(&encode_i32(nt.value_alarm_low_warning_severity, is_be));
233    out.extend_from_slice(&encode_i32(nt.value_alarm_high_warning_severity, is_be));
234    out.extend_from_slice(&encode_i32(nt.value_alarm_high_alarm_severity, is_be));
235    out.push(nt.value_alarm_hysteresis);
236    out
237}
238
239fn encode_i32(value: i32, is_be: bool) -> Vec<u8> {
240    if is_be {
241        value.to_be_bytes().to_vec()
242    } else {
243        value.to_le_bytes().to_vec()
244    }
245}
246
247fn encode_i64(value: i64, is_be: bool) -> Vec<u8> {
248    if is_be {
249        value.to_be_bytes().to_vec()
250    } else {
251        value.to_le_bytes().to_vec()
252    }
253}
254
255fn encode_f64(value: f64, is_be: bool) -> Vec<u8> {
256    if is_be {
257        value.to_be_bytes().to_vec()
258    } else {
259        value.to_le_bytes().to_vec()
260    }
261}
262
263pub fn nt_scalar_desc(value: &ScalarValue) -> StructureDesc {
264    let value_type = match value {
265        ScalarValue::Bool(_) => FieldType::Scalar(TypeCode::Boolean),
266        ScalarValue::I8(_) => FieldType::Scalar(TypeCode::Int8),
267        ScalarValue::I16(_) => FieldType::Scalar(TypeCode::Int16),
268        ScalarValue::I32(_) => FieldType::Scalar(TypeCode::Int32),
269        ScalarValue::I64(_) => FieldType::Scalar(TypeCode::Int64),
270        ScalarValue::U8(_) => FieldType::Scalar(TypeCode::UInt8),
271        ScalarValue::U16(_) => FieldType::Scalar(TypeCode::UInt16),
272        ScalarValue::U32(_) => FieldType::Scalar(TypeCode::UInt32),
273        ScalarValue::U64(_) => FieldType::Scalar(TypeCode::UInt64),
274        ScalarValue::F32(_) => FieldType::Scalar(TypeCode::Float32),
275        ScalarValue::F64(_) => FieldType::Scalar(TypeCode::Float64),
276        ScalarValue::Str(_) => FieldType::String,
277    };
278
279    StructureDesc {
280        struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
281        fields: vec![
282            FieldDesc {
283                name: "value".to_string(),
284                field_type: value_type,
285            },
286            FieldDesc {
287                name: "alarm".to_string(),
288                field_type: FieldType::Structure(StructureDesc {
289                    struct_id: Some("alarm_t".to_string()),
290                    fields: vec![
291                        FieldDesc {
292                            name: "severity".to_string(),
293                            field_type: FieldType::Scalar(TypeCode::Int32),
294                        },
295                        FieldDesc {
296                            name: "status".to_string(),
297                            field_type: FieldType::Scalar(TypeCode::Int32),
298                        },
299                        FieldDesc {
300                            name: "message".to_string(),
301                            field_type: FieldType::String,
302                        },
303                    ],
304                }),
305            },
306            FieldDesc {
307                name: "timeStamp".to_string(),
308                field_type: FieldType::Structure(StructureDesc {
309                    struct_id: None,
310                    fields: vec![
311                        FieldDesc {
312                            name: "secondsPastEpoch".to_string(),
313                            field_type: FieldType::Scalar(TypeCode::Int64),
314                        },
315                        FieldDesc {
316                            name: "nanoseconds".to_string(),
317                            field_type: FieldType::Scalar(TypeCode::Int32),
318                        },
319                        FieldDesc {
320                            name: "userTag".to_string(),
321                            field_type: FieldType::Scalar(TypeCode::Int32),
322                        },
323                    ],
324                }),
325            },
326            FieldDesc {
327                name: "display".to_string(),
328                field_type: FieldType::Structure(StructureDesc {
329                    struct_id: None,
330                    fields: vec![
331                        FieldDesc {
332                            name: "limitLow".to_string(),
333                            field_type: FieldType::Scalar(TypeCode::Float64),
334                        },
335                        FieldDesc {
336                            name: "limitHigh".to_string(),
337                            field_type: FieldType::Scalar(TypeCode::Float64),
338                        },
339                        FieldDesc {
340                            name: "description".to_string(),
341                            field_type: FieldType::String,
342                        },
343                        FieldDesc {
344                            name: "units".to_string(),
345                            field_type: FieldType::String,
346                        },
347                        FieldDesc {
348                            name: "precision".to_string(),
349                            field_type: FieldType::Scalar(TypeCode::Int32),
350                        },
351                        FieldDesc {
352                            name: "form".to_string(),
353                            field_type: FieldType::Structure(StructureDesc {
354                                struct_id: Some("enum_t".to_string()),
355                                fields: vec![
356                                    FieldDesc {
357                                        name: "index".to_string(),
358                                        field_type: FieldType::Scalar(TypeCode::Int32),
359                                    },
360                                    FieldDesc {
361                                        name: "choices".to_string(),
362                                        field_type: FieldType::StringArray,
363                                    },
364                                ],
365                            }),
366                        },
367                    ],
368                }),
369            },
370            FieldDesc {
371                name: "control".to_string(),
372                field_type: FieldType::Structure(StructureDesc {
373                    struct_id: Some("control_t".to_string()),
374                    fields: vec![
375                        FieldDesc {
376                            name: "limitLow".to_string(),
377                            field_type: FieldType::Scalar(TypeCode::Float64),
378                        },
379                        FieldDesc {
380                            name: "limitHigh".to_string(),
381                            field_type: FieldType::Scalar(TypeCode::Float64),
382                        },
383                        FieldDesc {
384                            name: "minStep".to_string(),
385                            field_type: FieldType::Scalar(TypeCode::Float64),
386                        },
387                    ],
388                }),
389            },
390            FieldDesc {
391                name: "valueAlarm".to_string(),
392                field_type: FieldType::Structure(StructureDesc {
393                    struct_id: Some("valueAlarm_t".to_string()),
394                    fields: vec![
395                        FieldDesc {
396                            name: "active".to_string(),
397                            field_type: FieldType::Scalar(TypeCode::Boolean),
398                        },
399                        FieldDesc {
400                            name: "lowAlarmLimit".to_string(),
401                            field_type: FieldType::Scalar(TypeCode::Float64),
402                        },
403                        FieldDesc {
404                            name: "lowWarningLimit".to_string(),
405                            field_type: FieldType::Scalar(TypeCode::Float64),
406                        },
407                        FieldDesc {
408                            name: "highWarningLimit".to_string(),
409                            field_type: FieldType::Scalar(TypeCode::Float64),
410                        },
411                        FieldDesc {
412                            name: "highAlarmLimit".to_string(),
413                            field_type: FieldType::Scalar(TypeCode::Float64),
414                        },
415                        FieldDesc {
416                            name: "lowAlarmSeverity".to_string(),
417                            field_type: FieldType::Scalar(TypeCode::Int32),
418                        },
419                        FieldDesc {
420                            name: "lowWarningSeverity".to_string(),
421                            field_type: FieldType::Scalar(TypeCode::Int32),
422                        },
423                        FieldDesc {
424                            name: "highWarningSeverity".to_string(),
425                            field_type: FieldType::Scalar(TypeCode::Int32),
426                        },
427                        FieldDesc {
428                            name: "highAlarmSeverity".to_string(),
429                            field_type: FieldType::Scalar(TypeCode::Int32),
430                        },
431                        FieldDesc {
432                            name: "hysteresis".to_string(),
433                            field_type: FieldType::Scalar(TypeCode::UInt8),
434                        },
435                    ],
436                }),
437            },
438        ],
439    }
440}
441
442pub fn encode_nt_scalar_full(nt: &NtScalar, is_be: bool) -> Vec<u8> {
443    let mut out = Vec::new();
444    out.extend_from_slice(&encode_scalar_value(&nt.value, is_be));
445    out.extend_from_slice(&encode_alarm(nt, is_be));
446    out.extend_from_slice(&encode_timestamp(nt, is_be));
447    out.extend_from_slice(&encode_display(nt, is_be));
448    out.extend_from_slice(&encode_control(nt, is_be));
449    out.extend_from_slice(&encode_value_alarm(nt, is_be));
450    out
451}
452
453fn encode_structure_bitset(desc: &StructureDesc, is_be: bool) -> Vec<u8> {
454    let total_bits = 1 + count_structure_fields(desc);
455    let bitset_size = (total_bits + 7) / 8;
456    let mut bitset = vec![0u8; bitset_size];
457    for bit in 0..total_bits {
458        let byte_idx = bit / 8;
459        let bit_idx = bit % 8;
460        bitset[byte_idx] |= 1 << bit_idx;
461    }
462    let mut out = Vec::new();
463    out.extend_from_slice(&encode_size_pvd(bitset_size, is_be));
464    out.extend_from_slice(&bitset);
465    out
466}
467
468fn encode_structure_with_bitset(desc: &StructureDesc, nt: &NtScalar, is_be: bool) -> Vec<u8> {
469    let mut out = Vec::new();
470    out.extend_from_slice(&encode_structure_bitset(desc, is_be));
471    out.extend_from_slice(&encode_nt_scalar_full(nt, is_be));
472    out
473}
474
475pub fn encode_nt_scalar_bitset(nt: &NtScalar, is_be: bool) -> Vec<u8> {
476    let desc = nt_scalar_desc(&nt.value);
477    encode_structure_with_bitset(&desc, nt, is_be)
478}
479
480pub fn encode_nt_scalar_bitset_parts(nt: &NtScalar, is_be: bool) -> (Vec<u8>, Vec<u8>) {
481    let desc = nt_scalar_desc(&nt.value);
482    let bitset = encode_structure_bitset(&desc, is_be);
483    let values = encode_nt_scalar_full(nt, is_be);
484    (bitset, values)
485}
486
487fn alarm_desc() -> StructureDesc {
488    StructureDesc {
489        struct_id: Some("alarm_t".to_string()),
490        fields: vec![
491            FieldDesc {
492                name: "severity".to_string(),
493                field_type: FieldType::Scalar(TypeCode::Int32),
494            },
495            FieldDesc {
496                name: "status".to_string(),
497                field_type: FieldType::Scalar(TypeCode::Int32),
498            },
499            FieldDesc {
500                name: "message".to_string(),
501                field_type: FieldType::String,
502            },
503        ],
504    }
505}
506
507fn timestamp_desc() -> StructureDesc {
508    StructureDesc {
509        struct_id: Some("time_t".to_string()),
510        fields: vec![
511            FieldDesc {
512                name: "secondsPastEpoch".to_string(),
513                field_type: FieldType::Scalar(TypeCode::Int64),
514            },
515            FieldDesc {
516                name: "nanoseconds".to_string(),
517                field_type: FieldType::Scalar(TypeCode::Int32),
518            },
519            FieldDesc {
520                name: "userTag".to_string(),
521                field_type: FieldType::Scalar(TypeCode::Int32),
522            },
523        ],
524    }
525}
526
527fn display_desc() -> StructureDesc {
528    StructureDesc {
529        struct_id: Some("display_t".to_string()),
530        fields: vec![
531            FieldDesc {
532                name: "limitLow".to_string(),
533                field_type: FieldType::Scalar(TypeCode::Float64),
534            },
535            FieldDesc {
536                name: "limitHigh".to_string(),
537                field_type: FieldType::Scalar(TypeCode::Float64),
538            },
539            FieldDesc {
540                name: "description".to_string(),
541                field_type: FieldType::String,
542            },
543            FieldDesc {
544                name: "units".to_string(),
545                field_type: FieldType::String,
546            },
547            FieldDesc {
548                name: "precision".to_string(),
549                field_type: FieldType::Scalar(TypeCode::Int32),
550            },
551        ],
552    }
553}
554
555fn scalar_array_field_type(value: &ScalarArrayValue) -> FieldType {
556    match value {
557        ScalarArrayValue::Bool(_) => FieldType::ScalarArray(TypeCode::Boolean),
558        ScalarArrayValue::I8(_) => FieldType::ScalarArray(TypeCode::Int8),
559        ScalarArrayValue::I16(_) => FieldType::ScalarArray(TypeCode::Int16),
560        ScalarArrayValue::I32(_) => FieldType::ScalarArray(TypeCode::Int32),
561        ScalarArrayValue::I64(_) => FieldType::ScalarArray(TypeCode::Int64),
562        ScalarArrayValue::U8(_) => FieldType::ScalarArray(TypeCode::UInt8),
563        ScalarArrayValue::U16(_) => FieldType::ScalarArray(TypeCode::UInt16),
564        ScalarArrayValue::U32(_) => FieldType::ScalarArray(TypeCode::UInt32),
565        ScalarArrayValue::U64(_) => FieldType::ScalarArray(TypeCode::UInt64),
566        ScalarArrayValue::F32(_) => FieldType::ScalarArray(TypeCode::Float32),
567        ScalarArrayValue::F64(_) => FieldType::ScalarArray(TypeCode::Float64),
568        ScalarArrayValue::Str(_) => FieldType::StringArray,
569    }
570}
571
572fn encode_scalar_array_value_pvd(value: &ScalarArrayValue, is_be: bool) -> Vec<u8> {
573    let mut out = Vec::new();
574    match value {
575        ScalarArrayValue::Bool(v) => {
576            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
577            for i in v {
578                out.push(if *i { 1 } else { 0 });
579            }
580        }
581        ScalarArrayValue::I8(v) => {
582            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
583            for i in v {
584                out.push(*i as u8);
585            }
586        }
587        ScalarArrayValue::I16(v) => {
588            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
589            for i in v {
590                let b = if is_be {
591                    i.to_be_bytes()
592                } else {
593                    i.to_le_bytes()
594                };
595                out.extend_from_slice(&b);
596            }
597        }
598        ScalarArrayValue::I32(v) => {
599            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
600            for i in v {
601                out.extend_from_slice(&encode_i32(*i, is_be));
602            }
603        }
604        ScalarArrayValue::I64(v) => {
605            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
606            for i in v {
607                out.extend_from_slice(&encode_i64(*i, is_be));
608            }
609        }
610        ScalarArrayValue::U8(v) => {
611            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
612            out.extend_from_slice(v);
613        }
614        ScalarArrayValue::U16(v) => {
615            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
616            for i in v {
617                let b = if is_be {
618                    i.to_be_bytes()
619                } else {
620                    i.to_le_bytes()
621                };
622                out.extend_from_slice(&b);
623            }
624        }
625        ScalarArrayValue::U32(v) => {
626            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
627            for i in v {
628                let b = if is_be {
629                    i.to_be_bytes()
630                } else {
631                    i.to_le_bytes()
632                };
633                out.extend_from_slice(&b);
634            }
635        }
636        ScalarArrayValue::U64(v) => {
637            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
638            for i in v {
639                let b = if is_be {
640                    i.to_be_bytes()
641                } else {
642                    i.to_le_bytes()
643                };
644                out.extend_from_slice(&b);
645            }
646        }
647        ScalarArrayValue::F32(v) => {
648            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
649            for i in v {
650                let b = if is_be {
651                    i.to_be_bytes()
652                } else {
653                    i.to_le_bytes()
654                };
655                out.extend_from_slice(&b);
656            }
657        }
658        ScalarArrayValue::F64(v) => {
659            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
660            for i in v {
661                out.extend_from_slice(&encode_f64(*i, is_be));
662            }
663        }
664        ScalarArrayValue::Str(v) => {
665            out.extend_from_slice(&encode_string_array(v, is_be));
666        }
667    }
668    out
669}
670
671fn encode_nt_alarm(alarm: &NtAlarm, is_be: bool) -> Vec<u8> {
672    let mut out = Vec::new();
673    out.extend_from_slice(&encode_i32(alarm.severity, is_be));
674    out.extend_from_slice(&encode_i32(alarm.status, is_be));
675    out.extend_from_slice(&encode_string_pvd(&alarm.message, is_be));
676    out
677}
678
679fn encode_nt_timestamp(ts: &NtTimeStamp, is_be: bool) -> Vec<u8> {
680    let mut out = Vec::new();
681    out.extend_from_slice(&encode_i64(ts.seconds_past_epoch, is_be));
682    out.extend_from_slice(&encode_i32(ts.nanoseconds, is_be));
683    out.extend_from_slice(&encode_i32(ts.user_tag, is_be));
684    out
685}
686
687fn encode_nt_display(display: &NtDisplay, is_be: bool) -> Vec<u8> {
688    let mut out = Vec::new();
689    out.extend_from_slice(&encode_f64(display.limit_low, is_be));
690    out.extend_from_slice(&encode_f64(display.limit_high, is_be));
691    out.extend_from_slice(&encode_string_pvd(&display.description, is_be));
692    out.extend_from_slice(&encode_string_pvd(&display.units, is_be));
693    out.extend_from_slice(&encode_i32(display.precision, is_be));
694    out
695}
696
697pub fn nt_scalar_array_desc(value: &ScalarArrayValue) -> StructureDesc {
698    StructureDesc {
699        struct_id: Some("epics:nt/NTScalarArray:1.0".to_string()),
700        fields: vec![
701            FieldDesc {
702                name: "value".to_string(),
703                field_type: scalar_array_field_type(value),
704            },
705            FieldDesc {
706                name: "alarm".to_string(),
707                field_type: FieldType::Structure(alarm_desc()),
708            },
709            FieldDesc {
710                name: "timeStamp".to_string(),
711                field_type: FieldType::Structure(timestamp_desc()),
712            },
713            FieldDesc {
714                name: "display".to_string(),
715                field_type: FieldType::Structure(display_desc()),
716            },
717            FieldDesc {
718                name: "control".to_string(),
719                field_type: FieldType::Structure(StructureDesc {
720                    struct_id: Some("control_t".to_string()),
721                    fields: vec![
722                        FieldDesc {
723                            name: "limitLow".to_string(),
724                            field_type: FieldType::Scalar(TypeCode::Float64),
725                        },
726                        FieldDesc {
727                            name: "limitHigh".to_string(),
728                            field_type: FieldType::Scalar(TypeCode::Float64),
729                        },
730                        FieldDesc {
731                            name: "minStep".to_string(),
732                            field_type: FieldType::Scalar(TypeCode::Float64),
733                        },
734                    ],
735                }),
736            },
737        ],
738    }
739}
740
741pub fn encode_nt_scalar_array_full(nt: &NtScalarArray, is_be: bool) -> Vec<u8> {
742    let mut out = Vec::new();
743    out.extend_from_slice(&encode_scalar_array_value_pvd(&nt.value, is_be));
744    out.extend_from_slice(&encode_nt_alarm(&nt.alarm, is_be));
745    out.extend_from_slice(&encode_nt_timestamp(&nt.time_stamp, is_be));
746    out.extend_from_slice(&encode_nt_display(&nt.display, is_be));
747    out.extend_from_slice(&encode_f64(nt.control.limit_low, is_be));
748    out.extend_from_slice(&encode_f64(nt.control.limit_high, is_be));
749    out.extend_from_slice(&encode_f64(nt.control.min_step, is_be));
750    out
751}
752
753pub fn nt_table_desc(nt: &NtTable) -> StructureDesc {
754    let mut value_fields: Vec<FieldDesc> = Vec::new();
755    for col in &nt.columns {
756        value_fields.push(FieldDesc {
757            name: col.name.clone(),
758            field_type: scalar_array_field_type(&col.values),
759        });
760    }
761    StructureDesc {
762        struct_id: Some("epics:nt/NTTable:1.0".to_string()),
763        fields: vec![
764            FieldDesc {
765                name: "labels".to_string(),
766                field_type: FieldType::StringArray,
767            },
768            FieldDesc {
769                name: "value".to_string(),
770                field_type: FieldType::Structure(StructureDesc {
771                    struct_id: None,
772                    fields: value_fields,
773                }),
774            },
775        ],
776    }
777}
778
779pub fn encode_nt_table_full(nt: &NtTable, is_be: bool) -> Vec<u8> {
780    let mut out = Vec::new();
781    out.extend_from_slice(&encode_string_array(&nt.labels, is_be));
782    for NtTableColumn { values, .. } in &nt.columns {
783        out.extend_from_slice(&encode_scalar_array_value_pvd(values, is_be));
784    }
785    out
786}
787
788fn nt_ndarray_value_union_fields() -> Vec<FieldDesc> {
789    vec![
790        FieldDesc {
791            name: "booleanValue".to_string(),
792            field_type: FieldType::ScalarArray(TypeCode::Boolean),
793        },
794        FieldDesc {
795            name: "byteValue".to_string(),
796            field_type: FieldType::ScalarArray(TypeCode::Int8),
797        },
798        FieldDesc {
799            name: "shortValue".to_string(),
800            field_type: FieldType::ScalarArray(TypeCode::Int16),
801        },
802        FieldDesc {
803            name: "intValue".to_string(),
804            field_type: FieldType::ScalarArray(TypeCode::Int32),
805        },
806        FieldDesc {
807            name: "longValue".to_string(),
808            field_type: FieldType::ScalarArray(TypeCode::Int64),
809        },
810        FieldDesc {
811            name: "ubyteValue".to_string(),
812            field_type: FieldType::ScalarArray(TypeCode::UInt8),
813        },
814        FieldDesc {
815            name: "ushortValue".to_string(),
816            field_type: FieldType::ScalarArray(TypeCode::UInt16),
817        },
818        FieldDesc {
819            name: "uintValue".to_string(),
820            field_type: FieldType::ScalarArray(TypeCode::UInt32),
821        },
822        FieldDesc {
823            name: "ulongValue".to_string(),
824            field_type: FieldType::ScalarArray(TypeCode::UInt64),
825        },
826        FieldDesc {
827            name: "floatValue".to_string(),
828            field_type: FieldType::ScalarArray(TypeCode::Float32),
829        },
830        FieldDesc {
831            name: "doubleValue".to_string(),
832            field_type: FieldType::ScalarArray(TypeCode::Float64),
833        },
834        FieldDesc {
835            name: "stringValue".to_string(),
836            field_type: FieldType::StringArray,
837        },
838    ]
839}
840
841fn ndarray_union_index(value: &ScalarArrayValue) -> usize {
842    match value {
843        ScalarArrayValue::Bool(_) => 0,
844        ScalarArrayValue::I8(_) => 1,
845        ScalarArrayValue::I16(_) => 2,
846        ScalarArrayValue::I32(_) => 3,
847        ScalarArrayValue::I64(_) => 4,
848        ScalarArrayValue::U8(_) => 5,
849        ScalarArrayValue::U16(_) => 6,
850        ScalarArrayValue::U32(_) => 7,
851        ScalarArrayValue::U64(_) => 8,
852        ScalarArrayValue::F32(_) => 9,
853        ScalarArrayValue::F64(_) => 10,
854        ScalarArrayValue::Str(_) => 11,
855    }
856}
857
858fn encode_ndarray_union(value: &ScalarArrayValue, is_be: bool) -> Vec<u8> {
859    let mut out = Vec::new();
860    out.extend_from_slice(&encode_size_pvd(ndarray_union_index(value), is_be));
861    out.extend_from_slice(&encode_scalar_array_value_pvd(value, is_be));
862    out
863}
864
865fn encode_codec_parameters(
866    parameters: &std::collections::HashMap<String, String>,
867    is_be: bool,
868) -> Vec<u8> {
869    if parameters.is_empty() {
870        return vec![0xFF];
871    }
872    let mut out = Vec::new();
873    out.push(0x80);
874    let mut fields = Vec::new();
875    for key in parameters.keys() {
876        fields.push(FieldDesc {
877            name: key.clone(),
878            field_type: FieldType::String,
879        });
880    }
881    let desc = StructureDesc {
882        struct_id: None,
883        fields,
884    };
885    out.extend_from_slice(&encode_structure_desc(&desc, is_be));
886    for value in parameters.values() {
887        out.extend_from_slice(&encode_string_pvd(value, is_be));
888    }
889    out
890}
891
892pub fn nt_ndarray_desc_default() -> StructureDesc {
893    nt_ndarray_desc(&NtNdArray::empty())
894}
895
896pub fn nt_ndarray_desc(_nt: &NtNdArray) -> StructureDesc {
897    StructureDesc {
898        struct_id: Some("epics:nt/NTNDArray:1.0".to_string()),
899        fields: vec![
900            FieldDesc {
901                name: "value".to_string(),
902                field_type: FieldType::Union(nt_ndarray_value_union_fields()),
903            },
904            FieldDesc {
905                name: "codec".to_string(),
906                field_type: FieldType::Structure(StructureDesc {
907                    struct_id: Some("codec_t".to_string()),
908                    fields: vec![
909                        FieldDesc {
910                            name: "name".to_string(),
911                            field_type: FieldType::String,
912                        },
913                        FieldDesc {
914                            name: "parameters".to_string(),
915                            field_type: FieldType::Variant,
916                        },
917                    ],
918                }),
919            },
920            FieldDesc {
921                name: "compressedSize".to_string(),
922                field_type: FieldType::Scalar(TypeCode::Int64),
923            },
924            FieldDesc {
925                name: "uncompressedSize".to_string(),
926                field_type: FieldType::Scalar(TypeCode::Int64),
927            },
928            FieldDesc {
929                name: "dimension".to_string(),
930                field_type: FieldType::StructureArray(StructureDesc {
931                    struct_id: Some("dimension_t".to_string()),
932                    fields: vec![
933                        FieldDesc {
934                            name: "size".to_string(),
935                            field_type: FieldType::Scalar(TypeCode::Int32),
936                        },
937                        FieldDesc {
938                            name: "offset".to_string(),
939                            field_type: FieldType::Scalar(TypeCode::Int32),
940                        },
941                        FieldDesc {
942                            name: "fullSize".to_string(),
943                            field_type: FieldType::Scalar(TypeCode::Int32),
944                        },
945                        FieldDesc {
946                            name: "binning".to_string(),
947                            field_type: FieldType::Scalar(TypeCode::Int32),
948                        },
949                        FieldDesc {
950                            name: "reverse".to_string(),
951                            field_type: FieldType::Scalar(TypeCode::Boolean),
952                        },
953                    ],
954                }),
955            },
956            FieldDesc {
957                name: "uniqueId".to_string(),
958                field_type: FieldType::Scalar(TypeCode::Int32),
959            },
960            FieldDesc {
961                name: "dataTimeStamp".to_string(),
962                field_type: FieldType::Structure(timestamp_desc()),
963            },
964            FieldDesc {
965                name: "attribute".to_string(),
966                field_type: FieldType::StructureArray(StructureDesc {
967                    struct_id: Some("NTAttribute".to_string()),
968                    fields: vec![
969                        FieldDesc {
970                            name: "name".to_string(),
971                            field_type: FieldType::String,
972                        },
973                        FieldDesc {
974                            name: "value".to_string(),
975                            field_type: FieldType::Variant,
976                        },
977                        FieldDesc {
978                            name: "descriptor".to_string(),
979                            field_type: FieldType::String,
980                        },
981                        FieldDesc {
982                            name: "sourceType".to_string(),
983                            field_type: FieldType::Scalar(TypeCode::Int32),
984                        },
985                        FieldDesc {
986                            name: "source".to_string(),
987                            field_type: FieldType::String,
988                        },
989                    ],
990                }),
991            },
992            FieldDesc {
993                name: "descriptor".to_string(),
994                field_type: FieldType::String,
995            },
996            FieldDesc {
997                name: "alarm".to_string(),
998                field_type: FieldType::Structure(alarm_desc()),
999            },
1000            FieldDesc {
1001                name: "timeStamp".to_string(),
1002                field_type: FieldType::Structure(timestamp_desc()),
1003            },
1004            FieldDesc {
1005                name: "display".to_string(),
1006                field_type: FieldType::Structure(display_desc()),
1007            },
1008        ],
1009    }
1010}
1011
1012fn encode_attribute_variant(attr: &NtAttribute, is_be: bool) -> Vec<u8> {
1013    match &attr.value {
1014        ScalarValue::Bool(v) => {
1015            let mut out = vec![TypeCode::Boolean as u8];
1016            out.push(if *v { 1 } else { 0 });
1017            out
1018        }
1019        ScalarValue::I8(v) => {
1020            let mut out = vec![TypeCode::Int8 as u8];
1021            out.push(*v as u8);
1022            out
1023        }
1024        ScalarValue::I16(v) => {
1025            let mut out = vec![TypeCode::Int16 as u8];
1026            out.extend_from_slice(&if is_be {
1027                v.to_be_bytes().to_vec()
1028            } else {
1029                v.to_le_bytes().to_vec()
1030            });
1031            out
1032        }
1033        ScalarValue::I32(v) => {
1034            let mut out = vec![TypeCode::Int32 as u8];
1035            out.extend_from_slice(&encode_i32(*v, is_be));
1036            out
1037        }
1038        ScalarValue::I64(v) => {
1039            let mut out = vec![TypeCode::Int64 as u8];
1040            out.extend_from_slice(&encode_i64(*v, is_be));
1041            out
1042        }
1043        ScalarValue::U8(v) => {
1044            let mut out = vec![TypeCode::UInt8 as u8];
1045            out.push(*v);
1046            out
1047        }
1048        ScalarValue::U16(v) => {
1049            let mut out = vec![TypeCode::UInt16 as u8];
1050            out.extend_from_slice(&if is_be {
1051                v.to_be_bytes().to_vec()
1052            } else {
1053                v.to_le_bytes().to_vec()
1054            });
1055            out
1056        }
1057        ScalarValue::U32(v) => {
1058            let mut out = vec![TypeCode::UInt32 as u8];
1059            out.extend_from_slice(&if is_be {
1060                v.to_be_bytes().to_vec()
1061            } else {
1062                v.to_le_bytes().to_vec()
1063            });
1064            out
1065        }
1066        ScalarValue::U64(v) => {
1067            let mut out = vec![TypeCode::UInt64 as u8];
1068            out.extend_from_slice(&if is_be {
1069                v.to_be_bytes().to_vec()
1070            } else {
1071                v.to_le_bytes().to_vec()
1072            });
1073            out
1074        }
1075        ScalarValue::F32(v) => {
1076            let mut out = vec![TypeCode::Float32 as u8];
1077            out.extend_from_slice(&if is_be {
1078                v.to_be_bytes().to_vec()
1079            } else {
1080                v.to_le_bytes().to_vec()
1081            });
1082            out
1083        }
1084        ScalarValue::F64(v) => {
1085            let mut out = vec![TypeCode::Float64 as u8];
1086            out.extend_from_slice(&encode_f64(*v, is_be));
1087            out
1088        }
1089        ScalarValue::Str(v) => {
1090            let mut out = vec![TypeCode::String as u8];
1091            out.extend_from_slice(&encode_string_pvd(v, is_be));
1092            out
1093        }
1094    }
1095}
1096
1097pub fn encode_nt_ndarray_full(nt: &NtNdArray, is_be: bool) -> Vec<u8> {
1098    let mut out = Vec::new();
1099    out.extend_from_slice(&encode_ndarray_union(&nt.value, is_be));
1100    out.extend_from_slice(&encode_string_pvd(&nt.codec.name, is_be));
1101    out.extend_from_slice(&encode_codec_parameters(&nt.codec.parameters, is_be));
1102    out.extend_from_slice(&encode_i64(nt.compressed_size, is_be));
1103    out.extend_from_slice(&encode_i64(nt.uncompressed_size, is_be));
1104    out.extend_from_slice(&encode_size_pvd(nt.dimension.len(), is_be));
1105    for NdDimension {
1106        size,
1107        offset,
1108        full_size,
1109        binning,
1110        reverse,
1111    } in &nt.dimension
1112    {
1113        out.push(1); // non-null element indicator
1114        out.extend_from_slice(&encode_i32(*size, is_be));
1115        out.extend_from_slice(&encode_i32(*offset, is_be));
1116        out.extend_from_slice(&encode_i32(*full_size, is_be));
1117        out.extend_from_slice(&encode_i32(*binning, is_be));
1118        out.push(if *reverse { 1 } else { 0 });
1119    }
1120    out.extend_from_slice(&encode_i32(nt.unique_id, is_be));
1121    out.extend_from_slice(&encode_nt_timestamp(&nt.data_time_stamp, is_be));
1122    out.extend_from_slice(&encode_size_pvd(nt.attribute.len(), is_be));
1123    for attr in &nt.attribute {
1124        out.push(1); // non-null element indicator
1125        out.extend_from_slice(&encode_string_pvd(&attr.name, is_be));
1126        out.extend_from_slice(&encode_attribute_variant(attr, is_be));
1127        out.extend_from_slice(&encode_string_pvd(&attr.descriptor, is_be));
1128        out.extend_from_slice(&encode_i32(attr.source_type, is_be));
1129        out.extend_from_slice(&encode_string_pvd(&attr.source, is_be));
1130    }
1131    out.extend_from_slice(&encode_string_pvd(
1132        nt.descriptor.as_deref().unwrap_or(""),
1133        is_be,
1134    ));
1135    out.extend_from_slice(&encode_nt_alarm(
1136        nt.alarm.as_ref().unwrap_or(&NtAlarm::default()),
1137        is_be,
1138    ));
1139    out.extend_from_slice(&encode_nt_timestamp(
1140        nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
1141        is_be,
1142    ));
1143    out.extend_from_slice(&encode_nt_display(
1144        nt.display.as_ref().unwrap_or(&NtDisplay::default()),
1145        is_be,
1146    ));
1147    out
1148}
1149
1150// ---------------------------------------------------------------------------
1151// NTEnum descriptor & encoder
1152// ---------------------------------------------------------------------------
1153
1154pub fn nt_enum_desc() -> StructureDesc {
1155    StructureDesc {
1156        struct_id: Some("epics:nt/NTEnum:1.0".to_string()),
1157        fields: vec![
1158            FieldDesc {
1159                name: "value".to_string(),
1160                field_type: FieldType::Structure(StructureDesc {
1161                    struct_id: Some("enum_t".to_string()),
1162                    fields: vec![
1163                        FieldDesc {
1164                            name: "index".to_string(),
1165                            field_type: FieldType::Scalar(TypeCode::Int32),
1166                        },
1167                        FieldDesc {
1168                            name: "choices".to_string(),
1169                            field_type: FieldType::StringArray,
1170                        },
1171                    ],
1172                }),
1173            },
1174            FieldDesc {
1175                name: "alarm".to_string(),
1176                field_type: FieldType::Structure(alarm_desc()),
1177            },
1178            FieldDesc {
1179                name: "timeStamp".to_string(),
1180                field_type: FieldType::Structure(timestamp_desc()),
1181            },
1182        ],
1183    }
1184}
1185
1186pub fn encode_nt_enum_full(nt: &NtEnum, is_be: bool) -> Vec<u8> {
1187    let mut out = Vec::new();
1188    // value — enum_t { index, choices }
1189    out.extend_from_slice(&encode_enum(nt.index, &nt.choices, is_be));
1190    // alarm
1191    out.extend_from_slice(&encode_nt_alarm(&nt.alarm, is_be));
1192    // timeStamp
1193    out.extend_from_slice(&encode_nt_timestamp(&nt.time_stamp, is_be));
1194    out
1195}
1196
1197// ---------------------------------------------------------------------------
1198// PvValue (generic recursive) descriptor & encoder
1199// ---------------------------------------------------------------------------
1200
1201fn scalar_value_type_code(v: &ScalarValue) -> TypeCode {
1202    match v {
1203        ScalarValue::Bool(_) => TypeCode::Boolean,
1204        ScalarValue::I8(_) => TypeCode::Int8,
1205        ScalarValue::I16(_) => TypeCode::Int16,
1206        ScalarValue::I32(_) => TypeCode::Int32,
1207        ScalarValue::I64(_) => TypeCode::Int64,
1208        ScalarValue::U8(_) => TypeCode::UInt8,
1209        ScalarValue::U16(_) => TypeCode::UInt16,
1210        ScalarValue::U32(_) => TypeCode::UInt32,
1211        ScalarValue::U64(_) => TypeCode::UInt64,
1212        ScalarValue::F32(_) => TypeCode::Float32,
1213        ScalarValue::F64(_) => TypeCode::Float64,
1214        ScalarValue::Str(_) => TypeCode::String,
1215    }
1216}
1217
1218/// Build a [`StructureDesc`] from a [`PvValue::Structure`].
1219pub fn pv_value_desc(struct_id: &str, fields: &[(String, PvValue)]) -> StructureDesc {
1220    StructureDesc {
1221        struct_id: if struct_id.is_empty() {
1222            None
1223        } else {
1224            Some(struct_id.to_string())
1225        },
1226        fields: fields
1227            .iter()
1228            .map(|(name, val)| FieldDesc {
1229                name: name.clone(),
1230                field_type: pv_value_field_type(val),
1231            })
1232            .collect(),
1233    }
1234}
1235
1236fn pv_value_field_type(val: &PvValue) -> FieldType {
1237    match val {
1238        PvValue::Scalar(sv) => {
1239            if matches!(sv, ScalarValue::Str(_)) {
1240                FieldType::String
1241            } else {
1242                FieldType::Scalar(scalar_value_type_code(sv))
1243            }
1244        }
1245        PvValue::ScalarArray(sa) => scalar_array_field_type(sa),
1246        PvValue::Structure { struct_id, fields } => {
1247            FieldType::Structure(pv_value_desc(struct_id, fields))
1248        }
1249    }
1250}
1251
1252/// Encode a [`PvValue`] tree to PVA wire bytes (values only, no descriptor).
1253pub fn encode_pv_value(val: &PvValue, is_be: bool) -> Vec<u8> {
1254    match val {
1255        PvValue::Scalar(sv) => encode_scalar_value(sv, is_be),
1256        PvValue::ScalarArray(sa) => encode_scalar_array_value_pvd(sa, is_be),
1257        PvValue::Structure { fields, .. } => {
1258            let mut out = Vec::new();
1259            for (_, v) in fields {
1260                out.extend_from_slice(&encode_pv_value(v, is_be));
1261            }
1262            out
1263        }
1264    }
1265}
1266
1267pub fn nt_payload_desc(payload: &NtPayload) -> StructureDesc {
1268    match payload {
1269        NtPayload::Scalar(nt) => nt_scalar_desc(&nt.value),
1270        NtPayload::ScalarArray(nt) => nt_scalar_array_desc(&nt.value),
1271        NtPayload::Table(nt) => nt_table_desc(nt),
1272        NtPayload::NdArray(nt) => nt_ndarray_desc(nt),
1273        NtPayload::Enum(_) => nt_enum_desc(),
1274        NtPayload::Generic { struct_id, fields } => pv_value_desc(struct_id, fields),
1275    }
1276}
1277
1278pub fn encode_nt_payload_full(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1279    match payload {
1280        NtPayload::Scalar(nt) => encode_nt_scalar_full(nt, is_be),
1281        NtPayload::ScalarArray(nt) => encode_nt_scalar_array_full(nt, is_be),
1282        NtPayload::Table(nt) => encode_nt_table_full(nt, is_be),
1283        NtPayload::NdArray(nt) => encode_nt_ndarray_full(nt, is_be),
1284        NtPayload::Enum(nt) => encode_nt_enum_full(nt, is_be),
1285        NtPayload::Generic { fields, .. } => {
1286            let mut out = Vec::new();
1287            for (_, v) in fields {
1288                out.extend_from_slice(&encode_pv_value(v, is_be));
1289            }
1290            out
1291        }
1292    }
1293}
1294
1295pub fn encode_nt_payload_bitset(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1296    let desc = nt_payload_desc(payload);
1297    let mut out = Vec::new();
1298    out.extend_from_slice(&encode_structure_bitset(&desc, is_be));
1299    out.extend_from_slice(&encode_nt_payload_full(payload, is_be));
1300    out
1301}
1302
1303pub fn encode_nt_payload_bitset_parts(payload: &NtPayload, is_be: bool) -> (Vec<u8>, Vec<u8>) {
1304    let desc = nt_payload_desc(payload);
1305    (
1306        encode_structure_bitset(&desc, is_be),
1307        encode_nt_payload_full(payload, is_be),
1308    )
1309}
1310
1311// ---------------------------------------------------------------------------
1312// Generic DecodedValue → wire bytes encoder
1313// ---------------------------------------------------------------------------
1314
1315use crate::spvd_decode::DecodedValue;
1316
1317/// Encode a `DecodedValue` back to PVA wire bytes.
1318pub fn encode_decoded_value(val: &DecodedValue, is_be: bool) -> Vec<u8> {
1319    match val {
1320        DecodedValue::Null => Vec::new(),
1321        DecodedValue::Boolean(v) => vec![if *v { 1 } else { 0 }],
1322        DecodedValue::Int8(v) => vec![*v as u8],
1323        DecodedValue::Int16(v) => {
1324            if is_be {
1325                v.to_be_bytes().to_vec()
1326            } else {
1327                v.to_le_bytes().to_vec()
1328            }
1329        }
1330        DecodedValue::Int32(v) => encode_i32(*v, is_be),
1331        DecodedValue::Int64(v) => encode_i64(*v, is_be),
1332        DecodedValue::UInt8(v) => vec![*v],
1333        DecodedValue::UInt16(v) => {
1334            if is_be {
1335                v.to_be_bytes().to_vec()
1336            } else {
1337                v.to_le_bytes().to_vec()
1338            }
1339        }
1340        DecodedValue::UInt32(v) => {
1341            if is_be {
1342                v.to_be_bytes().to_vec()
1343            } else {
1344                v.to_le_bytes().to_vec()
1345            }
1346        }
1347        DecodedValue::UInt64(v) => {
1348            if is_be {
1349                v.to_be_bytes().to_vec()
1350            } else {
1351                v.to_le_bytes().to_vec()
1352            }
1353        }
1354        DecodedValue::Float32(v) => {
1355            if is_be {
1356                v.to_be_bytes().to_vec()
1357            } else {
1358                v.to_le_bytes().to_vec()
1359            }
1360        }
1361        DecodedValue::Float64(v) => encode_f64(*v, is_be),
1362        DecodedValue::String(v) => encode_string_pvd(v, is_be),
1363        DecodedValue::Array(arr) => {
1364            let mut out = encode_size_pvd(arr.len(), is_be);
1365            for item in arr {
1366                out.extend_from_slice(&encode_decoded_value(item, is_be));
1367            }
1368            out
1369        }
1370        DecodedValue::Structure(fields) => {
1371            let mut out = Vec::new();
1372            for (_name, value) in fields {
1373                out.extend_from_slice(&encode_decoded_value(value, is_be));
1374            }
1375            out
1376        }
1377        DecodedValue::Raw(data) => data.clone(),
1378    }
1379}
1380
1381// ---------------------------------------------------------------------------
1382// pvRequest parsing & descriptor filtering
1383// ---------------------------------------------------------------------------
1384
1385/// Parse a pvRequest structure from the INIT body bytes and return the list
1386/// of requested field paths.
1387///
1388/// Paths are returned as dot-separated strings (e.g. `"value"`,
1389/// `"alarm.severity"`). An empty inner `field {}` structure, or a body that
1390/// cannot be parsed, is reported as `None`, meaning "return all fields" (no
1391/// filtering).
1392///
1393/// A field whose inner pvRequest sub-structure is itself empty selects the
1394/// whole sub-tree rooted at that field (so `field(alarm)` → `["alarm"]`
1395/// selects the entire `alarm` structure). Non-empty sub-structures expand
1396/// into one entry per leaf path (so `field(alarm{severity}) →
1397/// ["alarm.severity"]`).
1398pub fn decode_pv_request_fields(body: &[u8], is_be: bool) -> Option<Vec<String>> {
1399    if body.is_empty() {
1400        return None;
1401    }
1402    let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1403    let desc = decoder.parse_introspection(body)?;
1404    for field in &desc.fields {
1405        if field.name == "field" {
1406            if let FieldType::Structure(ref inner) = field.field_type {
1407                if inner.fields.is_empty() {
1408                    return None;
1409                }
1410                let mut paths = Vec::new();
1411                collect_pv_request_paths(inner, "", &mut paths);
1412                if paths.is_empty() {
1413                    return None;
1414                }
1415                return Some(paths);
1416            }
1417        }
1418    }
1419    None
1420}
1421
1422fn collect_pv_request_paths(desc: &StructureDesc, prefix: &str, out: &mut Vec<String>) {
1423    for field in &desc.fields {
1424        let joined = if prefix.is_empty() {
1425            field.name.clone()
1426        } else {
1427            format!("{}.{}", prefix, field.name)
1428        };
1429        match &field.field_type {
1430            FieldType::Structure(nested) if !nested.fields.is_empty() => {
1431                collect_pv_request_paths(nested, &joined, out);
1432            }
1433            _ => out.push(joined),
1434        }
1435    }
1436}
1437
1438/// Filter a [`StructureDesc`] to include only the listed field paths.
1439///
1440/// Paths may be dot-separated to descend into nested structures (e.g.
1441/// `"alarm.severity"`). A bare name selects the entire sub-tree rooted at
1442/// that field. Unknown paths are silently dropped. If `requested` is empty
1443/// the original descriptor is returned unchanged.
1444pub fn filter_structure_desc(desc: &StructureDesc, requested: &[String]) -> StructureDesc {
1445    if requested.is_empty() {
1446        return desc.clone();
1447    }
1448    let tree = build_path_tree(requested);
1449    prune_structure(desc, &tree)
1450}
1451
1452#[derive(Default, Debug, Clone)]
1453struct PathNode {
1454    /// When true, the whole sub-tree rooted at this node is selected and
1455    /// `children` should be ignored.
1456    select_all: bool,
1457    /// Insertion-ordered children. Using a Vec of pairs rather than a map
1458    /// preserves the field order implied by the caller, which matters for
1459    /// the PVA wire format (introspection field order is significant).
1460    children: Vec<(String, PathNode)>,
1461}
1462
1463impl PathNode {
1464    fn child_mut(&mut self, name: &str) -> &mut PathNode {
1465        if let Some(idx) = self.children.iter().position(|(n, _)| n == name) {
1466            return &mut self.children[idx].1;
1467        }
1468        self.children.push((name.to_string(), PathNode::default()));
1469        &mut self.children.last_mut().unwrap().1
1470    }
1471
1472    fn child(&self, name: &str) -> Option<&PathNode> {
1473        self.children
1474            .iter()
1475            .find(|(n, _)| n == name)
1476            .map(|(_, c)| c)
1477    }
1478}
1479
1480fn build_path_tree(paths: &[String]) -> PathNode {
1481    let mut root = PathNode::default();
1482    for p in paths {
1483        let parts: Vec<&str> = p.split('.').filter(|s| !s.is_empty()).collect();
1484        if parts.is_empty() {
1485            continue;
1486        }
1487        let mut node = &mut root;
1488        for (i, part) in parts.iter().enumerate() {
1489            let is_last = i == parts.len() - 1;
1490            let child = node.child_mut(part);
1491            if is_last {
1492                child.select_all = true;
1493                child.children.clear();
1494            }
1495            node = child;
1496        }
1497    }
1498    root
1499}
1500
1501fn prune_structure(desc: &StructureDesc, node: &PathNode) -> StructureDesc {
1502    if node.select_all {
1503        return desc.clone();
1504    }
1505    let mut fields = Vec::new();
1506    for field in &desc.fields {
1507        let Some(child) = node.child(&field.name) else {
1508            continue;
1509        };
1510        if child.select_all {
1511            fields.push(field.clone());
1512            continue;
1513        }
1514        match &field.field_type {
1515            FieldType::Structure(inner) => {
1516                let pruned = prune_structure(inner, child);
1517                if !pruned.fields.is_empty() {
1518                    fields.push(FieldDesc {
1519                        name: field.name.clone(),
1520                        field_type: FieldType::Structure(pruned),
1521                    });
1522                }
1523            }
1524            FieldType::StructureArray(inner) => {
1525                // For structure arrays we can only narrow the element
1526                // descriptor; we never drop the array field itself when a
1527                // sub-path is requested.
1528                let pruned = prune_structure(inner, child);
1529                if !pruned.fields.is_empty() {
1530                    fields.push(FieldDesc {
1531                        name: field.name.clone(),
1532                        field_type: FieldType::StructureArray(pruned),
1533                    });
1534                }
1535            }
1536            _ => {
1537                // Leaf referenced with a deeper path – drop it (unresolved).
1538            }
1539        }
1540    }
1541    StructureDesc {
1542        struct_id: desc.struct_id.clone(),
1543        fields,
1544    }
1545}
1546
1547/// Encode only the fields of an [`NtPayload`] whose paths appear in
1548/// `filtered_desc`.  The bitset and value bytes are computed against the
1549/// filtered descriptor so that a client that received the filtered INIT
1550/// descriptor will decode them correctly.
1551///
1552/// Supports nested filtering (e.g. a filtered descriptor that only contains
1553/// `alarm.severity`).
1554pub fn encode_nt_payload_filtered(
1555    payload: &NtPayload,
1556    filtered_desc: &StructureDesc,
1557    is_be: bool,
1558) -> (Vec<u8>, Vec<u8>) {
1559    let bitset = encode_structure_bitset(filtered_desc, is_be);
1560    let values = encode_nt_payload_values_for_desc(payload, filtered_desc, is_be);
1561    (bitset, values)
1562}
1563
1564/// Encode the value bytes of an `NtPayload` projected onto a (possibly
1565/// narrowed) descriptor. Fields not represented in `desc` are omitted;
1566/// sub-structures are encoded recursively.
1567pub fn encode_nt_payload_values_for_desc(
1568    payload: &NtPayload,
1569    desc: &StructureDesc,
1570    is_be: bool,
1571) -> Vec<u8> {
1572    let full_desc = nt_payload_desc(payload);
1573    if structure_desc_equal(&full_desc, desc) {
1574        // Fast path: no narrowing.
1575        return encode_nt_payload_full(payload, is_be);
1576    }
1577    let decoded = decode_payload_to_structure(payload, is_be).unwrap_or_else(|| {
1578        DecodedValue::Structure(Vec::new())
1579    });
1580    encode_decoded_projected(&decoded, desc, is_be)
1581}
1582
1583fn structure_desc_equal(a: &StructureDesc, b: &StructureDesc) -> bool {
1584    if a.struct_id != b.struct_id {
1585        return false;
1586    }
1587    if a.fields.len() != b.fields.len() {
1588        return false;
1589    }
1590    a.fields.iter().zip(&b.fields).all(|(x, y)| {
1591        x.name == y.name && field_type_equal(&x.field_type, &y.field_type)
1592    })
1593}
1594
1595fn field_type_equal(a: &FieldType, b: &FieldType) -> bool {
1596    match (a, b) {
1597        (FieldType::Scalar(x), FieldType::Scalar(y)) => x == y,
1598        (FieldType::ScalarArray(x), FieldType::ScalarArray(y)) => x == y,
1599        (FieldType::String, FieldType::String) => true,
1600        (FieldType::StringArray, FieldType::StringArray) => true,
1601        (FieldType::Structure(x), FieldType::Structure(y)) => structure_desc_equal(x, y),
1602        (FieldType::StructureArray(x), FieldType::StructureArray(y)) => {
1603            structure_desc_equal(x, y)
1604        }
1605        (FieldType::Variant, FieldType::Variant) => true,
1606        (FieldType::VariantArray, FieldType::VariantArray) => true,
1607        (FieldType::BoundedString(x), FieldType::BoundedString(y)) => x == y,
1608        // Treat unions as equal only by count (rare in NT; fine for fast-path).
1609        (FieldType::Union(x), FieldType::Union(y)) => x.len() == y.len(),
1610        (FieldType::UnionArray(x), FieldType::UnionArray(y)) => x.len() == y.len(),
1611        _ => false,
1612    }
1613}
1614
1615/// Round-trip an NtPayload through its full descriptor to obtain a
1616/// `DecodedValue::Structure` we can project against a narrowed descriptor.
1617fn decode_payload_to_structure(payload: &NtPayload, is_be: bool) -> Option<DecodedValue> {
1618    let desc = nt_payload_desc(payload);
1619    let bytes = encode_nt_payload_full(payload, is_be);
1620    let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1621    decoder.decode_structure(&bytes, &desc).map(|(v, _)| v)
1622}
1623
1624/// Re-encode a `DecodedValue::Structure` against a (possibly narrowed)
1625/// descriptor, omitting fields that are not present in the descriptor.
1626pub fn encode_decoded_projected(
1627    value: &DecodedValue,
1628    desc: &StructureDesc,
1629    is_be: bool,
1630) -> Vec<u8> {
1631    let DecodedValue::Structure(fields) = value else {
1632        // Fallback: not a structure – emit raw bytes.
1633        return encode_decoded_value(value, is_be);
1634    };
1635    let mut out = Vec::new();
1636    for target in &desc.fields {
1637        let Some((_, sub_value)) = fields.iter().find(|(n, _)| n == &target.name) else {
1638            continue;
1639        };
1640        match &target.field_type {
1641            FieldType::Structure(inner) => {
1642                out.extend_from_slice(&encode_decoded_projected(sub_value, inner, is_be));
1643            }
1644            _ => {
1645                out.extend_from_slice(&encode_decoded_value(sub_value, is_be));
1646            }
1647        }
1648    }
1649    out
1650}
1651
1652// ---------------------------------------------------------------------------
1653// Sparse delta encoding (Phase 3)
1654// ---------------------------------------------------------------------------
1655
1656/// Project an [`NtPayload`] onto `desc`, returning a [`DecodedValue::Structure`]
1657/// that contains only the fields represented in `desc`. Missing descriptor
1658/// fields are silently dropped.
1659fn project_payload_on_desc(
1660    payload: &NtPayload,
1661    desc: &StructureDesc,
1662    is_be: bool,
1663) -> DecodedValue {
1664    let decoded = decode_payload_to_structure(payload, is_be)
1665        .unwrap_or_else(|| DecodedValue::Structure(Vec::new()));
1666    project_decoded(&decoded, desc)
1667}
1668
1669fn project_decoded(value: &DecodedValue, desc: &StructureDesc) -> DecodedValue {
1670    let DecodedValue::Structure(fields) = value else {
1671        return value.clone();
1672    };
1673    let mut out: Vec<(String, DecodedValue)> = Vec::new();
1674    for target in &desc.fields {
1675        let Some((_, v)) = fields.iter().find(|(n, _)| n == &target.name) else {
1676            continue;
1677        };
1678        match &target.field_type {
1679            FieldType::Structure(inner) => {
1680                out.push((target.name.clone(), project_decoded(v, inner)));
1681            }
1682            _ => {
1683                out.push((target.name.clone(), v.clone()));
1684            }
1685        }
1686    }
1687    DecodedValue::Structure(out)
1688}
1689
1690/// Structural equality for [`DecodedValue`] with NaN treated as equal to NaN
1691/// (avoids spurious monitor flaps when a float field is NaN on both sides).
1692pub fn decoded_values_equal(a: &DecodedValue, b: &DecodedValue) -> bool {
1693    use DecodedValue::*;
1694    match (a, b) {
1695        (Null, Null) => true,
1696        (Boolean(x), Boolean(y)) => x == y,
1697        (Int8(x), Int8(y)) => x == y,
1698        (Int16(x), Int16(y)) => x == y,
1699        (Int32(x), Int32(y)) => x == y,
1700        (Int64(x), Int64(y)) => x == y,
1701        (UInt8(x), UInt8(y)) => x == y,
1702        (UInt16(x), UInt16(y)) => x == y,
1703        (UInt32(x), UInt32(y)) => x == y,
1704        (UInt64(x), UInt64(y)) => x == y,
1705        (Float32(x), Float32(y)) => x == y || (x.is_nan() && y.is_nan()),
1706        (Float64(x), Float64(y)) => x == y || (x.is_nan() && y.is_nan()),
1707        (String(x), String(y)) => x == y,
1708        (Raw(x), Raw(y)) => x == y,
1709        (Array(x), Array(y)) => {
1710            x.len() == y.len() && x.iter().zip(y).all(|(a, b)| decoded_values_equal(a, b))
1711        }
1712        (Structure(x), Structure(y)) => {
1713            x.len() == y.len()
1714                && x.iter().zip(y).all(|((ln, lv), (rn, rv))| {
1715                    ln == rn && decoded_values_equal(lv, rv)
1716                })
1717        }
1718        _ => false,
1719    }
1720}
1721
1722/// Walks `desc` in pre-order (matching the PVA wire convention that bit 0
1723/// represents the whole root structure and subsequent bits correspond to
1724/// fields in pre-order) and returns a per-bit flag vector marking leaves
1725/// whose value differs between `prev` and `next`.
1726///
1727/// Returns `None` if no leaves changed. Structure-type fields always have
1728/// their bit cleared — changes propagate to the descendants so a filtered
1729/// monitor client sees only the true differences.
1730pub fn compute_changed_bits(
1731    prev: &DecodedValue,
1732    next: &DecodedValue,
1733    desc: &StructureDesc,
1734) -> Option<Vec<bool>> {
1735    let total = 1 + spvd_count_structure_fields(desc);
1736    let mut bits = vec![false; total];
1737    let mut idx = 1usize;
1738    let any = fill_changed_bits(prev, next, desc, &mut bits, &mut idx);
1739    if any { Some(bits) } else { None }
1740}
1741
1742fn get_field_by_name<'a>(val: &'a DecodedValue, name: &str) -> Option<&'a DecodedValue> {
1743    match val {
1744        DecodedValue::Structure(f) => f.iter().find(|(n, _)| n == name).map(|(_, v)| v),
1745        _ => None,
1746    }
1747}
1748
1749fn fill_changed_bits(
1750    prev: &DecodedValue,
1751    next: &DecodedValue,
1752    desc: &StructureDesc,
1753    bits: &mut [bool],
1754    idx: &mut usize,
1755) -> bool {
1756    let mut any = false;
1757    for field in &desc.fields {
1758        let this = *idx;
1759        *idx += 1;
1760        let p = get_field_by_name(prev, &field.name);
1761        let n = get_field_by_name(next, &field.name);
1762        match &field.field_type {
1763            FieldType::Structure(inner) => {
1764                let empty = DecodedValue::Structure(Vec::new());
1765                let pv = p.unwrap_or(&empty);
1766                let nv = n.unwrap_or(&empty);
1767                if fill_changed_bits(pv, nv, inner, bits, idx) {
1768                    any = true;
1769                }
1770            }
1771            _ => {
1772                let changed = match (p, n) {
1773                    (Some(a), Some(b)) => !decoded_values_equal(a, b),
1774                    (Some(_), None) | (None, Some(_)) => true,
1775                    (None, None) => false,
1776                };
1777                if changed {
1778                    bits[this] = true;
1779                    any = true;
1780                }
1781            }
1782        }
1783    }
1784    any
1785}
1786
1787fn encode_values_for_bits(
1788    value: &DecodedValue,
1789    desc: &StructureDesc,
1790    bits: &[bool],
1791    idx: &mut usize,
1792    is_be: bool,
1793    out: &mut Vec<u8>,
1794) {
1795    for field in &desc.fields {
1796        let this = *idx;
1797        *idx += 1;
1798        let sub = get_field_by_name(value, &field.name);
1799        match &field.field_type {
1800            FieldType::Structure(inner) => {
1801                let empty = DecodedValue::Structure(Vec::new());
1802                let v = sub.unwrap_or(&empty);
1803                encode_values_for_bits(v, inner, bits, idx, is_be, out);
1804            }
1805            _ => {
1806                if bits[this] {
1807                    if let Some(v) = sub {
1808                        out.extend_from_slice(&encode_decoded_value(v, is_be));
1809                    }
1810                }
1811            }
1812        }
1813    }
1814}
1815
1816fn encode_bitset_from_flags(bits: &[bool], is_be: bool) -> Vec<u8> {
1817    let bitset_size = (bits.len() + 7) / 8;
1818    let mut bitset = vec![0u8; bitset_size];
1819    for (i, b) in bits.iter().enumerate() {
1820        if *b {
1821            bitset[i / 8] |= 1 << (i % 8);
1822        }
1823    }
1824    let mut out = Vec::new();
1825    out.extend_from_slice(&encode_size_pvd(bitset_size, is_be));
1826    out.extend_from_slice(&bitset);
1827    out
1828}
1829
1830/// Encode a sparse monitor-data delta between `prev` and `next` projected onto
1831/// `filtered_desc`. Returns `None` if nothing changed in the filtered view
1832/// (caller should suppress the update). Otherwise returns `(bitset, values)`
1833/// with only the changed leaves marked and encoded.
1834pub fn encode_nt_payload_delta(
1835    prev: &NtPayload,
1836    next: &NtPayload,
1837    filtered_desc: &StructureDesc,
1838    is_be: bool,
1839) -> Option<(Vec<u8>, Vec<u8>)> {
1840    let prev_proj = project_payload_on_desc(prev, filtered_desc, is_be);
1841    let next_proj = project_payload_on_desc(next, filtered_desc, is_be);
1842    let bits = compute_changed_bits(&prev_proj, &next_proj, filtered_desc)?;
1843    let bitset = encode_bitset_from_flags(&bits, is_be);
1844    let mut values = Vec::new();
1845    let mut idx = 1usize;
1846    encode_values_for_bits(&next_proj, filtered_desc, &bits, &mut idx, is_be, &mut values);
1847    Some((bitset, values))
1848}
1849
1850fn spvd_count_structure_fields(desc: &StructureDesc) -> usize {
1851    let mut count = 0;
1852    for field in &desc.fields {
1853        count += 1;
1854        if let FieldType::Structure(inner) = &field.field_type {
1855            count += spvd_count_structure_fields(inner);
1856        }
1857    }
1858    count
1859}
1860
1861// ---------------------------------------------------------------------------
1862// pvRequest builder
1863// ---------------------------------------------------------------------------
1864// ---------------------------------------------------------------------------
1865// pvRequest builder
1866// ---------------------------------------------------------------------------
1867
1868/// Build a pvRequest structure for the given field paths.
1869///
1870/// Each entry may be a simple top-level name (e.g. `"value"`) or a
1871/// dot-separated nested path (e.g. `"alarm.severity"`,
1872/// `"timeStamp.secondsPastEpoch"`).
1873///
1874/// A bare name selects the entire sub-tree rooted at that field. Nested
1875/// paths produce the corresponding nested sub-structure in the pvRequest so
1876/// that a PVA server can filter down to the requested leaves.
1877///
1878/// Examples:
1879/// - `encode_pv_request(&["value", "alarm", "timeStamp"], false)` →
1880///   `field(value,alarm,timeStamp)`
1881/// - `encode_pv_request(&["alarm.severity"], false)` →
1882///   `field(alarm{severity})`
1883///
1884/// The output is the *full* type-described pvRequest structure: a `0x80`
1885/// tag followed by the structure descriptor and empty-struct field values.
1886pub fn encode_pv_request(fields: &[&str], is_be: bool) -> Vec<u8> {
1887    let tree = build_path_tree_from_strs(fields);
1888    let inner_fields = path_tree_to_field_descs(&tree);
1889
1890    let field_desc = StructureDesc {
1891        struct_id: None,
1892        fields: inner_fields,
1893    };
1894
1895    let pv_request_desc = StructureDesc {
1896        struct_id: None,
1897        fields: vec![FieldDesc {
1898            name: "field".to_string(),
1899            field_type: FieldType::Structure(field_desc),
1900        }],
1901    };
1902
1903    let mut out = Vec::new();
1904    out.push(0x80); // structure tag
1905    out.extend_from_slice(&encode_structure_desc(&pv_request_desc, is_be));
1906    // Values: the field structure and all its children are empty structs, so
1907    // there are no value bytes to write.
1908    out
1909}
1910
1911fn build_path_tree_from_strs(paths: &[&str]) -> PathNode {
1912    let owned: Vec<String> = paths.iter().map(|s| (*s).to_string()).collect();
1913    build_path_tree(&owned)
1914}
1915
1916fn path_tree_to_field_descs(node: &PathNode) -> Vec<FieldDesc> {
1917    node.children
1918        .iter()
1919        .map(|(name, child)| {
1920            let nested_fields = if child.select_all {
1921                Vec::new()
1922            } else {
1923                path_tree_to_field_descs(child)
1924            };
1925            FieldDesc {
1926                name: name.clone(),
1927                field_type: FieldType::Structure(StructureDesc {
1928                    struct_id: None,
1929                    fields: nested_fields,
1930                }),
1931            }
1932        })
1933        .collect()
1934}
1935
1936#[cfg(test)]
1937mod tests {
1938    use super::*;
1939    use crate::spvd_decode::PvdDecoder;
1940
1941    #[test]
1942    fn nt_scalar_roundtrip() {
1943        let nt = NtScalar::from_value(ScalarValue::F64(12.5));
1944        let desc = nt_scalar_desc(&nt.value);
1945        let desc_bytes = encode_structure_desc(&desc, false);
1946        let mut pvd = Vec::new();
1947        pvd.push(0x80);
1948        pvd.extend_from_slice(&desc_bytes);
1949        pvd.extend_from_slice(&encode_nt_scalar_full(&nt, false));
1950
1951        let decoder = PvdDecoder::new(false);
1952        let parsed_desc = decoder.parse_introspection(&pvd).expect("desc");
1953        let (_, consumed) = decoder
1954            .decode_structure(&pvd[1 + desc_bytes.len()..], &parsed_desc)
1955            .expect("value");
1956        assert!(consumed > 0);
1957    }
1958
1959    #[test]
1960    fn nt_ndarray_roundtrip() {
1961        use spvirit_types::{
1962            NdCodec, NdDimension, NtAlarm, NtNdArray, NtTimeStamp, ScalarArrayValue,
1963        };
1964        use std::collections::HashMap;
1965
1966        let nt = NtNdArray {
1967            value: ScalarArrayValue::U8(vec![1, 2, 3, 4]),
1968            codec: NdCodec {
1969                name: String::new(),
1970                parameters: HashMap::new(),
1971            },
1972            compressed_size: 4,
1973            uncompressed_size: 4,
1974            dimension: vec![NdDimension {
1975                size: 2,
1976                offset: 0,
1977                full_size: 2,
1978                binning: 1,
1979                reverse: false,
1980            }],
1981            unique_id: 42,
1982            data_time_stamp: NtTimeStamp {
1983                seconds_past_epoch: 1000,
1984                nanoseconds: 500,
1985                user_tag: 0,
1986            },
1987            attribute: Vec::new(),
1988            descriptor: Some("test".to_string()),
1989            alarm: Some(NtAlarm::default()),
1990            time_stamp: Some(NtTimeStamp::default()),
1991            display: None,
1992        };
1993
1994        let desc = nt_ndarray_desc(&nt);
1995        let desc_bytes = encode_structure_desc(&desc, false);
1996        let data_bytes = encode_nt_ndarray_full(&nt, false);
1997
1998        // Build complete PVD: type_tag + desc + data
1999        let mut pvd = Vec::new();
2000        pvd.push(0x80);
2001        pvd.extend_from_slice(&desc_bytes);
2002        pvd.extend_from_slice(&data_bytes);
2003
2004        let decoder = PvdDecoder::new(false);
2005        let parsed_desc = decoder
2006            .parse_introspection(&pvd)
2007            .expect("desc parse failed");
2008        let data_start = 1 + desc_bytes.len();
2009        let (_decoded, consumed) = decoder
2010            .decode_structure(&pvd[data_start..], &parsed_desc)
2011            .expect("data decode failed");
2012        assert!(consumed > 0, "consumed should be > 0");
2013        assert_eq!(
2014            consumed,
2015            data_bytes.len(),
2016            "consumed should match data_bytes.len()"
2017        );
2018    }
2019
2020    #[test]
2021    fn pv_request_flat_roundtrip() {
2022        for is_be in [false, true] {
2023            let body = encode_pv_request(&["value", "alarm", "timeStamp"], is_be);
2024            let fields = decode_pv_request_fields(&body, is_be).expect("fields");
2025            assert_eq!(fields, vec!["value", "alarm", "timeStamp"]);
2026        }
2027    }
2028
2029    #[test]
2030    fn pv_request_nested_roundtrip() {
2031        let body = encode_pv_request(&["alarm.severity", "timeStamp.secondsPastEpoch"], false);
2032        let fields = decode_pv_request_fields(&body, false).expect("fields");
2033        assert_eq!(
2034            fields,
2035            vec!["alarm.severity".to_string(), "timeStamp.secondsPastEpoch".to_string()]
2036        );
2037    }
2038
2039    #[test]
2040    fn pv_request_whole_subtree_beats_leaf() {
2041        // Requesting both "alarm" and "alarm.severity" should collapse to the
2042        // whole-subtree selection.
2043        let body = encode_pv_request(&["alarm.severity", "alarm"], false);
2044        let fields = decode_pv_request_fields(&body, false).expect("fields");
2045        assert_eq!(fields, vec!["alarm".to_string()]);
2046    }
2047
2048    #[test]
2049    fn pv_request_empty_body_none() {
2050        assert!(decode_pv_request_fields(&[], false).is_none());
2051    }
2052
2053    #[test]
2054    fn filter_structure_desc_nested() {
2055        let alarm = StructureDesc {
2056            struct_id: Some("alarm_t".to_string()),
2057            fields: vec![
2058                FieldDesc { name: "severity".into(), field_type: FieldType::Scalar(TypeCode::Int32) },
2059                FieldDesc { name: "status".into(),   field_type: FieldType::Scalar(TypeCode::Int32) },
2060                FieldDesc { name: "message".into(),  field_type: FieldType::String },
2061            ],
2062        };
2063        let desc = StructureDesc {
2064            struct_id: Some("epics:nt/NTScalar:1.0".into()),
2065            fields: vec![
2066                FieldDesc { name: "value".into(), field_type: FieldType::Scalar(TypeCode::Float64) },
2067                FieldDesc { name: "alarm".into(), field_type: FieldType::Structure(alarm.clone()) },
2068            ],
2069        };
2070
2071        let pruned = filter_structure_desc(&desc, &["alarm.severity".to_string()]);
2072        assert_eq!(pruned.fields.len(), 1);
2073        assert_eq!(pruned.fields[0].name, "alarm");
2074        match &pruned.fields[0].field_type {
2075            FieldType::Structure(inner) => {
2076                assert_eq!(inner.fields.len(), 1);
2077                assert_eq!(inner.fields[0].name, "severity");
2078            }
2079            other => panic!("expected Structure, got {:?}", other),
2080        }
2081
2082        // Whole-subtree selection preserves the full alarm.
2083        let pruned_all = filter_structure_desc(&desc, &["alarm".to_string()]);
2084        match &pruned_all.fields[0].field_type {
2085            FieldType::Structure(inner) => assert_eq!(inner.fields.len(), 3),
2086            other => panic!("expected Structure, got {:?}", other),
2087        }
2088
2089        // Unknown paths are silently dropped.
2090        let pruned_unknown = filter_structure_desc(&desc, &["nope".into(), "alarm.missing".into()]);
2091        assert!(pruned_unknown.fields.is_empty());
2092    }
2093
2094    #[test]
2095    fn filtered_monitor_round_trip_nested() {
2096        use crate::spvd_decode::PvdDecoder;
2097        use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2098
2099        let mut nt = NtScalar::from_value(ScalarValue::F64(42.0));
2100        nt.alarm_severity = 2;
2101        nt.alarm_status = 7;
2102        nt.alarm_message = "hi".into();
2103        let payload = NtPayload::Scalar(nt);
2104
2105        // Client sends field(alarm.severity).
2106        let full_desc = nt_payload_desc(&payload);
2107        let paths = vec!["alarm.severity".to_string()];
2108        let filtered = filter_structure_desc(&full_desc, &paths);
2109        let (bitset, values) = encode_nt_payload_filtered(&payload, &filtered, false);
2110
2111        // Round-trip the filtered body using the filtered descriptor.
2112        let decoder = PvdDecoder::new(false);
2113        let mut body = bitset.clone();
2114        body.extend_from_slice(&values);
2115        let (decoded, _) = decoder
2116            .decode_structure_with_bitset(&body, &filtered)
2117            .expect("decode filtered");
2118
2119        let DecodedValue::Structure(fields) = decoded else {
2120            panic!("expected structure");
2121        };
2122        assert_eq!(fields.len(), 1);
2123        assert_eq!(fields[0].0, "alarm");
2124        match &fields[0].1 {
2125            DecodedValue::Structure(inner) => {
2126                assert_eq!(inner.len(), 1);
2127                assert_eq!(inner[0].0, "severity");
2128                assert!(matches!(inner[0].1, DecodedValue::Int32(2)));
2129            }
2130            other => panic!("expected Structure, got {:?}", other),
2131        }
2132
2133        // Verify that the filtered payload is genuinely smaller than an
2134        // unfiltered one (proves we're not emitting status/message bytes).
2135        let full_body_len = encode_nt_payload_full(&payload, false).len();
2136        assert!(values.len() < full_body_len);
2137    }
2138
2139    #[test]
2140    fn delta_returns_none_when_nothing_changed() {
2141        use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2142        let mut a = NtScalar::from_value(ScalarValue::F64(1.0));
2143        a.alarm_severity = 1;
2144        let p1 = NtPayload::Scalar(a.clone());
2145        let p2 = NtPayload::Scalar(a);
2146        let desc = filter_structure_desc(&nt_payload_desc(&p1), &["alarm.severity".to_string()]);
2147        assert!(encode_nt_payload_delta(&p1, &p2, &desc, false).is_none());
2148    }
2149
2150    #[test]
2151    fn delta_marks_only_changed_leaf() {
2152        use crate::spvd_decode::PvdDecoder;
2153        use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2154
2155        let mut a = NtScalar::from_value(ScalarValue::F64(1.0));
2156        a.alarm_severity = 1;
2157        a.alarm_status = 0;
2158        a.alarm_message = "ok".into();
2159        let mut b = a.clone();
2160        b.alarm_severity = 2; // only this leaf changes
2161        let p1 = NtPayload::Scalar(a);
2162        let p2 = NtPayload::Scalar(b);
2163        let desc = filter_structure_desc(
2164            &nt_payload_desc(&p1),
2165            &[
2166                "alarm.severity".to_string(),
2167                "alarm.status".to_string(),
2168                "alarm.message".to_string(),
2169            ],
2170        );
2171
2172        let (bitset, values) = encode_nt_payload_delta(&p1, &p2, &desc, false)
2173            .expect("delta must produce a frame when a leaf changed");
2174
2175        // Only one leaf bit set (severity). Bit layout for filtered_desc:
2176        //   bit 0 = root, bit 1 = alarm struct, bit 2 = severity,
2177        //   bit 3 = status, bit 4 = message.
2178        // => 5 bits → 1-byte bitset payload. The first byte is the size
2179        // prefix (1), followed by the bitset byte.
2180        assert_eq!(bitset[0], 1u8, "size prefix");
2181        let b0 = bitset[1];
2182        assert_eq!(b0 & 0x01, 0, "root bit must be clear");
2183        assert_eq!(b0 & 0x02, 0, "alarm struct bit must be clear");
2184        assert_eq!(b0 & 0x04, 0x04, "severity bit must be set");
2185        assert_eq!(b0 & 0x08, 0, "status bit must be clear");
2186        assert_eq!(b0 & 0x10, 0, "message bit must be clear");
2187
2188        // values should contain exactly one i32 (4 bytes).
2189        assert_eq!(values.len(), 4);
2190
2191        // Round-trip through the decoder and check severity.
2192        let decoder = PvdDecoder::new(false);
2193        let mut body = bitset.clone();
2194        body.extend_from_slice(&values);
2195        let (decoded, _) = decoder
2196            .decode_structure_with_bitset(&body, &desc)
2197            .expect("decode delta");
2198        let DecodedValue::Structure(fields) = decoded else {
2199            panic!("expected struct")
2200        };
2201        assert_eq!(fields.len(), 1);
2202        assert_eq!(fields[0].0, "alarm");
2203        match &fields[0].1 {
2204            DecodedValue::Structure(inner) => {
2205                assert_eq!(inner.len(), 1);
2206                assert_eq!(inner[0].0, "severity");
2207                assert!(matches!(inner[0].1, DecodedValue::Int32(2)));
2208            }
2209            other => panic!("expected struct got {:?}", other),
2210        }
2211    }
2212
2213    #[test]
2214    fn decoded_values_equal_treats_nan_as_equal() {
2215        let a = DecodedValue::Float64(f64::NAN);
2216        let b = DecodedValue::Float64(f64::NAN);
2217        assert!(decoded_values_equal(&a, &b));
2218        let c = DecodedValue::Float32(f32::NAN);
2219        let d = DecodedValue::Float32(f32::NAN);
2220        assert!(decoded_values_equal(&c, &d));
2221        // But different concrete values are still different.
2222        assert!(!decoded_values_equal(
2223            &DecodedValue::Float64(1.0),
2224            &DecodedValue::Float64(2.0)
2225        ));
2226    }
2227}