1use 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); 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); 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)); 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); 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); 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 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}