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, NtNdArray, NtPayload, NtScalar, NtScalarArray,
11    NtTable, NtTableColumn, NtTimeStamp, 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::I32(v) => {
98            if is_be {
99                v.to_be_bytes().to_vec()
100            } else {
101                v.to_le_bytes().to_vec()
102            }
103        }
104        ScalarValue::F64(v) => {
105            if is_be {
106                v.to_be_bytes().to_vec()
107            } else {
108                v.to_le_bytes().to_vec()
109            }
110        }
111        ScalarValue::Str(v) => encode_string_pvd(v, is_be),
112    }
113}
114
115fn encode_alarm(nt: &NtScalar, is_be: bool) -> Vec<u8> {
116    let mut out = Vec::new();
117    out.extend_from_slice(&encode_i32(nt.alarm_severity, is_be));
118    out.extend_from_slice(&encode_i32(nt.alarm_status, is_be));
119    out.extend_from_slice(&encode_string_pvd(&nt.alarm_message, is_be));
120    out
121}
122
123fn encode_bool(value: bool) -> Vec<u8> {
124    vec![if value { 1 } else { 0 }]
125}
126
127fn encode_string_array(values: &[String], is_be: bool) -> Vec<u8> {
128    let mut out = Vec::new();
129    out.extend_from_slice(&encode_size_pvd(values.len(), is_be));
130    for v in values {
131        out.extend_from_slice(&encode_string_pvd(v, is_be));
132    }
133    out
134}
135
136fn encode_enum(index: i32, choices: &[String], is_be: bool) -> Vec<u8> {
137    let mut out = Vec::new();
138    out.extend_from_slice(&encode_i32(index, is_be));
139    out.extend_from_slice(&encode_string_array(choices, is_be));
140    out
141}
142
143fn encode_timestamp(_nt: &NtScalar, is_be: bool) -> Vec<u8> {
144    let mut out = Vec::new();
145    let now = SystemTime::now()
146        .duration_since(UNIX_EPOCH)
147        .unwrap_or_default();
148    let seconds_past_epoch = now.as_secs() as i64;
149    let nanos = now.subsec_nanos() as i32;
150
151    out.extend_from_slice(&encode_i64(seconds_past_epoch, is_be));
152    out.extend_from_slice(&encode_i32(nanos, is_be));
153    out.extend_from_slice(&encode_i32(0, is_be)); // userTag
154    out
155}
156
157fn encode_display(nt: &NtScalar, is_be: bool) -> Vec<u8> {
158    let mut out = Vec::new();
159    out.extend_from_slice(&encode_f64(nt.display_low, is_be));
160    out.extend_from_slice(&encode_f64(nt.display_high, is_be));
161    out.extend_from_slice(&encode_string_pvd(&nt.display_description, is_be));
162    out.extend_from_slice(&encode_string_pvd(&nt.units, is_be));
163    out.extend_from_slice(&encode_i32(nt.display_precision, is_be));
164    out.extend_from_slice(&encode_enum(
165        nt.display_form_index,
166        &nt.display_form_choices,
167        is_be,
168    ));
169    out
170}
171
172fn encode_control(nt: &NtScalar, is_be: bool) -> Vec<u8> {
173    let mut out = Vec::new();
174    out.extend_from_slice(&encode_f64(nt.control_low, is_be));
175    out.extend_from_slice(&encode_f64(nt.control_high, is_be));
176    out.extend_from_slice(&encode_f64(nt.control_min_step, is_be));
177    out
178}
179
180fn encode_value_alarm(nt: &NtScalar, is_be: bool) -> Vec<u8> {
181    let mut out = Vec::new();
182    out.extend_from_slice(&encode_bool(nt.value_alarm_active));
183    out.extend_from_slice(&encode_f64(nt.value_alarm_low_alarm_limit, is_be));
184    out.extend_from_slice(&encode_f64(nt.value_alarm_low_warning_limit, is_be));
185    out.extend_from_slice(&encode_f64(nt.value_alarm_high_warning_limit, is_be));
186    out.extend_from_slice(&encode_f64(nt.value_alarm_high_alarm_limit, is_be));
187    out.extend_from_slice(&encode_i32(nt.value_alarm_low_alarm_severity, is_be));
188    out.extend_from_slice(&encode_i32(nt.value_alarm_low_warning_severity, is_be));
189    out.extend_from_slice(&encode_i32(nt.value_alarm_high_warning_severity, is_be));
190    out.extend_from_slice(&encode_i32(nt.value_alarm_high_alarm_severity, is_be));
191    out.push(nt.value_alarm_hysteresis);
192    out
193}
194
195fn encode_i32(value: i32, is_be: bool) -> Vec<u8> {
196    if is_be {
197        value.to_be_bytes().to_vec()
198    } else {
199        value.to_le_bytes().to_vec()
200    }
201}
202
203fn encode_i64(value: i64, is_be: bool) -> Vec<u8> {
204    if is_be {
205        value.to_be_bytes().to_vec()
206    } else {
207        value.to_le_bytes().to_vec()
208    }
209}
210
211fn encode_f64(value: f64, is_be: bool) -> Vec<u8> {
212    if is_be {
213        value.to_be_bytes().to_vec()
214    } else {
215        value.to_le_bytes().to_vec()
216    }
217}
218
219pub fn nt_scalar_desc(value: &ScalarValue) -> StructureDesc {
220    let value_type = match value {
221        ScalarValue::Bool(_) => FieldType::Scalar(TypeCode::Boolean),
222        ScalarValue::I32(_) => FieldType::Scalar(TypeCode::Int32),
223        ScalarValue::F64(_) => FieldType::Scalar(TypeCode::Float64),
224        ScalarValue::Str(_) => FieldType::String,
225    };
226
227    StructureDesc {
228        struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
229        fields: vec![
230            FieldDesc {
231                name: "value".to_string(),
232                field_type: value_type,
233            },
234            FieldDesc {
235                name: "alarm".to_string(),
236                field_type: FieldType::Structure(StructureDesc {
237                    struct_id: Some("alarm_t".to_string()),
238                    fields: vec![
239                        FieldDesc {
240                            name: "severity".to_string(),
241                            field_type: FieldType::Scalar(TypeCode::Int32),
242                        },
243                        FieldDesc {
244                            name: "status".to_string(),
245                            field_type: FieldType::Scalar(TypeCode::Int32),
246                        },
247                        FieldDesc {
248                            name: "message".to_string(),
249                            field_type: FieldType::String,
250                        },
251                    ],
252                }),
253            },
254            FieldDesc {
255                name: "timeStamp".to_string(),
256                field_type: FieldType::Structure(StructureDesc {
257                    struct_id: None,
258                    fields: vec![
259                        FieldDesc {
260                            name: "secondsPastEpoch".to_string(),
261                            field_type: FieldType::Scalar(TypeCode::Int64),
262                        },
263                        FieldDesc {
264                            name: "nanoseconds".to_string(),
265                            field_type: FieldType::Scalar(TypeCode::Int32),
266                        },
267                        FieldDesc {
268                            name: "userTag".to_string(),
269                            field_type: FieldType::Scalar(TypeCode::Int32),
270                        },
271                    ],
272                }),
273            },
274            FieldDesc {
275                name: "display".to_string(),
276                field_type: FieldType::Structure(StructureDesc {
277                    struct_id: None,
278                    fields: vec![
279                        FieldDesc {
280                            name: "limitLow".to_string(),
281                            field_type: FieldType::Scalar(TypeCode::Float64),
282                        },
283                        FieldDesc {
284                            name: "limitHigh".to_string(),
285                            field_type: FieldType::Scalar(TypeCode::Float64),
286                        },
287                        FieldDesc {
288                            name: "description".to_string(),
289                            field_type: FieldType::String,
290                        },
291                        FieldDesc {
292                            name: "units".to_string(),
293                            field_type: FieldType::String,
294                        },
295                        FieldDesc {
296                            name: "precision".to_string(),
297                            field_type: FieldType::Scalar(TypeCode::Int32),
298                        },
299                        FieldDesc {
300                            name: "form".to_string(),
301                            field_type: FieldType::Structure(StructureDesc {
302                                struct_id: Some("enum_t".to_string()),
303                                fields: vec![
304                                    FieldDesc {
305                                        name: "index".to_string(),
306                                        field_type: FieldType::Scalar(TypeCode::Int32),
307                                    },
308                                    FieldDesc {
309                                        name: "choices".to_string(),
310                                        field_type: FieldType::StringArray,
311                                    },
312                                ],
313                            }),
314                        },
315                    ],
316                }),
317            },
318            FieldDesc {
319                name: "control".to_string(),
320                field_type: FieldType::Structure(StructureDesc {
321                    struct_id: Some("control_t".to_string()),
322                    fields: vec![
323                        FieldDesc {
324                            name: "limitLow".to_string(),
325                            field_type: FieldType::Scalar(TypeCode::Float64),
326                        },
327                        FieldDesc {
328                            name: "limitHigh".to_string(),
329                            field_type: FieldType::Scalar(TypeCode::Float64),
330                        },
331                        FieldDesc {
332                            name: "minStep".to_string(),
333                            field_type: FieldType::Scalar(TypeCode::Float64),
334                        },
335                    ],
336                }),
337            },
338            FieldDesc {
339                name: "valueAlarm".to_string(),
340                field_type: FieldType::Structure(StructureDesc {
341                    struct_id: Some("valueAlarm_t".to_string()),
342                    fields: vec![
343                        FieldDesc {
344                            name: "active".to_string(),
345                            field_type: FieldType::Scalar(TypeCode::Boolean),
346                        },
347                        FieldDesc {
348                            name: "lowAlarmLimit".to_string(),
349                            field_type: FieldType::Scalar(TypeCode::Float64),
350                        },
351                        FieldDesc {
352                            name: "lowWarningLimit".to_string(),
353                            field_type: FieldType::Scalar(TypeCode::Float64),
354                        },
355                        FieldDesc {
356                            name: "highWarningLimit".to_string(),
357                            field_type: FieldType::Scalar(TypeCode::Float64),
358                        },
359                        FieldDesc {
360                            name: "highAlarmLimit".to_string(),
361                            field_type: FieldType::Scalar(TypeCode::Float64),
362                        },
363                        FieldDesc {
364                            name: "lowAlarmSeverity".to_string(),
365                            field_type: FieldType::Scalar(TypeCode::Int32),
366                        },
367                        FieldDesc {
368                            name: "lowWarningSeverity".to_string(),
369                            field_type: FieldType::Scalar(TypeCode::Int32),
370                        },
371                        FieldDesc {
372                            name: "highWarningSeverity".to_string(),
373                            field_type: FieldType::Scalar(TypeCode::Int32),
374                        },
375                        FieldDesc {
376                            name: "highAlarmSeverity".to_string(),
377                            field_type: FieldType::Scalar(TypeCode::Int32),
378                        },
379                        FieldDesc {
380                            name: "hysteresis".to_string(),
381                            field_type: FieldType::Scalar(TypeCode::UInt8),
382                        },
383                    ],
384                }),
385            },
386        ],
387    }
388}
389
390pub fn encode_nt_scalar_full(nt: &NtScalar, is_be: bool) -> Vec<u8> {
391    let mut out = Vec::new();
392    out.extend_from_slice(&encode_scalar_value(&nt.value, is_be));
393    out.extend_from_slice(&encode_alarm(nt, is_be));
394    out.extend_from_slice(&encode_timestamp(nt, is_be));
395    out.extend_from_slice(&encode_display(nt, is_be));
396    out.extend_from_slice(&encode_control(nt, is_be));
397    out.extend_from_slice(&encode_value_alarm(nt, is_be));
398    out
399}
400
401fn encode_structure_bitset(desc: &StructureDesc, is_be: bool) -> Vec<u8> {
402    let total_bits = 1 + count_structure_fields(desc);
403    let bitset_size = (total_bits + 7) / 8;
404    let mut bitset = vec![0u8; bitset_size];
405    for bit in 0..total_bits {
406        let byte_idx = bit / 8;
407        let bit_idx = bit % 8;
408        bitset[byte_idx] |= 1 << bit_idx;
409    }
410    let mut out = Vec::new();
411    out.extend_from_slice(&encode_size_pvd(bitset_size, is_be));
412    out.extend_from_slice(&bitset);
413    out
414}
415
416fn encode_structure_with_bitset(desc: &StructureDesc, nt: &NtScalar, is_be: bool) -> Vec<u8> {
417    let mut out = Vec::new();
418    out.extend_from_slice(&encode_structure_bitset(desc, is_be));
419    out.extend_from_slice(&encode_nt_scalar_full(nt, is_be));
420    out
421}
422
423pub fn encode_nt_scalar_bitset(nt: &NtScalar, is_be: bool) -> Vec<u8> {
424    let desc = nt_scalar_desc(&nt.value);
425    encode_structure_with_bitset(&desc, nt, is_be)
426}
427
428pub fn encode_nt_scalar_bitset_parts(nt: &NtScalar, is_be: bool) -> (Vec<u8>, Vec<u8>) {
429    let desc = nt_scalar_desc(&nt.value);
430    let bitset = encode_structure_bitset(&desc, is_be);
431    let values = encode_nt_scalar_full(nt, is_be);
432    (bitset, values)
433}
434
435fn alarm_desc() -> StructureDesc {
436    StructureDesc {
437        struct_id: Some("alarm_t".to_string()),
438        fields: vec![
439            FieldDesc {
440                name: "severity".to_string(),
441                field_type: FieldType::Scalar(TypeCode::Int32),
442            },
443            FieldDesc {
444                name: "status".to_string(),
445                field_type: FieldType::Scalar(TypeCode::Int32),
446            },
447            FieldDesc {
448                name: "message".to_string(),
449                field_type: FieldType::String,
450            },
451        ],
452    }
453}
454
455fn timestamp_desc() -> StructureDesc {
456    StructureDesc {
457        struct_id: Some("time_t".to_string()),
458        fields: vec![
459            FieldDesc {
460                name: "secondsPastEpoch".to_string(),
461                field_type: FieldType::Scalar(TypeCode::Int64),
462            },
463            FieldDesc {
464                name: "nanoseconds".to_string(),
465                field_type: FieldType::Scalar(TypeCode::Int32),
466            },
467            FieldDesc {
468                name: "userTag".to_string(),
469                field_type: FieldType::Scalar(TypeCode::Int32),
470            },
471        ],
472    }
473}
474
475fn display_desc() -> StructureDesc {
476    StructureDesc {
477        struct_id: Some("display_t".to_string()),
478        fields: vec![
479            FieldDesc {
480                name: "limitLow".to_string(),
481                field_type: FieldType::Scalar(TypeCode::Float64),
482            },
483            FieldDesc {
484                name: "limitHigh".to_string(),
485                field_type: FieldType::Scalar(TypeCode::Float64),
486            },
487            FieldDesc {
488                name: "description".to_string(),
489                field_type: FieldType::String,
490            },
491            FieldDesc {
492                name: "units".to_string(),
493                field_type: FieldType::String,
494            },
495            FieldDesc {
496                name: "precision".to_string(),
497                field_type: FieldType::Scalar(TypeCode::Int32),
498            },
499        ],
500    }
501}
502
503fn scalar_array_field_type(value: &ScalarArrayValue) -> FieldType {
504    match value {
505        ScalarArrayValue::Bool(_) => FieldType::ScalarArray(TypeCode::Boolean),
506        ScalarArrayValue::I8(_) => FieldType::ScalarArray(TypeCode::Int8),
507        ScalarArrayValue::I16(_) => FieldType::ScalarArray(TypeCode::Int16),
508        ScalarArrayValue::I32(_) => FieldType::ScalarArray(TypeCode::Int32),
509        ScalarArrayValue::I64(_) => FieldType::ScalarArray(TypeCode::Int64),
510        ScalarArrayValue::U8(_) => FieldType::ScalarArray(TypeCode::UInt8),
511        ScalarArrayValue::U16(_) => FieldType::ScalarArray(TypeCode::UInt16),
512        ScalarArrayValue::U32(_) => FieldType::ScalarArray(TypeCode::UInt32),
513        ScalarArrayValue::U64(_) => FieldType::ScalarArray(TypeCode::UInt64),
514        ScalarArrayValue::F32(_) => FieldType::ScalarArray(TypeCode::Float32),
515        ScalarArrayValue::F64(_) => FieldType::ScalarArray(TypeCode::Float64),
516        ScalarArrayValue::Str(_) => FieldType::StringArray,
517    }
518}
519
520fn encode_scalar_array_value_pvd(value: &ScalarArrayValue, is_be: bool) -> Vec<u8> {
521    let mut out = Vec::new();
522    match value {
523        ScalarArrayValue::Bool(v) => {
524            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
525            for i in v {
526                out.push(if *i { 1 } else { 0 });
527            }
528        }
529        ScalarArrayValue::I8(v) => {
530            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
531            for i in v {
532                out.push(*i as u8);
533            }
534        }
535        ScalarArrayValue::I16(v) => {
536            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
537            for i in v {
538                let b = if is_be {
539                    i.to_be_bytes()
540                } else {
541                    i.to_le_bytes()
542                };
543                out.extend_from_slice(&b);
544            }
545        }
546        ScalarArrayValue::I32(v) => {
547            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
548            for i in v {
549                out.extend_from_slice(&encode_i32(*i, is_be));
550            }
551        }
552        ScalarArrayValue::I64(v) => {
553            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
554            for i in v {
555                out.extend_from_slice(&encode_i64(*i, is_be));
556            }
557        }
558        ScalarArrayValue::U8(v) => {
559            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
560            out.extend_from_slice(v);
561        }
562        ScalarArrayValue::U16(v) => {
563            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
564            for i in v {
565                let b = if is_be {
566                    i.to_be_bytes()
567                } else {
568                    i.to_le_bytes()
569                };
570                out.extend_from_slice(&b);
571            }
572        }
573        ScalarArrayValue::U32(v) => {
574            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
575            for i in v {
576                let b = if is_be {
577                    i.to_be_bytes()
578                } else {
579                    i.to_le_bytes()
580                };
581                out.extend_from_slice(&b);
582            }
583        }
584        ScalarArrayValue::U64(v) => {
585            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
586            for i in v {
587                let b = if is_be {
588                    i.to_be_bytes()
589                } else {
590                    i.to_le_bytes()
591                };
592                out.extend_from_slice(&b);
593            }
594        }
595        ScalarArrayValue::F32(v) => {
596            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
597            for i in v {
598                let b = if is_be {
599                    i.to_be_bytes()
600                } else {
601                    i.to_le_bytes()
602                };
603                out.extend_from_slice(&b);
604            }
605        }
606        ScalarArrayValue::F64(v) => {
607            out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
608            for i in v {
609                out.extend_from_slice(&encode_f64(*i, is_be));
610            }
611        }
612        ScalarArrayValue::Str(v) => {
613            out.extend_from_slice(&encode_string_array(v, is_be));
614        }
615    }
616    out
617}
618
619fn encode_nt_alarm(alarm: &NtAlarm, is_be: bool) -> Vec<u8> {
620    let mut out = Vec::new();
621    out.extend_from_slice(&encode_i32(alarm.severity, is_be));
622    out.extend_from_slice(&encode_i32(alarm.status, is_be));
623    out.extend_from_slice(&encode_string_pvd(&alarm.message, is_be));
624    out
625}
626
627fn encode_nt_timestamp(ts: &NtTimeStamp, is_be: bool) -> Vec<u8> {
628    let mut out = Vec::new();
629    out.extend_from_slice(&encode_i64(ts.seconds_past_epoch, is_be));
630    out.extend_from_slice(&encode_i32(ts.nanoseconds, is_be));
631    out.extend_from_slice(&encode_i32(ts.user_tag, is_be));
632    out
633}
634
635fn encode_nt_display(display: &NtDisplay, is_be: bool) -> Vec<u8> {
636    let mut out = Vec::new();
637    out.extend_from_slice(&encode_f64(display.limit_low, is_be));
638    out.extend_from_slice(&encode_f64(display.limit_high, is_be));
639    out.extend_from_slice(&encode_string_pvd(&display.description, is_be));
640    out.extend_from_slice(&encode_string_pvd(&display.units, is_be));
641    out.extend_from_slice(&encode_i32(display.precision, is_be));
642    out
643}
644
645pub fn nt_scalar_array_desc(value: &ScalarArrayValue) -> StructureDesc {
646    StructureDesc {
647        struct_id: Some("epics:nt/NTScalarArray:1.0".to_string()),
648        fields: vec![
649            FieldDesc {
650                name: "value".to_string(),
651                field_type: scalar_array_field_type(value),
652            },
653            FieldDesc {
654                name: "alarm".to_string(),
655                field_type: FieldType::Structure(alarm_desc()),
656            },
657            FieldDesc {
658                name: "timeStamp".to_string(),
659                field_type: FieldType::Structure(timestamp_desc()),
660            },
661            FieldDesc {
662                name: "display".to_string(),
663                field_type: FieldType::Structure(display_desc()),
664            },
665            FieldDesc {
666                name: "control".to_string(),
667                field_type: FieldType::Structure(StructureDesc {
668                    struct_id: Some("control_t".to_string()),
669                    fields: vec![
670                        FieldDesc {
671                            name: "limitLow".to_string(),
672                            field_type: FieldType::Scalar(TypeCode::Float64),
673                        },
674                        FieldDesc {
675                            name: "limitHigh".to_string(),
676                            field_type: FieldType::Scalar(TypeCode::Float64),
677                        },
678                        FieldDesc {
679                            name: "minStep".to_string(),
680                            field_type: FieldType::Scalar(TypeCode::Float64),
681                        },
682                    ],
683                }),
684            },
685        ],
686    }
687}
688
689pub fn encode_nt_scalar_array_full(nt: &NtScalarArray, is_be: bool) -> Vec<u8> {
690    let mut out = Vec::new();
691    out.extend_from_slice(&encode_scalar_array_value_pvd(&nt.value, is_be));
692    out.extend_from_slice(&encode_nt_alarm(&nt.alarm, is_be));
693    out.extend_from_slice(&encode_nt_timestamp(&nt.time_stamp, is_be));
694    out.extend_from_slice(&encode_nt_display(&nt.display, is_be));
695    out.extend_from_slice(&encode_f64(nt.control.limit_low, is_be));
696    out.extend_from_slice(&encode_f64(nt.control.limit_high, is_be));
697    out.extend_from_slice(&encode_f64(nt.control.min_step, is_be));
698    out
699}
700
701pub fn nt_table_desc(nt: &NtTable) -> StructureDesc {
702    let mut value_fields: Vec<FieldDesc> = Vec::new();
703    for col in &nt.columns {
704        value_fields.push(FieldDesc {
705            name: col.name.clone(),
706            field_type: scalar_array_field_type(&col.values),
707        });
708    }
709    StructureDesc {
710        struct_id: Some("epics:nt/NTTable:1.0".to_string()),
711        fields: vec![
712            FieldDesc {
713                name: "labels".to_string(),
714                field_type: FieldType::StringArray,
715            },
716            FieldDesc {
717                name: "value".to_string(),
718                field_type: FieldType::Structure(StructureDesc {
719                    struct_id: None,
720                    fields: value_fields,
721                }),
722            },
723        ],
724    }
725}
726
727pub fn encode_nt_table_full(nt: &NtTable, is_be: bool) -> Vec<u8> {
728    let mut out = Vec::new();
729    out.extend_from_slice(&encode_string_array(&nt.labels, is_be));
730    for NtTableColumn { values, .. } in &nt.columns {
731        out.extend_from_slice(&encode_scalar_array_value_pvd(values, is_be));
732    }
733    out
734}
735
736fn nt_ndarray_value_union_fields() -> Vec<FieldDesc> {
737    vec![
738        FieldDesc {
739            name: "booleanValue".to_string(),
740            field_type: FieldType::ScalarArray(TypeCode::Boolean),
741        },
742        FieldDesc {
743            name: "byteValue".to_string(),
744            field_type: FieldType::ScalarArray(TypeCode::Int8),
745        },
746        FieldDesc {
747            name: "shortValue".to_string(),
748            field_type: FieldType::ScalarArray(TypeCode::Int16),
749        },
750        FieldDesc {
751            name: "intValue".to_string(),
752            field_type: FieldType::ScalarArray(TypeCode::Int32),
753        },
754        FieldDesc {
755            name: "longValue".to_string(),
756            field_type: FieldType::ScalarArray(TypeCode::Int64),
757        },
758        FieldDesc {
759            name: "ubyteValue".to_string(),
760            field_type: FieldType::ScalarArray(TypeCode::UInt8),
761        },
762        FieldDesc {
763            name: "ushortValue".to_string(),
764            field_type: FieldType::ScalarArray(TypeCode::UInt16),
765        },
766        FieldDesc {
767            name: "uintValue".to_string(),
768            field_type: FieldType::ScalarArray(TypeCode::UInt32),
769        },
770        FieldDesc {
771            name: "ulongValue".to_string(),
772            field_type: FieldType::ScalarArray(TypeCode::UInt64),
773        },
774        FieldDesc {
775            name: "floatValue".to_string(),
776            field_type: FieldType::ScalarArray(TypeCode::Float32),
777        },
778        FieldDesc {
779            name: "doubleValue".to_string(),
780            field_type: FieldType::ScalarArray(TypeCode::Float64),
781        },
782        FieldDesc {
783            name: "stringValue".to_string(),
784            field_type: FieldType::StringArray,
785        },
786    ]
787}
788
789fn ndarray_union_index(value: &ScalarArrayValue) -> usize {
790    match value {
791        ScalarArrayValue::Bool(_) => 0,
792        ScalarArrayValue::I8(_) => 1,
793        ScalarArrayValue::I16(_) => 2,
794        ScalarArrayValue::I32(_) => 3,
795        ScalarArrayValue::I64(_) => 4,
796        ScalarArrayValue::U8(_) => 5,
797        ScalarArrayValue::U16(_) => 6,
798        ScalarArrayValue::U32(_) => 7,
799        ScalarArrayValue::U64(_) => 8,
800        ScalarArrayValue::F32(_) => 9,
801        ScalarArrayValue::F64(_) => 10,
802        ScalarArrayValue::Str(_) => 11,
803    }
804}
805
806fn encode_ndarray_union(value: &ScalarArrayValue, is_be: bool) -> Vec<u8> {
807    let mut out = Vec::new();
808    out.extend_from_slice(&encode_size_pvd(ndarray_union_index(value), is_be));
809    out.extend_from_slice(&encode_scalar_array_value_pvd(value, is_be));
810    out
811}
812
813fn encode_codec_parameters(
814    parameters: &std::collections::HashMap<String, String>,
815    is_be: bool,
816) -> Vec<u8> {
817    if parameters.is_empty() {
818        return vec![0xFF];
819    }
820    let mut out = Vec::new();
821    out.push(0x80);
822    let mut fields = Vec::new();
823    for key in parameters.keys() {
824        fields.push(FieldDesc {
825            name: key.clone(),
826            field_type: FieldType::String,
827        });
828    }
829    let desc = StructureDesc {
830        struct_id: None,
831        fields,
832    };
833    out.extend_from_slice(&encode_structure_desc(&desc, is_be));
834    for value in parameters.values() {
835        out.extend_from_slice(&encode_string_pvd(value, is_be));
836    }
837    out
838}
839
840pub fn nt_ndarray_desc(_nt: &NtNdArray) -> StructureDesc {
841    StructureDesc {
842        struct_id: Some("epics:nt/NTNDArray:1.0".to_string()),
843        fields: vec![
844            FieldDesc {
845                name: "value".to_string(),
846                field_type: FieldType::Union(nt_ndarray_value_union_fields()),
847            },
848            FieldDesc {
849                name: "codec".to_string(),
850                field_type: FieldType::Structure(StructureDesc {
851                    struct_id: Some("codec_t".to_string()),
852                    fields: vec![
853                        FieldDesc {
854                            name: "name".to_string(),
855                            field_type: FieldType::String,
856                        },
857                        FieldDesc {
858                            name: "parameters".to_string(),
859                            field_type: FieldType::Variant,
860                        },
861                    ],
862                }),
863            },
864            FieldDesc {
865                name: "compressedSize".to_string(),
866                field_type: FieldType::Scalar(TypeCode::Int64),
867            },
868            FieldDesc {
869                name: "uncompressedSize".to_string(),
870                field_type: FieldType::Scalar(TypeCode::Int64),
871            },
872            FieldDesc {
873                name: "dimension".to_string(),
874                field_type: FieldType::StructureArray(StructureDesc {
875                    struct_id: Some("dimension_t".to_string()),
876                    fields: vec![
877                        FieldDesc {
878                            name: "size".to_string(),
879                            field_type: FieldType::Scalar(TypeCode::Int32),
880                        },
881                        FieldDesc {
882                            name: "offset".to_string(),
883                            field_type: FieldType::Scalar(TypeCode::Int32),
884                        },
885                        FieldDesc {
886                            name: "fullSize".to_string(),
887                            field_type: FieldType::Scalar(TypeCode::Int32),
888                        },
889                        FieldDesc {
890                            name: "binning".to_string(),
891                            field_type: FieldType::Scalar(TypeCode::Int32),
892                        },
893                        FieldDesc {
894                            name: "reverse".to_string(),
895                            field_type: FieldType::Scalar(TypeCode::Boolean),
896                        },
897                    ],
898                }),
899            },
900            FieldDesc {
901                name: "uniqueId".to_string(),
902                field_type: FieldType::Scalar(TypeCode::Int32),
903            },
904            FieldDesc {
905                name: "dataTimeStamp".to_string(),
906                field_type: FieldType::Structure(timestamp_desc()),
907            },
908            FieldDesc {
909                name: "attribute".to_string(),
910                field_type: FieldType::StructureArray(StructureDesc {
911                    struct_id: Some("NTAttribute".to_string()),
912                    fields: vec![
913                        FieldDesc {
914                            name: "name".to_string(),
915                            field_type: FieldType::String,
916                        },
917                        FieldDesc {
918                            name: "value".to_string(),
919                            field_type: FieldType::Variant,
920                        },
921                        FieldDesc {
922                            name: "descriptor".to_string(),
923                            field_type: FieldType::String,
924                        },
925                        FieldDesc {
926                            name: "sourceType".to_string(),
927                            field_type: FieldType::Scalar(TypeCode::Int32),
928                        },
929                        FieldDesc {
930                            name: "source".to_string(),
931                            field_type: FieldType::String,
932                        },
933                    ],
934                }),
935            },
936            FieldDesc {
937                name: "descriptor".to_string(),
938                field_type: FieldType::String,
939            },
940            FieldDesc {
941                name: "alarm".to_string(),
942                field_type: FieldType::Structure(alarm_desc()),
943            },
944            FieldDesc {
945                name: "timeStamp".to_string(),
946                field_type: FieldType::Structure(timestamp_desc()),
947            },
948            FieldDesc {
949                name: "display".to_string(),
950                field_type: FieldType::Structure(display_desc()),
951            },
952        ],
953    }
954}
955
956fn encode_attribute_variant(attr: &NtAttribute, is_be: bool) -> Vec<u8> {
957    match &attr.value {
958        ScalarValue::Bool(v) => {
959            let mut out = vec![TypeCode::Boolean as u8];
960            out.push(if *v { 1 } else { 0 });
961            out
962        }
963        ScalarValue::I32(v) => {
964            let mut out = vec![TypeCode::Int32 as u8];
965            out.extend_from_slice(&encode_i32(*v, is_be));
966            out
967        }
968        ScalarValue::F64(v) => {
969            let mut out = vec![TypeCode::Float64 as u8];
970            out.extend_from_slice(&encode_f64(*v, is_be));
971            out
972        }
973        ScalarValue::Str(v) => {
974            let mut out = vec![TypeCode::String as u8];
975            out.extend_from_slice(&encode_string_pvd(v, is_be));
976            out
977        }
978    }
979}
980
981pub fn encode_nt_ndarray_full(nt: &NtNdArray, is_be: bool) -> Vec<u8> {
982    let mut out = Vec::new();
983    out.extend_from_slice(&encode_ndarray_union(&nt.value, is_be));
984    out.extend_from_slice(&encode_string_pvd(&nt.codec.name, is_be));
985    out.extend_from_slice(&encode_codec_parameters(&nt.codec.parameters, is_be));
986    out.extend_from_slice(&encode_i64(nt.compressed_size, is_be));
987    out.extend_from_slice(&encode_i64(nt.uncompressed_size, is_be));
988    out.extend_from_slice(&encode_size_pvd(nt.dimension.len(), is_be));
989    for NdDimension {
990        size,
991        offset,
992        full_size,
993        binning,
994        reverse,
995    } in &nt.dimension
996    {
997        out.push(1); // non-null element indicator
998        out.extend_from_slice(&encode_i32(*size, is_be));
999        out.extend_from_slice(&encode_i32(*offset, is_be));
1000        out.extend_from_slice(&encode_i32(*full_size, is_be));
1001        out.extend_from_slice(&encode_i32(*binning, is_be));
1002        out.push(if *reverse { 1 } else { 0 });
1003    }
1004    out.extend_from_slice(&encode_i32(nt.unique_id, is_be));
1005    out.extend_from_slice(&encode_nt_timestamp(&nt.data_time_stamp, is_be));
1006    out.extend_from_slice(&encode_size_pvd(nt.attribute.len(), is_be));
1007    for attr in &nt.attribute {
1008        out.push(1); // non-null element indicator
1009        out.extend_from_slice(&encode_string_pvd(&attr.name, is_be));
1010        out.extend_from_slice(&encode_attribute_variant(attr, is_be));
1011        out.extend_from_slice(&encode_string_pvd(&attr.descriptor, is_be));
1012        out.extend_from_slice(&encode_i32(attr.source_type, is_be));
1013        out.extend_from_slice(&encode_string_pvd(&attr.source, is_be));
1014    }
1015    out.extend_from_slice(&encode_string_pvd(
1016        nt.descriptor.as_deref().unwrap_or(""),
1017        is_be,
1018    ));
1019    out.extend_from_slice(&encode_nt_alarm(
1020        nt.alarm.as_ref().unwrap_or(&NtAlarm::default()),
1021        is_be,
1022    ));
1023    out.extend_from_slice(&encode_nt_timestamp(
1024        nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
1025        is_be,
1026    ));
1027    out.extend_from_slice(&encode_nt_display(
1028        nt.display.as_ref().unwrap_or(&NtDisplay::default()),
1029        is_be,
1030    ));
1031    out
1032}
1033
1034pub fn nt_payload_desc(payload: &NtPayload) -> StructureDesc {
1035    match payload {
1036        NtPayload::Scalar(nt) => nt_scalar_desc(&nt.value),
1037        NtPayload::ScalarArray(nt) => nt_scalar_array_desc(&nt.value),
1038        NtPayload::Table(nt) => nt_table_desc(nt),
1039        NtPayload::NdArray(nt) => nt_ndarray_desc(nt),
1040    }
1041}
1042
1043pub fn encode_nt_payload_full(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1044    match payload {
1045        NtPayload::Scalar(nt) => encode_nt_scalar_full(nt, is_be),
1046        NtPayload::ScalarArray(nt) => encode_nt_scalar_array_full(nt, is_be),
1047        NtPayload::Table(nt) => encode_nt_table_full(nt, is_be),
1048        NtPayload::NdArray(nt) => encode_nt_ndarray_full(nt, is_be),
1049    }
1050}
1051
1052pub fn encode_nt_payload_bitset(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1053    let desc = nt_payload_desc(payload);
1054    let mut out = Vec::new();
1055    out.extend_from_slice(&encode_structure_bitset(&desc, is_be));
1056    out.extend_from_slice(&encode_nt_payload_full(payload, is_be));
1057    out
1058}
1059
1060pub fn encode_nt_payload_bitset_parts(payload: &NtPayload, is_be: bool) -> (Vec<u8>, Vec<u8>) {
1061    let desc = nt_payload_desc(payload);
1062    (
1063        encode_structure_bitset(&desc, is_be),
1064        encode_nt_payload_full(payload, is_be),
1065    )
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070    use super::*;
1071    use crate::spvd_decode::PvdDecoder;
1072
1073    #[test]
1074    fn nt_scalar_roundtrip() {
1075        let nt = NtScalar::from_value(ScalarValue::F64(12.5));
1076        let desc = nt_scalar_desc(&nt.value);
1077        let desc_bytes = encode_structure_desc(&desc, false);
1078        let mut pvd = Vec::new();
1079        pvd.push(0x80);
1080        pvd.extend_from_slice(&desc_bytes);
1081        pvd.extend_from_slice(&encode_nt_scalar_full(&nt, false));
1082
1083        let decoder = PvdDecoder::new(false);
1084        let parsed_desc = decoder.parse_introspection(&pvd).expect("desc");
1085        let (_, consumed) = decoder
1086            .decode_structure(&pvd[1 + desc_bytes.len()..], &parsed_desc)
1087            .expect("value");
1088        assert!(consumed > 0);
1089    }
1090
1091    #[test]
1092    fn nt_ndarray_roundtrip() {
1093        use spvirit_types::{NdCodec, NdDimension, NtAlarm, NtNdArray, NtTimeStamp, ScalarArrayValue};
1094        use std::collections::HashMap;
1095
1096        let nt = NtNdArray {
1097            value: ScalarArrayValue::U8(vec![1, 2, 3, 4]),
1098            codec: NdCodec {
1099                name: String::new(),
1100                parameters: HashMap::new(),
1101            },
1102            compressed_size: 4,
1103            uncompressed_size: 4,
1104            dimension: vec![NdDimension {
1105                size: 2,
1106                offset: 0,
1107                full_size: 2,
1108                binning: 1,
1109                reverse: false,
1110            }],
1111            unique_id: 42,
1112            data_time_stamp: NtTimeStamp {
1113                seconds_past_epoch: 1000,
1114                nanoseconds: 500,
1115                user_tag: 0,
1116            },
1117            attribute: Vec::new(),
1118            descriptor: Some("test".to_string()),
1119            alarm: Some(NtAlarm::default()),
1120            time_stamp: Some(NtTimeStamp::default()),
1121            display: None,
1122        };
1123
1124        let desc = nt_ndarray_desc(&nt);
1125        let desc_bytes = encode_structure_desc(&desc, false);
1126        let data_bytes = encode_nt_ndarray_full(&nt, false);
1127
1128        // Build complete PVD: type_tag + desc + data
1129        let mut pvd = Vec::new();
1130        pvd.push(0x80);
1131        pvd.extend_from_slice(&desc_bytes);
1132        pvd.extend_from_slice(&data_bytes);
1133
1134        let decoder = PvdDecoder::new(false);
1135        let parsed_desc = decoder.parse_introspection(&pvd).expect("desc parse failed");
1136        let data_start = 1 + desc_bytes.len();
1137        let (decoded, consumed) = decoder
1138            .decode_structure(&pvd[data_start..], &parsed_desc)
1139            .expect("data decode failed");
1140        assert!(consumed > 0, "consumed should be > 0");
1141        assert_eq!(consumed, data_bytes.len(), "consumed should match data_bytes.len()");
1142    }
1143}