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 FieldDesc {
787 name: "descriptor".to_string(),
788 field_type: FieldType::String,
789 },
790 FieldDesc {
791 name: "alarm".to_string(),
792 field_type: FieldType::Structure(alarm_desc()),
793 },
794 FieldDesc {
797 name: "timeStamp".to_string(),
798 field_type: FieldType::Structure(timestamp_desc()),
799 },
800 ],
801 }
802}
803
804pub fn encode_nt_table_full(nt: &NtTable, is_be: bool) -> Vec<u8> {
805 let mut out = Vec::new();
806 out.extend_from_slice(&encode_string_array(&nt.labels, is_be));
807 for NtTableColumn { values, .. } in &nt.columns {
808 out.extend_from_slice(&encode_scalar_array_value_pvd(values, is_be));
809 }
810 out.extend_from_slice(&encode_string_pvd(
811 nt.descriptor.as_deref().unwrap_or(""),
812 is_be,
813 ));
814 out.extend_from_slice(&encode_nt_alarm(
815 nt.alarm.as_ref().unwrap_or(&NtAlarm::default()),
816 is_be,
817 ));
818 out.extend_from_slice(&encode_nt_timestamp(
819 nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
820 is_be,
821 ));
822 out
823}
824
825fn nt_ndarray_value_union_fields() -> Vec<FieldDesc> {
826 vec![
827 FieldDesc {
828 name: "booleanValue".to_string(),
829 field_type: FieldType::ScalarArray(TypeCode::Boolean),
830 },
831 FieldDesc {
832 name: "byteValue".to_string(),
833 field_type: FieldType::ScalarArray(TypeCode::Int8),
834 },
835 FieldDesc {
836 name: "shortValue".to_string(),
837 field_type: FieldType::ScalarArray(TypeCode::Int16),
838 },
839 FieldDesc {
840 name: "intValue".to_string(),
841 field_type: FieldType::ScalarArray(TypeCode::Int32),
842 },
843 FieldDesc {
844 name: "longValue".to_string(),
845 field_type: FieldType::ScalarArray(TypeCode::Int64),
846 },
847 FieldDesc {
848 name: "ubyteValue".to_string(),
849 field_type: FieldType::ScalarArray(TypeCode::UInt8),
850 },
851 FieldDesc {
852 name: "ushortValue".to_string(),
853 field_type: FieldType::ScalarArray(TypeCode::UInt16),
854 },
855 FieldDesc {
856 name: "uintValue".to_string(),
857 field_type: FieldType::ScalarArray(TypeCode::UInt32),
858 },
859 FieldDesc {
860 name: "ulongValue".to_string(),
861 field_type: FieldType::ScalarArray(TypeCode::UInt64),
862 },
863 FieldDesc {
864 name: "floatValue".to_string(),
865 field_type: FieldType::ScalarArray(TypeCode::Float32),
866 },
867 FieldDesc {
868 name: "doubleValue".to_string(),
869 field_type: FieldType::ScalarArray(TypeCode::Float64),
870 },
871 FieldDesc {
872 name: "stringValue".to_string(),
873 field_type: FieldType::StringArray,
874 },
875 ]
876}
877
878fn ndarray_union_index(value: &ScalarArrayValue) -> usize {
879 match value {
880 ScalarArrayValue::Bool(_) => 0,
881 ScalarArrayValue::I8(_) => 1,
882 ScalarArrayValue::I16(_) => 2,
883 ScalarArrayValue::I32(_) => 3,
884 ScalarArrayValue::I64(_) => 4,
885 ScalarArrayValue::U8(_) => 5,
886 ScalarArrayValue::U16(_) => 6,
887 ScalarArrayValue::U32(_) => 7,
888 ScalarArrayValue::U64(_) => 8,
889 ScalarArrayValue::F32(_) => 9,
890 ScalarArrayValue::F64(_) => 10,
891 ScalarArrayValue::Str(_) => 11,
892 }
893}
894
895fn encode_ndarray_union(value: &ScalarArrayValue, is_be: bool) -> Vec<u8> {
896 let mut out = Vec::new();
897 out.extend_from_slice(&encode_size_pvd(ndarray_union_index(value), is_be));
898 out.extend_from_slice(&encode_scalar_array_value_pvd(value, is_be));
899 out
900}
901
902fn encode_codec_parameters(
903 parameters: &std::collections::HashMap<String, String>,
904 is_be: bool,
905) -> Vec<u8> {
906 if parameters.is_empty() {
907 return vec![0xFF];
908 }
909 let mut out = Vec::new();
910 out.push(0x80);
911 let mut fields = Vec::new();
912 for key in parameters.keys() {
913 fields.push(FieldDesc {
914 name: key.clone(),
915 field_type: FieldType::String,
916 });
917 }
918 let desc = StructureDesc {
919 struct_id: None,
920 fields,
921 };
922 out.extend_from_slice(&encode_structure_desc(&desc, is_be));
923 for value in parameters.values() {
924 out.extend_from_slice(&encode_string_pvd(value, is_be));
925 }
926 out
927}
928
929pub fn nt_ndarray_desc_default() -> StructureDesc {
930 nt_ndarray_desc(&NtNdArray::empty())
931}
932
933pub fn nt_ndarray_desc(_nt: &NtNdArray) -> StructureDesc {
934 StructureDesc {
935 struct_id: Some("epics:nt/NTNDArray:1.0".to_string()),
936 fields: vec![
937 FieldDesc {
938 name: "value".to_string(),
939 field_type: FieldType::Union(nt_ndarray_value_union_fields()),
940 },
941 FieldDesc {
942 name: "codec".to_string(),
943 field_type: FieldType::Structure(StructureDesc {
944 struct_id: Some("codec_t".to_string()),
945 fields: vec![
946 FieldDesc {
947 name: "name".to_string(),
948 field_type: FieldType::String,
949 },
950 FieldDesc {
951 name: "parameters".to_string(),
952 field_type: FieldType::Variant,
953 },
954 ],
955 }),
956 },
957 FieldDesc {
958 name: "compressedSize".to_string(),
959 field_type: FieldType::Scalar(TypeCode::Int64),
960 },
961 FieldDesc {
962 name: "uncompressedSize".to_string(),
963 field_type: FieldType::Scalar(TypeCode::Int64),
964 },
965 FieldDesc {
966 name: "dimension".to_string(),
967 field_type: FieldType::StructureArray(StructureDesc {
968 struct_id: Some("dimension_t".to_string()),
969 fields: vec![
970 FieldDesc {
971 name: "size".to_string(),
972 field_type: FieldType::Scalar(TypeCode::Int32),
973 },
974 FieldDesc {
975 name: "offset".to_string(),
976 field_type: FieldType::Scalar(TypeCode::Int32),
977 },
978 FieldDesc {
979 name: "fullSize".to_string(),
980 field_type: FieldType::Scalar(TypeCode::Int32),
981 },
982 FieldDesc {
983 name: "binning".to_string(),
984 field_type: FieldType::Scalar(TypeCode::Int32),
985 },
986 FieldDesc {
987 name: "reverse".to_string(),
988 field_type: FieldType::Scalar(TypeCode::Boolean),
989 },
990 ],
991 }),
992 },
993 FieldDesc {
994 name: "uniqueId".to_string(),
995 field_type: FieldType::Scalar(TypeCode::Int32),
996 },
997 FieldDesc {
998 name: "dataTimeStamp".to_string(),
999 field_type: FieldType::Structure(timestamp_desc()),
1000 },
1001 FieldDesc {
1002 name: "attribute".to_string(),
1003 field_type: FieldType::StructureArray(StructureDesc {
1004 struct_id: Some("NTAttribute".to_string()),
1005 fields: vec![
1006 FieldDesc {
1007 name: "name".to_string(),
1008 field_type: FieldType::String,
1009 },
1010 FieldDesc {
1011 name: "value".to_string(),
1012 field_type: FieldType::Variant,
1013 },
1014 FieldDesc {
1015 name: "descriptor".to_string(),
1016 field_type: FieldType::String,
1017 },
1018 FieldDesc {
1019 name: "sourceType".to_string(),
1020 field_type: FieldType::Scalar(TypeCode::Int32),
1021 },
1022 FieldDesc {
1023 name: "source".to_string(),
1024 field_type: FieldType::String,
1025 },
1026 ],
1027 }),
1028 },
1029 FieldDesc {
1030 name: "descriptor".to_string(),
1031 field_type: FieldType::String,
1032 },
1033 FieldDesc {
1034 name: "alarm".to_string(),
1035 field_type: FieldType::Structure(alarm_desc()),
1036 },
1037 FieldDesc {
1038 name: "timeStamp".to_string(),
1039 field_type: FieldType::Structure(timestamp_desc()),
1040 },
1041 FieldDesc {
1042 name: "display".to_string(),
1043 field_type: FieldType::Structure(display_desc()),
1044 },
1045 ],
1046 }
1047}
1048
1049fn encode_attribute_variant(attr: &NtAttribute, is_be: bool) -> Vec<u8> {
1050 match &attr.value {
1051 ScalarValue::Bool(v) => {
1052 let mut out = vec![TypeCode::Boolean as u8];
1053 out.push(if *v { 1 } else { 0 });
1054 out
1055 }
1056 ScalarValue::I8(v) => {
1057 let mut out = vec![TypeCode::Int8 as u8];
1058 out.push(*v as u8);
1059 out
1060 }
1061 ScalarValue::I16(v) => {
1062 let mut out = vec![TypeCode::Int16 as u8];
1063 out.extend_from_slice(&if is_be {
1064 v.to_be_bytes().to_vec()
1065 } else {
1066 v.to_le_bytes().to_vec()
1067 });
1068 out
1069 }
1070 ScalarValue::I32(v) => {
1071 let mut out = vec![TypeCode::Int32 as u8];
1072 out.extend_from_slice(&encode_i32(*v, is_be));
1073 out
1074 }
1075 ScalarValue::I64(v) => {
1076 let mut out = vec![TypeCode::Int64 as u8];
1077 out.extend_from_slice(&encode_i64(*v, is_be));
1078 out
1079 }
1080 ScalarValue::U8(v) => {
1081 let mut out = vec![TypeCode::UInt8 as u8];
1082 out.push(*v);
1083 out
1084 }
1085 ScalarValue::U16(v) => {
1086 let mut out = vec![TypeCode::UInt16 as u8];
1087 out.extend_from_slice(&if is_be {
1088 v.to_be_bytes().to_vec()
1089 } else {
1090 v.to_le_bytes().to_vec()
1091 });
1092 out
1093 }
1094 ScalarValue::U32(v) => {
1095 let mut out = vec![TypeCode::UInt32 as u8];
1096 out.extend_from_slice(&if is_be {
1097 v.to_be_bytes().to_vec()
1098 } else {
1099 v.to_le_bytes().to_vec()
1100 });
1101 out
1102 }
1103 ScalarValue::U64(v) => {
1104 let mut out = vec![TypeCode::UInt64 as u8];
1105 out.extend_from_slice(&if is_be {
1106 v.to_be_bytes().to_vec()
1107 } else {
1108 v.to_le_bytes().to_vec()
1109 });
1110 out
1111 }
1112 ScalarValue::F32(v) => {
1113 let mut out = vec![TypeCode::Float32 as u8];
1114 out.extend_from_slice(&if is_be {
1115 v.to_be_bytes().to_vec()
1116 } else {
1117 v.to_le_bytes().to_vec()
1118 });
1119 out
1120 }
1121 ScalarValue::F64(v) => {
1122 let mut out = vec![TypeCode::Float64 as u8];
1123 out.extend_from_slice(&encode_f64(*v, is_be));
1124 out
1125 }
1126 ScalarValue::Str(v) => {
1127 let mut out = vec![TypeCode::String as u8];
1128 out.extend_from_slice(&encode_string_pvd(v, is_be));
1129 out
1130 }
1131 }
1132}
1133
1134pub fn encode_nt_ndarray_full(nt: &NtNdArray, is_be: bool) -> Vec<u8> {
1135 let mut out = Vec::new();
1136 out.extend_from_slice(&encode_ndarray_union(&nt.value, is_be));
1137 out.extend_from_slice(&encode_string_pvd(&nt.codec.name, is_be));
1138 out.extend_from_slice(&encode_codec_parameters(&nt.codec.parameters, is_be));
1139 out.extend_from_slice(&encode_i64(nt.compressed_size, is_be));
1140 out.extend_from_slice(&encode_i64(nt.uncompressed_size, is_be));
1141 out.extend_from_slice(&encode_size_pvd(nt.dimension.len(), is_be));
1142 for NdDimension {
1143 size,
1144 offset,
1145 full_size,
1146 binning,
1147 reverse,
1148 } in &nt.dimension
1149 {
1150 out.push(1); out.extend_from_slice(&encode_i32(*size, is_be));
1152 out.extend_from_slice(&encode_i32(*offset, is_be));
1153 out.extend_from_slice(&encode_i32(*full_size, is_be));
1154 out.extend_from_slice(&encode_i32(*binning, is_be));
1155 out.push(if *reverse { 1 } else { 0 });
1156 }
1157 out.extend_from_slice(&encode_i32(nt.unique_id, is_be));
1158 out.extend_from_slice(&encode_nt_timestamp(&nt.data_time_stamp, is_be));
1159 out.extend_from_slice(&encode_size_pvd(nt.attribute.len(), is_be));
1160 for attr in &nt.attribute {
1161 out.push(1); out.extend_from_slice(&encode_string_pvd(&attr.name, is_be));
1163 out.extend_from_slice(&encode_attribute_variant(attr, is_be));
1164 out.extend_from_slice(&encode_string_pvd(&attr.descriptor, is_be));
1165 out.extend_from_slice(&encode_i32(attr.source_type, is_be));
1166 out.extend_from_slice(&encode_string_pvd(&attr.source, is_be));
1167 }
1168 out.extend_from_slice(&encode_string_pvd(
1169 nt.descriptor.as_deref().unwrap_or(""),
1170 is_be,
1171 ));
1172 out.extend_from_slice(&encode_nt_alarm(
1173 nt.alarm.as_ref().unwrap_or(&NtAlarm::default()),
1174 is_be,
1175 ));
1176 out.extend_from_slice(&encode_nt_timestamp(
1177 nt.time_stamp.as_ref().unwrap_or(&NtTimeStamp::default()),
1178 is_be,
1179 ));
1180 out.extend_from_slice(&encode_nt_display(
1181 nt.display.as_ref().unwrap_or(&NtDisplay::default()),
1182 is_be,
1183 ));
1184 out
1185}
1186
1187pub fn nt_enum_desc() -> StructureDesc {
1192 StructureDesc {
1193 struct_id: Some("epics:nt/NTEnum:1.0".to_string()),
1194 fields: vec![
1195 FieldDesc {
1196 name: "value".to_string(),
1197 field_type: FieldType::Structure(StructureDesc {
1198 struct_id: Some("enum_t".to_string()),
1199 fields: vec![
1200 FieldDesc {
1201 name: "index".to_string(),
1202 field_type: FieldType::Scalar(TypeCode::Int32),
1203 },
1204 FieldDesc {
1205 name: "choices".to_string(),
1206 field_type: FieldType::StringArray,
1207 },
1208 ],
1209 }),
1210 },
1211 FieldDesc {
1212 name: "alarm".to_string(),
1213 field_type: FieldType::Structure(alarm_desc()),
1214 },
1215 FieldDesc {
1216 name: "timeStamp".to_string(),
1217 field_type: FieldType::Structure(timestamp_desc()),
1218 },
1219 ],
1220 }
1221}
1222
1223pub fn encode_nt_enum_full(nt: &NtEnum, is_be: bool) -> Vec<u8> {
1224 let mut out = Vec::new();
1225 out.extend_from_slice(&encode_enum(nt.index, &nt.choices, is_be));
1227 out.extend_from_slice(&encode_nt_alarm(&nt.alarm, is_be));
1229 out.extend_from_slice(&encode_nt_timestamp(&nt.time_stamp, is_be));
1231 out
1232}
1233
1234fn scalar_value_type_code(v: &ScalarValue) -> TypeCode {
1239 match v {
1240 ScalarValue::Bool(_) => TypeCode::Boolean,
1241 ScalarValue::I8(_) => TypeCode::Int8,
1242 ScalarValue::I16(_) => TypeCode::Int16,
1243 ScalarValue::I32(_) => TypeCode::Int32,
1244 ScalarValue::I64(_) => TypeCode::Int64,
1245 ScalarValue::U8(_) => TypeCode::UInt8,
1246 ScalarValue::U16(_) => TypeCode::UInt16,
1247 ScalarValue::U32(_) => TypeCode::UInt32,
1248 ScalarValue::U64(_) => TypeCode::UInt64,
1249 ScalarValue::F32(_) => TypeCode::Float32,
1250 ScalarValue::F64(_) => TypeCode::Float64,
1251 ScalarValue::Str(_) => TypeCode::String,
1252 }
1253}
1254
1255pub fn pv_value_desc(struct_id: &str, fields: &[(String, PvValue)]) -> StructureDesc {
1257 StructureDesc {
1258 struct_id: if struct_id.is_empty() {
1259 None
1260 } else {
1261 Some(struct_id.to_string())
1262 },
1263 fields: fields
1264 .iter()
1265 .map(|(name, val)| FieldDesc {
1266 name: name.clone(),
1267 field_type: pv_value_field_type(val),
1268 })
1269 .collect(),
1270 }
1271}
1272
1273fn pv_value_field_type(val: &PvValue) -> FieldType {
1274 match val {
1275 PvValue::Scalar(sv) => {
1276 if matches!(sv, ScalarValue::Str(_)) {
1277 FieldType::String
1278 } else {
1279 FieldType::Scalar(scalar_value_type_code(sv))
1280 }
1281 }
1282 PvValue::ScalarArray(sa) => scalar_array_field_type(sa),
1283 PvValue::Structure { struct_id, fields } => {
1284 FieldType::Structure(pv_value_desc(struct_id, fields))
1285 }
1286 }
1287}
1288
1289pub fn encode_pv_value(val: &PvValue, is_be: bool) -> Vec<u8> {
1291 match val {
1292 PvValue::Scalar(sv) => encode_scalar_value(sv, is_be),
1293 PvValue::ScalarArray(sa) => encode_scalar_array_value_pvd(sa, is_be),
1294 PvValue::Structure { fields, .. } => {
1295 let mut out = Vec::new();
1296 for (_, v) in fields {
1297 out.extend_from_slice(&encode_pv_value(v, is_be));
1298 }
1299 out
1300 }
1301 }
1302}
1303
1304pub fn nt_payload_desc(payload: &NtPayload) -> StructureDesc {
1305 match payload {
1306 NtPayload::Scalar(nt) => nt_scalar_desc(&nt.value),
1307 NtPayload::ScalarArray(nt) => nt_scalar_array_desc(&nt.value),
1308 NtPayload::Table(nt) => nt_table_desc(nt),
1309 NtPayload::NdArray(nt) => nt_ndarray_desc(nt),
1310 NtPayload::Enum(_) => nt_enum_desc(),
1311 NtPayload::Generic { struct_id, fields } => pv_value_desc(struct_id, fields),
1312 }
1313}
1314
1315pub fn encode_nt_payload_full(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1316 match payload {
1317 NtPayload::Scalar(nt) => encode_nt_scalar_full(nt, is_be),
1318 NtPayload::ScalarArray(nt) => encode_nt_scalar_array_full(nt, is_be),
1319 NtPayload::Table(nt) => encode_nt_table_full(nt, is_be),
1320 NtPayload::NdArray(nt) => encode_nt_ndarray_full(nt, is_be),
1321 NtPayload::Enum(nt) => encode_nt_enum_full(nt, is_be),
1322 NtPayload::Generic { fields, .. } => {
1323 let mut out = Vec::new();
1324 for (_, v) in fields {
1325 out.extend_from_slice(&encode_pv_value(v, is_be));
1326 }
1327 out
1328 }
1329 }
1330}
1331
1332pub fn encode_nt_payload_bitset(payload: &NtPayload, is_be: bool) -> Vec<u8> {
1333 let desc = nt_payload_desc(payload);
1334 let mut out = Vec::new();
1335 out.extend_from_slice(&encode_structure_bitset(&desc, is_be));
1336 out.extend_from_slice(&encode_nt_payload_full(payload, is_be));
1337 out
1338}
1339
1340pub fn encode_nt_payload_bitset_parts(payload: &NtPayload, is_be: bool) -> (Vec<u8>, Vec<u8>) {
1341 let desc = nt_payload_desc(payload);
1342 (
1343 encode_structure_bitset(&desc, is_be),
1344 encode_nt_payload_full(payload, is_be),
1345 )
1346}
1347
1348use crate::spvd_decode::DecodedValue;
1353
1354pub fn encode_decoded_value(val: &DecodedValue, is_be: bool) -> Vec<u8> {
1356 match val {
1357 DecodedValue::Null => Vec::new(),
1358 DecodedValue::Boolean(v) => vec![if *v { 1 } else { 0 }],
1359 DecodedValue::Int8(v) => vec![*v as u8],
1360 DecodedValue::Int16(v) => {
1361 if is_be {
1362 v.to_be_bytes().to_vec()
1363 } else {
1364 v.to_le_bytes().to_vec()
1365 }
1366 }
1367 DecodedValue::Int32(v) => encode_i32(*v, is_be),
1368 DecodedValue::Int64(v) => encode_i64(*v, is_be),
1369 DecodedValue::UInt8(v) => vec![*v],
1370 DecodedValue::UInt16(v) => {
1371 if is_be {
1372 v.to_be_bytes().to_vec()
1373 } else {
1374 v.to_le_bytes().to_vec()
1375 }
1376 }
1377 DecodedValue::UInt32(v) => {
1378 if is_be {
1379 v.to_be_bytes().to_vec()
1380 } else {
1381 v.to_le_bytes().to_vec()
1382 }
1383 }
1384 DecodedValue::UInt64(v) => {
1385 if is_be {
1386 v.to_be_bytes().to_vec()
1387 } else {
1388 v.to_le_bytes().to_vec()
1389 }
1390 }
1391 DecodedValue::Float32(v) => {
1392 if is_be {
1393 v.to_be_bytes().to_vec()
1394 } else {
1395 v.to_le_bytes().to_vec()
1396 }
1397 }
1398 DecodedValue::Float64(v) => encode_f64(*v, is_be),
1399 DecodedValue::String(v) => encode_string_pvd(v, is_be),
1400 DecodedValue::Array(arr) => {
1401 let mut out = encode_size_pvd(arr.len(), is_be);
1402 for item in arr {
1403 out.extend_from_slice(&encode_decoded_value(item, is_be));
1404 }
1405 out
1406 }
1407 DecodedValue::Structure(fields) => {
1408 let mut out = Vec::new();
1409 for (_name, value) in fields {
1410 out.extend_from_slice(&encode_decoded_value(value, is_be));
1411 }
1412 out
1413 }
1414 DecodedValue::Raw(data) => data.clone(),
1415 }
1416}
1417
1418pub fn decode_pv_request_fields(body: &[u8], is_be: bool) -> Option<Vec<String>> {
1436 if body.is_empty() {
1437 return None;
1438 }
1439 let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1440 let desc = decoder.parse_introspection(body)?;
1441 for field in &desc.fields {
1442 if field.name == "field" {
1443 if let FieldType::Structure(ref inner) = field.field_type {
1444 if inner.fields.is_empty() {
1445 return None;
1446 }
1447 let mut paths = Vec::new();
1448 collect_pv_request_paths(inner, "", &mut paths);
1449 if paths.is_empty() {
1450 return None;
1451 }
1452 return Some(paths);
1453 }
1454 }
1455 }
1456 None
1457}
1458
1459fn collect_pv_request_paths(desc: &StructureDesc, prefix: &str, out: &mut Vec<String>) {
1460 for field in &desc.fields {
1461 let joined = if prefix.is_empty() {
1462 field.name.clone()
1463 } else {
1464 format!("{}.{}", prefix, field.name)
1465 };
1466 match &field.field_type {
1467 FieldType::Structure(nested) if !nested.fields.is_empty() => {
1468 collect_pv_request_paths(nested, &joined, out);
1469 }
1470 _ => out.push(joined),
1471 }
1472 }
1473}
1474
1475pub fn decode_pv_request_options(body: &[u8], is_be: bool) -> Option<Vec<(String, String)>> {
1480 if body.is_empty() {
1481 return None;
1482 }
1483 let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1484 let desc = decoder.parse_introspection(body)?;
1485 let options_desc = desc.fields.iter().find_map(|f| {
1486 if f.name != "record" {
1487 return None;
1488 }
1489 if let FieldType::Structure(inner) = &f.field_type {
1490 inner.fields.iter().find_map(|g| {
1491 if g.name != "_options" {
1492 return None;
1493 }
1494 if let FieldType::Structure(opts) = &g.field_type {
1495 Some(opts.clone())
1496 } else {
1497 None
1498 }
1499 })
1500 } else {
1501 None
1502 }
1503 })?;
1504
1505 let desc_bytes = encode_structure_desc(&desc, is_be);
1509 let values_start = 1 + desc_bytes.len();
1510 if values_start > body.len() {
1511 return None;
1512 }
1513 let mut cursor = &body[values_start..];
1514 let mut out = Vec::with_capacity(options_desc.fields.len());
1515 for f in &options_desc.fields {
1516 if !matches!(f.field_type, FieldType::String) {
1517 return None;
1518 }
1519 let (s, consumed) = crate::epics_decode::decode_string(cursor, is_be)?;
1520 out.push((f.name.clone(), s));
1521 cursor = &cursor[consumed..];
1522 }
1523 Some(out)
1524}
1525
1526pub fn filter_structure_desc(desc: &StructureDesc, requested: &[String]) -> StructureDesc {
1533 if requested.is_empty() {
1534 return desc.clone();
1535 }
1536 let tree = build_path_tree(requested);
1537 prune_structure(desc, &tree)
1538}
1539
1540#[derive(Default, Debug, Clone)]
1541struct PathNode {
1542 select_all: bool,
1545 children: Vec<(String, PathNode)>,
1549}
1550
1551impl PathNode {
1552 fn child_mut(&mut self, name: &str) -> &mut PathNode {
1553 if let Some(idx) = self.children.iter().position(|(n, _)| n == name) {
1554 return &mut self.children[idx].1;
1555 }
1556 self.children.push((name.to_string(), PathNode::default()));
1557 &mut self.children.last_mut().unwrap().1
1558 }
1559
1560 fn child(&self, name: &str) -> Option<&PathNode> {
1561 self.children
1562 .iter()
1563 .find(|(n, _)| n == name)
1564 .map(|(_, c)| c)
1565 }
1566}
1567
1568fn build_path_tree(paths: &[String]) -> PathNode {
1569 let mut root = PathNode::default();
1570 for p in paths {
1571 let parts: Vec<&str> = p.split('.').filter(|s| !s.is_empty()).collect();
1572 if parts.is_empty() {
1573 continue;
1574 }
1575 let mut node = &mut root;
1576 for (i, part) in parts.iter().enumerate() {
1577 let is_last = i == parts.len() - 1;
1578 let child = node.child_mut(part);
1579 if is_last {
1580 child.select_all = true;
1581 child.children.clear();
1582 }
1583 node = child;
1584 }
1585 }
1586 root
1587}
1588
1589fn prune_structure(desc: &StructureDesc, node: &PathNode) -> StructureDesc {
1590 if node.select_all {
1591 return desc.clone();
1592 }
1593 let mut fields = Vec::new();
1594 for field in &desc.fields {
1595 let Some(child) = node.child(&field.name) else {
1596 continue;
1597 };
1598 if child.select_all {
1599 fields.push(field.clone());
1600 continue;
1601 }
1602 match &field.field_type {
1603 FieldType::Structure(inner) => {
1604 let pruned = prune_structure(inner, child);
1605 if !pruned.fields.is_empty() {
1606 fields.push(FieldDesc {
1607 name: field.name.clone(),
1608 field_type: FieldType::Structure(pruned),
1609 });
1610 }
1611 }
1612 FieldType::StructureArray(inner) => {
1613 let pruned = prune_structure(inner, child);
1617 if !pruned.fields.is_empty() {
1618 fields.push(FieldDesc {
1619 name: field.name.clone(),
1620 field_type: FieldType::StructureArray(pruned),
1621 });
1622 }
1623 }
1624 _ => {
1625 }
1627 }
1628 }
1629 StructureDesc {
1630 struct_id: desc.struct_id.clone(),
1631 fields,
1632 }
1633}
1634
1635pub fn encode_nt_payload_filtered(
1643 payload: &NtPayload,
1644 filtered_desc: &StructureDesc,
1645 is_be: bool,
1646) -> (Vec<u8>, Vec<u8>) {
1647 let bitset = encode_structure_bitset(filtered_desc, is_be);
1648 let values = encode_nt_payload_values_for_desc(payload, filtered_desc, is_be);
1649 (bitset, values)
1650}
1651
1652pub fn encode_nt_payload_values_for_desc(
1656 payload: &NtPayload,
1657 desc: &StructureDesc,
1658 is_be: bool,
1659) -> Vec<u8> {
1660 let full_desc = nt_payload_desc(payload);
1661 if structure_desc_equal(&full_desc, desc) {
1662 return encode_nt_payload_full(payload, is_be);
1664 }
1665 let decoded = decode_payload_to_structure(payload, is_be)
1666 .unwrap_or_else(|| DecodedValue::Structure(Vec::new()));
1667 encode_decoded_projected(&decoded, desc, is_be)
1668}
1669
1670fn structure_desc_equal(a: &StructureDesc, b: &StructureDesc) -> bool {
1671 if a.struct_id != b.struct_id {
1672 return false;
1673 }
1674 if a.fields.len() != b.fields.len() {
1675 return false;
1676 }
1677 a.fields
1678 .iter()
1679 .zip(&b.fields)
1680 .all(|(x, y)| x.name == y.name && field_type_equal(&x.field_type, &y.field_type))
1681}
1682
1683fn field_type_equal(a: &FieldType, b: &FieldType) -> bool {
1684 match (a, b) {
1685 (FieldType::Scalar(x), FieldType::Scalar(y)) => x == y,
1686 (FieldType::ScalarArray(x), FieldType::ScalarArray(y)) => x == y,
1687 (FieldType::String, FieldType::String) => true,
1688 (FieldType::StringArray, FieldType::StringArray) => true,
1689 (FieldType::Structure(x), FieldType::Structure(y)) => structure_desc_equal(x, y),
1690 (FieldType::StructureArray(x), FieldType::StructureArray(y)) => structure_desc_equal(x, y),
1691 (FieldType::Variant, FieldType::Variant) => true,
1692 (FieldType::VariantArray, FieldType::VariantArray) => true,
1693 (FieldType::BoundedString(x), FieldType::BoundedString(y)) => x == y,
1694 (FieldType::Union(x), FieldType::Union(y)) => x.len() == y.len(),
1696 (FieldType::UnionArray(x), FieldType::UnionArray(y)) => x.len() == y.len(),
1697 _ => false,
1698 }
1699}
1700
1701fn decode_payload_to_structure(payload: &NtPayload, is_be: bool) -> Option<DecodedValue> {
1704 let desc = nt_payload_desc(payload);
1705 let bytes = encode_nt_payload_full(payload, is_be);
1706 let decoder = crate::spvd_decode::PvdDecoder::new(is_be);
1707 decoder.decode_structure(&bytes, &desc).map(|(v, _)| v)
1708}
1709
1710pub fn encode_decoded_projected(
1713 value: &DecodedValue,
1714 desc: &StructureDesc,
1715 is_be: bool,
1716) -> Vec<u8> {
1717 let DecodedValue::Structure(fields) = value else {
1718 return encode_decoded_value(value, is_be);
1720 };
1721 let mut out = Vec::new();
1722 for target in &desc.fields {
1723 let Some((_, sub_value)) = fields.iter().find(|(n, _)| n == &target.name) else {
1724 continue;
1725 };
1726 match &target.field_type {
1727 FieldType::Structure(inner) => {
1728 out.extend_from_slice(&encode_decoded_projected(sub_value, inner, is_be));
1729 }
1730 _ => {
1731 out.extend_from_slice(&encode_decoded_value(sub_value, is_be));
1732 }
1733 }
1734 }
1735 out
1736}
1737
1738fn project_payload_on_desc(payload: &NtPayload, desc: &StructureDesc, is_be: bool) -> DecodedValue {
1746 let decoded = decode_payload_to_structure(payload, is_be)
1747 .unwrap_or_else(|| DecodedValue::Structure(Vec::new()));
1748 project_decoded(&decoded, desc)
1749}
1750
1751fn project_decoded(value: &DecodedValue, desc: &StructureDesc) -> DecodedValue {
1752 let DecodedValue::Structure(fields) = value else {
1753 return value.clone();
1754 };
1755 let mut out: Vec<(String, DecodedValue)> = Vec::new();
1756 for target in &desc.fields {
1757 let Some((_, v)) = fields.iter().find(|(n, _)| n == &target.name) else {
1758 continue;
1759 };
1760 match &target.field_type {
1761 FieldType::Structure(inner) => {
1762 out.push((target.name.clone(), project_decoded(v, inner)));
1763 }
1764 _ => {
1765 out.push((target.name.clone(), v.clone()));
1766 }
1767 }
1768 }
1769 DecodedValue::Structure(out)
1770}
1771
1772pub fn decoded_values_equal(a: &DecodedValue, b: &DecodedValue) -> bool {
1775 use DecodedValue::*;
1776 match (a, b) {
1777 (Null, Null) => true,
1778 (Boolean(x), Boolean(y)) => x == y,
1779 (Int8(x), Int8(y)) => x == y,
1780 (Int16(x), Int16(y)) => x == y,
1781 (Int32(x), Int32(y)) => x == y,
1782 (Int64(x), Int64(y)) => x == y,
1783 (UInt8(x), UInt8(y)) => x == y,
1784 (UInt16(x), UInt16(y)) => x == y,
1785 (UInt32(x), UInt32(y)) => x == y,
1786 (UInt64(x), UInt64(y)) => x == y,
1787 (Float32(x), Float32(y)) => x == y || (x.is_nan() && y.is_nan()),
1788 (Float64(x), Float64(y)) => x == y || (x.is_nan() && y.is_nan()),
1789 (String(x), String(y)) => x == y,
1790 (Raw(x), Raw(y)) => x == y,
1791 (Array(x), Array(y)) => {
1792 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| decoded_values_equal(a, b))
1793 }
1794 (Structure(x), Structure(y)) => {
1795 x.len() == y.len()
1796 && x.iter()
1797 .zip(y)
1798 .all(|((ln, lv), (rn, rv))| ln == rn && decoded_values_equal(lv, rv))
1799 }
1800 _ => false,
1801 }
1802}
1803
1804pub fn compute_changed_bits(
1813 prev: &DecodedValue,
1814 next: &DecodedValue,
1815 desc: &StructureDesc,
1816) -> Option<Vec<bool>> {
1817 let total = 1 + spvd_count_structure_fields(desc);
1818 let mut bits = vec![false; total];
1819 let mut idx = 1usize;
1820 let any = fill_changed_bits(prev, next, desc, &mut bits, &mut idx);
1821 if any { Some(bits) } else { None }
1822}
1823
1824fn get_field_by_name<'a>(val: &'a DecodedValue, name: &str) -> Option<&'a DecodedValue> {
1825 match val {
1826 DecodedValue::Structure(f) => f.iter().find(|(n, _)| n == name).map(|(_, v)| v),
1827 _ => None,
1828 }
1829}
1830
1831fn fill_changed_bits(
1832 prev: &DecodedValue,
1833 next: &DecodedValue,
1834 desc: &StructureDesc,
1835 bits: &mut [bool],
1836 idx: &mut usize,
1837) -> bool {
1838 let mut any = false;
1839 for field in &desc.fields {
1840 let this = *idx;
1841 *idx += 1;
1842 let p = get_field_by_name(prev, &field.name);
1843 let n = get_field_by_name(next, &field.name);
1844 match &field.field_type {
1845 FieldType::Structure(inner) => {
1846 let empty = DecodedValue::Structure(Vec::new());
1847 let pv = p.unwrap_or(&empty);
1848 let nv = n.unwrap_or(&empty);
1849 if fill_changed_bits(pv, nv, inner, bits, idx) {
1850 any = true;
1851 }
1852 }
1853 _ => {
1854 let changed = match (p, n) {
1855 (Some(a), Some(b)) => !decoded_values_equal(a, b),
1856 (Some(_), None) | (None, Some(_)) => true,
1857 (None, None) => false,
1858 };
1859 if changed {
1860 bits[this] = true;
1861 any = true;
1862 }
1863 }
1864 }
1865 }
1866 any
1867}
1868
1869fn encode_values_for_bits(
1870 value: &DecodedValue,
1871 desc: &StructureDesc,
1872 bits: &[bool],
1873 idx: &mut usize,
1874 is_be: bool,
1875 out: &mut Vec<u8>,
1876) {
1877 for field in &desc.fields {
1878 let this = *idx;
1879 *idx += 1;
1880 let sub = get_field_by_name(value, &field.name);
1881 match &field.field_type {
1882 FieldType::Structure(inner) => {
1883 let empty = DecodedValue::Structure(Vec::new());
1884 let v = sub.unwrap_or(&empty);
1885 encode_values_for_bits(v, inner, bits, idx, is_be, out);
1886 }
1887 _ => {
1888 if bits[this] {
1889 if let Some(v) = sub {
1890 out.extend_from_slice(&encode_decoded_value(v, is_be));
1891 }
1892 }
1893 }
1894 }
1895 }
1896}
1897
1898fn encode_bitset_from_flags(bits: &[bool], is_be: bool) -> Vec<u8> {
1899 let bitset_size = (bits.len() + 7) / 8;
1900 let mut bitset = vec![0u8; bitset_size];
1901 for (i, b) in bits.iter().enumerate() {
1902 if *b {
1903 bitset[i / 8] |= 1 << (i % 8);
1904 }
1905 }
1906 let mut out = Vec::new();
1907 out.extend_from_slice(&encode_size_pvd(bitset_size, is_be));
1908 out.extend_from_slice(&bitset);
1909 out
1910}
1911
1912pub fn encode_nt_payload_delta(
1917 prev: &NtPayload,
1918 next: &NtPayload,
1919 filtered_desc: &StructureDesc,
1920 is_be: bool,
1921) -> Option<(Vec<u8>, Vec<u8>)> {
1922 let prev_proj = project_payload_on_desc(prev, filtered_desc, is_be);
1923 let next_proj = project_payload_on_desc(next, filtered_desc, is_be);
1924 let bits = compute_changed_bits(&prev_proj, &next_proj, filtered_desc)?;
1925 let bitset = encode_bitset_from_flags(&bits, is_be);
1926 let mut values = Vec::new();
1927 let mut idx = 1usize;
1928 encode_values_for_bits(
1929 &next_proj,
1930 filtered_desc,
1931 &bits,
1932 &mut idx,
1933 is_be,
1934 &mut values,
1935 );
1936 Some((bitset, values))
1937}
1938
1939fn spvd_count_structure_fields(desc: &StructureDesc) -> usize {
1940 let mut count = 0;
1941 for field in &desc.fields {
1942 count += 1;
1943 if let FieldType::Structure(inner) = &field.field_type {
1944 count += spvd_count_structure_fields(inner);
1945 }
1946 }
1947 count
1948}
1949
1950pub fn encode_pv_request(fields: &[&str], is_be: bool) -> Vec<u8> {
1976 encode_pv_request_with_options(fields, &[], is_be)
1977}
1978
1979pub fn encode_pv_request_with_options(
1989 fields: &[&str],
1990 options: &[(&str, &str)],
1991 is_be: bool,
1992) -> Vec<u8> {
1993 let tree = build_path_tree_from_strs(fields);
1994 let inner_fields = path_tree_to_field_descs(&tree);
1995
1996 let field_desc = StructureDesc {
1997 struct_id: None,
1998 fields: inner_fields,
1999 };
2000
2001 let mut top_fields = vec![FieldDesc {
2002 name: "field".to_string(),
2003 field_type: FieldType::Structure(field_desc),
2004 }];
2005
2006 if !options.is_empty() {
2007 let options_desc = StructureDesc {
2008 struct_id: None,
2009 fields: options
2010 .iter()
2011 .map(|(k, _)| FieldDesc {
2012 name: (*k).to_string(),
2013 field_type: FieldType::String,
2014 })
2015 .collect(),
2016 };
2017 let record_desc = StructureDesc {
2018 struct_id: None,
2019 fields: vec![FieldDesc {
2020 name: "_options".to_string(),
2021 field_type: FieldType::Structure(options_desc),
2022 }],
2023 };
2024 top_fields.push(FieldDesc {
2025 name: "record".to_string(),
2026 field_type: FieldType::Structure(record_desc),
2027 });
2028 }
2029
2030 let pv_request_desc = StructureDesc {
2031 struct_id: None,
2032 fields: top_fields,
2033 };
2034
2035 let mut out = Vec::new();
2036 out.push(0x80); out.extend_from_slice(&encode_structure_desc(&pv_request_desc, is_be));
2038 for (_, v) in options {
2042 out.extend_from_slice(&encode_string_pvd(v, is_be));
2043 }
2044 out
2045}
2046
2047fn build_path_tree_from_strs(paths: &[&str]) -> PathNode {
2048 let owned: Vec<String> = paths.iter().map(|s| (*s).to_string()).collect();
2049 build_path_tree(&owned)
2050}
2051
2052fn path_tree_to_field_descs(node: &PathNode) -> Vec<FieldDesc> {
2053 node.children
2054 .iter()
2055 .map(|(name, child)| {
2056 let nested_fields = if child.select_all {
2057 Vec::new()
2058 } else {
2059 path_tree_to_field_descs(child)
2060 };
2061 FieldDesc {
2062 name: name.clone(),
2063 field_type: FieldType::Structure(StructureDesc {
2064 struct_id: None,
2065 fields: nested_fields,
2066 }),
2067 }
2068 })
2069 .collect()
2070}
2071
2072#[cfg(test)]
2073mod tests {
2074 use super::*;
2075 use crate::spvd_decode::PvdDecoder;
2076
2077 #[test]
2078 fn nt_scalar_roundtrip() {
2079 let nt = NtScalar::from_value(ScalarValue::F64(12.5));
2080 let desc = nt_scalar_desc(&nt.value);
2081 let desc_bytes = encode_structure_desc(&desc, false);
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, false));
2086
2087 let decoder = PvdDecoder::new(false);
2088 let parsed_desc = decoder.parse_introspection(&pvd).expect("desc");
2089 let (_, consumed) = decoder
2090 .decode_structure(&pvd[1 + desc_bytes.len()..], &parsed_desc)
2091 .expect("value");
2092 assert!(consumed > 0);
2093 }
2094
2095 fn decode_nt_full(nt: &NtScalar, is_be: bool) -> DecodedValue {
2106 let desc = nt_scalar_desc(&nt.value);
2107 let desc_bytes = encode_structure_desc(&desc, is_be);
2108 let mut pvd = Vec::new();
2109 pvd.push(0x80);
2110 pvd.extend_from_slice(&desc_bytes);
2111 pvd.extend_from_slice(&encode_nt_scalar_full(nt, is_be));
2112 let decoder = PvdDecoder::new(is_be);
2113 let parsed_desc = decoder.parse_introspection(&pvd).expect("desc");
2114 let (val, _) = decoder
2115 .decode_structure(&pvd[1 + desc_bytes.len()..], &parsed_desc)
2116 .expect("value");
2117 val
2118 }
2119
2120 fn timestamp_fields(v: &DecodedValue) -> (i64, i32) {
2121 let DecodedValue::Structure(fields) = v else {
2122 panic!("top-level not a structure");
2123 };
2124 let (_, ts) = fields
2125 .iter()
2126 .find(|(n, _)| n == "timeStamp")
2127 .expect("timeStamp field");
2128 let DecodedValue::Structure(ts_fields) = ts else {
2129 panic!("timeStamp not a structure");
2130 };
2131 let mut secs = None;
2132 let mut nanos = None;
2133 for (n, val) in ts_fields {
2134 match (n.as_str(), val) {
2135 ("secondsPastEpoch", DecodedValue::Int64(s)) => secs = Some(*s),
2136 ("nanoseconds", DecodedValue::Int32(ns)) => nanos = Some(*ns),
2137 _ => {}
2138 }
2139 }
2140 (secs.expect("secondsPastEpoch"), nanos.expect("nanoseconds"))
2141 }
2142
2143 #[test]
2144 fn encode_timestamp_honors_stored_value() {
2145 for is_be in [false, true] {
2146 let nt = NtScalar::from_value(ScalarValue::F64(1.0)).with_timestamp(1_234_567_890, 42);
2147 let (secs, nanos) = timestamp_fields(&decode_nt_full(&nt, is_be));
2148 assert_eq!(
2149 secs, 1_234_567_890,
2150 "stored seconds must be encoded verbatim"
2151 );
2152 assert_eq!(nanos, 42, "stored nanoseconds must be encoded verbatim");
2153 }
2154 }
2155
2156 #[test]
2157 fn stored_timestamp_is_stable_across_encodes() {
2158 let nt = NtScalar::from_value(ScalarValue::F64(1.0)).with_timestamp(1000, 500);
2161 let a = encode_nt_scalar_full(&nt, false);
2162 let b = encode_nt_scalar_full(&nt, false);
2163 assert_eq!(a, b, "a stored timestamp must be stable across encodes");
2164 }
2165
2166 #[test]
2167 fn distinct_stored_timestamps_flag_seconds_changed() {
2168 let prev =
2171 NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(1.0)).with_timestamp(1000, 0));
2172 let next =
2173 NtPayload::Scalar(NtScalar::from_value(ScalarValue::F64(1.0)).with_timestamp(1001, 0));
2174 let desc = nt_scalar_desc(&ScalarValue::F64(1.0));
2175 let (_bitset, values) =
2176 encode_nt_payload_delta(&prev, &next, &desc, false).expect("delta present");
2177 assert!(!values.is_empty(), "delta must carry changed field values");
2179 let NtPayload::Scalar(prev_nt) = &prev else {
2181 unreachable!()
2182 };
2183 let NtPayload::Scalar(next_nt) = &next else {
2184 unreachable!()
2185 };
2186 assert_eq!(timestamp_fields(&decode_nt_full(prev_nt, false)).0, 1000);
2187 assert_eq!(timestamp_fields(&decode_nt_full(next_nt, false)).0, 1001);
2188 }
2189
2190 #[test]
2191 fn none_timestamp_falls_back_to_now() {
2192 let nt = NtScalar::from_value(ScalarValue::F64(1.0));
2194 let (secs, _) = timestamp_fields(&decode_nt_full(&nt, false));
2195 assert!(
2196 secs > 1_700_000_000,
2197 "now() fallback should yield a recent epoch, got {secs}"
2198 );
2199 }
2200
2201 #[test]
2202 fn nt_ndarray_roundtrip() {
2203 use spvirit_types::{
2204 NdCodec, NdDimension, NtAlarm, NtNdArray, NtTimeStamp, ScalarArrayValue,
2205 };
2206 use std::collections::HashMap;
2207
2208 let nt = NtNdArray {
2209 value: ScalarArrayValue::U8(vec![1, 2, 3, 4]),
2210 codec: NdCodec {
2211 name: String::new(),
2212 parameters: HashMap::new(),
2213 },
2214 compressed_size: 4,
2215 uncompressed_size: 4,
2216 dimension: vec![NdDimension {
2217 size: 2,
2218 offset: 0,
2219 full_size: 2,
2220 binning: 1,
2221 reverse: false,
2222 }],
2223 unique_id: 42,
2224 data_time_stamp: NtTimeStamp {
2225 seconds_past_epoch: 1000,
2226 nanoseconds: 500,
2227 user_tag: 0,
2228 },
2229 attribute: Vec::new(),
2230 descriptor: Some("test".to_string()),
2231 alarm: Some(NtAlarm::default()),
2232 time_stamp: Some(NtTimeStamp::default()),
2233 display: None,
2234 };
2235
2236 let desc = nt_ndarray_desc(&nt);
2237 let desc_bytes = encode_structure_desc(&desc, false);
2238 let data_bytes = encode_nt_ndarray_full(&nt, false);
2239
2240 let mut pvd = Vec::new();
2242 pvd.push(0x80);
2243 pvd.extend_from_slice(&desc_bytes);
2244 pvd.extend_from_slice(&data_bytes);
2245
2246 let decoder = PvdDecoder::new(false);
2247 let parsed_desc = decoder
2248 .parse_introspection(&pvd)
2249 .expect("desc parse failed");
2250 let data_start = 1 + desc_bytes.len();
2251 let (_decoded, consumed) = decoder
2252 .decode_structure(&pvd[data_start..], &parsed_desc)
2253 .expect("data decode failed");
2254 assert!(consumed > 0, "consumed should be > 0");
2255 assert_eq!(
2256 consumed,
2257 data_bytes.len(),
2258 "consumed should match data_bytes.len()"
2259 );
2260 }
2261
2262 #[test]
2263 fn nt_table_wire_format_carries_metadata() {
2264 use spvirit_types::{NtTable, NtTableColumn, ScalarArrayValue};
2265
2266 let nt = NtTable {
2270 labels: vec!["a".to_string()],
2271 columns: vec![NtTableColumn {
2272 name: "a".to_string(),
2273 values: ScalarArrayValue::F64(vec![1.0, 2.0]),
2274 }],
2275 descriptor: Some("desc".to_string()),
2276 alarm: None,
2277 time_stamp: Some(NtTimeStamp {
2278 seconds_past_epoch: 1_700_000_000,
2279 nanoseconds: 42,
2280 user_tag: 0,
2281 }),
2282 };
2283
2284 let desc = nt_table_desc(&nt);
2285 let names: Vec<&str> = desc.fields.iter().map(|f| f.name.as_str()).collect();
2286 assert!(names.contains(&"timeStamp"), "desc must expose timeStamp");
2287 assert!(names.contains(&"alarm"), "desc must expose alarm");
2288 assert!(names.contains(&"descriptor"), "desc must expose descriptor");
2289
2290 let data_bytes = encode_nt_table_full(&nt, false);
2292 let decoder = PvdDecoder::new(false);
2293 let (decoded, consumed) = decoder
2294 .decode_structure(&data_bytes, &desc)
2295 .expect("data decode failed");
2296 assert_eq!(consumed, data_bytes.len());
2297 let DecodedValue::Structure(fields) = decoded else {
2298 panic!("expected structure");
2299 };
2300 let (_, ts) = fields
2301 .iter()
2302 .find(|(n, _)| n == "timeStamp")
2303 .expect("timeStamp value present");
2304 let DecodedValue::Structure(ts_fields) = ts else {
2305 panic!("expected timeStamp structure");
2306 };
2307 let (_, secs) = ts_fields
2308 .iter()
2309 .find(|(n, _)| n == "secondsPastEpoch")
2310 .expect("secondsPastEpoch present");
2311 assert!(
2312 matches!(secs, DecodedValue::Int64(1_700_000_000)),
2313 "expected secondsPastEpoch=1700000000, got {secs:?}"
2314 );
2315 }
2316
2317 #[test]
2318 fn pv_request_flat_roundtrip() {
2319 for is_be in [false, true] {
2320 let body = encode_pv_request(&["value", "alarm", "timeStamp"], is_be);
2321 let fields = decode_pv_request_fields(&body, is_be).expect("fields");
2322 assert_eq!(fields, vec!["value", "alarm", "timeStamp"]);
2323 }
2324 }
2325
2326 #[test]
2327 fn pv_request_nested_roundtrip() {
2328 let body = encode_pv_request(&["alarm.severity", "timeStamp.secondsPastEpoch"], false);
2329 let fields = decode_pv_request_fields(&body, false).expect("fields");
2330 assert_eq!(
2331 fields,
2332 vec![
2333 "alarm.severity".to_string(),
2334 "timeStamp.secondsPastEpoch".to_string()
2335 ]
2336 );
2337 }
2338
2339 #[test]
2340 fn pv_request_whole_subtree_beats_leaf() {
2341 let body = encode_pv_request(&["alarm.severity", "alarm"], false);
2344 let fields = decode_pv_request_fields(&body, false).expect("fields");
2345 assert_eq!(fields, vec!["alarm".to_string()]);
2346 }
2347
2348 #[test]
2349 fn pv_request_with_pipeline_options_roundtrip() {
2350 for is_be in [false, true] {
2351 let body = encode_pv_request_with_options(
2352 &["value", "alarm"],
2353 &[("pipeline", "true"), ("queueSize", "4")],
2354 is_be,
2355 );
2356 let fields = decode_pv_request_fields(&body, is_be).expect("fields");
2358 assert_eq!(fields, vec!["value".to_string(), "alarm".to_string()]);
2359 let opts = decode_pv_request_options(&body, is_be).expect("opts");
2361 assert_eq!(
2362 opts,
2363 vec![
2364 ("pipeline".to_string(), "true".to_string()),
2365 ("queueSize".to_string(), "4".to_string()),
2366 ]
2367 );
2368 }
2369 }
2370
2371 #[test]
2372 fn pv_request_without_options_has_no_record() {
2373 let body = encode_pv_request(&["value"], false);
2374 assert!(decode_pv_request_options(&body, false).is_none());
2375 }
2376
2377 #[test]
2378 fn pv_request_empty_body_none() {
2379 assert!(decode_pv_request_fields(&[], false).is_none());
2380 }
2381
2382 #[test]
2383 fn filter_structure_desc_nested() {
2384 let alarm = StructureDesc {
2385 struct_id: Some("alarm_t".to_string()),
2386 fields: vec![
2387 FieldDesc {
2388 name: "severity".into(),
2389 field_type: FieldType::Scalar(TypeCode::Int32),
2390 },
2391 FieldDesc {
2392 name: "status".into(),
2393 field_type: FieldType::Scalar(TypeCode::Int32),
2394 },
2395 FieldDesc {
2396 name: "message".into(),
2397 field_type: FieldType::String,
2398 },
2399 ],
2400 };
2401 let desc = StructureDesc {
2402 struct_id: Some("epics:nt/NTScalar:1.0".into()),
2403 fields: vec![
2404 FieldDesc {
2405 name: "value".into(),
2406 field_type: FieldType::Scalar(TypeCode::Float64),
2407 },
2408 FieldDesc {
2409 name: "alarm".into(),
2410 field_type: FieldType::Structure(alarm.clone()),
2411 },
2412 ],
2413 };
2414
2415 let pruned = filter_structure_desc(&desc, &["alarm.severity".to_string()]);
2416 assert_eq!(pruned.fields.len(), 1);
2417 assert_eq!(pruned.fields[0].name, "alarm");
2418 match &pruned.fields[0].field_type {
2419 FieldType::Structure(inner) => {
2420 assert_eq!(inner.fields.len(), 1);
2421 assert_eq!(inner.fields[0].name, "severity");
2422 }
2423 other => panic!("expected Structure, got {:?}", other),
2424 }
2425
2426 let pruned_all = filter_structure_desc(&desc, &["alarm".to_string()]);
2428 match &pruned_all.fields[0].field_type {
2429 FieldType::Structure(inner) => assert_eq!(inner.fields.len(), 3),
2430 other => panic!("expected Structure, got {:?}", other),
2431 }
2432
2433 let pruned_unknown = filter_structure_desc(&desc, &["nope".into(), "alarm.missing".into()]);
2435 assert!(pruned_unknown.fields.is_empty());
2436 }
2437
2438 #[test]
2439 fn filtered_monitor_round_trip_nested() {
2440 use crate::spvd_decode::PvdDecoder;
2441 use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2442
2443 let mut nt = NtScalar::from_value(ScalarValue::F64(42.0));
2444 nt.alarm_severity = 2;
2445 nt.alarm_status = 7;
2446 nt.alarm_message = "hi".into();
2447 let payload = NtPayload::Scalar(nt);
2448
2449 let full_desc = nt_payload_desc(&payload);
2451 let paths = vec!["alarm.severity".to_string()];
2452 let filtered = filter_structure_desc(&full_desc, &paths);
2453 let (bitset, values) = encode_nt_payload_filtered(&payload, &filtered, false);
2454
2455 let decoder = PvdDecoder::new(false);
2457 let mut body = bitset.clone();
2458 body.extend_from_slice(&values);
2459 let (decoded, _) = decoder
2460 .decode_structure_with_bitset(&body, &filtered)
2461 .expect("decode filtered");
2462
2463 let DecodedValue::Structure(fields) = decoded else {
2464 panic!("expected structure");
2465 };
2466 assert_eq!(fields.len(), 1);
2467 assert_eq!(fields[0].0, "alarm");
2468 match &fields[0].1 {
2469 DecodedValue::Structure(inner) => {
2470 assert_eq!(inner.len(), 1);
2471 assert_eq!(inner[0].0, "severity");
2472 assert!(matches!(inner[0].1, DecodedValue::Int32(2)));
2473 }
2474 other => panic!("expected Structure, got {:?}", other),
2475 }
2476
2477 let full_body_len = encode_nt_payload_full(&payload, false).len();
2480 assert!(values.len() < full_body_len);
2481 }
2482
2483 #[test]
2484 fn delta_returns_none_when_nothing_changed() {
2485 use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2486 let mut a = NtScalar::from_value(ScalarValue::F64(1.0));
2487 a.alarm_severity = 1;
2488 let p1 = NtPayload::Scalar(a.clone());
2489 let p2 = NtPayload::Scalar(a);
2490 let desc = filter_structure_desc(&nt_payload_desc(&p1), &["alarm.severity".to_string()]);
2491 assert!(encode_nt_payload_delta(&p1, &p2, &desc, false).is_none());
2492 }
2493
2494 #[test]
2495 fn delta_marks_only_changed_leaf() {
2496 use crate::spvd_decode::PvdDecoder;
2497 use spvirit_types::{NtPayload, NtScalar, ScalarValue};
2498
2499 let mut a = NtScalar::from_value(ScalarValue::F64(1.0));
2500 a.alarm_severity = 1;
2501 a.alarm_status = 0;
2502 a.alarm_message = "ok".into();
2503 let mut b = a.clone();
2504 b.alarm_severity = 2; let p1 = NtPayload::Scalar(a);
2506 let p2 = NtPayload::Scalar(b);
2507 let desc = filter_structure_desc(
2508 &nt_payload_desc(&p1),
2509 &[
2510 "alarm.severity".to_string(),
2511 "alarm.status".to_string(),
2512 "alarm.message".to_string(),
2513 ],
2514 );
2515
2516 let (bitset, values) = encode_nt_payload_delta(&p1, &p2, &desc, false)
2517 .expect("delta must produce a frame when a leaf changed");
2518
2519 assert_eq!(bitset[0], 1u8, "size prefix");
2525 let b0 = bitset[1];
2526 assert_eq!(b0 & 0x01, 0, "root bit must be clear");
2527 assert_eq!(b0 & 0x02, 0, "alarm struct bit must be clear");
2528 assert_eq!(b0 & 0x04, 0x04, "severity bit must be set");
2529 assert_eq!(b0 & 0x08, 0, "status bit must be clear");
2530 assert_eq!(b0 & 0x10, 0, "message bit must be clear");
2531
2532 assert_eq!(values.len(), 4);
2534
2535 let decoder = PvdDecoder::new(false);
2537 let mut body = bitset.clone();
2538 body.extend_from_slice(&values);
2539 let (decoded, _) = decoder
2540 .decode_structure_with_bitset(&body, &desc)
2541 .expect("decode delta");
2542 let DecodedValue::Structure(fields) = decoded else {
2543 panic!("expected struct")
2544 };
2545 assert_eq!(fields.len(), 1);
2546 assert_eq!(fields[0].0, "alarm");
2547 match &fields[0].1 {
2548 DecodedValue::Structure(inner) => {
2549 assert_eq!(inner.len(), 1);
2550 assert_eq!(inner[0].0, "severity");
2551 assert!(matches!(inner[0].1, DecodedValue::Int32(2)));
2552 }
2553 other => panic!("expected struct got {:?}", other),
2554 }
2555 }
2556
2557 #[test]
2558 fn decoded_values_equal_treats_nan_as_equal() {
2559 let a = DecodedValue::Float64(f64::NAN);
2560 let b = DecodedValue::Float64(f64::NAN);
2561 assert!(decoded_values_equal(&a, &b));
2562 let c = DecodedValue::Float32(f32::NAN);
2563 let d = DecodedValue::Float32(f32::NAN);
2564 assert!(decoded_values_equal(&c, &d));
2565 assert!(!decoded_values_equal(
2567 &DecodedValue::Float64(1.0),
2568 &DecodedValue::Float64(2.0)
2569 ));
2570 }
2571}