1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
4use spvirit_types::{NtPayload, NtScalar};
5use crate::spvd_decode::StructureDesc;
6use crate::spvd_encode::{
7 encode_nt_payload_bitset, encode_nt_payload_bitset_parts, encode_nt_payload_full,
8 encode_nt_scalar_bitset, encode_nt_scalar_full, encode_structure_desc,
9 nt_payload_desc,
10};
11
12pub fn encode_size_pva(size: usize, is_be: bool) -> Vec<u8> {
13 crate::encode_common::encode_size(size, is_be)
14}
15
16pub fn encode_string_pva(value: &str, is_be: bool) -> Vec<u8> {
17 crate::encode_common::encode_string(value, is_be)
18}
19
20fn encode_status_ok() -> Vec<u8> {
21 vec![0xFF]
22}
23
24fn encode_status_error(message: &str, is_be: bool) -> Vec<u8> {
25 let mut out = Vec::new();
26 out.push(0x02);
27 out.extend_from_slice(&encode_string_pva(message, is_be));
28 out.extend_from_slice(&encode_string_pva("", is_be));
29 out
30}
31
32pub fn encode_message_error(message: &str, version: u8, is_be: bool) -> Vec<u8> {
33 let payload = encode_status_error(message, is_be);
34 let mut out = encode_header(true, is_be, false, version, 18, payload.len() as u32);
35 out.extend_from_slice(&payload);
36 out
37}
38
39pub fn encode_header(
40 is_server: bool,
41 is_be: bool,
42 is_control: bool,
43 version: u8,
44 command: u8,
45 payload_length: u32,
46) -> Vec<u8> {
47 let magic = 0xCA;
48 let mut flags = 0u8;
49 if is_control {
50 flags |= 0x01;
51 }
52 if is_server {
53 flags |= 0x40;
54 }
55 if is_be {
56 flags |= 0x80;
57 }
58 let mut out = vec![magic, version, flags, command];
59 let len_bytes = if is_be {
60 payload_length.to_be_bytes()
61 } else {
62 payload_length.to_le_bytes()
63 };
64 out.extend_from_slice(&len_bytes);
65 out
66}
67
68pub fn encode_search_response(
69 guid: [u8; 12],
70 seq: u32,
71 addr: [u8; 16],
72 port: u16,
73 protocol: &str,
74 found: bool,
75 cids: &[u32],
76 version: u8,
77 is_be: bool,
78) -> Vec<u8> {
79 let mut payload = Vec::new();
80 payload.extend_from_slice(&guid);
81 payload.extend_from_slice(&if is_be {
82 seq.to_be_bytes()
83 } else {
84 seq.to_le_bytes()
85 });
86 payload.extend_from_slice(&addr);
87 payload.extend_from_slice(&if is_be {
88 port.to_be_bytes()
89 } else {
90 port.to_le_bytes()
91 });
92 payload.extend_from_slice(&encode_string_pva(protocol, is_be));
93 payload.push(if found { 1 } else { 0 });
94 let count = cids.len() as u16;
95 payload.extend_from_slice(&if is_be {
96 count.to_be_bytes()
97 } else {
98 count.to_le_bytes()
99 });
100 for cid in cids {
101 payload.extend_from_slice(&if is_be {
102 cid.to_be_bytes()
103 } else {
104 cid.to_le_bytes()
105 });
106 }
107
108 let mut out = encode_header(true, is_be, false, version, 4, payload.len() as u32);
109 out.extend_from_slice(&payload);
110 out
111}
112
113pub fn encode_connection_validated(version: u8, is_be: bool) -> Vec<u8> {
114 let payload = encode_status_ok();
115 let mut out = encode_header(true, is_be, false, version, 9, payload.len() as u32);
116 out.extend_from_slice(&payload);
117 out
118}
119
120pub fn encode_control_message(
121 is_server: bool,
122 is_be: bool,
123 version: u8,
124 command: u8,
125 data: u32,
126) -> Vec<u8> {
127 encode_header(is_server, is_be, true, version, command, data)
129}
130
131pub fn encode_connection_validation(
132 buffer_size: u32,
133 introspection_registry_size: u16,
134 qos: u16,
135 authz_name: &str,
136 version: u8,
137 is_be: bool,
138) -> Vec<u8> {
139 let mut payload = Vec::new();
140 payload.extend_from_slice(&if is_be {
141 buffer_size.to_be_bytes()
142 } else {
143 buffer_size.to_le_bytes()
144 });
145 payload.extend_from_slice(&if is_be {
146 introspection_registry_size.to_be_bytes()
147 } else {
148 introspection_registry_size.to_le_bytes()
149 });
150 payload.extend_from_slice(&if is_be {
151 qos.to_be_bytes()
152 } else {
153 qos.to_le_bytes()
154 });
155 payload.extend_from_slice(&encode_string_pva(authz_name, is_be));
156 let mut out = encode_header(true, is_be, false, version, 1, payload.len() as u32);
157 out.extend_from_slice(&payload);
158 out
159}
160
161pub fn encode_create_channel_response(cid: u32, sid: u32, version: u8, is_be: bool) -> Vec<u8> {
162 let mut payload = Vec::new();
163 payload.extend_from_slice(&if is_be {
164 cid.to_be_bytes()
165 } else {
166 cid.to_le_bytes()
167 });
168 payload.extend_from_slice(&if is_be {
169 sid.to_be_bytes()
170 } else {
171 sid.to_le_bytes()
172 });
173 payload.extend_from_slice(&encode_status_ok());
174 let mut out = encode_header(true, is_be, false, version, 7, payload.len() as u32);
175 out.extend_from_slice(&payload);
176 out
177}
178
179pub fn encode_create_channel_error(cid: u32, message: &str, version: u8, is_be: bool) -> Vec<u8> {
180 let mut payload = Vec::new();
181 payload.extend_from_slice(&if is_be {
182 cid.to_be_bytes()
183 } else {
184 cid.to_le_bytes()
185 });
186 payload.extend_from_slice(&if is_be {
187 0u32.to_be_bytes()
188 } else {
189 0u32.to_le_bytes()
190 });
191 payload.push(0x01);
192 payload.extend_from_slice(&encode_string_pva(message, is_be));
193 payload.extend_from_slice(&encode_string_pva("", is_be));
194 let mut out = encode_header(true, is_be, false, version, 7, payload.len() as u32);
195 out.extend_from_slice(&payload);
196 out
197}
198
199pub fn encode_get_field_response(
200 request_id: u32,
201 desc: &StructureDesc,
202 version: u8,
203 is_be: bool,
204) -> Vec<u8> {
205 let mut payload = Vec::new();
206 payload.extend_from_slice(&if is_be {
207 request_id.to_be_bytes()
208 } else {
209 request_id.to_le_bytes()
210 });
211 payload.extend_from_slice(&encode_status_ok());
212 payload.push(0x80);
213 payload.extend_from_slice(&encode_structure_desc(desc, is_be));
214 let mut out = encode_header(true, is_be, false, version, 17, payload.len() as u32);
215 out.extend_from_slice(&payload);
216 out
217}
218
219pub fn encode_get_field_error(request_id: u32, message: &str, version: u8, is_be: bool) -> Vec<u8> {
220 let mut payload = Vec::new();
221 payload.extend_from_slice(&if is_be {
222 request_id.to_be_bytes()
223 } else {
224 request_id.to_le_bytes()
225 });
226 payload.extend_from_slice(&encode_status_error(message, is_be));
227 let mut out = encode_header(true, is_be, false, version, 17, payload.len() as u32);
228 out.extend_from_slice(&payload);
229 out
230}
231
232pub fn encode_op_init_response(
233 command: u8,
234 ioid: u32,
235 subcmd: u8,
236 desc: &StructureDesc,
237 nt: &NtScalar,
238 version: u8,
239 is_be: bool,
240) -> Vec<u8> {
241 let mut payload = Vec::new();
242 payload.extend_from_slice(&if is_be {
243 ioid.to_be_bytes()
244 } else {
245 ioid.to_le_bytes()
246 });
247 payload.push(subcmd);
248 payload.extend_from_slice(&encode_status_ok());
249 payload.push(0x80); payload.extend_from_slice(&encode_structure_desc(desc, is_be));
251 payload.extend_from_slice(&encode_nt_scalar_full(nt, is_be));
252
253 let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
254 out.extend_from_slice(&payload);
255 out
256}
257
258pub fn encode_op_init_response_desc(
259 command: u8,
260 ioid: u32,
261 subcmd: u8,
262 desc: &StructureDesc,
263 version: u8,
264 is_be: bool,
265) -> Vec<u8> {
266 let mut payload = Vec::new();
267 payload.extend_from_slice(&if is_be {
268 ioid.to_be_bytes()
269 } else {
270 ioid.to_le_bytes()
271 });
272 payload.push(subcmd);
273 payload.extend_from_slice(&encode_status_ok());
274 payload.push(0x80); payload.extend_from_slice(&encode_structure_desc(desc, is_be));
276
277 let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
278 out.extend_from_slice(&payload);
279 out
280}
281
282pub fn encode_op_data_response(
283 command: u8,
284 ioid: u32,
285 nt: &NtScalar,
286 version: u8,
287 is_be: bool,
288) -> Vec<u8> {
289 let mut payload = Vec::new();
290 payload.extend_from_slice(&if is_be {
291 ioid.to_be_bytes()
292 } else {
293 ioid.to_le_bytes()
294 });
295 payload.push(0x00);
296 payload.extend_from_slice(&encode_nt_scalar_bitset(nt, is_be));
297 let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
298 out.extend_from_slice(&payload);
299 out
300}
301
302pub fn encode_op_get_data_response_payload(
303 ioid: u32,
304 payload_value: &NtPayload,
305 version: u8,
306 is_be: bool,
307) -> Vec<u8> {
308 encode_op_data_response_payload(10, ioid, payload_value, version, is_be)
309}
310
311pub fn encode_op_data_response_payload(
312 command: u8,
313 ioid: u32,
314 payload_value: &NtPayload,
315 version: u8,
316 is_be: bool,
317) -> Vec<u8> {
318 let mut payload = Vec::new();
319 payload.extend_from_slice(&if is_be {
320 ioid.to_be_bytes()
321 } else {
322 ioid.to_le_bytes()
323 });
324 payload.push(0x00);
325 payload.extend_from_slice(&encode_status_ok());
326 payload.extend_from_slice(&encode_nt_payload_bitset(payload_value, is_be));
327 let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
328 out.extend_from_slice(&payload);
329 out
330}
331
332pub fn encode_op_status_response(
333 command: u8,
334 ioid: u32,
335 subcmd: u8,
336 version: u8,
337 is_be: bool,
338) -> Vec<u8> {
339 let mut payload = Vec::new();
340 payload.extend_from_slice(&if is_be {
341 ioid.to_be_bytes()
342 } else {
343 ioid.to_le_bytes()
344 });
345 payload.push(subcmd);
346 payload.extend_from_slice(&encode_status_ok());
347 let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
348 out.extend_from_slice(&payload);
349 out
350}
351
352pub fn encode_op_status_error_response(
353 command: u8,
354 ioid: u32,
355 subcmd: u8,
356 message: &str,
357 version: u8,
358 is_be: bool,
359) -> Vec<u8> {
360 let mut payload = Vec::new();
361 payload.extend_from_slice(&if is_be {
362 ioid.to_be_bytes()
363 } else {
364 ioid.to_le_bytes()
365 });
366 payload.push(subcmd);
367 payload.extend_from_slice(&encode_status_error(message, is_be));
368 let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
369 out.extend_from_slice(&payload);
370 out
371}
372
373pub fn encode_op_rpc_data_response_payload(
374 ioid: u32,
375 subcmd: u8,
376 payload_value: &NtPayload,
377 version: u8,
378 is_be: bool,
379) -> Vec<u8> {
380 let desc = nt_payload_desc(payload_value);
381 let mut payload = Vec::new();
382 payload.extend_from_slice(&if is_be {
383 ioid.to_be_bytes()
384 } else {
385 ioid.to_le_bytes()
386 });
387 payload.push(subcmd);
388 payload.extend_from_slice(&encode_status_ok());
389 payload.push(0x80);
390 payload.extend_from_slice(&encode_structure_desc(&desc, is_be));
391 payload.extend_from_slice(&encode_nt_payload_full(payload_value, is_be));
392 let mut out = encode_header(true, is_be, false, version, 20, payload.len() as u32);
393 out.extend_from_slice(&payload);
394 out
395}
396
397pub fn encode_op_put_get_init_response(
398 ioid: u32,
399 put_desc: &StructureDesc,
400 get_desc: &StructureDesc,
401 version: u8,
402 is_be: bool,
403) -> Vec<u8> {
404 let mut payload = Vec::new();
405 payload.extend_from_slice(&if is_be {
406 ioid.to_be_bytes()
407 } else {
408 ioid.to_le_bytes()
409 });
410 payload.push(0x08);
411 payload.extend_from_slice(&encode_status_ok());
412 payload.push(0x80);
413 payload.extend_from_slice(&encode_structure_desc(put_desc, is_be));
414 payload.push(0x80);
415 payload.extend_from_slice(&encode_structure_desc(get_desc, is_be));
416 let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
417 out.extend_from_slice(&payload);
418 out
419}
420
421pub fn encode_op_put_get_data_response(
422 ioid: u32,
423 nt: &NtScalar,
424 version: u8,
425 is_be: bool,
426) -> Vec<u8> {
427 encode_op_put_get_data_response_payload(ioid, &NtPayload::Scalar(nt.clone()), version, is_be)
428}
429
430pub fn encode_op_put_get_data_response_payload(
431 ioid: u32,
432 payload_value: &NtPayload,
433 version: u8,
434 is_be: bool,
435) -> Vec<u8> {
436 let mut payload = Vec::new();
437 payload.extend_from_slice(&if is_be {
438 ioid.to_be_bytes()
439 } else {
440 ioid.to_le_bytes()
441 });
442 payload.push(0x00);
443 payload.extend_from_slice(&encode_status_ok());
444 payload.extend_from_slice(&encode_nt_payload_bitset(payload_value, is_be));
445 let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
446 out.extend_from_slice(&payload);
447 out
448}
449
450pub fn encode_op_put_response(ioid: u32, version: u8, is_be: bool) -> Vec<u8> {
451 let mut payload = Vec::new();
452 payload.extend_from_slice(&if is_be {
453 ioid.to_be_bytes()
454 } else {
455 ioid.to_le_bytes()
456 });
457 payload.push(0x00);
458 payload.extend_from_slice(&encode_status_ok());
459 let mut out = encode_header(true, is_be, false, version, 11, payload.len() as u32);
460 out.extend_from_slice(&payload);
461 out
462}
463
464pub fn encode_op_put_status_response(
465 ioid: u32,
466 subcmd: u8,
467 message: &str,
468 version: u8,
469 is_be: bool,
470) -> Vec<u8> {
471 let mut payload = Vec::new();
472 payload.extend_from_slice(&if is_be {
473 ioid.to_be_bytes()
474 } else {
475 ioid.to_le_bytes()
476 });
477 payload.push(subcmd);
478 payload.extend_from_slice(&encode_status_error(message, is_be));
479 let mut out = encode_header(true, is_be, false, version, 11, payload.len() as u32);
480 out.extend_from_slice(&payload);
481 out
482}
483
484pub fn encode_op_put_getput_response(
485 ioid: u32,
486 nt: &NtScalar,
487 version: u8,
488 is_be: bool,
489) -> Vec<u8> {
490 encode_op_put_getput_response_payload(ioid, &NtPayload::Scalar(nt.clone()), version, is_be)
491}
492
493pub fn encode_op_put_getput_response_payload(
494 ioid: u32,
495 payload_value: &NtPayload,
496 version: u8,
497 is_be: bool,
498) -> Vec<u8> {
499 let mut payload = Vec::new();
500 payload.extend_from_slice(&if is_be {
501 ioid.to_be_bytes()
502 } else {
503 ioid.to_le_bytes()
504 });
505 payload.push(0x40);
506 payload.extend_from_slice(&encode_status_ok());
507 payload.extend_from_slice(&encode_nt_payload_bitset(payload_value, is_be));
508 let mut out = encode_header(true, is_be, false, version, 11, payload.len() as u32);
509 out.extend_from_slice(&payload);
510 out
511}
512
513pub fn encode_op_put_get_init_error_response(
514 ioid: u32,
515 message: &str,
516 version: u8,
517 is_be: bool,
518) -> Vec<u8> {
519 let mut payload = Vec::new();
520 payload.extend_from_slice(&if is_be {
521 ioid.to_be_bytes()
522 } else {
523 ioid.to_le_bytes()
524 });
525 payload.push(0x08);
526 payload.extend_from_slice(&encode_status_error(message, is_be));
527 let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
528 out.extend_from_slice(&payload);
529 out
530}
531
532pub fn encode_op_put_get_data_error_response(
533 ioid: u32,
534 message: &str,
535 version: u8,
536 is_be: bool,
537) -> Vec<u8> {
538 let mut payload = Vec::new();
539 payload.extend_from_slice(&if is_be {
540 ioid.to_be_bytes()
541 } else {
542 ioid.to_le_bytes()
543 });
544 payload.push(0x00);
545 payload.extend_from_slice(&encode_status_error(message, is_be));
546 let mut out = encode_header(true, is_be, false, version, 12, payload.len() as u32);
547 out.extend_from_slice(&payload);
548 out
549}
550
551pub fn encode_monitor_data_response(
552 ioid: u32,
553 subcmd: u8,
554 nt: &NtScalar,
555 version: u8,
556 is_be: bool,
557) -> Vec<u8> {
558 encode_monitor_data_response_payload(
559 ioid,
560 subcmd,
561 &NtPayload::Scalar(nt.clone()),
562 version,
563 is_be,
564 )
565}
566
567pub fn encode_monitor_data_response_payload(
568 ioid: u32,
569 subcmd: u8,
570 payload_value: &NtPayload,
571 version: u8,
572 is_be: bool,
573) -> Vec<u8> {
574 let (changed_bitset, values) = encode_nt_payload_bitset_parts(payload_value, is_be);
575 let mut payload = Vec::new();
576 payload.extend_from_slice(&if is_be {
577 ioid.to_be_bytes()
578 } else {
579 ioid.to_le_bytes()
580 });
581 payload.push(subcmd);
582 if (subcmd & 0x10) != 0 {
583 payload.extend_from_slice(&encode_status_ok());
584 }
585 payload.extend_from_slice(&changed_bitset);
586 payload.extend_from_slice(&values);
587 payload.extend_from_slice(&encode_size_pva(0, is_be));
589 let mut out = encode_header(true, is_be, false, version, 13, payload.len() as u32);
590 out.extend_from_slice(&payload);
591 out
592}
593
594pub fn encode_destroy_channel_response(sid: u32, cid: u32, version: u8, is_be: bool) -> Vec<u8> {
595 let mut payload = Vec::new();
596 payload.extend_from_slice(&if is_be {
597 sid.to_be_bytes()
598 } else {
599 sid.to_le_bytes()
600 });
601 payload.extend_from_slice(&if is_be {
602 cid.to_be_bytes()
603 } else {
604 cid.to_le_bytes()
605 });
606 let mut out = encode_header(true, is_be, false, version, 8, payload.len() as u32);
607 out.extend_from_slice(&payload);
608 out
609}
610
611pub fn encode_op_error(command: u8, ioid: u32, message: &str, version: u8, is_be: bool) -> Vec<u8> {
612 let mut payload = Vec::new();
613 payload.extend_from_slice(&if is_be {
614 ioid.to_be_bytes()
615 } else {
616 ioid.to_le_bytes()
617 });
618 payload.push(0x08);
619 payload.push(0x01); payload.extend_from_slice(&encode_string_pva(message, is_be));
621 payload.extend_from_slice(&encode_string_pva("", is_be));
622 let mut out = encode_header(true, is_be, false, version, command, payload.len() as u32);
623 out.extend_from_slice(&payload);
624 out
625}
626
627pub fn encode_beacon(
628 guid: [u8; 12],
629 seq: u8,
630 change_count: u16,
631 addr: [u8; 16],
632 port: u16,
633 protocol: &str,
634 version: u8,
635 is_be: bool,
636) -> Vec<u8> {
637 let mut payload = Vec::new();
638 payload.extend_from_slice(&guid);
639 payload.push(0x00); payload.push(seq);
641 payload.extend_from_slice(&if is_be {
642 change_count.to_be_bytes()
643 } else {
644 change_count.to_le_bytes()
645 });
646 payload.extend_from_slice(&addr);
647 payload.extend_from_slice(&if is_be {
648 port.to_be_bytes()
649 } else {
650 port.to_le_bytes()
651 });
652 payload.extend_from_slice(&encode_string_pva(protocol, is_be));
653 payload.push(0xFF);
657 let mut out = encode_header(true, is_be, false, version, 0, payload.len() as u32);
658 out.extend_from_slice(&payload);
659 out
660}
661
662pub fn ip_to_bytes(ip: IpAddr) -> [u8; 16] {
671 match ip {
672 IpAddr::V4(v4) => {
673 let mut out = [0u8; 16];
674 out[10] = 0xFF;
675 out[11] = 0xFF;
676 out[12..16].copy_from_slice(&v4.octets());
677 out
678 }
679 IpAddr::V6(v6) => v6.octets(),
680 }
681}
682
683pub fn ip_from_bytes(addr: &[u8; 16]) -> Option<IpAddr> {
688 if addr.iter().all(|&b| b == 0) {
689 return None;
690 }
691 if addr[0..10].iter().all(|&b| b == 0) && addr[10] == 0xFF && addr[11] == 0xFF {
693 return Some(IpAddr::V4(Ipv4Addr::new(
694 addr[12], addr[13], addr[14], addr[15],
695 )));
696 }
697 Some(IpAddr::V6(Ipv6Addr::from(*addr)))
698}
699
700pub fn format_pva_address(addr: &[u8; 16]) -> String {
704 match ip_from_bytes(addr) {
705 Some(ip) => ip.to_string(),
706 None => "0.0.0.0".to_string(),
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use crate::epics_decode::{PvaPacket, PvaPacketCommand};
714
715 #[test]
716 fn encode_decode_connection_validation_roundtrip() {
717 let msg = encode_connection_validation(4096, 2, 0x10, "test", 2, true);
718 let mut pkt = PvaPacket::new(&msg);
719 let cmd = pkt.decode_payload().expect("decoded");
720 match cmd {
721 PvaPacketCommand::ConnectionValidation(payload) => {
722 assert_eq!(payload.buffer_size, 4096);
723 assert_eq!(payload.introspection_registry_size, 2);
724 assert_eq!(payload.qos, 0x10);
725 assert_eq!(payload.authz.as_deref(), Some("test"));
726 }
727 other => panic!("unexpected decode: {:?}", other),
728 }
729 }
730
731 #[test]
732 fn encode_decode_search_response_roundtrip() {
733 let guid = [1u8; 12];
734 let seq = 42;
735 let addr = [0u8; 16];
736 let port = 5075;
737 let cids = vec![100u32, 101u32];
738 let msg = encode_search_response(guid, seq, addr, port, "tcp", true, &cids, 2, false);
739 let mut pkt = PvaPacket::new(&msg);
740 let cmd = pkt.decode_payload().expect("decoded");
741 match cmd {
742 PvaPacketCommand::SearchResponse(payload) => {
743 assert_eq!(payload.guid, guid);
744 assert_eq!(payload.seq, seq);
745 assert_eq!(payload.port, port);
746 assert!(payload.found);
747 assert_eq!(payload.cids, cids);
748 }
749 other => panic!("unexpected decode: {:?}", other),
750 }
751 }
752
753 #[test]
754 fn encode_decode_connection_validated_roundtrip() {
755 let msg = encode_connection_validated(2, false);
756 let mut pkt = PvaPacket::new(&msg);
757 let cmd = pkt.decode_payload().expect("decoded");
758 match cmd {
759 PvaPacketCommand::ConnectionValidated(payload) => {
760 assert!(payload.status.is_none());
762 }
763 other => panic!("unexpected decode: {:?}", other),
764 }
765 }
766
767 #[test]
768 fn get_data_response_includes_status() {
769 let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(1.0));
770 let msg = encode_op_get_data_response_payload(
771 0x11223344,
772 &NtPayload::Scalar(nt),
773 2,
774 false,
775 );
776 assert!(msg.len() > 13);
777 let status_offset = 8 + 4 + 1;
778 assert_eq!(msg[status_offset], 0xFF);
779
780 let mut pkt = PvaPacket::new(&msg);
781 let cmd = pkt.decode_payload().expect("decoded");
782 match cmd {
783 PvaPacketCommand::Op(op) => {
784 assert_eq!(op.command, 10);
785 assert_eq!(op.subcmd, 0x00);
786 assert!(!op.body.is_empty());
787 }
788 other => panic!("unexpected decode: {:?}", other),
789 }
790 }
791
792 #[test]
793 fn put_get_init_includes_two_descriptors() {
794 let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(1.0));
795 let desc = crate::spvd_encode::nt_scalar_desc(&nt.value);
796 let msg = encode_op_put_get_init_response(0x01020304, &desc, &desc, 2, false);
797
798 let payload = &msg[8..];
799 assert!(payload.len() > 6);
800 assert_eq!(payload[5], 0xFF);
802 let rest = &payload[6..];
803 let first = rest.first().copied().unwrap_or(0);
804 assert_eq!(first, 0x80);
805 let second_pos = rest.iter().skip(1).position(|b| *b == 0x80);
806 assert!(second_pos.is_some(), "expected second descriptor marker");
807 }
808
809 #[test]
810 fn put_get_data_includes_status() {
811 let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(2.0));
812 let msg = encode_op_put_get_data_response(0x55667788, &nt, 2, false);
813 assert!(msg.len() > 13);
814 let status_offset = 8 + 4 + 1;
815 assert_eq!(msg[status_offset], 0xFF);
816 }
817
818 #[test]
819 fn put_getput_response_encodes_subcmd_0x40() {
820 let nt = NtScalar::from_value(spvirit_types::ScalarValue::F64(2.0));
821 let msg = encode_op_put_getput_response(0x01020304, &nt, 2, false);
822 assert!(msg.len() > 13);
823 let status_offset = 8 + 4 + 1;
824 assert_eq!(msg[status_offset], 0xFF);
825 let mut pkt = PvaPacket::new(&msg);
826 let cmd = pkt.decode_payload().expect("decoded");
827 match cmd {
828 PvaPacketCommand::Op(op) => {
829 assert_eq!(op.command, 11);
830 assert_eq!(op.subcmd, 0x40);
831 }
832 other => panic!("unexpected decode: {:?}", other),
833 }
834 }
835
836 #[test]
837 fn encode_get_field_response_roundtrip() {
838 let desc = StructureDesc {
839 struct_id: Some("epics:nt/NTScalar:1.0".to_string()),
840 fields: vec![crate::spvd_decode::FieldDesc {
841 name: "value".to_string(),
842 field_type: crate::spvd_decode::FieldType::Scalar(
843 crate::spvd_decode::TypeCode::String,
844 ),
845 }],
846 };
847 let msg = encode_get_field_response(11, &desc, 2, false);
848 let mut pkt = PvaPacket::new(&msg);
849 let cmd = pkt.decode_payload().expect("decoded");
850 match cmd {
851 PvaPacketCommand::GetField(payload) => {
852 assert!(payload.is_server);
853 assert_eq!(payload.cid, 11);
854 assert!(payload.status.is_none());
855 let intro = payload.introspection.expect("introspection");
856 assert_eq!(intro.fields.len(), 1);
857 assert_eq!(intro.fields[0].name, "value");
858 }
859 other => panic!("unexpected decode: {:?}", other),
860 }
861 }
862
863 #[test]
864 fn encode_get_field_error_roundtrip() {
865 let msg = encode_get_field_error(7, "listing disabled", 2, false);
866 let mut pkt = PvaPacket::new(&msg);
867 let cmd = pkt.decode_payload().expect("decoded");
868 match cmd {
869 PvaPacketCommand::GetField(payload) => {
870 assert!(payload.is_server);
871 assert_eq!(payload.cid, 7);
872 let status = payload.status.expect("status");
873 assert_eq!(status.code, 0x02);
874 assert_eq!(status.message.as_deref(), Some("listing disabled"));
875 }
876 other => panic!("unexpected decode: {:?}", other),
877 }
878 }
879}