1use hex;
7use std::fmt;
8use tracing::debug;
9
10use crate::spvirit_encode::format_pva_address;
11use crate::spvd_decode::{format_compact_value, DecodedValue, PvdDecoder, StructureDesc};
12
13const PVA_COMMAND_NAMES: &[&str] = &[
17 "BEACON", "CONNECTION_VALIDATION", "ECHO", "SEARCH", "SEARCH_RESPONSE", "AUTHNZ", "ACL_CHANGE", "CREATE_CHANNEL", "DESTROY_CHANNEL", "CONNECTION_VALIDATED", "GET", "PUT", "PUT_GET", "MONITOR", "ARRAY", "DESTROY_REQUEST", "PROCESS", "GET_FIELD", "MESSAGE", "MULTIPLE_DATA", "RPC", "CANCEL_REQUEST", "ORIGIN_TAG", ];
41
42pub fn command_name(code: u8) -> &'static str {
44 PVA_COMMAND_NAMES
45 .get(code as usize)
46 .copied()
47 .unwrap_or("Unknown")
48}
49
50pub fn command_to_integer(command: &str) -> u8 {
52 PVA_COMMAND_NAMES
53 .iter()
54 .position(|&name| name == command)
55 .map(|i| i as u8)
56 .unwrap_or(255)
57}
58
59#[derive(Debug)]
62pub struct PvaCommands;
63
64impl PvaCommands {
65 pub fn new() -> Self {
66 Self
67 }
68
69 pub fn get_command(&self, code: u8) -> &'static str {
70 command_name(code)
71 }
72}
73#[derive(Debug)]
74pub struct PvaControlFlags {
75 pub raw: u8,
76 pub is_application: bool,
82 pub is_control: bool,
83 pub is_segmented: u8,
84 pub is_first_segment: bool,
85 pub is_last_segment: bool,
86 pub is_middle_segment: bool,
87 pub is_client: bool,
88 pub is_server: bool,
89 pub is_lsb: bool,
90 pub is_msb: bool,
91 pub is_valid: bool,
92}
93
94impl PvaControlFlags {
95 pub fn new(raw: u8) -> Self {
96 let is_application = (raw & 0x01) == 0; let is_control = (raw & 0x01) != 0; let is_segmented = (raw & 0x30) >> 4; let is_first_segment = is_segmented == 0x01; let is_last_segment = is_segmented == 0x02; let is_middle_segment = is_segmented == 0x03; let is_client = (raw & 0x40) == 0; let is_server = (raw & 0x40) != 0; let is_lsb = (raw & 0x80) == 0; let is_msb = (raw & 0x80) != 0; let is_valid = (raw & 0x0E) == 0; Self {
109 raw,
110 is_application,
111 is_control,
112 is_segmented,
113 is_first_segment,
114 is_last_segment,
115 is_middle_segment,
116 is_client,
117 is_server,
118 is_lsb,
119 is_msb,
120 is_valid,
121 }
122 }
123 fn is_valid(&self) -> bool {
124 self.is_valid
125 }
126}
127#[derive(Debug)]
128pub struct PvaHeader {
129 pub magic: u8,
130 pub version: u8,
131 pub flags: PvaControlFlags,
132 pub command: u8,
133 pub payload_length: u32,
134}
135
136impl PvaHeader {
137 pub fn new(raw: &[u8]) -> Self {
138 if raw.len() < 8 {
139 panic!("PVA header is too short");
140 }
141 let magic = raw[0];
142 let version = raw[1];
143 let flags = PvaControlFlags::new(raw[2]);
144 let command: u8 = raw[3];
145 let payload_length_bytes: [u8; 4] = raw[4..8]
146 .try_into()
147 .expect("Slice for payload_length has incorrect length");
148 let payload_length = if flags.is_msb {
149 u32::from_be_bytes(payload_length_bytes)
150 } else {
151 u32::from_le_bytes(payload_length_bytes)
152 };
153
154 Self {
155 magic,
156 version,
157 flags,
158 command,
159 payload_length,
160 }
161 }
162 pub fn is_valid(&self) -> bool {
163 self.magic == 0xCA && self.flags.is_valid()
164 }
165}
166
167#[derive(Debug)]
168pub enum PvaPacketCommand {
169 Control(PvaControlPayload),
170 Search(PvaSearchPayload),
171 SearchResponse(PvaSearchResponsePayload),
172 Beacon(PvaBeaconPayload),
173 ConnectionValidation(PvaConnectionValidationPayload),
174 ConnectionValidated(PvaConnectionValidatedPayload),
175 AuthNZ(PvaAuthNzPayload),
176 AclChange(PvaAclChangePayload),
177 Op(PvaOpPayload),
178 CreateChannel(PvaCreateChannelPayload),
179 DestroyChannel(PvaDestroyChannelPayload),
180 GetField(PvaGetFieldPayload),
181 Message(PvaMessagePayload),
182 MultipleData(PvaMultipleDataPayload),
183 CancelRequest(PvaCancelRequestPayload),
184 DestroyRequest(PvaDestroyRequestPayload),
185 OriginTag(PvaOriginTagPayload),
186 Echo(Vec<u8>),
187 Unknown(PvaUnknownPayload),
188}
189#[derive(Debug)]
190pub struct PvaPacket {
191 pub header: PvaHeader,
192 pub payload: Vec<u8>,
193}
194
195impl PvaPacket {
196 pub fn new(raw: &[u8]) -> Self {
197 let header = PvaHeader::new(raw);
198 let payload = raw.to_vec();
199 Self { header, payload }
200 }
201 pub fn decode_payload(&mut self) -> Option<PvaPacketCommand> {
202 let pva_header_size = 8;
203 if self.payload.len() < pva_header_size {
204 debug!("Packet too short to contain a PVA payload beyond the header.");
205 return None;
206 }
207
208 let expected_total_len = if self.header.flags.is_control {
209 pva_header_size
210 } else {
211 pva_header_size + self.header.payload_length as usize
212 };
213 if self.payload.len() < expected_total_len {
214 debug!(
215 "Packet data length {} is less than expected total length {} (header {} + payload_length {})",
216 self.payload.len(),
217 expected_total_len,
218 pva_header_size,
219 self.header.payload_length
220 );
221 return None;
222 }
223
224 let command_payload_slice = &self.payload[pva_header_size..expected_total_len];
225
226 if self.header.flags.is_control {
227 return Some(PvaPacketCommand::Control(PvaControlPayload::new(
228 self.header.command,
229 self.header.payload_length,
230 )));
231 }
232
233 let decoded = match self.header.command {
234 0 => PvaBeaconPayload::new(command_payload_slice, self.header.flags.is_msb)
235 .map(PvaPacketCommand::Beacon),
236 2 => Some(PvaPacketCommand::Echo(command_payload_slice.to_vec())),
237 1 => PvaConnectionValidationPayload::new(
238 command_payload_slice,
239 self.header.flags.is_msb,
240 self.header.flags.is_server,
241 )
242 .map(PvaPacketCommand::ConnectionValidation),
243 3 => PvaSearchPayload::new(command_payload_slice, self.header.flags.is_msb)
244 .map(PvaPacketCommand::Search),
245 4 => PvaSearchResponsePayload::new(command_payload_slice, self.header.flags.is_msb)
246 .map(PvaPacketCommand::SearchResponse),
247 5 => PvaAuthNzPayload::new(command_payload_slice, self.header.flags.is_msb)
248 .map(PvaPacketCommand::AuthNZ),
249 6 => PvaAclChangePayload::new(command_payload_slice, self.header.flags.is_msb)
250 .map(PvaPacketCommand::AclChange),
251 7 => PvaCreateChannelPayload::new(
252 command_payload_slice,
253 self.header.flags.is_msb,
254 self.header.flags.is_server,
255 )
256 .map(PvaPacketCommand::CreateChannel),
257 8 => PvaDestroyChannelPayload::new(command_payload_slice, self.header.flags.is_msb)
258 .map(PvaPacketCommand::DestroyChannel),
259 9 => {
260 PvaConnectionValidatedPayload::new(command_payload_slice, self.header.flags.is_msb)
261 .map(PvaPacketCommand::ConnectionValidated)
262 }
263 10 | 11 | 12 | 13 | 14 | 16 | 20 => PvaOpPayload::new(
264 command_payload_slice,
265 self.header.flags.is_msb,
266 self.header.flags.is_server,
267 self.header.command,
268 )
269 .map(PvaPacketCommand::Op),
270 15 => PvaDestroyRequestPayload::new(command_payload_slice, self.header.flags.is_msb)
271 .map(PvaPacketCommand::DestroyRequest),
272 17 => PvaGetFieldPayload::new(
273 command_payload_slice,
274 self.header.flags.is_msb,
275 self.header.flags.is_server,
276 )
277 .map(PvaPacketCommand::GetField),
278 18 => PvaMessagePayload::new(command_payload_slice, self.header.flags.is_msb)
279 .map(PvaPacketCommand::Message),
280 19 => PvaMultipleDataPayload::new(command_payload_slice, self.header.flags.is_msb)
281 .map(PvaPacketCommand::MultipleData),
282 21 => PvaCancelRequestPayload::new(command_payload_slice, self.header.flags.is_msb)
283 .map(PvaPacketCommand::CancelRequest),
284 22 => PvaOriginTagPayload::new(command_payload_slice).map(PvaPacketCommand::OriginTag),
285 _ => None,
286 };
287
288 if let Some(cmd) = decoded {
289 Some(cmd)
290 } else {
291 debug!(
292 "Decoding not implemented or unknown command: {}",
293 self.header.command
294 );
295 Some(PvaPacketCommand::Unknown(PvaUnknownPayload::new(
296 self.header.command,
297 false,
298 command_payload_slice.len(),
299 )))
300 }
301 }
302
303 pub fn is_valid(&self) -> bool {
304 self.header.is_valid()
305 }
306}
307
308pub fn decode_size(raw: &[u8], is_be: bool) -> Option<(usize, usize)> {
310 if raw.is_empty() {
311 return None;
312 }
313
314 match raw[0] {
315 255 => Some((0, 1)),
316 254 => {
317 if raw.len() < 5 {
318 return None;
319 }
320 let size_bytes = &raw[1..5];
321 let size = if is_be {
322 u32::from_be_bytes(size_bytes.try_into().unwrap())
323 } else {
324 u32::from_le_bytes(size_bytes.try_into().unwrap())
325 };
326 Some((size as usize, 5))
327 }
328 short_len => Some((short_len as usize, 1)),
329 }
330}
331
332pub fn decode_string(raw: &[u8], is_be: bool) -> Option<(String, usize)> {
334 let (size, offset) = decode_size(raw, is_be)?;
335 let total_len = offset + size;
336 if raw.len() < total_len {
337 return None;
338 }
339
340 let string_bytes = &raw[offset..total_len];
341 let s = String::from_utf8_lossy(string_bytes).to_string();
342 Some((s, total_len))
343}
344
345fn decode_status(raw: &[u8], is_be: bool) -> (Option<PvaStatus>, usize) {
346 if raw.is_empty() {
347 return (None, 0);
348 }
349 let code = raw[0];
350 if code == 0xff {
351 return (None, 1);
352 }
353 let mut idx = 1usize;
354 let mut message: Option<String> = None;
355 let mut stack: Option<String> = None;
356 if let Some((msg, consumed)) = decode_string(&raw[idx..], is_be) {
357 message = Some(msg);
358 idx += consumed;
359 if let Some((st, consumed2)) = decode_string(&raw[idx..], is_be) {
360 stack = Some(st);
361 idx += consumed2;
362 }
363 }
364 (
365 Some(PvaStatus {
366 code,
367 message,
368 stack,
369 }),
370 idx,
371 )
372}
373
374#[derive(Debug)]
375pub struct PvaControlPayload {
376 pub command: u8,
377 pub data: u32,
378}
379
380impl PvaControlPayload {
381 pub fn new(command: u8, data: u32) -> Self {
382 Self { command, data }
383 }
384}
385
386#[derive(Debug)]
387pub struct PvaSearchResponsePayload {
388 pub guid: [u8; 12],
389 pub seq: u32,
390 pub addr: [u8; 16],
391 pub port: u16,
392 pub protocol: String,
393 pub found: bool,
394 pub cids: Vec<u32>,
395}
396
397impl PvaSearchResponsePayload {
398 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
399 if raw.len() < 34 {
400 debug!("PvaSearchResponsePayload::new: raw too short {}", raw.len());
401 return None;
402 }
403 let guid: [u8; 12] = raw[0..12].try_into().ok()?;
404 let seq = if is_be {
405 u32::from_be_bytes(raw[12..16].try_into().ok()?)
406 } else {
407 u32::from_le_bytes(raw[12..16].try_into().ok()?)
408 };
409 let addr: [u8; 16] = raw[16..32].try_into().ok()?;
410 let port = if is_be {
411 u16::from_be_bytes(raw[32..34].try_into().ok()?)
412 } else {
413 u16::from_le_bytes(raw[32..34].try_into().ok()?)
414 };
415
416 let mut offset = 34;
417 let (protocol, consumed) = decode_string(&raw[offset..], is_be)?;
418 offset += consumed;
419
420 if raw.len() <= offset {
421 return Some(Self {
422 guid,
423 seq,
424 addr,
425 port,
426 protocol,
427 found: false,
428 cids: vec![],
429 });
430 }
431
432 let found = raw[offset] != 0;
433 offset += 1;
434 let mut cids: Vec<u32> = vec![];
435 if raw.len() >= offset + 2 {
436 let count = if is_be {
437 u16::from_be_bytes(raw[offset..offset + 2].try_into().ok()?)
438 } else {
439 u16::from_le_bytes(raw[offset..offset + 2].try_into().ok()?)
440 };
441 offset += 2;
442 for _ in 0..count {
443 if raw.len() < offset + 4 {
444 break;
445 }
446 let cid = if is_be {
447 u32::from_be_bytes(raw[offset..offset + 4].try_into().ok()?)
448 } else {
449 u32::from_le_bytes(raw[offset..offset + 4].try_into().ok()?)
450 };
451 cids.push(cid);
452 offset += 4;
453 }
454 }
455
456 Some(Self {
457 guid,
458 seq,
459 addr,
460 port,
461 protocol,
462 found,
463 cids,
464 })
465 }
466}
467
468#[derive(Debug)]
469pub struct PvaConnectionValidationPayload {
470 pub is_server: bool,
471 pub buffer_size: u32,
472 pub introspection_registry_size: u16,
473 pub qos: u16,
474 pub authz: Option<String>,
475}
476
477impl PvaConnectionValidationPayload {
478 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
479 if raw.len() < 8 {
480 debug!(
481 "PvaConnectionValidationPayload::new: raw too short {}",
482 raw.len()
483 );
484 return None;
485 }
486 let buffer_size = if is_be {
487 u32::from_be_bytes(raw[0..4].try_into().ok()?)
488 } else {
489 u32::from_le_bytes(raw[0..4].try_into().ok()?)
490 };
491 let introspection_registry_size = if is_be {
492 u16::from_be_bytes(raw[4..6].try_into().ok()?)
493 } else {
494 u16::from_le_bytes(raw[4..6].try_into().ok()?)
495 };
496 let qos = if is_be {
497 u16::from_be_bytes(raw[6..8].try_into().ok()?)
498 } else {
499 u16::from_le_bytes(raw[6..8].try_into().ok()?)
500 };
501 let authz = if raw.len() > 8 {
502 if let Some((s, consumed)) = decode_string(&raw[8..], is_be) {
504 if 8 + consumed == raw.len() {
505 Some(s)
506 } else {
507 let mut offset = 9; let name = decode_string(&raw[offset..], is_be).map(|(s, c)| {
510 offset += c;
511 s
512 });
513 let method = decode_string(&raw[offset..], is_be).map(|(s, _)| s);
514 match (name, method) {
515 (Some(n), _) if !n.is_empty() => Some(n),
516 (_, Some(m)) if !m.is_empty() => Some(m),
517 _ => None,
518 }
519 }
520 } else {
521 None
522 }
523 } else {
524 None
525 };
526
527 Some(Self {
528 is_server,
529 buffer_size,
530 introspection_registry_size,
531 qos,
532 authz,
533 })
534 }
535}
536
537#[derive(Debug)]
538pub struct PvaConnectionValidatedPayload {
539 pub status: Option<PvaStatus>,
540}
541
542impl PvaConnectionValidatedPayload {
543 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
544 let (status, _consumed) = decode_status(raw, is_be);
545 Some(Self { status })
546 }
547}
548
549#[derive(Debug)]
550pub struct PvaAuthNzPayload {
551 pub raw: Vec<u8>,
552 pub strings: Vec<String>,
553}
554
555impl PvaAuthNzPayload {
556 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
557 let mut strings = vec![];
558 if let Some((count, consumed)) = decode_size(raw, is_be) {
559 let mut offset = consumed;
560 for _ in 0..count {
561 if let Some((s, len)) = decode_string(&raw[offset..], is_be) {
562 strings.push(s);
563 offset += len;
564 } else {
565 break;
566 }
567 }
568 }
569 Some(Self {
570 raw: raw.to_vec(),
571 strings,
572 })
573 }
574}
575
576#[derive(Debug)]
577pub struct PvaAclChangePayload {
578 pub status: Option<PvaStatus>,
579 pub raw: Vec<u8>,
580}
581
582impl PvaAclChangePayload {
583 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
584 let (status, consumed) = decode_status(raw, is_be);
585 let raw_rem = if raw.len() > consumed {
586 raw[consumed..].to_vec()
587 } else {
588 vec![]
589 };
590 Some(Self {
591 status,
592 raw: raw_rem,
593 })
594 }
595}
596
597#[derive(Debug)]
598pub struct PvaGetFieldPayload {
599 pub is_server: bool,
600 pub cid: u32,
601 pub sid: Option<u32>,
602 pub ioid: Option<u32>,
603 pub field_name: Option<String>,
604 pub status: Option<PvaStatus>,
605 pub introspection: Option<StructureDesc>,
606 pub raw: Vec<u8>,
607}
608
609impl PvaGetFieldPayload {
610 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
611 if !is_server {
612 if raw.len() < 4 {
613 debug!(
614 "PvaGetFieldPayload::new (client): raw too short {}",
615 raw.len()
616 );
617 return None;
618 }
619 let cid = if is_be {
620 u32::from_be_bytes(raw[0..4].try_into().ok()?)
621 } else {
622 u32::from_le_bytes(raw[0..4].try_into().ok()?)
623 };
624
625 let legacy_field = if raw.len() > 4 {
629 decode_string(&raw[4..], is_be).and_then(|(s, consumed)| {
630 (4 + consumed == raw.len()).then_some(s)
631 })
632 } else {
633 None
634 };
635
636 let epics_variant = if raw.len() >= 9 {
637 let ioid = if is_be {
638 u32::from_be_bytes(raw[4..8].try_into().ok()?)
639 } else {
640 u32::from_le_bytes(raw[4..8].try_into().ok()?)
641 };
642 decode_string(&raw[8..], is_be).and_then(|(s, consumed)| {
643 (8 + consumed == raw.len()).then_some((ioid, s))
644 })
645 } else {
646 None
647 };
648
649 let (sid, ioid, field_name) = if let Some((ioid, field)) = epics_variant {
650 (Some(cid), Some(ioid), Some(field))
651 } else {
652 (None, None, legacy_field)
653 };
654
655 return Some(Self {
656 is_server,
657 cid,
658 sid,
659 ioid,
660 field_name,
661 status: None,
662 introspection: None,
663 raw: vec![],
664 });
665 }
666
667 let parse_status_then_intro = |bytes: &[u8]| {
668 let (status, consumed) = decode_status(bytes, is_be);
669 let pvd_raw = if bytes.len() > consumed {
670 bytes[consumed..].to_vec()
671 } else {
672 vec![]
673 };
674 let introspection = if !pvd_raw.is_empty() {
675 let decoder = PvdDecoder::new(is_be);
676 decoder.parse_introspection(&pvd_raw)
677 } else {
678 None
679 };
680 (status, pvd_raw, introspection)
681 };
682
683 let (cid, status, pvd_raw, introspection) = if raw.len() >= 4 {
687 let parsed_cid = if is_be {
688 u32::from_be_bytes(raw[0..4].try_into().ok()?)
689 } else {
690 u32::from_le_bytes(raw[0..4].try_into().ok()?)
691 };
692 let (status, pvd_raw, introspection) = parse_status_then_intro(&raw[4..]);
693 (parsed_cid, status, pvd_raw, introspection)
694 } else {
695 let (status, pvd_raw, introspection) = parse_status_then_intro(raw);
696 (0, status, pvd_raw, introspection)
697 };
698
699 Some(Self {
700 is_server,
701 cid,
702 sid: None,
703 ioid: None,
704 field_name: None,
705 status,
706 introspection,
707 raw: pvd_raw,
708 })
709 }
710}
711
712#[derive(Debug)]
713pub struct PvaMessagePayload {
714 pub status: Option<PvaStatus>,
715 pub raw: Vec<u8>,
716}
717
718impl PvaMessagePayload {
719 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
720 let (status, consumed) = decode_status(raw, is_be);
721 let remainder = if raw.len() > consumed {
722 raw[consumed..].to_vec()
723 } else {
724 vec![]
725 };
726 Some(Self {
727 status,
728 raw: remainder,
729 })
730 }
731}
732
733#[derive(Debug)]
734pub struct PvaMultipleDataEntry {
735 pub ioid: u32,
736 pub subcmd: u8,
737}
738
739#[derive(Debug)]
740pub struct PvaMultipleDataPayload {
741 pub entries: Vec<PvaMultipleDataEntry>,
742 pub raw: Vec<u8>,
743}
744
745impl PvaMultipleDataPayload {
746 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
747 let mut entries: Vec<PvaMultipleDataEntry> = vec![];
748 if let Some((count, consumed)) = decode_size(raw, is_be) {
749 let mut offset = consumed;
750 for _ in 0..count {
751 if raw.len() < offset + 5 {
752 break;
753 }
754 let ioid = if is_be {
755 u32::from_be_bytes(raw[offset..offset + 4].try_into().ok()?)
756 } else {
757 u32::from_le_bytes(raw[offset..offset + 4].try_into().ok()?)
758 };
759 let subcmd = raw[offset + 4];
760 entries.push(PvaMultipleDataEntry { ioid, subcmd });
761 offset += 5;
762 }
763 }
764 Some(Self {
765 entries,
766 raw: raw.to_vec(),
767 })
768 }
769}
770
771#[derive(Debug)]
772pub struct PvaCancelRequestPayload {
773 pub request_id: u32,
774 pub status: Option<PvaStatus>,
775}
776
777impl PvaCancelRequestPayload {
778 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
779 if raw.len() < 4 {
780 debug!("PvaCancelRequestPayload::new: raw too short {}", raw.len());
781 return None;
782 }
783 let request_id = if is_be {
784 u32::from_be_bytes(raw[0..4].try_into().ok()?)
785 } else {
786 u32::from_le_bytes(raw[0..4].try_into().ok()?)
787 };
788 let (status, _) = if raw.len() > 4 {
789 decode_status(&raw[4..], is_be)
790 } else {
791 (None, 0)
792 };
793 Some(Self { request_id, status })
794 }
795}
796
797#[derive(Debug)]
798pub struct PvaDestroyRequestPayload {
799 pub request_id: u32,
800 pub status: Option<PvaStatus>,
801}
802
803impl PvaDestroyRequestPayload {
804 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
805 if raw.len() < 4 {
806 debug!("PvaDestroyRequestPayload::new: raw too short {}", raw.len());
807 return None;
808 }
809 let request_id = if is_be {
810 u32::from_be_bytes(raw[0..4].try_into().ok()?)
811 } else {
812 u32::from_le_bytes(raw[0..4].try_into().ok()?)
813 };
814 let (status, _) = if raw.len() > 4 {
815 decode_status(&raw[4..], is_be)
816 } else {
817 (None, 0)
818 };
819 Some(Self { request_id, status })
820 }
821}
822
823#[derive(Debug)]
824pub struct PvaOriginTagPayload {
825 pub address: [u8; 16],
826}
827
828impl PvaOriginTagPayload {
829 pub fn new(raw: &[u8]) -> Option<Self> {
830 if raw.len() < 16 {
831 debug!("PvaOriginTagPayload::new: raw too short {}", raw.len());
832 return None;
833 }
834 let address: [u8; 16] = raw[0..16].try_into().ok()?;
835 Some(Self { address })
836 }
837}
838
839#[derive(Debug)]
840pub struct PvaUnknownPayload {
841 pub command: u8,
842 pub is_control: bool,
843 pub raw_len: usize,
844}
845
846impl PvaUnknownPayload {
847 pub fn new(command: u8, is_control: bool, raw_len: usize) -> Self {
848 Self {
849 command,
850 is_control,
851 raw_len,
852 }
853 }
854}
855
856#[derive(Debug)]
859pub struct PvaSearchPayload {
860 pub seq: u32,
861 pub mask: u8,
862 pub addr: [u8; 16],
863 pub port: u16,
864 pub protocols: Vec<String>,
865 pub pv_requests: Vec<(u32, String)>,
866 pub pv_names: Vec<String>,
867}
868
869impl PvaSearchPayload {
870 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
871 if raw.is_empty() {
872 debug!("PvaSearchPayload::new received an empty raw slice.");
873 return None;
874 }
875 const MIN_FIXED_SEARCH_PAYLOAD_SIZE: usize = 26;
876 if raw.len() < MIN_FIXED_SEARCH_PAYLOAD_SIZE {
877 debug!(
878 "PvaSearchPayload::new: raw slice length {} is less than min fixed size {}.",
879 raw.len(),
880 MIN_FIXED_SEARCH_PAYLOAD_SIZE
881 );
882 return None;
883 }
884
885 let seq = if is_be {
886 u32::from_be_bytes(raw[0..4].try_into().unwrap())
887 } else {
888 u32::from_le_bytes(raw[0..4].try_into().unwrap())
889 };
890
891 let mask = raw[4];
892 let addr: [u8; 16] = raw[8..24].try_into().unwrap();
893 let port = if is_be {
894 u16::from_be_bytes(raw[24..26].try_into().unwrap())
895 } else {
896 u16::from_le_bytes(raw[24..26].try_into().unwrap())
897 };
898
899 let mut offset = 26;
900
901 let (protocol_count, consumed) = decode_size(&raw[offset..], is_be)?;
902 offset += consumed;
903
904 let mut protocols = vec![];
905 for _ in 0..protocol_count {
906 let (protocol, len) = decode_string(&raw[offset..], is_be)?;
907 protocols.push(protocol);
908 offset += len;
909 }
910
911 if raw.len() < offset + 2 {
913 return None;
914 }
915 let pv_count = if is_be {
916 u16::from_be_bytes(raw[offset..offset + 2].try_into().unwrap())
917 } else {
918 u16::from_le_bytes(raw[offset..offset + 2].try_into().unwrap())
919 };
920 offset += 2;
921
922 let mut pv_names = vec![];
923 let mut pv_requests = vec![];
924 for _ in 0..pv_count {
925 if raw.len() < offset + 4 {
926 debug!(
927 "PvaSearchPayload::new: not enough data for PV CID at offset {}. Raw len: {}",
928 offset,
929 raw.len()
930 );
931 return None;
932 }
933 let cid = if is_be {
934 u32::from_be_bytes(raw[offset..offset + 4].try_into().unwrap())
935 } else {
936 u32::from_le_bytes(raw[offset..offset + 4].try_into().unwrap())
937 };
938 offset += 4;
939 let (pv_name, len) = decode_string(&raw[offset..], is_be)?;
940 pv_names.push(pv_name.clone());
941 pv_requests.push((cid, pv_name));
942 offset += len;
943 }
944
945 Some(Self {
946 seq,
947 mask,
948 addr,
949 port,
950 protocols,
951 pv_requests,
952 pv_names,
953 })
954 }
955}
956
957#[derive(Debug)]
959pub struct PvaBeaconPayload {
960 pub guid: [u8; 12],
961 pub flags: u8,
962 pub beacon_sequence_id: u8,
963 pub change_count: u16,
964 pub server_address: [u8; 16],
965 pub server_port: u16,
966 pub protocol: String,
967 pub server_status_if: String,
968}
969
970impl PvaBeaconPayload {
971 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
972 const MIN_FIXED_BEACON_PAYLOAD_SIZE: usize = 12 + 1 + 1 + 2 + 16 + 2;
974
975 if raw.len() < MIN_FIXED_BEACON_PAYLOAD_SIZE {
976 debug!(
977 "PvaBeaconPayload::new: raw slice length {} is less than min fixed size {}.",
978 raw.len(),
979 MIN_FIXED_BEACON_PAYLOAD_SIZE
980 );
981 return None;
982 }
983
984 let guid: [u8; 12] = raw[0..12].try_into().unwrap();
985 let flags = raw[12];
986 let beacon_sequence_id = raw[13];
987 let change_count = if is_be {
988 u16::from_be_bytes(raw[14..16].try_into().unwrap())
989 } else {
990 u16::from_le_bytes(raw[14..16].try_into().unwrap())
991 };
992 let server_address: [u8; 16] = raw[16..32].try_into().unwrap();
993 let server_port = if is_be {
994 u16::from_be_bytes(raw[32..34].try_into().unwrap())
995 } else {
996 u16::from_le_bytes(raw[32..34].try_into().unwrap())
997 };
998 let (protocol, len) = decode_string(&raw[34..], is_be)?;
999 let protocol = protocol;
1000 let server_status_if = if len > 0 {
1001 let (server_status_if, len) = decode_string(&raw[34 + len..], is_be)?;
1002 server_status_if
1003 } else {
1004 String::new()
1005 };
1006
1007 Some(Self {
1008 guid,
1009 flags,
1010 beacon_sequence_id,
1011 change_count,
1012 server_address,
1013 server_port,
1014 protocol,
1015 server_status_if,
1016 })
1017 }
1018}
1019
1020#[derive(Debug)]
1024pub struct PvaCreateChannelPayload {
1025 pub is_server: bool,
1027 pub channels: Vec<(u32, String)>,
1029 pub cid: u32,
1031 pub sid: u32,
1033 pub status: Option<PvaStatus>,
1035}
1036
1037impl PvaCreateChannelPayload {
1038 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
1039 if raw.is_empty() {
1040 debug!("PvaCreateChannelPayload::new received an empty raw slice.");
1041 return None;
1042 }
1043
1044 if is_server {
1045 if raw.len() < 8 {
1047 debug!("CREATE_CHANNEL server response too short: {}", raw.len());
1048 return None;
1049 }
1050
1051 let cid = if is_be {
1052 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1053 } else {
1054 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1055 };
1056
1057 let sid = if is_be {
1058 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1059 } else {
1060 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1061 };
1062
1063 let status = if raw.len() > 8 {
1065 let code = raw[8];
1066 if code == 0xff {
1067 None } else {
1069 let mut idx = 9;
1070 let message = if idx < raw.len() {
1071 decode_string(&raw[idx..], is_be).map(|(msg, consumed)| {
1072 idx += consumed;
1073 msg
1074 })
1075 } else {
1076 None
1077 };
1078 let stack = if idx < raw.len() {
1079 decode_string(&raw[idx..], is_be).map(|(s, _)| s)
1080 } else {
1081 None
1082 };
1083 Some(PvaStatus {
1084 code,
1085 message,
1086 stack,
1087 })
1088 }
1089 } else {
1090 None
1091 };
1092
1093 Some(Self {
1094 is_server: true,
1095 channels: vec![],
1096 cid,
1097 sid,
1098 status,
1099 })
1100 } else {
1101 if raw.len() < 2 {
1103 debug!("CREATE_CHANNEL client request too short: {}", raw.len());
1104 return None;
1105 }
1106
1107 let count = if is_be {
1108 u16::from_be_bytes(raw[0..2].try_into().unwrap())
1109 } else {
1110 u16::from_le_bytes(raw[0..2].try_into().unwrap())
1111 };
1112
1113 let mut offset = 2;
1114 let mut channels = Vec::with_capacity(count as usize);
1115
1116 for _ in 0..count {
1117 if raw.len() < offset + 4 {
1118 debug!(
1119 "CREATE_CHANNEL: not enough data for CID at offset {}",
1120 offset
1121 );
1122 break;
1123 }
1124
1125 let cid = if is_be {
1126 u32::from_be_bytes(raw[offset..offset + 4].try_into().unwrap())
1127 } else {
1128 u32::from_le_bytes(raw[offset..offset + 4].try_into().unwrap())
1129 };
1130 offset += 4;
1131
1132 if let Some((pv_name, consumed)) = decode_string(&raw[offset..], is_be) {
1133 offset += consumed;
1134 channels.push((cid, pv_name));
1135 } else {
1136 debug!(
1137 "CREATE_CHANNEL: failed to decode PV name at offset {}",
1138 offset
1139 );
1140 break;
1141 }
1142 }
1143
1144 Some(Self {
1145 is_server: false,
1146 channels,
1147 cid: 0,
1148 sid: 0,
1149 status: None,
1150 })
1151 }
1152 }
1153}
1154
1155impl fmt::Display for PvaCreateChannelPayload {
1156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1157 if self.is_server {
1158 let status_text = if let Some(s) = &self.status {
1159 format!(" status={}", s.code)
1160 } else {
1161 String::new()
1162 };
1163 write!(
1164 f,
1165 "CREATE_CHANNEL(cid={}, sid={}{})",
1166 self.cid, self.sid, status_text
1167 )
1168 } else {
1169 let pv_list: Vec<String> = self
1170 .channels
1171 .iter()
1172 .map(|(cid, name)| format!("{}:'{}'", cid, name))
1173 .collect();
1174 write!(f, "CREATE_CHANNEL({})", pv_list.join(", "))
1175 }
1176 }
1177}
1178
1179#[derive(Debug)]
1182pub struct PvaDestroyChannelPayload {
1183 pub sid: u32,
1185 pub cid: u32,
1187}
1188
1189impl PvaDestroyChannelPayload {
1190 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
1191 if raw.len() < 8 {
1192 debug!("DESTROY_CHANNEL payload too short: {}", raw.len());
1193 return None;
1194 }
1195
1196 let sid = if is_be {
1197 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1198 } else {
1199 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1200 };
1201
1202 let cid = if is_be {
1203 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1204 } else {
1205 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1206 };
1207
1208 Some(Self { sid, cid })
1209 }
1210}
1211
1212impl fmt::Display for PvaDestroyChannelPayload {
1213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1214 write!(f, "DESTROY_CHANNEL(sid={}, cid={})", self.sid, self.cid)
1215 }
1216}
1217
1218#[derive(Debug)]
1220pub struct PvaOpPayload {
1221 pub sid_or_cid: u32,
1222 pub ioid: u32,
1223 pub subcmd: u8,
1224 pub body: Vec<u8>,
1225 pub command: u8,
1226 pub is_server: bool,
1227 pub status: Option<PvaStatus>,
1228 pub pv_names: Vec<String>,
1229 pub introspection: Option<StructureDesc>,
1231 pub decoded_value: Option<DecodedValue>,
1233}
1234
1235fn extract_pv_names(raw: &[u8]) -> Vec<String> {
1237 let mut names: Vec<String> = Vec::new();
1238 let mut i = 0usize;
1239 while i < raw.len() {
1240 if raw[i].is_ascii_alphanumeric() {
1242 let start = i;
1243 i += 1;
1244 while i < raw.len() {
1245 let b = raw[i];
1246 if b.is_ascii_alphanumeric()
1247 || b == b':'
1248 || b == b'.'
1249 || b == b'_'
1250 || b == b'-'
1251 || b == b'/'
1252 {
1253 i += 1;
1254 } else {
1255 break;
1256 }
1257 }
1258 let len = i - start;
1259 if len >= 3 && len <= 128 {
1260 if let Ok(s) = std::str::from_utf8(&raw[start..start + len]) {
1261 if s.chars().any(|c| c.is_ascii_alphabetic()) {
1263 if !names.contains(&s.to_string()) {
1264 names.push(s.to_string());
1265 if names.len() >= 8 {
1266 break;
1267 }
1268 }
1269 }
1270 }
1271 }
1272 } else {
1273 i += 1;
1274 }
1275 }
1276 names
1277}
1278
1279impl PvaOpPayload {
1280 pub fn new(raw: &[u8], is_be: bool, is_server: bool, command: u8) -> Option<Self> {
1281 if raw.len() < 5 {
1283 debug!("PvaOpPayload::new: raw too short {}", raw.len());
1284 return None;
1285 }
1286
1287 let (sid_or_cid, ioid, subcmd, mut offset) = if is_server {
1288 if raw.len() < 5 {
1290 return None;
1291 }
1292 let ioid = if is_be {
1293 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1294 } else {
1295 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1296 };
1297 let subcmd = raw[4];
1298 (0, ioid, subcmd, 5)
1299 } else {
1300 if raw.len() < 9 {
1302 return None;
1303 }
1304 let sid = if is_be {
1305 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1306 } else {
1307 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1308 };
1309 let ioid = if is_be {
1310 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1311 } else {
1312 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1313 };
1314 let subcmd = raw[8];
1315 (sid, ioid, subcmd, 9)
1316 };
1317
1318 let body = if raw.len() > offset {
1319 raw[offset..].to_vec()
1320 } else {
1321 vec![]
1322 };
1323
1324 let mut status: Option<PvaStatus> = None;
1330 let mut pvd_raw: Vec<u8> = vec![];
1331
1332 let has_status = is_server && (subcmd & 0x08) != 0;
1335
1336 if !body.is_empty() {
1337 if has_status {
1338 let (parsed, consumed) = decode_status(&body, is_be);
1339 status = parsed;
1340 pvd_raw = if body.len() > consumed {
1341 body[consumed..].to_vec()
1342 } else {
1343 vec![]
1344 };
1345 } else {
1346 if body[0] == 0xFF {
1349 pvd_raw = body[1..].to_vec();
1350 } else {
1351 pvd_raw = body.clone();
1352 }
1353 }
1354 }
1355
1356 let pv_names = extract_pv_names(&pvd_raw);
1357
1358 let introspection = if is_server && (subcmd & 0x08) != 0 && !pvd_raw.is_empty() {
1360 let decoder = PvdDecoder::new(is_be);
1361 decoder.parse_introspection(&pvd_raw)
1362 } else {
1363 None
1364 };
1365
1366 let result = Some(Self {
1367 sid_or_cid,
1368 ioid,
1369 subcmd,
1370 body: pvd_raw,
1371 command,
1372 is_server,
1373 status: status.clone(),
1374 pv_names,
1375 introspection,
1376 decoded_value: None, });
1378
1379 result
1380 }
1381
1382 pub fn decode_with_field_desc(&mut self, field_desc: &StructureDesc, is_be: bool) {
1384 if self.body.is_empty() {
1385 return;
1386 }
1387
1388 let decoder = PvdDecoder::new(is_be);
1389
1390 if self.subcmd == 0x00 || (self.subcmd & 0x40) != 0 {
1392 if self.command == 13 {
1393 let cand_overrun_pre =
1394 decoder.decode_structure_with_bitset_and_overrun(&self.body, field_desc);
1395 let cand_overrun_post =
1396 decoder.decode_structure_with_bitset_then_overrun(&self.body, field_desc);
1397 let cand_legacy = decoder.decode_structure_with_bitset(&self.body, field_desc);
1398 self.decoded_value =
1399 choose_best_decoded_multi([cand_overrun_pre, cand_overrun_post, cand_legacy]);
1400 } else if let Some((value, _)) =
1401 decoder.decode_structure_with_bitset(&self.body, field_desc)
1402 {
1403 self.decoded_value = Some(value);
1404 }
1405 } else {
1406 if let Some((value, _)) = decoder.decode_structure(&self.body, field_desc) {
1408 self.decoded_value = Some(value);
1409 }
1410 }
1411 }
1412}
1413
1414fn choose_best_decoded_multi(cands: [Option<(DecodedValue, usize)>; 3]) -> Option<DecodedValue> {
1415 let mut best_value: Option<DecodedValue> = None;
1416 let mut best_score = i32::MIN;
1417 let mut best_consumed = 0usize;
1418 let mut best_idx = 0usize;
1419
1420 for (idx, cand) in cands.into_iter().enumerate() {
1421 let Some((value, consumed)) = cand else {
1422 continue;
1423 };
1424 let score = score_decoded(&value);
1425 let better = score > best_score
1426 || (score == best_score && consumed > best_consumed)
1427 || (score == best_score && consumed == best_consumed && idx > best_idx);
1428 if better {
1429 best_score = score;
1430 best_consumed = consumed;
1431 best_idx = idx;
1432 best_value = Some(value);
1433 }
1434 }
1435
1436 best_value
1437}
1438
1439fn score_decoded(value: &DecodedValue) -> i32 {
1440 let DecodedValue::Structure(fields) = value else {
1441 return -1;
1442 };
1443
1444 let mut score = fields.len() as i32;
1445
1446 let mut has_value = false;
1447 let mut has_alarm = false;
1448 let mut has_ts = false;
1449
1450 for (name, val) in fields {
1451 match name.as_str() {
1452 "value" => {
1453 has_value = true;
1454 score += 4;
1455 match val {
1456 DecodedValue::Array(items) => {
1457 if items.is_empty() {
1458 score -= 2;
1459 } else {
1460 score += 6 + (items.len().min(8) as i32);
1461 }
1462 }
1463 DecodedValue::Structure(_) => score += 1,
1464 _ => score += 2,
1465 }
1466 }
1467 "alarm" => {
1468 has_alarm = true;
1469 score += 2;
1470 }
1471 "timeStamp" => {
1472 has_ts = true;
1473 score += 2;
1474 if let DecodedValue::Structure(ts_fields) = val {
1475 if let Some(secs) = ts_fields.iter().find_map(|(n, v)| {
1476 if n == "secondsPastEpoch" {
1477 if let DecodedValue::Int64(s) = v {
1478 return Some(*s);
1479 }
1480 }
1481 None
1482 }) {
1483 if (0..=4_000_000_000i64).contains(&secs) {
1484 score += 2;
1485 } else if secs.abs() > 10_000_000_000i64 {
1486 score -= 2;
1487 }
1488 }
1489 }
1490 }
1491 "display" | "control" => {
1492 score += 1;
1493 }
1494 _ => {}
1495 }
1496 }
1497
1498 if !has_value {
1499 score -= 2;
1500 }
1501 if !has_alarm {
1502 score -= 1;
1503 }
1504 if !has_ts {
1505 score -= 1;
1506 }
1507
1508 score
1509}
1510
1511#[derive(Debug, Clone)]
1512pub struct PvaStatus {
1513 pub code: u8,
1514 pub message: Option<String>,
1515 pub stack: Option<String>,
1516}
1517
1518impl fmt::Display for PvaBeaconPayload {
1521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1522 write!(
1523 f,
1524 "Beacon:GUID=[{}],Flags=[{}],SeqId=[{}],ChangeCount=[{}],ServerAddress=[{}],ServerPort=[{}],Protocol=[{}]",
1525 hex::encode(self.guid),
1526 self.flags,
1527 self.beacon_sequence_id,
1528 self.change_count,
1529 format_pva_address(&self.server_address),
1530 self.server_port,
1531 self.protocol
1532 )
1533 }
1534}
1535
1536impl fmt::Display for PvaSearchPayload {
1538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1539 write!(f, "Search:PVs=[{}]", self.pv_names.join(","))
1540 }
1541}
1542
1543impl fmt::Display for PvaControlPayload {
1544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1545 let name = match self.command {
1546 0 => "MARK_TOTAL_BYTES_SENT",
1547 1 => "ACK_TOTAL_BYTES_RECEIVED",
1548 2 => "SET_BYTE_ORDER",
1549 3 => "ECHO_REQUEST",
1550 4 => "ECHO_RESPONSE",
1551 _ => "CONTROL",
1552 };
1553 write!(f, "{}(data={})", name, self.data)
1554 }
1555}
1556
1557impl fmt::Display for PvaSearchResponsePayload {
1558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1559 let found_text = if self.found { "true" } else { "false" };
1560 if self.cids.is_empty() {
1561 write!(
1562 f,
1563 "SearchResponse(found={}, proto={})",
1564 found_text, self.protocol
1565 )
1566 } else {
1567 write!(
1568 f,
1569 "SearchResponse(found={}, proto={}, cids=[{}])",
1570 found_text,
1571 self.protocol,
1572 self.cids
1573 .iter()
1574 .map(|c| c.to_string())
1575 .collect::<Vec<String>>()
1576 .join(",")
1577 )
1578 }
1579 }
1580}
1581
1582impl fmt::Display for PvaConnectionValidationPayload {
1583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1584 let dir = if self.is_server { "server" } else { "client" };
1585 let authz = self.authz.as_deref().unwrap_or("");
1586 if authz.is_empty() {
1587 write!(
1588 f,
1589 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x})",
1590 dir, self.buffer_size, self.introspection_registry_size, self.qos
1591 )
1592 } else {
1593 write!(
1594 f,
1595 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x}, authz={})",
1596 dir, self.buffer_size, self.introspection_registry_size, self.qos, authz
1597 )
1598 }
1599 }
1600}
1601
1602impl fmt::Display for PvaConnectionValidatedPayload {
1603 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1604 match &self.status {
1605 Some(s) => write!(f, "ConnectionValidated(status={})", s.code),
1606 None => write!(f, "ConnectionValidated(status=OK)"),
1607 }
1608 }
1609}
1610
1611impl fmt::Display for PvaAuthNzPayload {
1612 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1613 if !self.strings.is_empty() {
1614 write!(f, "AuthNZ(strings=[{}])", self.strings.join(","))
1615 } else {
1616 write!(f, "AuthNZ(raw_len={})", self.raw.len())
1617 }
1618 }
1619}
1620
1621impl fmt::Display for PvaAclChangePayload {
1622 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1623 match &self.status {
1624 Some(s) => write!(f, "ACL_CHANGE(status={})", s.code),
1625 None => write!(f, "ACL_CHANGE(status=OK)"),
1626 }
1627 }
1628}
1629
1630impl fmt::Display for PvaGetFieldPayload {
1631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1632 if self.is_server {
1633 let status = self.status.as_ref().map(|s| s.code).unwrap_or(0xff);
1634 write!(f, "GET_FIELD(status={})", status)
1635 } else {
1636 let field = self.field_name.as_deref().unwrap_or("");
1637 if field.is_empty() {
1638 write!(f, "GET_FIELD(cid={})", self.cid)
1639 } else {
1640 write!(f, "GET_FIELD(cid={}, field={})", self.cid, field)
1641 }
1642 }
1643 }
1644}
1645
1646impl fmt::Display for PvaMessagePayload {
1647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1648 match &self.status {
1649 Some(s) => {
1650 if let Some(msg) = &s.message {
1651 write!(f, "MESSAGE(status={}, msg='{}')", s.code, msg)
1652 } else {
1653 write!(f, "MESSAGE(status={})", s.code)
1654 }
1655 }
1656 None => write!(f, "MESSAGE(status=OK)"),
1657 }
1658 }
1659}
1660
1661impl fmt::Display for PvaMultipleDataPayload {
1662 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1663 if self.entries.is_empty() {
1664 write!(f, "MULTIPLE_DATA(raw_len={})", self.raw.len())
1665 } else {
1666 write!(f, "MULTIPLE_DATA(entries={})", self.entries.len())
1667 }
1668 }
1669}
1670
1671impl fmt::Display for PvaCancelRequestPayload {
1672 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1673 let status = self.status.as_ref().map(|s| s.code);
1674 match status {
1675 Some(code) => write!(f, "CANCEL_REQUEST(id={}, status={})", self.request_id, code),
1676 None => write!(f, "CANCEL_REQUEST(id={})", self.request_id),
1677 }
1678 }
1679}
1680
1681impl fmt::Display for PvaDestroyRequestPayload {
1682 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1683 let status = self.status.as_ref().map(|s| s.code);
1684 match status {
1685 Some(code) => write!(
1686 f,
1687 "DESTROY_REQUEST(id={}, status={})",
1688 self.request_id, code
1689 ),
1690 None => write!(f, "DESTROY_REQUEST(id={})", self.request_id),
1691 }
1692 }
1693}
1694
1695impl fmt::Display for PvaOriginTagPayload {
1696 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1697 write!(
1698 f,
1699 "ORIGIN_TAG(addr={})",
1700 format_pva_address(&self.address)
1701 )
1702 }
1703}
1704
1705impl fmt::Display for PvaUnknownPayload {
1706 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1707 let kind = if self.is_control {
1708 "CONTROL"
1709 } else {
1710 "APPLICATION"
1711 };
1712 write!(
1713 f,
1714 "UNKNOWN(cmd={}, type={}, raw_len={})",
1715 self.command, kind, self.raw_len
1716 )
1717 }
1718}
1719
1720impl fmt::Display for PvaPacketCommand {
1722 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1723 match self {
1724 PvaPacketCommand::Control(payload) => write!(f, "{}", payload),
1725 PvaPacketCommand::Search(payload) => write!(f, "{}", payload),
1726 PvaPacketCommand::SearchResponse(payload) => write!(f, "{}", payload),
1727 PvaPacketCommand::Beacon(payload) => write!(f, "{}", payload),
1728 PvaPacketCommand::ConnectionValidation(payload) => write!(f, "{}", payload),
1729 PvaPacketCommand::ConnectionValidated(payload) => write!(f, "{}", payload),
1730 PvaPacketCommand::AuthNZ(payload) => write!(f, "{}", payload),
1731 PvaPacketCommand::AclChange(payload) => write!(f, "{}", payload),
1732 PvaPacketCommand::Op(payload) => write!(f, "{}", payload),
1733 PvaPacketCommand::CreateChannel(payload) => write!(f, "{}", payload),
1734 PvaPacketCommand::DestroyChannel(payload) => write!(f, "{}", payload),
1735 PvaPacketCommand::GetField(payload) => write!(f, "{}", payload),
1736 PvaPacketCommand::Message(payload) => write!(f, "{}", payload),
1737 PvaPacketCommand::MultipleData(payload) => write!(f, "{}", payload),
1738 PvaPacketCommand::CancelRequest(payload) => write!(f, "{}", payload),
1739 PvaPacketCommand::DestroyRequest(payload) => write!(f, "{}", payload),
1740 PvaPacketCommand::OriginTag(payload) => write!(f, "{}", payload),
1741 PvaPacketCommand::Echo(bytes) => write!(f, "ECHO ({} bytes)", bytes.len()),
1742 PvaPacketCommand::Unknown(payload) => write!(f, "{}", payload),
1743 }
1744 }
1745}
1746
1747impl fmt::Display for PvaOpPayload {
1748 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1749 let cmd_name = match self.command {
1750 10 => "GET",
1751 11 => "PUT",
1752 12 => "PUT_GET",
1753 13 => "MONITOR",
1754 14 => "ARRAY",
1755 16 => "PROCESS",
1756 20 => "RPC",
1757 _ => "OP",
1758 };
1759
1760 let status_text = if let Some(s) = &self.status {
1761 match &s.message {
1762 Some(m) if !m.is_empty() => format!(" status={} msg='{}'", s.code, m),
1763 _ => format!(" status={}", s.code),
1764 }
1765 } else {
1766 String::new()
1767 };
1768
1769 let value_text = if let Some(ref decoded) = self.decoded_value {
1771 let formatted = format_compact_value(decoded);
1772 if formatted.is_empty() || formatted == "{}" {
1773 String::new()
1774 } else {
1775 format!(" [{}]", formatted)
1776 }
1777 } else if !self.pv_names.is_empty() {
1778 format!(" data=[{}]", self.pv_names.join(","))
1779 } else {
1780 String::new()
1781 };
1782
1783 if self.is_server {
1784 write!(
1785 f,
1786 "{}(ioid={}, sub=0x{:02x}{}{})",
1787 cmd_name, self.ioid, self.subcmd, status_text, value_text
1788 )
1789 } else {
1790 write!(
1791 f,
1792 "{}(sid={}, ioid={}, sub=0x{:02x}{}{})",
1793 cmd_name, self.sid_or_cid, self.ioid, self.subcmd, status_text, value_text
1794 )
1795 }
1796 }
1797}
1798
1799#[cfg(test)]
1800mod tests {
1801 use super::*;
1802 use spvirit_types::{
1803 NtPayload, NtScalar, NtScalarArray, ScalarArrayValue, ScalarValue,
1804 };
1805 use crate::spvirit_encode::encode_header;
1806 use crate::spvd_decode::extract_nt_scalar_value;
1807 use crate::spvd_encode::{
1808 encode_nt_payload_bitset_parts, encode_nt_scalar_bitset_parts, encode_size_pvd,
1809 nt_payload_desc, nt_scalar_desc,
1810 };
1811
1812 #[test]
1813 fn test_decode_status_ok() {
1814 let raw = [0xff];
1815 let (status, consumed) = decode_status(&raw, false);
1816 assert!(status.is_none());
1817 assert_eq!(consumed, 1);
1818 }
1819
1820 #[test]
1821 fn test_decode_status_message() {
1822 let raw = [1u8, 2, b'h', b'i', 2, b's', b't'];
1823 let (status, consumed) = decode_status(&raw, false);
1824 assert_eq!(consumed, 7);
1825 let status = status.unwrap();
1826 assert_eq!(status.code, 1);
1827 assert_eq!(status.message.as_deref(), Some("hi"));
1828 assert_eq!(status.stack.as_deref(), Some("st"));
1829 }
1830
1831 #[test]
1832 fn test_search_response_decode() {
1833 let mut raw: Vec<u8> = vec![];
1834 raw.extend_from_slice(&[0u8; 12]); raw.extend_from_slice(&1u32.to_le_bytes()); raw.extend_from_slice(&[0u8; 16]); raw.extend_from_slice(&5076u16.to_le_bytes()); raw.push(3); raw.extend_from_slice(b"tcp");
1840 raw.push(1); raw.extend_from_slice(&1u16.to_le_bytes()); raw.extend_from_slice(&42u32.to_le_bytes()); let decoded = PvaSearchResponsePayload::new(&raw, false).unwrap();
1845 assert!(decoded.found);
1846 assert_eq!(decoded.protocol, "tcp");
1847 assert_eq!(decoded.cids, vec![42u32]);
1848 }
1849
1850 fn build_monitor_packet(ioid: u32, subcmd: u8, body: &[u8]) -> Vec<u8> {
1851 let mut payload = Vec::new();
1852 payload.extend_from_slice(&ioid.to_le_bytes());
1853 payload.push(subcmd);
1854 payload.extend_from_slice(body);
1855 let mut out = encode_header(true, false, false, 2, 13, payload.len() as u32);
1856 out.extend_from_slice(&payload);
1857 out
1858 }
1859
1860 #[test]
1861 fn test_monitor_decode_overrun_and_legacy() {
1862 let nt = NtScalar::from_value(ScalarValue::F64(3.5));
1863 let desc = nt_scalar_desc(&nt.value);
1864 let (changed_bitset, values) = encode_nt_scalar_bitset_parts(&nt, false);
1865
1866 let mut body_overrun = Vec::new();
1867 body_overrun.extend_from_slice(&changed_bitset);
1868 body_overrun.extend_from_slice(&encode_size_pvd(0, false));
1869 body_overrun.extend_from_slice(&values);
1870
1871 let pkt = build_monitor_packet(1, 0x00, &body_overrun);
1872 let mut pva = PvaPacket::new(&pkt);
1873 let mut cmd = pva.decode_payload().expect("decoded");
1874 if let PvaPacketCommand::Op(ref mut op) = cmd {
1875 op.decode_with_field_desc(&desc, false);
1876 let decoded = op.decoded_value.as_ref().expect("decoded");
1877 let value = extract_nt_scalar_value(decoded).expect("value");
1878 match value {
1879 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1880 other => panic!("unexpected value {:?}", other),
1881 }
1882 } else {
1883 panic!("unexpected cmd");
1884 }
1885
1886 let mut body_legacy = Vec::new();
1887 body_legacy.extend_from_slice(&changed_bitset);
1888 body_legacy.extend_from_slice(&values);
1889
1890 let pkt = build_monitor_packet(1, 0x00, &body_legacy);
1891 let mut pva = PvaPacket::new(&pkt);
1892 let mut cmd = pva.decode_payload().expect("decoded");
1893 if let PvaPacketCommand::Op(ref mut op) = cmd {
1894 op.decode_with_field_desc(&desc, false);
1895 let decoded = op.decoded_value.as_ref().expect("decoded");
1896 let value = extract_nt_scalar_value(decoded).expect("value");
1897 match value {
1898 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1899 other => panic!("unexpected value {:?}", other),
1900 }
1901 } else {
1902 panic!("unexpected cmd");
1903 }
1904
1905 let mut body_spec = Vec::new();
1906 body_spec.extend_from_slice(&changed_bitset);
1907 body_spec.extend_from_slice(&values);
1908 body_spec.extend_from_slice(&encode_size_pvd(0, false));
1909
1910 let pkt = build_monitor_packet(1, 0x00, &body_spec);
1911 let mut pva = PvaPacket::new(&pkt);
1912 let mut cmd = pva.decode_payload().expect("decoded");
1913 if let PvaPacketCommand::Op(ref mut op) = cmd {
1914 op.decode_with_field_desc(&desc, false);
1915 let decoded = op.decoded_value.as_ref().expect("decoded");
1916 let value = extract_nt_scalar_value(decoded).expect("value");
1917 match value {
1918 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1919 other => panic!("unexpected value {:?}", other),
1920 }
1921 } else {
1922 panic!("unexpected cmd");
1923 }
1924 }
1925
1926 #[test]
1927 fn test_monitor_decode_prefers_spec_order_for_array_payload() {
1928 let payload_value =
1929 NtPayload::ScalarArray(NtScalarArray::from_value(ScalarArrayValue::F64(vec![
1930 1.0, 2.0, 3.0, 4.0,
1931 ])));
1932 let desc = nt_payload_desc(&payload_value);
1933 let (changed_bitset, values) = encode_nt_payload_bitset_parts(&payload_value, false);
1934
1935 let mut body_spec = Vec::new();
1936 body_spec.extend_from_slice(&changed_bitset);
1937 body_spec.extend_from_slice(&values);
1938 body_spec.extend_from_slice(&encode_size_pvd(0, false));
1939
1940 let pkt = build_monitor_packet(11, 0x00, &body_spec);
1941 let mut pva = PvaPacket::new(&pkt);
1942 let mut cmd = pva.decode_payload().expect("decoded");
1943 if let PvaPacketCommand::Op(ref mut op) = cmd {
1944 op.decode_with_field_desc(&desc, false);
1945 let decoded = op.decoded_value.as_ref().expect("decoded");
1946 let value = extract_nt_scalar_value(decoded).expect("value");
1947 match value {
1948 DecodedValue::Array(items) => {
1949 assert_eq!(items.len(), 4);
1950 assert!(matches!(items[0], DecodedValue::Float64(v) if (v - 1.0).abs() < 1e-6));
1951 assert!(matches!(items[3], DecodedValue::Float64(v) if (v - 4.0).abs() < 1e-6));
1952 }
1953 other => panic!("unexpected value {:?}", other),
1954 }
1955 } else {
1956 panic!("unexpected cmd");
1957 }
1958 }
1959}