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 top-level field names.
1387///
1388/// Returns `None` if the body is empty or cannot be parsed, which should be
1389/// treated as "return all fields" (no filtering).
1390pub fn decode_pv_request_fields(body: &[u8], is_be: bool) -> Option<Vec<String>> {
1391    if body.is_empty() {
1392        return None;
1393    }
1394    let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1395    let desc = decoder.parse_introspection(body)?;
1396    // Find the "field" sub-structure.
1397    for field in &desc.fields {
1398        if field.name == "field" {
1399            if let FieldType::Structure(ref inner) = field.field_type {
1400                if inner.fields.is_empty() {
1401                    // Empty "field {}" means all fields.
1402                    return None;
1403                }
1404                let names: Vec<String> = inner.fields.iter().map(|f| f.name.clone()).collect();
1405                return Some(names);
1406            }
1407        }
1408    }
1409    None
1410}
1411
1412/// Filter a [`StructureDesc`] to include only the listed top-level field
1413/// names.  Unknown names are silently ignored.  If `requested` is empty the
1414/// original descriptor is returned unchanged.
1415pub fn filter_structure_desc(desc: &StructureDesc, requested: &[String]) -> StructureDesc {
1416    if requested.is_empty() {
1417        return desc.clone();
1418    }
1419    StructureDesc {
1420        struct_id: desc.struct_id.clone(),
1421        fields: desc
1422            .fields
1423            .iter()
1424            .filter(|f| requested.iter().any(|r| r == &f.name))
1425            .cloned()
1426            .collect(),
1427    }
1428}
1429
1430/// Encode only the fields of an [`NtPayload`] whose names appear in
1431/// `desc`.  The bitset and value bytes are computed against the filtered
1432/// descriptor so that a client that received the filtered INIT descriptor
1433/// will decode them correctly.
1434pub fn encode_nt_payload_filtered(
1435    payload: &NtPayload,
1436    filtered_desc: &StructureDesc,
1437    is_be: bool,
1438) -> (Vec<u8>, Vec<u8>) {
1439    let requested: Vec<&str> = filtered_desc
1440        .fields
1441        .iter()
1442        .map(|f| f.name.as_str())
1443        .collect();
1444    let full_desc = nt_payload_desc(payload);
1445    let full_fields = &full_desc.fields;
1446
1447    // Map each field in the full descriptor to its encoded bytes.
1448    let field_bytes: Vec<(&str, Vec<u8>)> = encode_nt_payload_fields(payload, full_fields, is_be);
1449
1450    // Build the filtered values and a full-set bitset over the filtered desc.
1451    let mut values = Vec::new();
1452    for (name, bytes) in &field_bytes {
1453        if requested.iter().any(|r| *r == *name) {
1454            values.extend_from_slice(bytes);
1455        }
1456    }
1457
1458    let bitset = encode_structure_bitset(filtered_desc, is_be);
1459    (bitset, values)
1460}
1461
1462/// Helper: encode each top-level field of an NtPayload separately, returning
1463/// `(field_name, encoded_bytes)` pairs in descriptor order.
1464fn encode_nt_table_field(nt: &NtTable, name: &str, is_be: bool) -> Vec<u8> {
1465    match name {
1466        "labels" => encode_string_array(&nt.labels, is_be),
1467        "value" => {
1468            let mut out = Vec::new();
1469            for NtTableColumn { values, .. } in &nt.columns {
1470                out.extend_from_slice(&encode_scalar_array_value_pvd(values, is_be));
1471            }
1472            out
1473        }
1474        _ => Vec::new(),
1475    }
1476}
1477
1478fn encode_nt_ndarray_field(nt: &NtNdArray, name: &str, is_be: bool) -> Vec<u8> {
1479    match name {
1480        "value" => encode_ndarray_union(&nt.value, is_be),
1481        "codec" => {
1482            let mut out = Vec::new();
1483            out.extend_from_slice(&encode_string_pvd(&nt.codec.name, is_be));
1484            out.extend_from_slice(&encode_codec_parameters(&nt.codec.parameters, is_be));
1485            out
1486        }
1487        "compressedSize" => encode_i64(nt.compressed_size, is_be),
1488        "uncompressedSize" => encode_i64(nt.uncompressed_size, is_be),
1489        "dimension" => {
1490            let mut out = encode_size_pvd(nt.dimension.len(), is_be);
1491            for d in &nt.dimension {
1492                out.push(1);
1493                out.extend_from_slice(&encode_i32(d.size, is_be));
1494                out.extend_from_slice(&encode_i32(d.offset, is_be));
1495                out.extend_from_slice(&encode_i32(d.full_size, is_be));
1496                out.extend_from_slice(&encode_i32(d.binning, is_be));
1497                out.push(if d.reverse { 1 } else { 0 });
1498            }
1499            out
1500        }
1501        "uniqueId" => encode_i32(nt.unique_id, is_be),
1502        "dataTimeStamp" => encode_nt_timestamp(&nt.data_time_stamp, is_be),
1503        "attribute" => {
1504            let mut out = encode_size_pvd(nt.attribute.len(), is_be);
1505            for attr in &nt.attribute {
1506                out.push(1);
1507                out.extend_from_slice(&encode_string_pvd(&attr.name, is_be));
1508                out.extend_from_slice(&encode_attribute_variant(attr, is_be));
1509                out.extend_from_slice(&encode_string_pvd(&attr.descriptor, is_be));
1510                out.extend_from_slice(&encode_i32(attr.source_type, is_be));
1511                out.extend_from_slice(&encode_string_pvd(&attr.source, is_be));
1512            }
1513            out
1514        }
1515        "descriptor" => encode_string_pvd(nt.descriptor.as_deref().unwrap_or(""), is_be),
1516        "alarm" => encode_nt_alarm(nt.alarm.as_ref().unwrap_or(&NtAlarm::default()), is_be),
1517        "timeStamp" => encode_nt_timestamp(
1518            nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
1519            is_be,
1520        ),
1521        "display" => encode_nt_display(nt.display.as_ref().unwrap_or(&NtDisplay::default()), is_be),
1522        _ => Vec::new(),
1523    }
1524}
1525
1526fn encode_nt_payload_fields<'a>(
1527    payload: &'a NtPayload,
1528    full_fields: &'a [FieldDesc],
1529    is_be: bool,
1530) -> Vec<(&'a str, Vec<u8>)> {
1531    // NTScalar field encoders
1532    fn scalar_field(nt: &NtScalar, name: &str, is_be: bool) -> Vec<u8> {
1533        match name {
1534            "value" => encode_scalar_value(&nt.value, is_be),
1535            "alarm" => encode_alarm(nt, is_be),
1536            "timeStamp" => encode_timestamp(nt, is_be),
1537            "display" => encode_display(nt, is_be),
1538            "control" => encode_control(nt, is_be),
1539            "valueAlarm" => encode_value_alarm(nt, is_be),
1540            _ => Vec::new(),
1541        }
1542    }
1543
1544    fn scalar_array_field(nt: &NtScalarArray, name: &str, is_be: bool) -> Vec<u8> {
1545        match name {
1546            "value" => encode_scalar_array_value_pvd(&nt.value, is_be),
1547            "alarm" => encode_nt_alarm(&nt.alarm, is_be),
1548            "timeStamp" => encode_nt_timestamp(&nt.time_stamp, is_be),
1549            "display" => encode_nt_display(&nt.display, is_be),
1550            "control" => {
1551                let mut out = Vec::new();
1552                out.extend_from_slice(&encode_f64(nt.control.limit_low, is_be));
1553                out.extend_from_slice(&encode_f64(nt.control.limit_high, is_be));
1554                out.extend_from_slice(&encode_f64(nt.control.min_step, is_be));
1555                out
1556            }
1557            _ => Vec::new(),
1558        }
1559    }
1560
1561    fn enum_field(nt: &NtEnum, name: &str, is_be: bool) -> Vec<u8> {
1562        match name {
1563            "value" => encode_enum(nt.index, &nt.choices, is_be),
1564            "alarm" => encode_nt_alarm(&nt.alarm, is_be),
1565            "timeStamp" => encode_nt_timestamp(&nt.time_stamp, is_be),
1566            _ => Vec::new(),
1567        }
1568    }
1569
1570    full_fields
1571        .iter()
1572        .map(|f| {
1573            let name = f.name.as_str();
1574            let bytes = match payload {
1575                NtPayload::Scalar(nt) => scalar_field(nt, name, is_be),
1576                NtPayload::ScalarArray(nt) => scalar_array_field(nt, name, is_be),
1577                NtPayload::Table(nt) => encode_nt_table_field(nt, name, is_be),
1578                NtPayload::NdArray(nt) => encode_nt_ndarray_field(nt, name, is_be),
1579                NtPayload::Enum(nt) => enum_field(nt, name, is_be),
1580                NtPayload::Generic { fields, .. } => {
1581                    if let Some((_, v)) = fields.iter().find(|(n, _)| n == name) {
1582                        encode_pv_value(v, is_be)
1583                    } else {
1584                        Vec::new()
1585                    }
1586                }
1587            };
1588            (name, bytes)
1589        })
1590        .collect()
1591}
1592
1593// ---------------------------------------------------------------------------
1594// pvRequest builder
1595// ---------------------------------------------------------------------------
1596
1597/// Build a pvRequest structure for the given top-level field names.
1598///
1599/// Produces the byte sequence that a client sends inside an INIT request to
1600/// select which fields to subscribe to, e.g.
1601/// `encode_pv_request(&["value", "alarm", "timeStamp"], false)` produces the
1602/// equivalent of `field(value,alarm,timeStamp)`.
1603///
1604/// The output is the *full* type-described pvRequest structure: a `0xFD` /
1605/// `0x80` tag followed by the structure descriptor and empty-struct field values.
1606pub fn encode_pv_request(fields: &[&str], is_be: bool) -> Vec<u8> {
1607    // Build inner "field" structure descriptor: each requested field is an
1608    // empty sub-structure (no fields).
1609    let inner_fields: Vec<FieldDesc> = fields
1610        .iter()
1611        .map(|name| FieldDesc {
1612            name: name.to_string(),
1613            field_type: FieldType::Structure(StructureDesc {
1614                struct_id: None,
1615                fields: Vec::new(),
1616            }),
1617        })
1618        .collect();
1619
1620    let field_desc = StructureDesc {
1621        struct_id: None,
1622        fields: inner_fields,
1623    };
1624
1625    let pv_request_desc = StructureDesc {
1626        struct_id: None,
1627        fields: vec![FieldDesc {
1628            name: "field".to_string(),
1629            field_type: FieldType::Structure(field_desc),
1630        }],
1631    };
1632
1633    let mut out = Vec::new();
1634    out.push(0x80); // structure tag
1635    out.extend_from_slice(&encode_structure_desc(&pv_request_desc, is_be));
1636    // Values: the field structure and all its children are empty structs, so
1637    // there are no value bytes to write.
1638    out
1639}
1640
1641#[cfg(test)]
1642mod tests {
1643    use super::*;
1644    use crate::spvd_decode::PvdDecoder;
1645
1646    #[test]
1647    fn nt_scalar_roundtrip() {
1648        let nt = NtScalar::from_value(ScalarValue::F64(12.5));
1649        let desc = nt_scalar_desc(&nt.value);
1650        let desc_bytes = encode_structure_desc(&desc, false);
1651        let mut pvd = Vec::new();
1652        pvd.push(0x80);
1653        pvd.extend_from_slice(&desc_bytes);
1654        pvd.extend_from_slice(&encode_nt_scalar_full(&nt, false));
1655
1656        let decoder = PvdDecoder::new(false);
1657        let parsed_desc = decoder.parse_introspection(&pvd).expect("desc");
1658        let (_, consumed) = decoder
1659            .decode_structure(&pvd[1 + desc_bytes.len()..], &parsed_desc)
1660            .expect("value");
1661        assert!(consumed > 0);
1662    }
1663
1664    #[test]
1665    fn nt_ndarray_roundtrip() {
1666        use spvirit_types::{
1667            NdCodec, NdDimension, NtAlarm, NtNdArray, NtTimeStamp, ScalarArrayValue,
1668        };
1669        use std::collections::HashMap;
1670
1671        let nt = NtNdArray {
1672            value: ScalarArrayValue::U8(vec![1, 2, 3, 4]),
1673            codec: NdCodec {
1674                name: String::new(),
1675                parameters: HashMap::new(),
1676            },
1677            compressed_size: 4,
1678            uncompressed_size: 4,
1679            dimension: vec![NdDimension {
1680                size: 2,
1681                offset: 0,
1682                full_size: 2,
1683                binning: 1,
1684                reverse: false,
1685            }],
1686            unique_id: 42,
1687            data_time_stamp: NtTimeStamp {
1688                seconds_past_epoch: 1000,
1689                nanoseconds: 500,
1690                user_tag: 0,
1691            },
1692            attribute: Vec::new(),
1693            descriptor: Some("test".to_string()),
1694            alarm: Some(NtAlarm::default()),
1695            time_stamp: Some(NtTimeStamp::default()),
1696            display: None,
1697        };
1698
1699        let desc = nt_ndarray_desc(&nt);
1700        let desc_bytes = encode_structure_desc(&desc, false);
1701        let data_bytes = encode_nt_ndarray_full(&nt, false);
1702
1703        // Build complete PVD: type_tag + desc + data
1704        let mut pvd = Vec::new();
1705        pvd.push(0x80);
1706        pvd.extend_from_slice(&desc_bytes);
1707        pvd.extend_from_slice(&data_bytes);
1708
1709        let decoder = PvdDecoder::new(false);
1710        let parsed_desc = decoder
1711            .parse_introspection(&pvd)
1712            .expect("desc parse failed");
1713        let data_start = 1 + desc_bytes.len();
1714        let (_decoded, consumed) = decoder
1715            .decode_structure(&pvd[data_start..], &parsed_desc)
1716            .expect("data decode failed");
1717        assert!(consumed > 0, "consumed should be > 0");
1718        assert_eq!(
1719            consumed,
1720            data_bytes.len(),
1721            "consumed should match data_bytes.len()"
1722        );
1723    }
1724}