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