1use 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); 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::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)); out
199}
200
201fn encode_display(nt: &NtScalar, is_be: bool) -> Vec<u8> {
202 let mut out = Vec::new();
203 out.extend_from_slice(&encode_f64(nt.display_low, is_be));
204 out.extend_from_slice(&encode_f64(nt.display_high, is_be));
205 out.extend_from_slice(&encode_string_pvd(&nt.display_description, is_be));
206 out.extend_from_slice(&encode_string_pvd(&nt.units, is_be));
207 out.extend_from_slice(&encode_i32(nt.display_precision, is_be));
208 out.extend_from_slice(&encode_enum(
209 nt.display_form_index,
210 &nt.display_form_choices,
211 is_be,
212 ));
213 out
214}
215
216fn encode_control(nt: &NtScalar, is_be: bool) -> Vec<u8> {
217 let mut out = Vec::new();
218 out.extend_from_slice(&encode_f64(nt.control_low, is_be));
219 out.extend_from_slice(&encode_f64(nt.control_high, is_be));
220 out.extend_from_slice(&encode_f64(nt.control_min_step, is_be));
221 out
222}
223
224fn encode_value_alarm(nt: &NtScalar, is_be: bool) -> Vec<u8> {
225 let mut out = Vec::new();
226 out.extend_from_slice(&encode_bool(nt.value_alarm_active));
227 out.extend_from_slice(&encode_f64(nt.value_alarm_low_alarm_limit, is_be));
228 out.extend_from_slice(&encode_f64(nt.value_alarm_low_warning_limit, is_be));
229 out.extend_from_slice(&encode_f64(nt.value_alarm_high_warning_limit, is_be));
230 out.extend_from_slice(&encode_f64(nt.value_alarm_high_alarm_limit, is_be));
231 out.extend_from_slice(&encode_i32(nt.value_alarm_low_alarm_severity, is_be));
232 out.extend_from_slice(&encode_i32(nt.value_alarm_low_warning_severity, is_be));
233 out.extend_from_slice(&encode_i32(nt.value_alarm_high_warning_severity, is_be));
234 out.extend_from_slice(&encode_i32(nt.value_alarm_high_alarm_severity, is_be));
235 out.push(nt.value_alarm_hysteresis);
236 out
237}
238
239fn encode_i32(value: i32, is_be: bool) -> Vec<u8> {
240 if is_be {
241 value.to_be_bytes().to_vec()
242 } else {
243 value.to_le_bytes().to_vec()
244 }
245}
246
247fn encode_i64(value: i64, is_be: bool) -> Vec<u8> {
248 if is_be {
249 value.to_be_bytes().to_vec()
250 } else {
251 value.to_le_bytes().to_vec()
252 }
253}
254
255fn encode_f64(value: f64, is_be: bool) -> Vec<u8> {
256 if is_be {
257 value.to_be_bytes().to_vec()
258 } else {
259 value.to_le_bytes().to_vec()
260 }
261}
262
263pub fn nt_scalar_desc(value: &ScalarValue) -> StructureDesc {
264 let value_type = match value {
265 ScalarValue::Bool(_) => FieldType::Scalar(TypeCode::Boolean),
266 ScalarValue::I8(_) => FieldType::Scalar(TypeCode::Int8),
267 ScalarValue::I16(_) => FieldType::Scalar(TypeCode::Int16),
268 ScalarValue::I32(_) => FieldType::Scalar(TypeCode::Int32),
269 ScalarValue::I64(_) => FieldType::Scalar(TypeCode::Int64),
270 ScalarValue::U8(_) => FieldType::Scalar(TypeCode::UInt8),
271 ScalarValue::U16(_) => FieldType::Scalar(TypeCode::UInt16),
272 ScalarValue::U32(_) => FieldType::Scalar(TypeCode::UInt32),
273 ScalarValue::U64(_) => FieldType::Scalar(TypeCode::UInt64),
274 ScalarValue::F32(_) => FieldType::Scalar(TypeCode::Float32),
275 ScalarValue::F64(_) => FieldType::Scalar(TypeCode::Float64),
276 ScalarValue::Str(_) => FieldType::String,
277 };
278
279 StructureDesc {
280 struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
281 fields: vec![
282 FieldDesc {
283 name: "value".to_string(),
284 field_type: value_type,
285 },
286 FieldDesc {
287 name: "alarm".to_string(),
288 field_type: FieldType::Structure(StructureDesc {
289 struct_id: Some("alarm_t".to_string()),
290 fields: vec![
291 FieldDesc {
292 name: "severity".to_string(),
293 field_type: FieldType::Scalar(TypeCode::Int32),
294 },
295 FieldDesc {
296 name: "status".to_string(),
297 field_type: FieldType::Scalar(TypeCode::Int32),
298 },
299 FieldDesc {
300 name: "message".to_string(),
301 field_type: FieldType::String,
302 },
303 ],
304 }),
305 },
306 FieldDesc {
307 name: "timeStamp".to_string(),
308 field_type: FieldType::Structure(StructureDesc {
309 struct_id: None,
310 fields: vec![
311 FieldDesc {
312 name: "secondsPastEpoch".to_string(),
313 field_type: FieldType::Scalar(TypeCode::Int64),
314 },
315 FieldDesc {
316 name: "nanoseconds".to_string(),
317 field_type: FieldType::Scalar(TypeCode::Int32),
318 },
319 FieldDesc {
320 name: "userTag".to_string(),
321 field_type: FieldType::Scalar(TypeCode::Int32),
322 },
323 ],
324 }),
325 },
326 FieldDesc {
327 name: "display".to_string(),
328 field_type: FieldType::Structure(StructureDesc {
329 struct_id: None,
330 fields: vec![
331 FieldDesc {
332 name: "limitLow".to_string(),
333 field_type: FieldType::Scalar(TypeCode::Float64),
334 },
335 FieldDesc {
336 name: "limitHigh".to_string(),
337 field_type: FieldType::Scalar(TypeCode::Float64),
338 },
339 FieldDesc {
340 name: "description".to_string(),
341 field_type: FieldType::String,
342 },
343 FieldDesc {
344 name: "units".to_string(),
345 field_type: FieldType::String,
346 },
347 FieldDesc {
348 name: "precision".to_string(),
349 field_type: FieldType::Scalar(TypeCode::Int32),
350 },
351 FieldDesc {
352 name: "form".to_string(),
353 field_type: FieldType::Structure(StructureDesc {
354 struct_id: Some("enum_t".to_string()),
355 fields: vec![
356 FieldDesc {
357 name: "index".to_string(),
358 field_type: FieldType::Scalar(TypeCode::Int32),
359 },
360 FieldDesc {
361 name: "choices".to_string(),
362 field_type: FieldType::StringArray,
363 },
364 ],
365 }),
366 },
367 ],
368 }),
369 },
370 FieldDesc {
371 name: "control".to_string(),
372 field_type: FieldType::Structure(StructureDesc {
373 struct_id: Some("control_t".to_string()),
374 fields: vec![
375 FieldDesc {
376 name: "limitLow".to_string(),
377 field_type: FieldType::Scalar(TypeCode::Float64),
378 },
379 FieldDesc {
380 name: "limitHigh".to_string(),
381 field_type: FieldType::Scalar(TypeCode::Float64),
382 },
383 FieldDesc {
384 name: "minStep".to_string(),
385 field_type: FieldType::Scalar(TypeCode::Float64),
386 },
387 ],
388 }),
389 },
390 FieldDesc {
391 name: "valueAlarm".to_string(),
392 field_type: FieldType::Structure(StructureDesc {
393 struct_id: Some("valueAlarm_t".to_string()),
394 fields: vec![
395 FieldDesc {
396 name: "active".to_string(),
397 field_type: FieldType::Scalar(TypeCode::Boolean),
398 },
399 FieldDesc {
400 name: "lowAlarmLimit".to_string(),
401 field_type: FieldType::Scalar(TypeCode::Float64),
402 },
403 FieldDesc {
404 name: "lowWarningLimit".to_string(),
405 field_type: FieldType::Scalar(TypeCode::Float64),
406 },
407 FieldDesc {
408 name: "highWarningLimit".to_string(),
409 field_type: FieldType::Scalar(TypeCode::Float64),
410 },
411 FieldDesc {
412 name: "highAlarmLimit".to_string(),
413 field_type: FieldType::Scalar(TypeCode::Float64),
414 },
415 FieldDesc {
416 name: "lowAlarmSeverity".to_string(),
417 field_type: FieldType::Scalar(TypeCode::Int32),
418 },
419 FieldDesc {
420 name: "lowWarningSeverity".to_string(),
421 field_type: FieldType::Scalar(TypeCode::Int32),
422 },
423 FieldDesc {
424 name: "highWarningSeverity".to_string(),
425 field_type: FieldType::Scalar(TypeCode::Int32),
426 },
427 FieldDesc {
428 name: "highAlarmSeverity".to_string(),
429 field_type: FieldType::Scalar(TypeCode::Int32),
430 },
431 FieldDesc {
432 name: "hysteresis".to_string(),
433 field_type: FieldType::Scalar(TypeCode::UInt8),
434 },
435 ],
436 }),
437 },
438 ],
439 }
440}
441
442pub fn encode_nt_scalar_full(nt: &NtScalar, is_be: bool) -> Vec<u8> {
443 let mut out = Vec::new();
444 out.extend_from_slice(&encode_scalar_value(&nt.value, is_be));
445 out.extend_from_slice(&encode_alarm(nt, is_be));
446 out.extend_from_slice(&encode_timestamp(nt, is_be));
447 out.extend_from_slice(&encode_display(nt, is_be));
448 out.extend_from_slice(&encode_control(nt, is_be));
449 out.extend_from_slice(&encode_value_alarm(nt, is_be));
450 out
451}
452
453fn encode_structure_bitset(desc: &StructureDesc, is_be: bool) -> Vec<u8> {
454 let total_bits = 1 + count_structure_fields(desc);
455 let bitset_size = (total_bits + 7) / 8;
456 let mut bitset = vec![0u8; bitset_size];
457 for bit in 0..total_bits {
458 let byte_idx = bit / 8;
459 let bit_idx = bit % 8;
460 bitset[byte_idx] |= 1 << bit_idx;
461 }
462 let mut out = Vec::new();
463 out.extend_from_slice(&encode_size_pvd(bitset_size, is_be));
464 out.extend_from_slice(&bitset);
465 out
466}
467
468fn encode_structure_with_bitset(desc: &StructureDesc, nt: &NtScalar, is_be: bool) -> Vec<u8> {
469 let mut out = Vec::new();
470 out.extend_from_slice(&encode_structure_bitset(desc, is_be));
471 out.extend_from_slice(&encode_nt_scalar_full(nt, is_be));
472 out
473}
474
475pub fn encode_nt_scalar_bitset(nt: &NtScalar, is_be: bool) -> Vec<u8> {
476 let desc = nt_scalar_desc(&nt.value);
477 encode_structure_with_bitset(&desc, nt, is_be)
478}
479
480pub fn encode_nt_scalar_bitset_parts(nt: &NtScalar, is_be: bool) -> (Vec<u8>, Vec<u8>) {
481 let desc = nt_scalar_desc(&nt.value);
482 let bitset = encode_structure_bitset(&desc, is_be);
483 let values = encode_nt_scalar_full(nt, is_be);
484 (bitset, values)
485}
486
487fn alarm_desc() -> StructureDesc {
488 StructureDesc {
489 struct_id: Some("alarm_t".to_string()),
490 fields: vec![
491 FieldDesc {
492 name: "severity".to_string(),
493 field_type: FieldType::Scalar(TypeCode::Int32),
494 },
495 FieldDesc {
496 name: "status".to_string(),
497 field_type: FieldType::Scalar(TypeCode::Int32),
498 },
499 FieldDesc {
500 name: "message".to_string(),
501 field_type: FieldType::String,
502 },
503 ],
504 }
505}
506
507fn timestamp_desc() -> StructureDesc {
508 StructureDesc {
509 struct_id: Some("time_t".to_string()),
510 fields: vec![
511 FieldDesc {
512 name: "secondsPastEpoch".to_string(),
513 field_type: FieldType::Scalar(TypeCode::Int64),
514 },
515 FieldDesc {
516 name: "nanoseconds".to_string(),
517 field_type: FieldType::Scalar(TypeCode::Int32),
518 },
519 FieldDesc {
520 name: "userTag".to_string(),
521 field_type: FieldType::Scalar(TypeCode::Int32),
522 },
523 ],
524 }
525}
526
527fn display_desc() -> StructureDesc {
528 StructureDesc {
529 struct_id: Some("display_t".to_string()),
530 fields: vec![
531 FieldDesc {
532 name: "limitLow".to_string(),
533 field_type: FieldType::Scalar(TypeCode::Float64),
534 },
535 FieldDesc {
536 name: "limitHigh".to_string(),
537 field_type: FieldType::Scalar(TypeCode::Float64),
538 },
539 FieldDesc {
540 name: "description".to_string(),
541 field_type: FieldType::String,
542 },
543 FieldDesc {
544 name: "units".to_string(),
545 field_type: FieldType::String,
546 },
547 FieldDesc {
548 name: "precision".to_string(),
549 field_type: FieldType::Scalar(TypeCode::Int32),
550 },
551 ],
552 }
553}
554
555fn scalar_array_field_type(value: &ScalarArrayValue) -> FieldType {
556 match value {
557 ScalarArrayValue::Bool(_) => FieldType::ScalarArray(TypeCode::Boolean),
558 ScalarArrayValue::I8(_) => FieldType::ScalarArray(TypeCode::Int8),
559 ScalarArrayValue::I16(_) => FieldType::ScalarArray(TypeCode::Int16),
560 ScalarArrayValue::I32(_) => FieldType::ScalarArray(TypeCode::Int32),
561 ScalarArrayValue::I64(_) => FieldType::ScalarArray(TypeCode::Int64),
562 ScalarArrayValue::U8(_) => FieldType::ScalarArray(TypeCode::UInt8),
563 ScalarArrayValue::U16(_) => FieldType::ScalarArray(TypeCode::UInt16),
564 ScalarArrayValue::U32(_) => FieldType::ScalarArray(TypeCode::UInt32),
565 ScalarArrayValue::U64(_) => FieldType::ScalarArray(TypeCode::UInt64),
566 ScalarArrayValue::F32(_) => FieldType::ScalarArray(TypeCode::Float32),
567 ScalarArrayValue::F64(_) => FieldType::ScalarArray(TypeCode::Float64),
568 ScalarArrayValue::Str(_) => FieldType::StringArray,
569 }
570}
571
572fn encode_scalar_array_value_pvd(value: &ScalarArrayValue, is_be: bool) -> Vec<u8> {
573 let mut out = Vec::new();
574 match value {
575 ScalarArrayValue::Bool(v) => {
576 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
577 for i in v {
578 out.push(if *i { 1 } else { 0 });
579 }
580 }
581 ScalarArrayValue::I8(v) => {
582 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
583 for i in v {
584 out.push(*i as u8);
585 }
586 }
587 ScalarArrayValue::I16(v) => {
588 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
589 for i in v {
590 let b = if is_be {
591 i.to_be_bytes()
592 } else {
593 i.to_le_bytes()
594 };
595 out.extend_from_slice(&b);
596 }
597 }
598 ScalarArrayValue::I32(v) => {
599 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
600 for i in v {
601 out.extend_from_slice(&encode_i32(*i, is_be));
602 }
603 }
604 ScalarArrayValue::I64(v) => {
605 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
606 for i in v {
607 out.extend_from_slice(&encode_i64(*i, is_be));
608 }
609 }
610 ScalarArrayValue::U8(v) => {
611 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
612 out.extend_from_slice(v);
613 }
614 ScalarArrayValue::U16(v) => {
615 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
616 for i in v {
617 let b = if is_be {
618 i.to_be_bytes()
619 } else {
620 i.to_le_bytes()
621 };
622 out.extend_from_slice(&b);
623 }
624 }
625 ScalarArrayValue::U32(v) => {
626 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
627 for i in v {
628 let b = if is_be {
629 i.to_be_bytes()
630 } else {
631 i.to_le_bytes()
632 };
633 out.extend_from_slice(&b);
634 }
635 }
636 ScalarArrayValue::U64(v) => {
637 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
638 for i in v {
639 let b = if is_be {
640 i.to_be_bytes()
641 } else {
642 i.to_le_bytes()
643 };
644 out.extend_from_slice(&b);
645 }
646 }
647 ScalarArrayValue::F32(v) => {
648 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
649 for i in v {
650 let b = if is_be {
651 i.to_be_bytes()
652 } else {
653 i.to_le_bytes()
654 };
655 out.extend_from_slice(&b);
656 }
657 }
658 ScalarArrayValue::F64(v) => {
659 out.extend_from_slice(&encode_size_pvd(v.len(), is_be));
660 for i in v {
661 out.extend_from_slice(&encode_f64(*i, is_be));
662 }
663 }
664 ScalarArrayValue::Str(v) => {
665 out.extend_from_slice(&encode_string_array(v, is_be));
666 }
667 }
668 out
669}
670
671fn encode_nt_alarm(alarm: &NtAlarm, is_be: bool) -> Vec<u8> {
672 let mut out = Vec::new();
673 out.extend_from_slice(&encode_i32(alarm.severity, is_be));
674 out.extend_from_slice(&encode_i32(alarm.status, is_be));
675 out.extend_from_slice(&encode_string_pvd(&alarm.message, is_be));
676 out
677}
678
679fn encode_nt_timestamp(ts: &NtTimeStamp, is_be: bool) -> Vec<u8> {
680 let mut out = Vec::new();
681 out.extend_from_slice(&encode_i64(ts.seconds_past_epoch, is_be));
682 out.extend_from_slice(&encode_i32(ts.nanoseconds, is_be));
683 out.extend_from_slice(&encode_i32(ts.user_tag, is_be));
684 out
685}
686
687fn encode_nt_display(display: &NtDisplay, is_be: bool) -> Vec<u8> {
688 let mut out = Vec::new();
689 out.extend_from_slice(&encode_f64(display.limit_low, is_be));
690 out.extend_from_slice(&encode_f64(display.limit_high, is_be));
691 out.extend_from_slice(&encode_string_pvd(&display.description, is_be));
692 out.extend_from_slice(&encode_string_pvd(&display.units, is_be));
693 out.extend_from_slice(&encode_i32(display.precision, is_be));
694 out
695}
696
697pub fn nt_scalar_array_desc(value: &ScalarArrayValue) -> StructureDesc {
698 StructureDesc {
699 struct_id: Some("epics:nt/NTScalarArray:1.0".to_string()),
700 fields: vec![
701 FieldDesc {
702 name: "value".to_string(),
703 field_type: scalar_array_field_type(value),
704 },
705 FieldDesc {
706 name: "alarm".to_string(),
707 field_type: FieldType::Structure(alarm_desc()),
708 },
709 FieldDesc {
710 name: "timeStamp".to_string(),
711 field_type: FieldType::Structure(timestamp_desc()),
712 },
713 FieldDesc {
714 name: "display".to_string(),
715 field_type: FieldType::Structure(display_desc()),
716 },
717 FieldDesc {
718 name: "control".to_string(),
719 field_type: FieldType::Structure(StructureDesc {
720 struct_id: Some("control_t".to_string()),
721 fields: vec![
722 FieldDesc {
723 name: "limitLow".to_string(),
724 field_type: FieldType::Scalar(TypeCode::Float64),
725 },
726 FieldDesc {
727 name: "limitHigh".to_string(),
728 field_type: FieldType::Scalar(TypeCode::Float64),
729 },
730 FieldDesc {
731 name: "minStep".to_string(),
732 field_type: FieldType::Scalar(TypeCode::Float64),
733 },
734 ],
735 }),
736 },
737 ],
738 }
739}
740
741pub fn encode_nt_scalar_array_full(nt: &NtScalarArray, is_be: bool) -> Vec<u8> {
742 let mut out = Vec::new();
743 out.extend_from_slice(&encode_scalar_array_value_pvd(&nt.value, is_be));
744 out.extend_from_slice(&encode_nt_alarm(&nt.alarm, is_be));
745 out.extend_from_slice(&encode_nt_timestamp(&nt.time_stamp, is_be));
746 out.extend_from_slice(&encode_nt_display(&nt.display, is_be));
747 out.extend_from_slice(&encode_f64(nt.control.limit_low, is_be));
748 out.extend_from_slice(&encode_f64(nt.control.limit_high, is_be));
749 out.extend_from_slice(&encode_f64(nt.control.min_step, is_be));
750 out
751}
752
753pub fn nt_table_desc(nt: &NtTable) -> StructureDesc {
754 let mut value_fields: Vec<FieldDesc> = Vec::new();
755 for col in &nt.columns {
756 value_fields.push(FieldDesc {
757 name: col.name.clone(),
758 field_type: scalar_array_field_type(&col.values),
759 });
760 }
761 StructureDesc {
762 struct_id: Some("epics:nt/NTTable:1.0".to_string()),
763 fields: vec![
764 FieldDesc {
765 name: "labels".to_string(),
766 field_type: FieldType::StringArray,
767 },
768 FieldDesc {
769 name: "value".to_string(),
770 field_type: FieldType::Structure(StructureDesc {
771 struct_id: None,
772 fields: value_fields,
773 }),
774 },
775 ],
776 }
777}
778
779pub fn encode_nt_table_full(nt: &NtTable, is_be: bool) -> Vec<u8> {
780 let mut out = Vec::new();
781 out.extend_from_slice(&encode_string_array(&nt.labels, is_be));
782 for NtTableColumn { values, .. } in &nt.columns {
783 out.extend_from_slice(&encode_scalar_array_value_pvd(values, is_be));
784 }
785 out
786}
787
788fn nt_ndarray_value_union_fields() -> Vec<FieldDesc> {
789 vec![
790 FieldDesc {
791 name: "booleanValue".to_string(),
792 field_type: FieldType::ScalarArray(TypeCode::Boolean),
793 },
794 FieldDesc {
795 name: "byteValue".to_string(),
796 field_type: FieldType::ScalarArray(TypeCode::Int8),
797 },
798 FieldDesc {
799 name: "shortValue".to_string(),
800 field_type: FieldType::ScalarArray(TypeCode::Int16),
801 },
802 FieldDesc {
803 name: "intValue".to_string(),
804 field_type: FieldType::ScalarArray(TypeCode::Int32),
805 },
806 FieldDesc {
807 name: "longValue".to_string(),
808 field_type: FieldType::ScalarArray(TypeCode::Int64),
809 },
810 FieldDesc {
811 name: "ubyteValue".to_string(),
812 field_type: FieldType::ScalarArray(TypeCode::UInt8),
813 },
814 FieldDesc {
815 name: "ushortValue".to_string(),
816 field_type: FieldType::ScalarArray(TypeCode::UInt16),
817 },
818 FieldDesc {
819 name: "uintValue".to_string(),
820 field_type: FieldType::ScalarArray(TypeCode::UInt32),
821 },
822 FieldDesc {
823 name: "ulongValue".to_string(),
824 field_type: FieldType::ScalarArray(TypeCode::UInt64),
825 },
826 FieldDesc {
827 name: "floatValue".to_string(),
828 field_type: FieldType::ScalarArray(TypeCode::Float32),
829 },
830 FieldDesc {
831 name: "doubleValue".to_string(),
832 field_type: FieldType::ScalarArray(TypeCode::Float64),
833 },
834 FieldDesc {
835 name: "stringValue".to_string(),
836 field_type: FieldType::StringArray,
837 },
838 ]
839}
840
841fn ndarray_union_index(value: &ScalarArrayValue) -> usize {
842 match value {
843 ScalarArrayValue::Bool(_) => 0,
844 ScalarArrayValue::I8(_) => 1,
845 ScalarArrayValue::I16(_) => 2,
846 ScalarArrayValue::I32(_) => 3,
847 ScalarArrayValue::I64(_) => 4,
848 ScalarArrayValue::U8(_) => 5,
849 ScalarArrayValue::U16(_) => 6,
850 ScalarArrayValue::U32(_) => 7,
851 ScalarArrayValue::U64(_) => 8,
852 ScalarArrayValue::F32(_) => 9,
853 ScalarArrayValue::F64(_) => 10,
854 ScalarArrayValue::Str(_) => 11,
855 }
856}
857
858fn encode_ndarray_union(value: &ScalarArrayValue, is_be: bool) -> Vec<u8> {
859 let mut out = Vec::new();
860 out.extend_from_slice(&encode_size_pvd(ndarray_union_index(value), is_be));
861 out.extend_from_slice(&encode_scalar_array_value_pvd(value, is_be));
862 out
863}
864
865fn encode_codec_parameters(
866 parameters: &std::collections::HashMap<String, String>,
867 is_be: bool,
868) -> Vec<u8> {
869 if parameters.is_empty() {
870 return vec![0xFF];
871 }
872 let mut out = Vec::new();
873 out.push(0x80);
874 let mut fields = Vec::new();
875 for key in parameters.keys() {
876 fields.push(FieldDesc {
877 name: key.clone(),
878 field_type: FieldType::String,
879 });
880 }
881 let desc = StructureDesc {
882 struct_id: None,
883 fields,
884 };
885 out.extend_from_slice(&encode_structure_desc(&desc, is_be));
886 for value in parameters.values() {
887 out.extend_from_slice(&encode_string_pvd(value, is_be));
888 }
889 out
890}
891
892pub fn nt_ndarray_desc_default() -> StructureDesc {
893 nt_ndarray_desc(&NtNdArray::empty())
894}
895
896pub fn nt_ndarray_desc(_nt: &NtNdArray) -> StructureDesc {
897 StructureDesc {
898 struct_id: Some("epics:nt/NTNDArray:1.0".to_string()),
899 fields: vec![
900 FieldDesc {
901 name: "value".to_string(),
902 field_type: FieldType::Union(nt_ndarray_value_union_fields()),
903 },
904 FieldDesc {
905 name: "codec".to_string(),
906 field_type: FieldType::Structure(StructureDesc {
907 struct_id: Some("codec_t".to_string()),
908 fields: vec![
909 FieldDesc {
910 name: "name".to_string(),
911 field_type: FieldType::String,
912 },
913 FieldDesc {
914 name: "parameters".to_string(),
915 field_type: FieldType::Variant,
916 },
917 ],
918 }),
919 },
920 FieldDesc {
921 name: "compressedSize".to_string(),
922 field_type: FieldType::Scalar(TypeCode::Int64),
923 },
924 FieldDesc {
925 name: "uncompressedSize".to_string(),
926 field_type: FieldType::Scalar(TypeCode::Int64),
927 },
928 FieldDesc {
929 name: "dimension".to_string(),
930 field_type: FieldType::StructureArray(StructureDesc {
931 struct_id: Some("dimension_t".to_string()),
932 fields: vec![
933 FieldDesc {
934 name: "size".to_string(),
935 field_type: FieldType::Scalar(TypeCode::Int32),
936 },
937 FieldDesc {
938 name: "offset".to_string(),
939 field_type: FieldType::Scalar(TypeCode::Int32),
940 },
941 FieldDesc {
942 name: "fullSize".to_string(),
943 field_type: FieldType::Scalar(TypeCode::Int32),
944 },
945 FieldDesc {
946 name: "binning".to_string(),
947 field_type: FieldType::Scalar(TypeCode::Int32),
948 },
949 FieldDesc {
950 name: "reverse".to_string(),
951 field_type: FieldType::Scalar(TypeCode::Boolean),
952 },
953 ],
954 }),
955 },
956 FieldDesc {
957 name: "uniqueId".to_string(),
958 field_type: FieldType::Scalar(TypeCode::Int32),
959 },
960 FieldDesc {
961 name: "dataTimeStamp".to_string(),
962 field_type: FieldType::Structure(timestamp_desc()),
963 },
964 FieldDesc {
965 name: "attribute".to_string(),
966 field_type: FieldType::StructureArray(StructureDesc {
967 struct_id: Some("NTAttribute".to_string()),
968 fields: vec![
969 FieldDesc {
970 name: "name".to_string(),
971 field_type: FieldType::String,
972 },
973 FieldDesc {
974 name: "value".to_string(),
975 field_type: FieldType::Variant,
976 },
977 FieldDesc {
978 name: "descriptor".to_string(),
979 field_type: FieldType::String,
980 },
981 FieldDesc {
982 name: "sourceType".to_string(),
983 field_type: FieldType::Scalar(TypeCode::Int32),
984 },
985 FieldDesc {
986 name: "source".to_string(),
987 field_type: FieldType::String,
988 },
989 ],
990 }),
991 },
992 FieldDesc {
993 name: "descriptor".to_string(),
994 field_type: FieldType::String,
995 },
996 FieldDesc {
997 name: "alarm".to_string(),
998 field_type: FieldType::Structure(alarm_desc()),
999 },
1000 FieldDesc {
1001 name: "timeStamp".to_string(),
1002 field_type: FieldType::Structure(timestamp_desc()),
1003 },
1004 FieldDesc {
1005 name: "display".to_string(),
1006 field_type: FieldType::Structure(display_desc()),
1007 },
1008 ],
1009 }
1010}
1011
1012fn encode_attribute_variant(attr: &NtAttribute, is_be: bool) -> Vec<u8> {
1013 match &attr.value {
1014 ScalarValue::Bool(v) => {
1015 let mut out = vec![TypeCode::Boolean as u8];
1016 out.push(if *v { 1 } else { 0 });
1017 out
1018 }
1019 ScalarValue::I8(v) => {
1020 let mut out = vec![TypeCode::Int8 as u8];
1021 out.push(*v as u8);
1022 out
1023 }
1024 ScalarValue::I16(v) => {
1025 let mut out = vec![TypeCode::Int16 as u8];
1026 out.extend_from_slice(&if is_be {
1027 v.to_be_bytes().to_vec()
1028 } else {
1029 v.to_le_bytes().to_vec()
1030 });
1031 out
1032 }
1033 ScalarValue::I32(v) => {
1034 let mut out = vec![TypeCode::Int32 as u8];
1035 out.extend_from_slice(&encode_i32(*v, is_be));
1036 out
1037 }
1038 ScalarValue::I64(v) => {
1039 let mut out = vec![TypeCode::Int64 as u8];
1040 out.extend_from_slice(&encode_i64(*v, is_be));
1041 out
1042 }
1043 ScalarValue::U8(v) => {
1044 let mut out = vec![TypeCode::UInt8 as u8];
1045 out.push(*v);
1046 out
1047 }
1048 ScalarValue::U16(v) => {
1049 let mut out = vec![TypeCode::UInt16 as u8];
1050 out.extend_from_slice(&if is_be {
1051 v.to_be_bytes().to_vec()
1052 } else {
1053 v.to_le_bytes().to_vec()
1054 });
1055 out
1056 }
1057 ScalarValue::U32(v) => {
1058 let mut out = vec![TypeCode::UInt32 as u8];
1059 out.extend_from_slice(&if is_be {
1060 v.to_be_bytes().to_vec()
1061 } else {
1062 v.to_le_bytes().to_vec()
1063 });
1064 out
1065 }
1066 ScalarValue::U64(v) => {
1067 let mut out = vec![TypeCode::UInt64 as u8];
1068 out.extend_from_slice(&if is_be {
1069 v.to_be_bytes().to_vec()
1070 } else {
1071 v.to_le_bytes().to_vec()
1072 });
1073 out
1074 }
1075 ScalarValue::F32(v) => {
1076 let mut out = vec![TypeCode::Float32 as u8];
1077 out.extend_from_slice(&if is_be {
1078 v.to_be_bytes().to_vec()
1079 } else {
1080 v.to_le_bytes().to_vec()
1081 });
1082 out
1083 }
1084 ScalarValue::F64(v) => {
1085 let mut out = vec![TypeCode::Float64 as u8];
1086 out.extend_from_slice(&encode_f64(*v, is_be));
1087 out
1088 }
1089 ScalarValue::Str(v) => {
1090 let mut out = vec![TypeCode::String as u8];
1091 out.extend_from_slice(&encode_string_pvd(v, is_be));
1092 out
1093 }
1094 }
1095}
1096
1097pub fn encode_nt_ndarray_full(nt: &NtNdArray, is_be: bool) -> Vec<u8> {
1098 let mut out = Vec::new();
1099 out.extend_from_slice(&encode_ndarray_union(&nt.value, is_be));
1100 out.extend_from_slice(&encode_string_pvd(&nt.codec.name, is_be));
1101 out.extend_from_slice(&encode_codec_parameters(&nt.codec.parameters, is_be));
1102 out.extend_from_slice(&encode_i64(nt.compressed_size, is_be));
1103 out.extend_from_slice(&encode_i64(nt.uncompressed_size, is_be));
1104 out.extend_from_slice(&encode_size_pvd(nt.dimension.len(), is_be));
1105 for NdDimension {
1106 size,
1107 offset,
1108 full_size,
1109 binning,
1110 reverse,
1111 } in &nt.dimension
1112 {
1113 out.push(1); out.extend_from_slice(&encode_i32(*size, is_be));
1115 out.extend_from_slice(&encode_i32(*offset, is_be));
1116 out.extend_from_slice(&encode_i32(*full_size, is_be));
1117 out.extend_from_slice(&encode_i32(*binning, is_be));
1118 out.push(if *reverse { 1 } else { 0 });
1119 }
1120 out.extend_from_slice(&encode_i32(nt.unique_id, is_be));
1121 out.extend_from_slice(&encode_nt_timestamp(&nt.data_time_stamp, is_be));
1122 out.extend_from_slice(&encode_size_pvd(nt.attribute.len(), is_be));
1123 for attr in &nt.attribute {
1124 out.push(1); out.extend_from_slice(&encode_string_pvd(&attr.name, is_be));
1126 out.extend_from_slice(&encode_attribute_variant(attr, is_be));
1127 out.extend_from_slice(&encode_string_pvd(&attr.descriptor, is_be));
1128 out.extend_from_slice(&encode_i32(attr.source_type, is_be));
1129 out.extend_from_slice(&encode_string_pvd(&attr.source, is_be));
1130 }
1131 out.extend_from_slice(&encode_string_pvd(
1132 nt.descriptor.as_deref().unwrap_or(""),
1133 is_be,
1134 ));
1135 out.extend_from_slice(&encode_nt_alarm(
1136 nt.alarm.as_ref().unwrap_or(&NtAlarm::default()),
1137 is_be,
1138 ));
1139 out.extend_from_slice(&encode_nt_timestamp(
1140 nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
1141 is_be,
1142 ));
1143 out.extend_from_slice(&encode_nt_display(
1144 nt.display.as_ref().unwrap_or(&NtDisplay::default()),
1145 is_be,
1146 ));
1147 out
1148}
1149
1150pub fn nt_enum_desc() -> StructureDesc {
1155 StructureDesc {
1156 struct_id: Some("epics:nt/NTEnum:1.0".to_string()),
1157 fields: vec![
1158 FieldDesc {
1159 name: "value".to_string(),
1160 field_type: FieldType::Structure(StructureDesc {
1161 struct_id: Some("enum_t".to_string()),
1162 fields: vec![
1163 FieldDesc {
1164 name: "index".to_string(),
1165 field_type: FieldType::Scalar(TypeCode::Int32),
1166 },
1167 FieldDesc {
1168 name: "choices".to_string(),
1169 field_type: FieldType::StringArray,
1170 },
1171 ],
1172 }),
1173 },
1174 FieldDesc {
1175 name: "alarm".to_string(),
1176 field_type: FieldType::Structure(alarm_desc()),
1177 },
1178 FieldDesc {
1179 name: "timeStamp".to_string(),
1180 field_type: FieldType::Structure(timestamp_desc()),
1181 },
1182 ],
1183 }
1184}
1185
1186pub fn encode_nt_enum_full(nt: &NtEnum, is_be: bool) -> Vec<u8> {
1187 let mut out = Vec::new();
1188 out.extend_from_slice(&encode_enum(nt.index, &nt.choices, is_be));
1190 out.extend_from_slice(&encode_nt_alarm(&nt.alarm, is_be));
1192 out.extend_from_slice(&encode_nt_timestamp(&nt.time_stamp, is_be));
1194 out
1195}
1196
1197fn scalar_value_type_code(v: &ScalarValue) -> TypeCode {
1202 match v {
1203 ScalarValue::Bool(_) => TypeCode::Boolean,
1204 ScalarValue::I8(_) => TypeCode::Int8,
1205 ScalarValue::I16(_) => TypeCode::Int16,
1206 ScalarValue::I32(_) => TypeCode::Int32,
1207 ScalarValue::I64(_) => TypeCode::Int64,
1208 ScalarValue::U8(_) => TypeCode::UInt8,
1209 ScalarValue::U16(_) => TypeCode::UInt16,
1210 ScalarValue::U32(_) => TypeCode::UInt32,
1211 ScalarValue::U64(_) => TypeCode::UInt64,
1212 ScalarValue::F32(_) => TypeCode::Float32,
1213 ScalarValue::F64(_) => TypeCode::Float64,
1214 ScalarValue::Str(_) => TypeCode::String,
1215 }
1216}
1217
1218pub fn pv_value_desc(struct_id: &str, fields: &[(String, PvValue)]) -> StructureDesc {
1220 StructureDesc {
1221 struct_id: if struct_id.is_empty() {
1222 None
1223 } else {
1224 Some(struct_id.to_string())
1225 },
1226 fields: fields
1227 .iter()
1228 .map(|(name, val)| FieldDesc {
1229 name: name.clone(),
1230 field_type: pv_value_field_type(val),
1231 })
1232 .collect(),
1233 }
1234}
1235
1236fn pv_value_field_type(val: &PvValue) -> FieldType {
1237 match val {
1238 PvValue::Scalar(sv) => {
1239 if matches!(sv, ScalarValue::Str(_)) {
1240 FieldType::String
1241 } else {
1242 FieldType::Scalar(scalar_value_type_code(sv))
1243 }
1244 }
1245 PvValue::ScalarArray(sa) => scalar_array_field_type(sa),
1246 PvValue::Structure { struct_id, fields } => {
1247 FieldType::Structure(pv_value_desc(struct_id, fields))
1248 }
1249 }
1250}
1251
1252pub fn encode_pv_value(val: &PvValue, is_be: bool) -> Vec<u8> {
1254 match val {
1255 PvValue::Scalar(sv) => encode_scalar_value(sv, is_be),
1256 PvValue::ScalarArray(sa) => encode_scalar_array_value_pvd(sa, is_be),
1257 PvValue::Structure { fields, .. } => {
1258 let mut out = Vec::new();
1259 for (_, v) in fields {
1260 out.extend_from_slice(&encode_pv_value(v, is_be));
1261 }
1262 out
1263 }
1264 }
1265}
1266
1267pub fn nt_payload_desc(payload: &NtPayload) -> StructureDesc {
1268 match payload {
1269 NtPayload::Scalar(nt) => nt_scalar_desc(&nt.value),
1270 NtPayload::ScalarArray(nt) => nt_scalar_array_desc(&nt.value),
1271 NtPayload::Table(nt) => nt_table_desc(nt),
1272 NtPayload::NdArray(nt) => nt_ndarray_desc(nt),
1273 NtPayload::Enum(_) => nt_enum_desc(),
1274 NtPayload::Generic { struct_id, fields } => pv_value_desc(struct_id, fields),
1275 }
1276}
1277
1278pub fn encode_nt_payload_full(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1279 match payload {
1280 NtPayload::Scalar(nt) => encode_nt_scalar_full(nt, is_be),
1281 NtPayload::ScalarArray(nt) => encode_nt_scalar_array_full(nt, is_be),
1282 NtPayload::Table(nt) => encode_nt_table_full(nt, is_be),
1283 NtPayload::NdArray(nt) => encode_nt_ndarray_full(nt, is_be),
1284 NtPayload::Enum(nt) => encode_nt_enum_full(nt, is_be),
1285 NtPayload::Generic { fields, .. } => {
1286 let mut out = Vec::new();
1287 for (_, v) in fields {
1288 out.extend_from_slice(&encode_pv_value(v, is_be));
1289 }
1290 out
1291 }
1292 }
1293}
1294
1295pub fn encode_nt_payload_bitset(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1296 let desc = nt_payload_desc(payload);
1297 let mut out = Vec::new();
1298 out.extend_from_slice(&encode_structure_bitset(&desc, is_be));
1299 out.extend_from_slice(&encode_nt_payload_full(payload, is_be));
1300 out
1301}
1302
1303pub fn encode_nt_payload_bitset_parts(payload: &NtPayload, is_be: bool) -> (Vec<u8>, Vec<u8>) {
1304 let desc = nt_payload_desc(payload);
1305 (
1306 encode_structure_bitset(&desc, is_be),
1307 encode_nt_payload_full(payload, is_be),
1308 )
1309}
1310
1311use crate::spvd_decode::DecodedValue;
1316
1317pub fn encode_decoded_value(val: &DecodedValue, is_be: bool) -> Vec<u8> {
1319 match val {
1320 DecodedValue::Null => Vec::new(),
1321 DecodedValue::Boolean(v) => vec![if *v { 1 } else { 0 }],
1322 DecodedValue::Int8(v) => vec![*v as u8],
1323 DecodedValue::Int16(v) => {
1324 if is_be {
1325 v.to_be_bytes().to_vec()
1326 } else {
1327 v.to_le_bytes().to_vec()
1328 }
1329 }
1330 DecodedValue::Int32(v) => encode_i32(*v, is_be),
1331 DecodedValue::Int64(v) => encode_i64(*v, is_be),
1332 DecodedValue::UInt8(v) => vec![*v],
1333 DecodedValue::UInt16(v) => {
1334 if is_be {
1335 v.to_be_bytes().to_vec()
1336 } else {
1337 v.to_le_bytes().to_vec()
1338 }
1339 }
1340 DecodedValue::UInt32(v) => {
1341 if is_be {
1342 v.to_be_bytes().to_vec()
1343 } else {
1344 v.to_le_bytes().to_vec()
1345 }
1346 }
1347 DecodedValue::UInt64(v) => {
1348 if is_be {
1349 v.to_be_bytes().to_vec()
1350 } else {
1351 v.to_le_bytes().to_vec()
1352 }
1353 }
1354 DecodedValue::Float32(v) => {
1355 if is_be {
1356 v.to_be_bytes().to_vec()
1357 } else {
1358 v.to_le_bytes().to_vec()
1359 }
1360 }
1361 DecodedValue::Float64(v) => encode_f64(*v, is_be),
1362 DecodedValue::String(v) => encode_string_pvd(v, is_be),
1363 DecodedValue::Array(arr) => {
1364 let mut out = encode_size_pvd(arr.len(), is_be);
1365 for item in arr {
1366 out.extend_from_slice(&encode_decoded_value(item, is_be));
1367 }
1368 out
1369 }
1370 DecodedValue::Structure(fields) => {
1371 let mut out = Vec::new();
1372 for (_name, value) in fields {
1373 out.extend_from_slice(&encode_decoded_value(value, is_be));
1374 }
1375 out
1376 }
1377 DecodedValue::Raw(data) => data.clone(),
1378 }
1379}
1380
1381pub fn decode_pv_request_fields(body: &[u8], is_be: bool) -> Option<Vec<String>> {
1399 if body.is_empty() {
1400 return None;
1401 }
1402 let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1403 let desc = decoder.parse_introspection(body)?;
1404 for field in &desc.fields {
1405 if field.name == "field" {
1406 if let FieldType::Structure(ref inner) = field.field_type {
1407 if inner.fields.is_empty() {
1408 return None;
1409 }
1410 let mut paths = Vec::new();
1411 collect_pv_request_paths(inner, "", &mut paths);
1412 if paths.is_empty() {
1413 return None;
1414 }
1415 return Some(paths);
1416 }
1417 }
1418 }
1419 None
1420}
1421
1422fn collect_pv_request_paths(desc: &StructureDesc, prefix: &str, out: &mut Vec<String>) {
1423 for field in &desc.fields {
1424 let joined = if prefix.is_empty() {
1425 field.name.clone()
1426 } else {
1427 format!("{}.{}", prefix, field.name)
1428 };
1429 match &field.field_type {
1430 FieldType::Structure(nested) if !nested.fields.is_empty() => {
1431 collect_pv_request_paths(nested, &joined, out);
1432 }
1433 _ => out.push(joined),
1434 }
1435 }
1436}
1437
1438pub fn decode_pv_request_options(body: &[u8], is_be: bool) -> Option<Vec<(String, String)>> {
1443 if body.is_empty() {
1444 return None;
1445 }
1446 let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1447 let desc = decoder.parse_introspection(body)?;
1448 let options_desc = desc.fields.iter().find_map(|f| {
1449 if f.name != "record" {
1450 return None;
1451 }
1452 if let FieldType::Structure(inner) = &f.field_type {
1453 inner.fields.iter().find_map(|g| {
1454 if g.name != "_options" {
1455 return None;
1456 }
1457 if let FieldType::Structure(opts) = &g.field_type {
1458 Some(opts.clone())
1459 } else {
1460 None
1461 }
1462 })
1463 } else {
1464 None
1465 }
1466 })?;
1467
1468 let desc_bytes = encode_structure_desc(&desc, is_be);
1472 let values_start = 1 + desc_bytes.len();
1473 if values_start > body.len() {
1474 return None;
1475 }
1476 let mut cursor = &body[values_start..];
1477 let mut out = Vec::with_capacity(options_desc.fields.len());
1478 for f in &options_desc.fields {
1479 if !matches!(f.field_type, FieldType::String) {
1480 return None;
1481 }
1482 let (s, consumed) = crate::epics_decode::decode_string(cursor, is_be)?;
1483 out.push((f.name.clone(), s));
1484 cursor = &cursor[consumed..];
1485 }
1486 Some(out)
1487}
1488
1489pub fn filter_structure_desc(desc: &StructureDesc, requested: &[String]) -> StructureDesc {
1496 if requested.is_empty() {
1497 return desc.clone();
1498 }
1499 let tree = build_path_tree(requested);
1500 prune_structure(desc, &tree)
1501}
1502
1503#[derive(Default, Debug, Clone)]
1504struct PathNode {
1505 select_all: bool,
1508 children: Vec<(String, PathNode)>,
1512}
1513
1514impl PathNode {
1515 fn child_mut(&mut self, name: &str) -> &mut PathNode {
1516 if let Some(idx) = self.children.iter().position(|(n, _)| n == name) {
1517 return &mut self.children[idx].1;
1518 }
1519 self.children.push((name.to_string(), PathNode::default()));
1520 &mut self.children.last_mut().unwrap().1
1521 }
1522
1523 fn child(&self, name: &str) -> Option<&PathNode> {
1524 self.children
1525 .iter()
1526 .find(|(n, _)| n == name)
1527 .map(|(_, c)| c)
1528 }
1529}
1530
1531fn build_path_tree(paths: &[String]) -> PathNode {
1532 let mut root = PathNode::default();
1533 for p in paths {
1534 let parts: Vec<&str> = p.split('.').filter(|s| !s.is_empty()).collect();
1535 if parts.is_empty() {
1536 continue;
1537 }
1538 let mut node = &mut root;
1539 for (i, part) in parts.iter().enumerate() {
1540 let is_last = i == parts.len() - 1;
1541 let child = node.child_mut(part);
1542 if is_last {
1543 child.select_all = true;
1544 child.children.clear();
1545 }
1546 node = child;
1547 }
1548 }
1549 root
1550}
1551
1552fn prune_structure(desc: &StructureDesc, node: &PathNode) -> StructureDesc {
1553 if node.select_all {
1554 return desc.clone();
1555 }
1556 let mut fields = Vec::new();
1557 for field in &desc.fields {
1558 let Some(child) = node.child(&field.name) else {
1559 continue;
1560 };
1561 if child.select_all {
1562 fields.push(field.clone());
1563 continue;
1564 }
1565 match &field.field_type {
1566 FieldType::Structure(inner) => {
1567 let pruned = prune_structure(inner, child);
1568 if !pruned.fields.is_empty() {
1569 fields.push(FieldDesc {
1570 name: field.name.clone(),
1571 field_type: FieldType::Structure(pruned),
1572 });
1573 }
1574 }
1575 FieldType::StructureArray(inner) => {
1576 let pruned = prune_structure(inner, child);
1580 if !pruned.fields.is_empty() {
1581 fields.push(FieldDesc {
1582 name: field.name.clone(),
1583 field_type: FieldType::StructureArray(pruned),
1584 });
1585 }
1586 }
1587 _ => {
1588 }
1590 }
1591 }
1592 StructureDesc {
1593 struct_id: desc.struct_id.clone(),
1594 fields,
1595 }
1596}
1597
1598pub fn encode_nt_payload_filtered(
1606 payload: &NtPayload,
1607 filtered_desc: &StructureDesc,
1608 is_be: bool,
1609) -> (Vec<u8>, Vec<u8>) {
1610 let bitset = encode_structure_bitset(filtered_desc, is_be);
1611 let values = encode_nt_payload_values_for_desc(payload, filtered_desc, is_be);
1612 (bitset, values)
1613}
1614
1615pub fn encode_nt_payload_values_for_desc(
1619 payload: &NtPayload,
1620 desc: &StructureDesc,
1621 is_be: bool,
1622) -> Vec<u8> {
1623 let full_desc = nt_payload_desc(payload);
1624 if structure_desc_equal(&full_desc, desc) {
1625 return encode_nt_payload_full(payload, is_be);
1627 }
1628 let decoded = decode_payload_to_structure(payload, is_be)
1629 .unwrap_or_else(|| DecodedValue::Structure(Vec::new()));
1630 encode_decoded_projected(&decoded, desc, is_be)
1631}
1632
1633fn structure_desc_equal(a: &StructureDesc, b: &StructureDesc) -> bool {
1634 if a.struct_id != b.struct_id {
1635 return false;
1636 }
1637 if a.fields.len() != b.fields.len() {
1638 return false;
1639 }
1640 a.fields
1641 .iter()
1642 .zip(&b.fields)
1643 .all(|(x, y)| x.name == y.name && field_type_equal(&x.field_type, &y.field_type))
1644}
1645
1646fn field_type_equal(a: &FieldType, b: &FieldType) -> bool {
1647 match (a, b) {
1648 (FieldType::Scalar(x), FieldType::Scalar(y)) => x == y,
1649 (FieldType::ScalarArray(x), FieldType::ScalarArray(y)) => x == y,
1650 (FieldType::String, FieldType::String) => true,
1651 (FieldType::StringArray, FieldType::StringArray) => true,
1652 (FieldType::Structure(x), FieldType::Structure(y)) => structure_desc_equal(x, y),
1653 (FieldType::StructureArray(x), FieldType::StructureArray(y)) => structure_desc_equal(x, y),
1654 (FieldType::Variant, FieldType::Variant) => true,
1655 (FieldType::VariantArray, FieldType::VariantArray) => true,
1656 (FieldType::BoundedString(x), FieldType::BoundedString(y)) => x == y,
1657 (FieldType::Union(x), FieldType::Union(y)) => x.len() == y.len(),
1659 (FieldType::UnionArray(x), FieldType::UnionArray(y)) => x.len() == y.len(),
1660 _ => false,
1661 }
1662}
1663
1664fn decode_payload_to_structure(payload: &NtPayload, is_be: bool) -> Option<DecodedValue> {
1667 let desc = nt_payload_desc(payload);
1668 let bytes = encode_nt_payload_full(payload, is_be);
1669 let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1670 decoder.decode_structure(&bytes, &desc).map(|(v, _)| v)
1671}
1672
1673pub fn encode_decoded_projected(
1676 value: &DecodedValue,
1677 desc: &StructureDesc,
1678 is_be: bool,
1679) -> Vec<u8> {
1680 let DecodedValue::Structure(fields) = value else {
1681 return encode_decoded_value(value, is_be);
1683 };
1684 let mut out = Vec::new();
1685 for target in &desc.fields {
1686 let Some((_, sub_value)) = fields.iter().find(|(n, _)| n == &target.name) else {
1687 continue;
1688 };
1689 match &target.field_type {
1690 FieldType::Structure(inner) => {
1691 out.extend_from_slice(&encode_decoded_projected(sub_value, inner, is_be));
1692 }
1693 _ => {
1694 out.extend_from_slice(&encode_decoded_value(sub_value, is_be));
1695 }
1696 }
1697 }
1698 out
1699}
1700
1701fn project_payload_on_desc(payload: &NtPayload, desc: &StructureDesc, is_be: bool) -> DecodedValue {
1709 let decoded = decode_payload_to_structure(payload, is_be)
1710 .unwrap_or_else(|| DecodedValue::Structure(Vec::new()));
1711 project_decoded(&decoded, desc)
1712}
1713
1714fn project_decoded(value: &DecodedValue, desc: &StructureDesc) -> DecodedValue {
1715 let DecodedValue::Structure(fields) = value else {
1716 return value.clone();
1717 };
1718 let mut out: Vec<(String, DecodedValue)> = Vec::new();
1719 for target in &desc.fields {
1720 let Some((_, v)) = fields.iter().find(|(n, _)| n == &target.name) else {
1721 continue;
1722 };
1723 match &target.field_type {
1724 FieldType::Structure(inner) => {
1725 out.push((target.name.clone(), project_decoded(v, inner)));
1726 }
1727 _ => {
1728 out.push((target.name.clone(), v.clone()));
1729 }
1730 }
1731 }
1732 DecodedValue::Structure(out)
1733}
1734
1735pub fn decoded_values_equal(a: &DecodedValue, b: &DecodedValue) -> bool {
1738 use DecodedValue::*;
1739 match (a, b) {
1740 (Null, Null) => true,
1741 (Boolean(x), Boolean(y)) => x == y,
1742 (Int8(x), Int8(y)) => x == y,
1743 (Int16(x), Int16(y)) => x == y,
1744 (Int32(x), Int32(y)) => x == y,
1745 (Int64(x), Int64(y)) => x == y,
1746 (UInt8(x), UInt8(y)) => x == y,
1747 (UInt16(x), UInt16(y)) => x == y,
1748 (UInt32(x), UInt32(y)) => x == y,
1749 (UInt64(x), UInt64(y)) => x == y,
1750 (Float32(x), Float32(y)) => x == y || (x.is_nan() && y.is_nan()),
1751 (Float64(x), Float64(y)) => x == y || (x.is_nan() && y.is_nan()),
1752 (String(x), String(y)) => x == y,
1753 (Raw(x), Raw(y)) => x == y,
1754 (Array(x), Array(y)) => {
1755 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| decoded_values_equal(a, b))
1756 }
1757 (Structure(x), Structure(y)) => {
1758 x.len() == y.len()
1759 && x.iter()
1760 .zip(y)
1761 .all(|((ln, lv), (rn, rv))| ln == rn && decoded_values_equal(lv, rv))
1762 }
1763 _ => false,
1764 }
1765}
1766
1767pub fn compute_changed_bits(
1776 prev: &DecodedValue,
1777 next: &DecodedValue,
1778 desc: &StructureDesc,
1779) -> Option<Vec<bool>> {
1780 let total = 1 + spvd_count_structure_fields(desc);
1781 let mut bits = vec![false; total];
1782 let mut idx = 1usize;
1783 let any = fill_changed_bits(prev, next, desc, &mut bits, &mut idx);
1784 if any { Some(bits) } else { None }
1785}
1786
1787fn get_field_by_name<'a>(val: &'a DecodedValue, name: &str) -> Option<&'a DecodedValue> {
1788 match val {
1789 DecodedValue::Structure(f) => f.iter().find(|(n, _)| n == name).map(|(_, v)| v),
1790 _ => None,
1791 }
1792}
1793
1794fn fill_changed_bits(
1795 prev: &DecodedValue,
1796 next: &DecodedValue,
1797 desc: &StructureDesc,
1798 bits: &mut [bool],
1799 idx: &mut usize,
1800) -> bool {
1801 let mut any = false;
1802 for field in &desc.fields {
1803 let this = *idx;
1804 *idx += 1;
1805 let p = get_field_by_name(prev, &field.name);
1806 let n = get_field_by_name(next, &field.name);
1807 match &field.field_type {
1808 FieldType::Structure(inner) => {
1809 let empty = DecodedValue::Structure(Vec::new());
1810 let pv = p.unwrap_or(&empty);
1811 let nv = n.unwrap_or(&empty);
1812 if fill_changed_bits(pv, nv, inner, bits, idx) {
1813 any = true;
1814 }
1815 }
1816 _ => {
1817 let changed = match (p, n) {
1818 (Some(a), Some(b)) => !decoded_values_equal(a, b),
1819 (Some(_), None) | (None, Some(_)) => true,
1820 (None, None) => false,
1821 };
1822 if changed {
1823 bits[this] = true;
1824 any = true;
1825 }
1826 }
1827 }
1828 }
1829 any
1830}
1831
1832fn encode_values_for_bits(
1833 value: &DecodedValue,
1834 desc: &StructureDesc,
1835 bits: &[bool],
1836 idx: &mut usize,
1837 is_be: bool,
1838 out: &mut Vec<u8>,
1839) {
1840 for field in &desc.fields {
1841 let this = *idx;
1842 *idx += 1;
1843 let sub = get_field_by_name(value, &field.name);
1844 match &field.field_type {
1845 FieldType::Structure(inner) => {
1846 let empty = DecodedValue::Structure(Vec::new());
1847 let v = sub.unwrap_or(&empty);
1848 encode_values_for_bits(v, inner, bits, idx, is_be, out);
1849 }
1850 _ => {
1851 if bits[this] {
1852 if let Some(v) = sub {
1853 out.extend_from_slice(&encode_decoded_value(v, is_be));
1854 }
1855 }
1856 }
1857 }
1858 }
1859}
1860
1861fn encode_bitset_from_flags(bits: &[bool], is_be: bool) -> Vec<u8> {
1862 let bitset_size = (bits.len() + 7) / 8;
1863 let mut bitset = vec![0u8; bitset_size];
1864 for (i, b) in bits.iter().enumerate() {
1865 if *b {
1866 bitset[i / 8] |= 1 << (i % 8);
1867 }
1868 }
1869 let mut out = Vec::new();
1870 out.extend_from_slice(&encode_size_pvd(bitset_size, is_be));
1871 out.extend_from_slice(&bitset);
1872 out
1873}
1874
1875pub fn encode_nt_payload_delta(
1880 prev: &NtPayload,
1881 next: &NtPayload,
1882 filtered_desc: &StructureDesc,
1883 is_be: bool,
1884) -> Option<(Vec<u8>, Vec<u8>)> {
1885 let prev_proj = project_payload_on_desc(prev, filtered_desc, is_be);
1886 let next_proj = project_payload_on_desc(next, filtered_desc, is_be);
1887 let bits = compute_changed_bits(&prev_proj, &next_proj, filtered_desc)?;
1888 let bitset = encode_bitset_from_flags(&bits, is_be);
1889 let mut values = Vec::new();
1890 let mut idx = 1usize;
1891 encode_values_for_bits(
1892 &next_proj,
1893 filtered_desc,
1894 &bits,
1895 &mut idx,
1896 is_be,
1897 &mut values,
1898 );
1899 Some((bitset, values))
1900}
1901
1902fn spvd_count_structure_fields(desc: &StructureDesc) -> usize {
1903 let mut count = 0;
1904 for field in &desc.fields {
1905 count += 1;
1906 if let FieldType::Structure(inner) = &field.field_type {
1907 count += spvd_count_structure_fields(inner);
1908 }
1909 }
1910 count
1911}
1912
1913pub fn encode_pv_request(fields: &[&str], is_be: bool) -> Vec<u8> {
1939 encode_pv_request_with_options(fields, &[], is_be)
1940}
1941
1942pub fn encode_pv_request_with_options(
1952 fields: &[&str],
1953 options: &[(&str, &str)],
1954 is_be: bool,
1955) -> Vec<u8> {
1956 let tree = build_path_tree_from_strs(fields);
1957 let inner_fields = path_tree_to_field_descs(&tree);
1958
1959 let field_desc = StructureDesc {
1960 struct_id: None,
1961 fields: inner_fields,
1962 };
1963
1964 let mut top_fields = vec![FieldDesc {
1965 name: "field".to_string(),
1966 field_type: FieldType::Structure(field_desc),
1967 }];
1968
1969 if !options.is_empty() {
1970 let options_desc = StructureDesc {
1971 struct_id: None,
1972 fields: options
1973 .iter()
1974 .map(|(k, _)| FieldDesc {
1975 name: (*k).to_string(),
1976 field_type: FieldType::String,
1977 })
1978 .collect(),
1979 };
1980 let record_desc = StructureDesc {
1981 struct_id: None,
1982 fields: vec![FieldDesc {
1983 name: "_options".to_string(),
1984 field_type: FieldType::Structure(options_desc),
1985 }],
1986 };
1987 top_fields.push(FieldDesc {
1988 name: "record".to_string(),
1989 field_type: FieldType::Structure(record_desc),
1990 });
1991 }
1992
1993 let pv_request_desc = StructureDesc {
1994 struct_id: None,
1995 fields: top_fields,
1996 };
1997
1998 let mut out = Vec::new();
1999 out.push(0x80); out.extend_from_slice(&encode_structure_desc(&pv_request_desc, is_be));
2001 for (_, v) in options {
2005 out.extend_from_slice(&encode_string_pvd(v, is_be));
2006 }
2007 out
2008}
2009
2010fn build_path_tree_from_strs(paths: &[&str]) -> PathNode {
2011 let owned: Vec<String> = paths.iter().map(|s| (*s).to_string()).collect();
2012 build_path_tree(&owned)
2013}
2014
2015fn path_tree_to_field_descs(node: &PathNode) -> Vec<FieldDesc> {
2016 node.children
2017 .iter()
2018 .map(|(name, child)| {
2019 let nested_fields = if child.select_all {
2020 Vec::new()
2021 } else {
2022 path_tree_to_field_descs(child)
2023 };
2024 FieldDesc {
2025 name: name.clone(),
2026 field_type: FieldType::Structure(StructureDesc {
2027 struct_id: None,
2028 fields: nested_fields,
2029 }),
2030 }
2031 })
2032 .collect()
2033}
2034
2035#[cfg(test)]
2036mod tests {
2037 use super::*;
2038 use crate::spvd_decode::PvdDecoder;
2039
2040 #[test]
2041 fn nt_scalar_roundtrip() {
2042 let nt = NtScalar::from_value(ScalarValue::F64(12.5));
2043 let desc = nt_scalar_desc(&nt.value);
2044 let desc_bytes = encode_structure_desc(&desc, false);
2045 let mut pvd = Vec::new();
2046 pvd.push(0x80);
2047 pvd.extend_from_slice(&desc_bytes);
2048 pvd.extend_from_slice(&encode_nt_scalar_full(&nt, false));
2049
2050 let decoder = PvdDecoder::new(false);
2051 let parsed_desc = decoder.parse_introspection(&pvd).expect("desc");
2052 let (_, consumed) = decoder
2053 .decode_structure(&pvd[1 + desc_bytes.len()..], &parsed_desc)
2054 .expect("value");
2055 assert!(consumed > 0);
2056 }
2057
2058 #[test]
2059 fn nt_ndarray_roundtrip() {
2060 use spvirit_types::{
2061 NdCodec, NdDimension, NtAlarm, NtNdArray, NtTimeStamp, ScalarArrayValue,
2062 };
2063 use std::collections::HashMap;
2064
2065 let nt = NtNdArray {
2066 value: ScalarArrayValue::U8(vec![1, 2, 3, 4]),
2067 codec: NdCodec {
2068 name: String::new(),
2069 parameters: HashMap::new(),
2070 },
2071 compressed_size: 4,
2072 uncompressed_size: 4,
2073 dimension: vec![NdDimension {
2074 size: 2,
2075 offset: 0,
2076 full_size: 2,
2077 binning: 1,
2078 reverse: false,
2079 }],
2080 unique_id: 42,
2081 data_time_stamp: NtTimeStamp {
2082 seconds_past_epoch: 1000,
2083 nanoseconds: 500,
2084 user_tag: 0,
2085 },
2086 attribute: Vec::new(),
2087 descriptor: Some("test".to_string()),
2088 alarm: Some(NtAlarm::default()),
2089 time_stamp: Some(NtTimeStamp::default()),
2090 display: None,
2091 };
2092
2093 let desc = nt_ndarray_desc(&nt);
2094 let desc_bytes = encode_structure_desc(&desc, false);
2095 let data_bytes = encode_nt_ndarray_full(&nt, false);
2096
2097 let mut pvd = Vec::new();
2099 pvd.push(0x80);
2100 pvd.extend_from_slice(&desc_bytes);
2101 pvd.extend_from_slice(&data_bytes);
2102
2103 let decoder = PvdDecoder::new(false);
2104 let parsed_desc = decoder
2105 .parse_introspection(&pvd)
2106 .expect("desc parse failed");
2107 let data_start = 1 + desc_bytes.len();
2108 let (_decoded, consumed) = decoder
2109 .decode_structure(&pvd[data_start..], &parsed_desc)
2110 .expect("data decode failed");
2111 assert!(consumed > 0, "consumed should be > 0");
2112 assert_eq!(
2113 consumed,
2114 data_bytes.len(),
2115 "consumed should match data_bytes.len()"
2116 );
2117 }
2118
2119 #[test]
2120 fn pv_request_flat_roundtrip() {
2121 for is_be in [false, true] {
2122 let body = encode_pv_request(&["value", "alarm", "timeStamp"], is_be);
2123 let fields = decode_pv_request_fields(&body, is_be).expect("fields");
2124 assert_eq!(fields, vec!["value", "alarm", "timeStamp"]);
2125 }
2126 }
2127
2128 #[test]
2129 fn pv_request_nested_roundtrip() {
2130 let body = encode_pv_request(&["alarm.severity", "timeStamp.secondsPastEpoch"], false);
2131 let fields = decode_pv_request_fields(&body, false).expect("fields");
2132 assert_eq!(
2133 fields,
2134 vec![
2135 "alarm.severity".to_string(),
2136 "timeStamp.secondsPastEpoch".to_string()
2137 ]
2138 );
2139 }
2140
2141 #[test]
2142 fn pv_request_whole_subtree_beats_leaf() {
2143 let body = encode_pv_request(&["alarm.severity", "alarm"], false);
2146 let fields = decode_pv_request_fields(&body, false).expect("fields");
2147 assert_eq!(fields, vec!["alarm".to_string()]);
2148 }
2149
2150 #[test]
2151 fn pv_request_with_pipeline_options_roundtrip() {
2152 for is_be in [false, true] {
2153 let body = encode_pv_request_with_options(
2154 &["value", "alarm"],
2155 &[("pipeline", "true"), ("queueSize", "4")],
2156 is_be,
2157 );
2158 let fields = decode_pv_request_fields(&body, is_be).expect("fields");
2160 assert_eq!(fields, vec!["value".to_string(), "alarm".to_string()]);
2161 let opts = decode_pv_request_options(&body, is_be).expect("opts");
2163 assert_eq!(
2164 opts,
2165 vec![
2166 ("pipeline".to_string(), "true".to_string()),
2167 ("queueSize".to_string(), "4".to_string()),
2168 ]
2169 );
2170 }
2171 }
2172
2173 #[test]
2174 fn pv_request_without_options_has_no_record() {
2175 let body = encode_pv_request(&["value"], false);
2176 assert!(decode_pv_request_options(&body, false).is_none());
2177 }
2178
2179 #[test]
2180 fn pv_request_empty_body_none() {
2181 assert!(decode_pv_request_fields(&[], false).is_none());
2182 }
2183
2184 #[test]
2185 fn filter_structure_desc_nested() {
2186 let alarm = StructureDesc {
2187 struct_id: Some("alarm_t".to_string()),
2188 fields: vec![
2189 FieldDesc {
2190 name: "severity".into(),
2191 field_type: FieldType::Scalar(TypeCode::Int32),
2192 },
2193 FieldDesc {
2194 name: "status".into(),
2195 field_type: FieldType::Scalar(TypeCode::Int32),
2196 },
2197 FieldDesc {
2198 name: "message".into(),
2199 field_type: FieldType::String,
2200 },
2201 ],
2202 };
2203 let desc = StructureDesc {
2204 struct_id: Some("epics:nt/NTScalar:1.0".into()),
2205 fields: vec![
2206 FieldDesc {
2207 name: "value".into(),
2208 field_type: FieldType::Scalar(TypeCode::Float64),
2209 },
2210 FieldDesc {
2211 name: "alarm".into(),
2212 field_type: FieldType::Structure(alarm.clone()),
2213 },
2214 ],
2215 };
2216
2217 let pruned = filter_structure_desc(&desc, &["alarm.severity".to_string()]);
2218 assert_eq!(pruned.fields.len(), 1);
2219 assert_eq!(pruned.fields[0].name, "alarm");
2220 match &pruned.fields[0].field_type {
2221 FieldType::Structure(inner) => {
2222 assert_eq!(inner.fields.len(), 1);
2223 assert_eq!(inner.fields[0].name, "severity");
2224 }
2225 other => panic!("expected Structure, got {:?}", other),
2226 }
2227
2228 let pruned_all = filter_structure_desc(&desc, &["alarm".to_string()]);
2230 match &pruned_all.fields[0].field_type {
2231 FieldType::Structure(inner) => assert_eq!(inner.fields.len(), 3),
2232 other => panic!("expected Structure, got {:?}", other),
2233 }
2234
2235 let pruned_unknown = filter_structure_desc(&desc, &["nope".into(), "alarm.missing".into()]);
2237 assert!(pruned_unknown.fields.is_empty());
2238 }
2239
2240 #[test]
2241 fn filtered_monitor_round_trip_nested() {
2242 use crate::spvd_decode::PvdDecoder;
2243 use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2244
2245 let mut nt = NtScalar::from_value(ScalarValue::F64(42.0));
2246 nt.alarm_severity = 2;
2247 nt.alarm_status = 7;
2248 nt.alarm_message = "hi".into();
2249 let payload = NtPayload::Scalar(nt);
2250
2251 let full_desc = nt_payload_desc(&payload);
2253 let paths = vec!["alarm.severity".to_string()];
2254 let filtered = filter_structure_desc(&full_desc, &paths);
2255 let (bitset, values) = encode_nt_payload_filtered(&payload, &filtered, false);
2256
2257 let decoder = PvdDecoder::new(false);
2259 let mut body = bitset.clone();
2260 body.extend_from_slice(&values);
2261 let (decoded, _) = decoder
2262 .decode_structure_with_bitset(&body, &filtered)
2263 .expect("decode filtered");
2264
2265 let DecodedValue::Structure(fields) = decoded else {
2266 panic!("expected structure");
2267 };
2268 assert_eq!(fields.len(), 1);
2269 assert_eq!(fields[0].0, "alarm");
2270 match &fields[0].1 {
2271 DecodedValue::Structure(inner) => {
2272 assert_eq!(inner.len(), 1);
2273 assert_eq!(inner[0].0, "severity");
2274 assert!(matches!(inner[0].1, DecodedValue::Int32(2)));
2275 }
2276 other => panic!("expected Structure, got {:?}", other),
2277 }
2278
2279 let full_body_len = encode_nt_payload_full(&payload, false).len();
2282 assert!(values.len() < full_body_len);
2283 }
2284
2285 #[test]
2286 fn delta_returns_none_when_nothing_changed() {
2287 use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2288 let mut a = NtScalar::from_value(ScalarValue::F64(1.0));
2289 a.alarm_severity = 1;
2290 let p1 = NtPayload::Scalar(a.clone());
2291 let p2 = NtPayload::Scalar(a);
2292 let desc = filter_structure_desc(&nt_payload_desc(&p1), &["alarm.severity".to_string()]);
2293 assert!(encode_nt_payload_delta(&p1, &p2, &desc, false).is_none());
2294 }
2295
2296 #[test]
2297 fn delta_marks_only_changed_leaf() {
2298 use crate::spvd_decode::PvdDecoder;
2299 use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2300
2301 let mut a = NtScalar::from_value(ScalarValue::F64(1.0));
2302 a.alarm_severity = 1;
2303 a.alarm_status = 0;
2304 a.alarm_message = "ok".into();
2305 let mut b = a.clone();
2306 b.alarm_severity = 2; let p1 = NtPayload::Scalar(a);
2308 let p2 = NtPayload::Scalar(b);
2309 let desc = filter_structure_desc(
2310 &nt_payload_desc(&p1),
2311 &[
2312 "alarm.severity".to_string(),
2313 "alarm.status".to_string(),
2314 "alarm.message".to_string(),
2315 ],
2316 );
2317
2318 let (bitset, values) = encode_nt_payload_delta(&p1, &p2, &desc, false)
2319 .expect("delta must produce a frame when a leaf changed");
2320
2321 assert_eq!(bitset[0], 1u8, "size prefix");
2327 let b0 = bitset[1];
2328 assert_eq!(b0 & 0x01, 0, "root bit must be clear");
2329 assert_eq!(b0 & 0x02, 0, "alarm struct bit must be clear");
2330 assert_eq!(b0 & 0x04, 0x04, "severity bit must be set");
2331 assert_eq!(b0 & 0x08, 0, "status bit must be clear");
2332 assert_eq!(b0 & 0x10, 0, "message bit must be clear");
2333
2334 assert_eq!(values.len(), 4);
2336
2337 let decoder = PvdDecoder::new(false);
2339 let mut body = bitset.clone();
2340 body.extend_from_slice(&values);
2341 let (decoded, _) = decoder
2342 .decode_structure_with_bitset(&body, &desc)
2343 .expect("decode delta");
2344 let DecodedValue::Structure(fields) = decoded else {
2345 panic!("expected struct")
2346 };
2347 assert_eq!(fields.len(), 1);
2348 assert_eq!(fields[0].0, "alarm");
2349 match &fields[0].1 {
2350 DecodedValue::Structure(inner) => {
2351 assert_eq!(inner.len(), 1);
2352 assert_eq!(inner[0].0, "severity");
2353 assert!(matches!(inner[0].1, DecodedValue::Int32(2)));
2354 }
2355 other => panic!("expected struct got {:?}", other),
2356 }
2357 }
2358
2359 #[test]
2360 fn decoded_values_equal_treats_nan_as_equal() {
2361 let a = DecodedValue::Float64(f64::NAN);
2362 let b = DecodedValue::Float64(f64::NAN);
2363 assert!(decoded_values_equal(&a, &b));
2364 let c = DecodedValue::Float32(f32::NAN);
2365 let d = DecodedValue::Float32(f32::NAN);
2366 assert!(decoded_values_equal(&c, &d));
2367 assert!(!decoded_values_equal(
2369 &DecodedValue::Float64(1.0),
2370 &DecodedValue::Float64(2.0)
2371 ));
2372 }
2373}