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(_nt: &NtNdArray) -> StructureDesc {
893    StructureDesc {
894        struct_id: Some("epics:nt/NTNDArray:1.0".to_string()),
895        fields: vec![
896            FieldDesc {
897                name: "value".to_string(),
898                field_type: FieldType::Union(nt_ndarray_value_union_fields()),
899            },
900            FieldDesc {
901                name: "codec".to_string(),
902                field_type: FieldType::Structure(StructureDesc {
903                    struct_id: Some("codec_t".to_string()),
904                    fields: vec![
905                        FieldDesc {
906                            name: "name".to_string(),
907                            field_type: FieldType::String,
908                        },
909                        FieldDesc {
910                            name: "parameters".to_string(),
911                            field_type: FieldType::Variant,
912                        },
913                    ],
914                }),
915            },
916            FieldDesc {
917                name: "compressedSize".to_string(),
918                field_type: FieldType::Scalar(TypeCode::Int64),
919            },
920            FieldDesc {
921                name: "uncompressedSize".to_string(),
922                field_type: FieldType::Scalar(TypeCode::Int64),
923            },
924            FieldDesc {
925                name: "dimension".to_string(),
926                field_type: FieldType::StructureArray(StructureDesc {
927                    struct_id: Some("dimension_t".to_string()),
928                    fields: vec![
929                        FieldDesc {
930                            name: "size".to_string(),
931                            field_type: FieldType::Scalar(TypeCode::Int32),
932                        },
933                        FieldDesc {
934                            name: "offset".to_string(),
935                            field_type: FieldType::Scalar(TypeCode::Int32),
936                        },
937                        FieldDesc {
938                            name: "fullSize".to_string(),
939                            field_type: FieldType::Scalar(TypeCode::Int32),
940                        },
941                        FieldDesc {
942                            name: "binning".to_string(),
943                            field_type: FieldType::Scalar(TypeCode::Int32),
944                        },
945                        FieldDesc {
946                            name: "reverse".to_string(),
947                            field_type: FieldType::Scalar(TypeCode::Boolean),
948                        },
949                    ],
950                }),
951            },
952            FieldDesc {
953                name: "uniqueId".to_string(),
954                field_type: FieldType::Scalar(TypeCode::Int32),
955            },
956            FieldDesc {
957                name: "dataTimeStamp".to_string(),
958                field_type: FieldType::Structure(timestamp_desc()),
959            },
960            FieldDesc {
961                name: "attribute".to_string(),
962                field_type: FieldType::StructureArray(StructureDesc {
963                    struct_id: Some("NTAttribute".to_string()),
964                    fields: vec![
965                        FieldDesc {
966                            name: "name".to_string(),
967                            field_type: FieldType::String,
968                        },
969                        FieldDesc {
970                            name: "value".to_string(),
971                            field_type: FieldType::Variant,
972                        },
973                        FieldDesc {
974                            name: "descriptor".to_string(),
975                            field_type: FieldType::String,
976                        },
977                        FieldDesc {
978                            name: "sourceType".to_string(),
979                            field_type: FieldType::Scalar(TypeCode::Int32),
980                        },
981                        FieldDesc {
982                            name: "source".to_string(),
983                            field_type: FieldType::String,
984                        },
985                    ],
986                }),
987            },
988            FieldDesc {
989                name: "descriptor".to_string(),
990                field_type: FieldType::String,
991            },
992            FieldDesc {
993                name: "alarm".to_string(),
994                field_type: FieldType::Structure(alarm_desc()),
995            },
996            FieldDesc {
997                name: "timeStamp".to_string(),
998                field_type: FieldType::Structure(timestamp_desc()),
999            },
1000            FieldDesc {
1001                name: "display".to_string(),
1002                field_type: FieldType::Structure(display_desc()),
1003            },
1004        ],
1005    }
1006}
1007
1008fn encode_attribute_variant(attr: &NtAttribute, is_be: bool) -> Vec<u8> {
1009    match &attr.value {
1010        ScalarValue::Bool(v) => {
1011            let mut out = vec![TypeCode::Boolean as u8];
1012            out.push(if *v { 1 } else { 0 });
1013            out
1014        }
1015        ScalarValue::I8(v) => {
1016            let mut out = vec![TypeCode::Int8 as u8];
1017            out.push(*v as u8);
1018            out
1019        }
1020        ScalarValue::I16(v) => {
1021            let mut out = vec![TypeCode::Int16 as u8];
1022            out.extend_from_slice(&if is_be {
1023                v.to_be_bytes().to_vec()
1024            } else {
1025                v.to_le_bytes().to_vec()
1026            });
1027            out
1028        }
1029        ScalarValue::I32(v) => {
1030            let mut out = vec![TypeCode::Int32 as u8];
1031            out.extend_from_slice(&encode_i32(*v, is_be));
1032            out
1033        }
1034        ScalarValue::I64(v) => {
1035            let mut out = vec![TypeCode::Int64 as u8];
1036            out.extend_from_slice(&encode_i64(*v, is_be));
1037            out
1038        }
1039        ScalarValue::U8(v) => {
1040            let mut out = vec![TypeCode::UInt8 as u8];
1041            out.push(*v);
1042            out
1043        }
1044        ScalarValue::U16(v) => {
1045            let mut out = vec![TypeCode::UInt16 as u8];
1046            out.extend_from_slice(&if is_be {
1047                v.to_be_bytes().to_vec()
1048            } else {
1049                v.to_le_bytes().to_vec()
1050            });
1051            out
1052        }
1053        ScalarValue::U32(v) => {
1054            let mut out = vec![TypeCode::UInt32 as u8];
1055            out.extend_from_slice(&if is_be {
1056                v.to_be_bytes().to_vec()
1057            } else {
1058                v.to_le_bytes().to_vec()
1059            });
1060            out
1061        }
1062        ScalarValue::U64(v) => {
1063            let mut out = vec![TypeCode::UInt64 as u8];
1064            out.extend_from_slice(&if is_be {
1065                v.to_be_bytes().to_vec()
1066            } else {
1067                v.to_le_bytes().to_vec()
1068            });
1069            out
1070        }
1071        ScalarValue::F32(v) => {
1072            let mut out = vec![TypeCode::Float32 as u8];
1073            out.extend_from_slice(&if is_be {
1074                v.to_be_bytes().to_vec()
1075            } else {
1076                v.to_le_bytes().to_vec()
1077            });
1078            out
1079        }
1080        ScalarValue::F64(v) => {
1081            let mut out = vec![TypeCode::Float64 as u8];
1082            out.extend_from_slice(&encode_f64(*v, is_be));
1083            out
1084        }
1085        ScalarValue::Str(v) => {
1086            let mut out = vec![TypeCode::String as u8];
1087            out.extend_from_slice(&encode_string_pvd(v, is_be));
1088            out
1089        }
1090    }
1091}
1092
1093pub fn encode_nt_ndarray_full(nt: &NtNdArray, is_be: bool) -> Vec<u8> {
1094    let mut out = Vec::new();
1095    out.extend_from_slice(&encode_ndarray_union(&nt.value, is_be));
1096    out.extend_from_slice(&encode_string_pvd(&nt.codec.name, is_be));
1097    out.extend_from_slice(&encode_codec_parameters(&nt.codec.parameters, is_be));
1098    out.extend_from_slice(&encode_i64(nt.compressed_size, is_be));
1099    out.extend_from_slice(&encode_i64(nt.uncompressed_size, is_be));
1100    out.extend_from_slice(&encode_size_pvd(nt.dimension.len(), is_be));
1101    for NdDimension {
1102        size,
1103        offset,
1104        full_size,
1105        binning,
1106        reverse,
1107    } in &nt.dimension
1108    {
1109        out.push(1); // non-null element indicator
1110        out.extend_from_slice(&encode_i32(*size, is_be));
1111        out.extend_from_slice(&encode_i32(*offset, is_be));
1112        out.extend_from_slice(&encode_i32(*full_size, is_be));
1113        out.extend_from_slice(&encode_i32(*binning, is_be));
1114        out.push(if *reverse { 1 } else { 0 });
1115    }
1116    out.extend_from_slice(&encode_i32(nt.unique_id, is_be));
1117    out.extend_from_slice(&encode_nt_timestamp(&nt.data_time_stamp, is_be));
1118    out.extend_from_slice(&encode_size_pvd(nt.attribute.len(), is_be));
1119    for attr in &nt.attribute {
1120        out.push(1); // non-null element indicator
1121        out.extend_from_slice(&encode_string_pvd(&attr.name, is_be));
1122        out.extend_from_slice(&encode_attribute_variant(attr, is_be));
1123        out.extend_from_slice(&encode_string_pvd(&attr.descriptor, is_be));
1124        out.extend_from_slice(&encode_i32(attr.source_type, is_be));
1125        out.extend_from_slice(&encode_string_pvd(&attr.source, is_be));
1126    }
1127    out.extend_from_slice(&encode_string_pvd(
1128        nt.descriptor.as_deref().unwrap_or(""),
1129        is_be,
1130    ));
1131    out.extend_from_slice(&encode_nt_alarm(
1132        nt.alarm.as_ref().unwrap_or(&NtAlarm::default()),
1133        is_be,
1134    ));
1135    out.extend_from_slice(&encode_nt_timestamp(
1136        nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
1137        is_be,
1138    ));
1139    out.extend_from_slice(&encode_nt_display(
1140        nt.display.as_ref().unwrap_or(&NtDisplay::default()),
1141        is_be,
1142    ));
1143    out
1144}
1145
1146// ---------------------------------------------------------------------------
1147// NTEnum descriptor & encoder
1148// ---------------------------------------------------------------------------
1149
1150pub fn nt_enum_desc() -> StructureDesc {
1151    StructureDesc {
1152        struct_id: Some("epics:nt/NTEnum:1.0".to_string()),
1153        fields: vec![
1154            FieldDesc {
1155                name: "value".to_string(),
1156                field_type: FieldType::Structure(StructureDesc {
1157                    struct_id: Some("enum_t".to_string()),
1158                    fields: vec![
1159                        FieldDesc {
1160                            name: "index".to_string(),
1161                            field_type: FieldType::Scalar(TypeCode::Int32),
1162                        },
1163                        FieldDesc {
1164                            name: "choices".to_string(),
1165                            field_type: FieldType::StringArray,
1166                        },
1167                    ],
1168                }),
1169            },
1170            FieldDesc {
1171                name: "alarm".to_string(),
1172                field_type: FieldType::Structure(alarm_desc()),
1173            },
1174            FieldDesc {
1175                name: "timeStamp".to_string(),
1176                field_type: FieldType::Structure(timestamp_desc()),
1177            },
1178        ],
1179    }
1180}
1181
1182pub fn encode_nt_enum_full(nt: &NtEnum, is_be: bool) -> Vec<u8> {
1183    let mut out = Vec::new();
1184    // value — enum_t { index, choices }
1185    out.extend_from_slice(&encode_enum(nt.index, &nt.choices, is_be));
1186    // alarm
1187    out.extend_from_slice(&encode_nt_alarm(&nt.alarm, is_be));
1188    // timeStamp
1189    out.extend_from_slice(&encode_nt_timestamp(&nt.time_stamp, is_be));
1190    out
1191}
1192
1193// ---------------------------------------------------------------------------
1194// PvValue (generic recursive) descriptor & encoder
1195// ---------------------------------------------------------------------------
1196
1197fn scalar_value_type_code(v: &ScalarValue) -> TypeCode {
1198    match v {
1199        ScalarValue::Bool(_) => TypeCode::Boolean,
1200        ScalarValue::I8(_) => TypeCode::Int8,
1201        ScalarValue::I16(_) => TypeCode::Int16,
1202        ScalarValue::I32(_) => TypeCode::Int32,
1203        ScalarValue::I64(_) => TypeCode::Int64,
1204        ScalarValue::U8(_) => TypeCode::UInt8,
1205        ScalarValue::U16(_) => TypeCode::UInt16,
1206        ScalarValue::U32(_) => TypeCode::UInt32,
1207        ScalarValue::U64(_) => TypeCode::UInt64,
1208        ScalarValue::F32(_) => TypeCode::Float32,
1209        ScalarValue::F64(_) => TypeCode::Float64,
1210        ScalarValue::Str(_) => TypeCode::String,
1211    }
1212}
1213
1214/// Build a [`StructureDesc`] from a [`PvValue::Structure`].
1215pub fn pv_value_desc(struct_id: &str, fields: &[(String, PvValue)]) -> StructureDesc {
1216    StructureDesc {
1217        struct_id: if struct_id.is_empty() {
1218            None
1219        } else {
1220            Some(struct_id.to_string())
1221        },
1222        fields: fields
1223            .iter()
1224            .map(|(name, val)| FieldDesc {
1225                name: name.clone(),
1226                field_type: pv_value_field_type(val),
1227            })
1228            .collect(),
1229    }
1230}
1231
1232fn pv_value_field_type(val: &PvValue) -> FieldType {
1233    match val {
1234        PvValue::Scalar(sv) => {
1235            if matches!(sv, ScalarValue::Str(_)) {
1236                FieldType::String
1237            } else {
1238                FieldType::Scalar(scalar_value_type_code(sv))
1239            }
1240        }
1241        PvValue::ScalarArray(sa) => scalar_array_field_type(sa),
1242        PvValue::Structure { struct_id, fields } => {
1243            FieldType::Structure(pv_value_desc(struct_id, fields))
1244        }
1245    }
1246}
1247
1248/// Encode a [`PvValue`] tree to PVA wire bytes (values only, no descriptor).
1249pub fn encode_pv_value(val: &PvValue, is_be: bool) -> Vec<u8> {
1250    match val {
1251        PvValue::Scalar(sv) => encode_scalar_value(sv, is_be),
1252        PvValue::ScalarArray(sa) => encode_scalar_array_value_pvd(sa, is_be),
1253        PvValue::Structure { fields, .. } => {
1254            let mut out = Vec::new();
1255            for (_, v) in fields {
1256                out.extend_from_slice(&encode_pv_value(v, is_be));
1257            }
1258            out
1259        }
1260    }
1261}
1262
1263pub fn nt_payload_desc(payload: &NtPayload) -> StructureDesc {
1264    match payload {
1265        NtPayload::Scalar(nt) => nt_scalar_desc(&nt.value),
1266        NtPayload::ScalarArray(nt) => nt_scalar_array_desc(&nt.value),
1267        NtPayload::Table(nt) => nt_table_desc(nt),
1268        NtPayload::NdArray(nt) => nt_ndarray_desc(nt),
1269        NtPayload::Enum(_) => nt_enum_desc(),
1270        NtPayload::Generic { struct_id, fields } => pv_value_desc(struct_id, fields),
1271    }
1272}
1273
1274pub fn encode_nt_payload_full(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1275    match payload {
1276        NtPayload::Scalar(nt) => encode_nt_scalar_full(nt, is_be),
1277        NtPayload::ScalarArray(nt) => encode_nt_scalar_array_full(nt, is_be),
1278        NtPayload::Table(nt) => encode_nt_table_full(nt, is_be),
1279        NtPayload::NdArray(nt) => encode_nt_ndarray_full(nt, is_be),
1280        NtPayload::Enum(nt) => encode_nt_enum_full(nt, is_be),
1281        NtPayload::Generic { fields, .. } => {
1282            let mut out = Vec::new();
1283            for (_, v) in fields {
1284                out.extend_from_slice(&encode_pv_value(v, is_be));
1285            }
1286            out
1287        }
1288    }
1289}
1290
1291pub fn encode_nt_payload_bitset(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1292    let desc = nt_payload_desc(payload);
1293    let mut out = Vec::new();
1294    out.extend_from_slice(&encode_structure_bitset(&desc, is_be));
1295    out.extend_from_slice(&encode_nt_payload_full(payload, is_be));
1296    out
1297}
1298
1299pub fn encode_nt_payload_bitset_parts(payload: &NtPayload, is_be: bool) -> (Vec<u8>, Vec<u8>) {
1300    let desc = nt_payload_desc(payload);
1301    (
1302        encode_structure_bitset(&desc, is_be),
1303        encode_nt_payload_full(payload, is_be),
1304    )
1305}
1306
1307// ---------------------------------------------------------------------------
1308// Generic DecodedValue → wire bytes encoder
1309// ---------------------------------------------------------------------------
1310
1311use crate::spvd_decode::DecodedValue;
1312
1313/// Encode a `DecodedValue` back to PVA wire bytes.
1314pub fn encode_decoded_value(val: &DecodedValue, is_be: bool) -> Vec<u8> {
1315    match val {
1316        DecodedValue::Null => Vec::new(),
1317        DecodedValue::Boolean(v) => vec![if *v { 1 } else { 0 }],
1318        DecodedValue::Int8(v) => vec![*v as u8],
1319        DecodedValue::Int16(v) => {
1320            if is_be {
1321                v.to_be_bytes().to_vec()
1322            } else {
1323                v.to_le_bytes().to_vec()
1324            }
1325        }
1326        DecodedValue::Int32(v) => encode_i32(*v, is_be),
1327        DecodedValue::Int64(v) => encode_i64(*v, is_be),
1328        DecodedValue::UInt8(v) => vec![*v],
1329        DecodedValue::UInt16(v) => {
1330            if is_be {
1331                v.to_be_bytes().to_vec()
1332            } else {
1333                v.to_le_bytes().to_vec()
1334            }
1335        }
1336        DecodedValue::UInt32(v) => {
1337            if is_be {
1338                v.to_be_bytes().to_vec()
1339            } else {
1340                v.to_le_bytes().to_vec()
1341            }
1342        }
1343        DecodedValue::UInt64(v) => {
1344            if is_be {
1345                v.to_be_bytes().to_vec()
1346            } else {
1347                v.to_le_bytes().to_vec()
1348            }
1349        }
1350        DecodedValue::Float32(v) => {
1351            if is_be {
1352                v.to_be_bytes().to_vec()
1353            } else {
1354                v.to_le_bytes().to_vec()
1355            }
1356        }
1357        DecodedValue::Float64(v) => encode_f64(*v, is_be),
1358        DecodedValue::String(v) => encode_string_pvd(v, is_be),
1359        DecodedValue::Array(arr) => {
1360            let mut out = encode_size_pvd(arr.len(), is_be);
1361            for item in arr {
1362                out.extend_from_slice(&encode_decoded_value(item, is_be));
1363            }
1364            out
1365        }
1366        DecodedValue::Structure(fields) => {
1367            let mut out = Vec::new();
1368            for (_name, value) in fields {
1369                out.extend_from_slice(&encode_decoded_value(value, is_be));
1370            }
1371            out
1372        }
1373        DecodedValue::Raw(data) => data.clone(),
1374    }
1375}
1376
1377// ---------------------------------------------------------------------------
1378// pvRequest parsing & descriptor filtering
1379// ---------------------------------------------------------------------------
1380
1381/// Parse a pvRequest structure from the INIT body bytes and return the list
1382/// of requested top-level field names.
1383///
1384/// Returns `None` if the body is empty or cannot be parsed, which should be
1385/// treated as "return all fields" (no filtering).
1386pub fn decode_pv_request_fields(body: &[u8], is_be: bool) -> Option<Vec<String>> {
1387    if body.is_empty() {
1388        return None;
1389    }
1390    let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1391    let desc = decoder.parse_introspection(body)?;
1392    // Find the "field" sub-structure.
1393    for field in &desc.fields {
1394        if field.name == "field" {
1395            if let FieldType::Structure(ref inner) = field.field_type {
1396                if inner.fields.is_empty() {
1397                    // Empty "field {}" means all fields.
1398                    return None;
1399                }
1400                let names: Vec<String> = inner.fields.iter().map(|f| f.name.clone()).collect();
1401                return Some(names);
1402            }
1403        }
1404    }
1405    None
1406}
1407
1408/// Filter a [`StructureDesc`] to include only the listed top-level field
1409/// names.  Unknown names are silently ignored.  If `requested` is empty the
1410/// original descriptor is returned unchanged.
1411pub fn filter_structure_desc(desc: &StructureDesc, requested: &[String]) -> StructureDesc {
1412    if requested.is_empty() {
1413        return desc.clone();
1414    }
1415    StructureDesc {
1416        struct_id: desc.struct_id.clone(),
1417        fields: desc
1418            .fields
1419            .iter()
1420            .filter(|f| requested.iter().any(|r| r == &f.name))
1421            .cloned()
1422            .collect(),
1423    }
1424}
1425
1426/// Encode only the fields of an [`NtPayload`] whose names appear in
1427/// `desc`.  The bitset and value bytes are computed against the filtered
1428/// descriptor so that a client that received the filtered INIT descriptor
1429/// will decode them correctly.
1430pub fn encode_nt_payload_filtered(
1431    payload: &NtPayload,
1432    filtered_desc: &StructureDesc,
1433    is_be: bool,
1434) -> (Vec<u8>, Vec<u8>) {
1435    let requested: Vec<&str> = filtered_desc
1436        .fields
1437        .iter()
1438        .map(|f| f.name.as_str())
1439        .collect();
1440    let full_desc = nt_payload_desc(payload);
1441    let full_fields = &full_desc.fields;
1442
1443    // Map each field in the full descriptor to its encoded bytes.
1444    let field_bytes: Vec<(&str, Vec<u8>)> = encode_nt_payload_fields(payload, full_fields, is_be);
1445
1446    // Build the filtered values and a full-set bitset over the filtered desc.
1447    let mut values = Vec::new();
1448    for (name, bytes) in &field_bytes {
1449        if requested.iter().any(|r| *r == *name) {
1450            values.extend_from_slice(bytes);
1451        }
1452    }
1453
1454    let bitset = encode_structure_bitset(filtered_desc, is_be);
1455    (bitset, values)
1456}
1457
1458/// Helper: encode each top-level field of an NtPayload separately, returning
1459/// `(field_name, encoded_bytes)` pairs in descriptor order.
1460fn encode_nt_table_field(nt: &NtTable, name: &str, is_be: bool) -> Vec<u8> {
1461    match name {
1462        "labels" => encode_string_array(&nt.labels, is_be),
1463        "value" => {
1464            let mut out = Vec::new();
1465            for NtTableColumn { values, .. } in &nt.columns {
1466                out.extend_from_slice(&encode_scalar_array_value_pvd(values, is_be));
1467            }
1468            out
1469        }
1470        _ => Vec::new(),
1471    }
1472}
1473
1474fn encode_nt_ndarray_field(nt: &NtNdArray, name: &str, is_be: bool) -> Vec<u8> {
1475    match name {
1476        "value" => encode_ndarray_union(&nt.value, is_be),
1477        "codec" => {
1478            let mut out = Vec::new();
1479            out.extend_from_slice(&encode_string_pvd(&nt.codec.name, is_be));
1480            out.extend_from_slice(&encode_codec_parameters(&nt.codec.parameters, is_be));
1481            out
1482        }
1483        "compressedSize" => encode_i64(nt.compressed_size, is_be),
1484        "uncompressedSize" => encode_i64(nt.uncompressed_size, is_be),
1485        "dimension" => {
1486            let mut out = encode_size_pvd(nt.dimension.len(), is_be);
1487            for d in &nt.dimension {
1488                out.push(1);
1489                out.extend_from_slice(&encode_i32(d.size, is_be));
1490                out.extend_from_slice(&encode_i32(d.offset, is_be));
1491                out.extend_from_slice(&encode_i32(d.full_size, is_be));
1492                out.extend_from_slice(&encode_i32(d.binning, is_be));
1493                out.push(if d.reverse { 1 } else { 0 });
1494            }
1495            out
1496        }
1497        "uniqueId" => encode_i32(nt.unique_id, is_be),
1498        "dataTimeStamp" => encode_nt_timestamp(&nt.data_time_stamp, is_be),
1499        "attribute" => {
1500            let mut out = encode_size_pvd(nt.attribute.len(), is_be);
1501            for attr in &nt.attribute {
1502                out.push(1);
1503                out.extend_from_slice(&encode_string_pvd(&attr.name, is_be));
1504                out.extend_from_slice(&encode_attribute_variant(attr, is_be));
1505                out.extend_from_slice(&encode_string_pvd(&attr.descriptor, is_be));
1506                out.extend_from_slice(&encode_i32(attr.source_type, is_be));
1507                out.extend_from_slice(&encode_string_pvd(&attr.source, is_be));
1508            }
1509            out
1510        }
1511        "descriptor" => encode_string_pvd(nt.descriptor.as_deref().unwrap_or(""), is_be),
1512        "alarm" => encode_nt_alarm(nt.alarm.as_ref().unwrap_or(&NtAlarm::default()), is_be),
1513        "timeStamp" => encode_nt_timestamp(
1514            nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
1515            is_be,
1516        ),
1517        "display" => encode_nt_display(nt.display.as_ref().unwrap_or(&NtDisplay::default()), is_be),
1518        _ => Vec::new(),
1519    }
1520}
1521
1522fn encode_nt_payload_fields<'a>(
1523    payload: &'a NtPayload,
1524    full_fields: &'a [FieldDesc],
1525    is_be: bool,
1526) -> Vec<(&'a str, Vec<u8>)> {
1527    // NTScalar field encoders
1528    fn scalar_field(nt: &NtScalar, name: &str, is_be: bool) -> Vec<u8> {
1529        match name {
1530            "value" => encode_scalar_value(&nt.value, is_be),
1531            "alarm" => encode_alarm(nt, is_be),
1532            "timeStamp" => encode_timestamp(nt, is_be),
1533            "display" => encode_display(nt, is_be),
1534            "control" => encode_control(nt, is_be),
1535            "valueAlarm" => encode_value_alarm(nt, is_be),
1536            _ => Vec::new(),
1537        }
1538    }
1539
1540    fn scalar_array_field(nt: &NtScalarArray, name: &str, is_be: bool) -> Vec<u8> {
1541        match name {
1542            "value" => encode_scalar_array_value_pvd(&nt.value, is_be),
1543            "alarm" => encode_nt_alarm(&nt.alarm, is_be),
1544            "timeStamp" => encode_nt_timestamp(&nt.time_stamp, is_be),
1545            "display" => encode_nt_display(&nt.display, is_be),
1546            "control" => {
1547                let mut out = Vec::new();
1548                out.extend_from_slice(&encode_f64(nt.control.limit_low, is_be));
1549                out.extend_from_slice(&encode_f64(nt.control.limit_high, is_be));
1550                out.extend_from_slice(&encode_f64(nt.control.min_step, is_be));
1551                out
1552            }
1553            _ => Vec::new(),
1554        }
1555    }
1556
1557    fn enum_field(nt: &NtEnum, name: &str, is_be: bool) -> Vec<u8> {
1558        match name {
1559            "value" => encode_enum(nt.index, &nt.choices, is_be),
1560            "alarm" => encode_nt_alarm(&nt.alarm, is_be),
1561            "timeStamp" => encode_nt_timestamp(&nt.time_stamp, is_be),
1562            _ => Vec::new(),
1563        }
1564    }
1565
1566    full_fields
1567        .iter()
1568        .map(|f| {
1569            let name = f.name.as_str();
1570            let bytes = match payload {
1571                NtPayload::Scalar(nt) => scalar_field(nt, name, is_be),
1572                NtPayload::ScalarArray(nt) => scalar_array_field(nt, name, is_be),
1573                NtPayload::Table(nt) => encode_nt_table_field(nt, name, is_be),
1574                NtPayload::NdArray(nt) => encode_nt_ndarray_field(nt, name, is_be),
1575                NtPayload::Enum(nt) => enum_field(nt, name, is_be),
1576                NtPayload::Generic { fields, .. } => {
1577                    if let Some((_, v)) = fields.iter().find(|(n, _)| n == name) {
1578                        encode_pv_value(v, is_be)
1579                    } else {
1580                        Vec::new()
1581                    }
1582                }
1583            };
1584            (name, bytes)
1585        })
1586        .collect()
1587}
1588
1589// ---------------------------------------------------------------------------
1590// pvRequest builder
1591// ---------------------------------------------------------------------------
1592
1593/// Build a pvRequest structure for the given top-level field names.
1594///
1595/// Produces the byte sequence that a client sends inside an INIT request to
1596/// select which fields to subscribe to, e.g.
1597/// `encode_pv_request(&["value", "alarm", "timeStamp"], false)` produces the
1598/// equivalent of `field(value,alarm,timeStamp)`.
1599///
1600/// The output is the *full* type-described pvRequest structure: a `0xFD` /
1601/// `0x80` tag followed by the structure descriptor and empty-struct field values.
1602pub fn encode_pv_request(fields: &[&str], is_be: bool) -> Vec<u8> {
1603    // Build inner "field" structure descriptor: each requested field is an
1604    // empty sub-structure (no fields).
1605    let inner_fields: Vec<FieldDesc> = fields
1606        .iter()
1607        .map(|name| FieldDesc {
1608            name: name.to_string(),
1609            field_type: FieldType::Structure(StructureDesc {
1610                struct_id: None,
1611                fields: Vec::new(),
1612            }),
1613        })
1614        .collect();
1615
1616    let field_desc = StructureDesc {
1617        struct_id: None,
1618        fields: inner_fields,
1619    };
1620
1621    let pv_request_desc = StructureDesc {
1622        struct_id: None,
1623        fields: vec![FieldDesc {
1624            name: "field".to_string(),
1625            field_type: FieldType::Structure(field_desc),
1626        }],
1627    };
1628
1629    let mut out = Vec::new();
1630    out.push(0x80); // structure tag
1631    out.extend_from_slice(&encode_structure_desc(&pv_request_desc, is_be));
1632    // Values: the field structure and all its children are empty structs, so
1633    // there are no value bytes to write.
1634    out
1635}
1636
1637#[cfg(test)]
1638mod tests {
1639    use super::*;
1640    use crate::spvd_decode::PvdDecoder;
1641
1642    #[test]
1643    fn nt_scalar_roundtrip() {
1644        let nt = NtScalar::from_value(ScalarValue::F64(12.5));
1645        let desc = nt_scalar_desc(&nt.value);
1646        let desc_bytes = encode_structure_desc(&desc, false);
1647        let mut pvd = Vec::new();
1648        pvd.push(0x80);
1649        pvd.extend_from_slice(&desc_bytes);
1650        pvd.extend_from_slice(&encode_nt_scalar_full(&nt, false));
1651
1652        let decoder = PvdDecoder::new(false);
1653        let parsed_desc = decoder.parse_introspection(&pvd).expect("desc");
1654        let (_, consumed) = decoder
1655            .decode_structure(&pvd[1 + desc_bytes.len()..], &parsed_desc)
1656            .expect("value");
1657        assert!(consumed > 0);
1658    }
1659
1660    #[test]
1661    fn nt_ndarray_roundtrip() {
1662        use spvirit_types::{
1663            NdCodec, NdDimension, NtAlarm, NtNdArray, NtTimeStamp, ScalarArrayValue,
1664        };
1665        use std::collections::HashMap;
1666
1667        let nt = NtNdArray {
1668            value: ScalarArrayValue::U8(vec![1, 2, 3, 4]),
1669            codec: NdCodec {
1670                name: String::new(),
1671                parameters: HashMap::new(),
1672            },
1673            compressed_size: 4,
1674            uncompressed_size: 4,
1675            dimension: vec![NdDimension {
1676                size: 2,
1677                offset: 0,
1678                full_size: 2,
1679                binning: 1,
1680                reverse: false,
1681            }],
1682            unique_id: 42,
1683            data_time_stamp: NtTimeStamp {
1684                seconds_past_epoch: 1000,
1685                nanoseconds: 500,
1686                user_tag: 0,
1687            },
1688            attribute: Vec::new(),
1689            descriptor: Some("test".to_string()),
1690            alarm: Some(NtAlarm::default()),
1691            time_stamp: Some(NtTimeStamp::default()),
1692            display: None,
1693        };
1694
1695        let desc = nt_ndarray_desc(&nt);
1696        let desc_bytes = encode_structure_desc(&desc, false);
1697        let data_bytes = encode_nt_ndarray_full(&nt, false);
1698
1699        // Build complete PVD: type_tag + desc + data
1700        let mut pvd = Vec::new();
1701        pvd.push(0x80);
1702        pvd.extend_from_slice(&desc_bytes);
1703        pvd.extend_from_slice(&data_bytes);
1704
1705        let decoder = PvdDecoder::new(false);
1706        let parsed_desc = decoder
1707            .parse_introspection(&pvd)
1708            .expect("desc parse failed");
1709        let data_start = 1 + desc_bytes.len();
1710        let (_decoded, consumed) = decoder
1711            .decode_structure(&pvd[data_start..], &parsed_desc)
1712            .expect("data decode failed");
1713        assert!(consumed > 0, "consumed should be > 0");
1714        assert_eq!(
1715            consumed,
1716            data_bytes.len(),
1717            "consumed should match data_bytes.len()"
1718        );
1719    }
1720}