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