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
1414 && ((subcmd & 0x08) != 0 || (command != 13 && command != 14));
1415
1416 if !body.is_empty() {
1417 if has_status {
1418 let (parsed, consumed) = decode_status(&body, is_be);
1419 status = parsed;
1420 pvd_raw = if body.len() > consumed {
1421 body[consumed..].to_vec()
1422 } else {
1423 vec![]
1424 };
1425 } else {
1426 pvd_raw = body.clone();
1427 }
1428 }
1429
1430 let pv_names = extract_pv_names(&pvd_raw);
1431
1432 let introspection = if is_server && (subcmd & 0x08) != 0 && !pvd_raw.is_empty() {
1434 let decoder = PvdDecoder::new(is_be);
1435 decoder.parse_introspection(&pvd_raw)
1436 } else {
1437 None
1438 };
1439
1440 let result = Some(Self {
1441 sid_or_cid,
1442 ioid,
1443 subcmd,
1444 body: pvd_raw,
1445 command,
1446 is_server,
1447 status: status.clone(),
1448 pv_names,
1449 introspection,
1450 decoded_value: None, });
1452
1453 result
1454 }
1455
1456 pub fn decode_with_field_desc(&mut self, field_desc: &StructureDesc, is_be: bool) {
1458 if self.body.is_empty() {
1459 return;
1460 }
1461
1462 let decoder = PvdDecoder::new(is_be);
1463
1464 if self.subcmd == 0x00 || (self.subcmd & 0x40) != 0 {
1466 if self.command == 13 {
1467 let cand_overrun_pre =
1468 decoder.decode_structure_with_bitset_and_overrun(&self.body, field_desc);
1469 let cand_overrun_post =
1470 decoder.decode_structure_with_bitset_then_overrun(&self.body, field_desc);
1471 let cand_legacy = decoder.decode_structure_with_bitset(&self.body, field_desc);
1472 self.decoded_value =
1473 choose_best_decoded_multi([cand_overrun_pre, cand_overrun_post, cand_legacy]);
1474 } else if let Some((value, _)) =
1475 decoder.decode_structure_with_bitset(&self.body, field_desc)
1476 {
1477 self.decoded_value = Some(value);
1478 }
1479 } else {
1480 if let Some((value, _)) = decoder.decode_structure(&self.body, field_desc) {
1482 self.decoded_value = Some(value);
1483 }
1484 }
1485 }
1486}
1487
1488fn choose_best_decoded_multi(cands: [Option<(DecodedValue, usize)>; 3]) -> Option<DecodedValue> {
1489 let mut best_value: Option<DecodedValue> = None;
1490 let mut best_score = i32::MIN;
1491 let mut best_consumed = 0usize;
1492 let mut best_idx = 0usize;
1493
1494 for (idx, cand) in cands.into_iter().enumerate() {
1495 let Some((value, consumed)) = cand else {
1496 continue;
1497 };
1498 let score = score_decoded(&value);
1499 let better = score > best_score
1500 || (score == best_score && consumed > best_consumed)
1501 || (score == best_score && consumed == best_consumed && idx > best_idx);
1502 if better {
1503 best_score = score;
1504 best_consumed = consumed;
1505 best_idx = idx;
1506 best_value = Some(value);
1507 }
1508 }
1509
1510 best_value
1511}
1512
1513fn score_decoded(value: &DecodedValue) -> i32 {
1514 let DecodedValue::Structure(fields) = value else {
1515 return -1;
1516 };
1517
1518 let mut score = fields.len() as i32;
1519
1520 let mut has_value = false;
1521 let mut has_alarm = false;
1522 let mut has_ts = false;
1523
1524 for (name, val) in fields {
1525 match name.as_str() {
1526 "value" => {
1527 has_value = true;
1528 score += 4;
1529 match val {
1530 DecodedValue::Array(items) => {
1531 if items.is_empty() {
1532 score -= 2;
1533 } else {
1534 score += 6 + (items.len().min(8) as i32);
1535 }
1536 }
1537 DecodedValue::Structure(_) => score += 1,
1538 _ => score += 2,
1539 }
1540 }
1541 "alarm" => {
1542 has_alarm = true;
1543 score += 2;
1544 }
1545 "timeStamp" => {
1546 has_ts = true;
1547 score += 2;
1548 if let DecodedValue::Structure(ts_fields) = val {
1549 if let Some(secs) = ts_fields.iter().find_map(|(n, v)| {
1550 if n == "secondsPastEpoch" {
1551 if let DecodedValue::Int64(s) = v {
1552 return Some(*s);
1553 }
1554 }
1555 None
1556 }) {
1557 if (0..=4_000_000_000i64).contains(&secs) {
1558 score += 2;
1559 } else if secs.abs() > 10_000_000_000i64 {
1560 score -= 2;
1561 }
1562 }
1563 }
1564 }
1565 "display" | "control" => {
1566 score += 1;
1567 }
1568 _ => {}
1569 }
1570 }
1571
1572 if !has_value {
1573 score -= 2;
1574 }
1575 if !has_alarm {
1576 score -= 1;
1577 }
1578 if !has_ts {
1579 score -= 1;
1580 }
1581
1582 score
1583}
1584
1585#[derive(Debug, Clone)]
1586pub struct PvaStatus {
1587 pub code: u8,
1588 pub message: Option<String>,
1589 pub stack: Option<String>,
1590}
1591
1592impl PvaStatus {
1593 pub fn is_error(&self) -> bool {
1594 self.code != 0
1595 }
1596}
1597
1598impl fmt::Display for PvaBeaconPayload {
1601 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1602 write!(
1603 f,
1604 "Beacon:GUID=[{}],Flags=[{}],SeqId=[{}],ChangeCount=[{}],ServerAddress=[{}],ServerPort=[{}],Protocol=[{}]",
1605 hex::encode(self.guid),
1606 self.flags,
1607 self.beacon_sequence_id,
1608 self.change_count,
1609 format_pva_address(&self.server_address),
1610 self.server_port,
1611 self.protocol
1612 )
1613 }
1614}
1615
1616impl fmt::Display for PvaSearchPayload {
1618 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1619 write!(f, "Search:PVs=[{}]", self.pv_names.join(","))
1620 }
1621}
1622
1623impl fmt::Display for PvaControlPayload {
1624 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1625 let name = match self.command {
1626 0 => "MARK_TOTAL_BYTES_SENT",
1627 1 => "ACK_TOTAL_BYTES_RECEIVED",
1628 2 => "SET_BYTE_ORDER",
1629 3 => "ECHO_REQUEST",
1630 4 => "ECHO_RESPONSE",
1631 _ => "CONTROL",
1632 };
1633 write!(f, "{}(data={})", name, self.data)
1634 }
1635}
1636
1637impl fmt::Display for PvaSearchResponsePayload {
1638 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1639 let found_text = if self.found { "true" } else { "false" };
1640 if self.cids.is_empty() {
1641 write!(
1642 f,
1643 "SearchResponse(found={}, proto={})",
1644 found_text, self.protocol
1645 )
1646 } else {
1647 write!(
1648 f,
1649 "SearchResponse(found={}, proto={}, cids=[{}])",
1650 found_text,
1651 self.protocol,
1652 self.cids
1653 .iter()
1654 .map(|c| c.to_string())
1655 .collect::<Vec<String>>()
1656 .join(",")
1657 )
1658 }
1659 }
1660}
1661
1662impl fmt::Display for PvaConnectionValidationPayload {
1663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1664 let dir = if self.is_server { "server" } else { "client" };
1665 let authz = self.authz.as_deref().unwrap_or("");
1666 if authz.is_empty() {
1667 write!(
1668 f,
1669 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x})",
1670 dir, self.buffer_size, self.introspection_registry_size, self.qos
1671 )
1672 } else {
1673 write!(
1674 f,
1675 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x}, authz={})",
1676 dir, self.buffer_size, self.introspection_registry_size, self.qos, authz
1677 )
1678 }
1679 }
1680}
1681
1682impl fmt::Display for PvaStatus {
1683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1684 write!(
1685 f,
1686 "code={} message={} stack={}",
1687 self.code,
1688 self.message.as_deref().unwrap_or(""),
1689 self.stack.as_deref().unwrap_or("")
1690 )
1691 }
1692}
1693
1694impl fmt::Display for PvaConnectionValidatedPayload {
1695 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1696 match &self.status {
1697 Some(s) => write!(f, "ConnectionValidated(status={})", s.code),
1698 None => write!(f, "ConnectionValidated(status=OK)"),
1699 }
1700 }
1701}
1702
1703impl fmt::Display for PvaAuthNzPayload {
1704 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1705 if !self.strings.is_empty() {
1706 write!(f, "AuthNZ(strings=[{}])", self.strings.join(","))
1707 } else {
1708 write!(f, "AuthNZ(raw_len={})", self.raw.len())
1709 }
1710 }
1711}
1712
1713impl fmt::Display for PvaAclChangePayload {
1714 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1715 match &self.status {
1716 Some(s) => write!(f, "ACL_CHANGE(status={})", s.code),
1717 None => write!(f, "ACL_CHANGE(status=OK)"),
1718 }
1719 }
1720}
1721
1722impl fmt::Display for PvaGetFieldPayload {
1723 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1724 if self.is_server {
1725 let status = self.status.as_ref().map(|s| s.code).unwrap_or(0xff);
1726 write!(f, "GET_FIELD(status={})", status)
1727 } else {
1728 let field = self.field_name.as_deref().unwrap_or("");
1729 if field.is_empty() {
1730 write!(f, "GET_FIELD(cid={})", self.cid)
1731 } else {
1732 write!(f, "GET_FIELD(cid={}, field={})", self.cid, field)
1733 }
1734 }
1735 }
1736}
1737
1738impl fmt::Display for PvaMessagePayload {
1739 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1740 match &self.status {
1741 Some(s) => {
1742 if let Some(msg) = &s.message {
1743 write!(f, "MESSAGE(status={}, msg='{}')", s.code, msg)
1744 } else {
1745 write!(f, "MESSAGE(status={})", s.code)
1746 }
1747 }
1748 None => write!(f, "MESSAGE(status=OK)"),
1749 }
1750 }
1751}
1752
1753impl fmt::Display for PvaMultipleDataPayload {
1754 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1755 if self.entries.is_empty() {
1756 write!(f, "MULTIPLE_DATA(raw_len={})", self.raw.len())
1757 } else {
1758 write!(f, "MULTIPLE_DATA(entries={})", self.entries.len())
1759 }
1760 }
1761}
1762
1763impl fmt::Display for PvaCancelRequestPayload {
1764 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1765 let status = self.status.as_ref().map(|s| s.code);
1766 match status {
1767 Some(code) => write!(f, "CANCEL_REQUEST(id={}, status={})", self.request_id, code),
1768 None => write!(f, "CANCEL_REQUEST(id={})", self.request_id),
1769 }
1770 }
1771}
1772
1773impl fmt::Display for PvaDestroyRequestPayload {
1774 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1775 let status = self.status.as_ref().map(|s| s.code);
1776 match status {
1777 Some(code) => write!(
1778 f,
1779 "DESTROY_REQUEST(id={}, status={})",
1780 self.request_id, code
1781 ),
1782 None => write!(f, "DESTROY_REQUEST(id={})", self.request_id),
1783 }
1784 }
1785}
1786
1787impl fmt::Display for PvaOriginTagPayload {
1788 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1789 write!(f, "ORIGIN_TAG(addr={})", format_pva_address(&self.address))
1790 }
1791}
1792
1793impl fmt::Display for PvaUnknownPayload {
1794 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1795 let kind = if self.is_control {
1796 "CONTROL"
1797 } else {
1798 "APPLICATION"
1799 };
1800 write!(
1801 f,
1802 "UNKNOWN(cmd={}, type={}, raw_len={})",
1803 self.command, kind, self.raw_len
1804 )
1805 }
1806}
1807
1808impl fmt::Display for PvaPacketCommand {
1810 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1811 match self {
1812 PvaPacketCommand::Control(payload) => write!(f, "{}", payload),
1813 PvaPacketCommand::Search(payload) => write!(f, "{}", payload),
1814 PvaPacketCommand::SearchResponse(payload) => write!(f, "{}", payload),
1815 PvaPacketCommand::Beacon(payload) => write!(f, "{}", payload),
1816 PvaPacketCommand::ConnectionValidation(payload) => write!(f, "{}", payload),
1817 PvaPacketCommand::ConnectionValidated(payload) => write!(f, "{}", payload),
1818 PvaPacketCommand::AuthNZ(payload) => write!(f, "{}", payload),
1819 PvaPacketCommand::AclChange(payload) => write!(f, "{}", payload),
1820 PvaPacketCommand::Op(payload) => write!(f, "{}", payload),
1821 PvaPacketCommand::CreateChannel(payload) => write!(f, "{}", payload),
1822 PvaPacketCommand::DestroyChannel(payload) => write!(f, "{}", payload),
1823 PvaPacketCommand::GetField(payload) => write!(f, "{}", payload),
1824 PvaPacketCommand::Message(payload) => write!(f, "{}", payload),
1825 PvaPacketCommand::MultipleData(payload) => write!(f, "{}", payload),
1826 PvaPacketCommand::CancelRequest(payload) => write!(f, "{}", payload),
1827 PvaPacketCommand::DestroyRequest(payload) => write!(f, "{}", payload),
1828 PvaPacketCommand::OriginTag(payload) => write!(f, "{}", payload),
1829 PvaPacketCommand::Echo(bytes) => write!(f, "ECHO ({} bytes)", bytes.len()),
1830 PvaPacketCommand::Unknown(payload) => write!(f, "{}", payload),
1831 }
1832 }
1833}
1834
1835impl fmt::Display for PvaOpPayload {
1836 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1837 let cmd_name = match self.command {
1838 10 => "GET",
1839 11 => "PUT",
1840 12 => "PUT_GET",
1841 13 => "MONITOR",
1842 14 => "ARRAY",
1843 16 => "PROCESS",
1844 20 => "RPC",
1845 _ => "OP",
1846 };
1847
1848 let status_text = if let Some(s) = &self.status {
1849 match &s.message {
1850 Some(m) if !m.is_empty() => format!(" status={} msg='{}'", s.code, m),
1851 _ => format!(" status={}", s.code),
1852 }
1853 } else {
1854 String::new()
1855 };
1856
1857 let value_text = if let Some(ref decoded) = self.decoded_value {
1859 let formatted = format_compact_value(decoded);
1860 if formatted.is_empty() || formatted == "{}" {
1861 String::new()
1862 } else {
1863 format!(" [{}]", formatted)
1864 }
1865 } else if !self.pv_names.is_empty() {
1866 format!(" data=[{}]", self.pv_names.join(","))
1867 } else {
1868 String::new()
1869 };
1870
1871 if self.is_server {
1872 write!(
1873 f,
1874 "{}(ioid={}, sub=0x{:02x}{}{})",
1875 cmd_name, self.ioid, self.subcmd, status_text, value_text
1876 )
1877 } else {
1878 write!(
1879 f,
1880 "{}(sid={}, ioid={}, sub=0x{:02x}{}{})",
1881 cmd_name, self.sid_or_cid, self.ioid, self.subcmd, status_text, value_text
1882 )
1883 }
1884 }
1885}
1886
1887#[cfg(test)]
1888mod tests {
1889 use super::*;
1890 use crate::spvd_decode::extract_nt_scalar_value;
1891 use crate::spvd_encode::{
1892 encode_nt_payload_bitset_parts, encode_nt_scalar_bitset_parts, encode_size_pvd,
1893 nt_payload_desc, nt_scalar_desc,
1894 };
1895 use crate::spvirit_encode::encode_header;
1896 use spvirit_types::{NtPayload, NtScalar, NtScalarArray, ScalarArrayValue, ScalarValue};
1897
1898 #[test]
1899 fn test_decode_status_ok() {
1900 let raw = [0xff];
1901 let (status, consumed) = decode_status(&raw, false);
1902 assert!(status.is_none());
1903 assert_eq!(consumed, 1);
1904 }
1905
1906 #[test]
1907 fn test_decode_status_message() {
1908 let raw = [1u8, 2, b'h', b'i', 2, b's', b't'];
1909 let (status, consumed) = decode_status(&raw, false);
1910 assert_eq!(consumed, 7);
1911 let status = status.unwrap();
1912 assert_eq!(status.code, 1);
1913 assert_eq!(status.message.as_deref(), Some("hi"));
1914 assert_eq!(status.stack.as_deref(), Some("st"));
1915 }
1916
1917 #[test]
1918 fn test_search_response_decode() {
1919 let mut raw: Vec<u8> = vec![];
1920 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");
1926 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();
1931 assert!(decoded.found);
1932 assert_eq!(decoded.protocol, "tcp");
1933 assert_eq!(decoded.cids, vec![42u32]);
1934 }
1935
1936 fn build_monitor_packet(ioid: u32, subcmd: u8, body: &[u8]) -> Vec<u8> {
1937 let mut payload = Vec::new();
1938 payload.extend_from_slice(&ioid.to_le_bytes());
1939 payload.push(subcmd);
1940 payload.extend_from_slice(body);
1941 let mut out = encode_header(true, false, false, 2, 13, payload.len() as u32);
1942 out.extend_from_slice(&payload);
1943 out
1944 }
1945
1946 #[test]
1947 fn test_monitor_decode_overrun_and_legacy() {
1948 let nt = NtScalar::from_value(ScalarValue::F64(3.5));
1949 let desc = nt_scalar_desc(&nt.value);
1950 let (changed_bitset, values) = encode_nt_scalar_bitset_parts(&nt, false);
1951
1952 let mut body_overrun = Vec::new();
1953 body_overrun.extend_from_slice(&changed_bitset);
1954 body_overrun.extend_from_slice(&encode_size_pvd(0, false));
1955 body_overrun.extend_from_slice(&values);
1956
1957 let pkt = build_monitor_packet(1, 0x00, &body_overrun);
1958 let mut pva = PvaPacket::new(&pkt);
1959 let mut cmd = pva.decode_payload().expect("decoded");
1960 if let PvaPacketCommand::Op(ref mut op) = cmd {
1961 op.decode_with_field_desc(&desc, false);
1962 let decoded = op.decoded_value.as_ref().expect("decoded");
1963 let value = extract_nt_scalar_value(decoded).expect("value");
1964 match value {
1965 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1966 other => panic!("unexpected value {:?}", other),
1967 }
1968 } else {
1969 panic!("unexpected cmd");
1970 }
1971
1972 let mut body_legacy = Vec::new();
1973 body_legacy.extend_from_slice(&changed_bitset);
1974 body_legacy.extend_from_slice(&values);
1975
1976 let pkt = build_monitor_packet(1, 0x00, &body_legacy);
1977 let mut pva = PvaPacket::new(&pkt);
1978 let mut cmd = pva.decode_payload().expect("decoded");
1979 if let PvaPacketCommand::Op(ref mut op) = cmd {
1980 op.decode_with_field_desc(&desc, false);
1981 let decoded = op.decoded_value.as_ref().expect("decoded");
1982 let value = extract_nt_scalar_value(decoded).expect("value");
1983 match value {
1984 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1985 other => panic!("unexpected value {:?}", other),
1986 }
1987 } else {
1988 panic!("unexpected cmd");
1989 }
1990
1991 let mut body_spec = Vec::new();
1992 body_spec.extend_from_slice(&changed_bitset);
1993 body_spec.extend_from_slice(&values);
1994 body_spec.extend_from_slice(&encode_size_pvd(0, false));
1995
1996 let pkt = build_monitor_packet(1, 0x00, &body_spec);
1997 let mut pva = PvaPacket::new(&pkt);
1998 let mut cmd = pva.decode_payload().expect("decoded");
1999 if let PvaPacketCommand::Op(ref mut op) = cmd {
2000 op.decode_with_field_desc(&desc, false);
2001 let decoded = op.decoded_value.as_ref().expect("decoded");
2002 let value = extract_nt_scalar_value(decoded).expect("value");
2003 match value {
2004 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
2005 other => panic!("unexpected value {:?}", other),
2006 }
2007 } else {
2008 panic!("unexpected cmd");
2009 }
2010 }
2011
2012 #[test]
2013 fn test_monitor_decode_prefers_spec_order_for_array_payload() {
2014 let payload_value =
2015 NtPayload::ScalarArray(NtScalarArray::from_value(ScalarArrayValue::F64(vec![
2016 1.0, 2.0, 3.0, 4.0,
2017 ])));
2018 let desc = nt_payload_desc(&payload_value);
2019 let (changed_bitset, values) = encode_nt_payload_bitset_parts(&payload_value, false);
2020
2021 let mut body_spec = Vec::new();
2022 body_spec.extend_from_slice(&changed_bitset);
2023 body_spec.extend_from_slice(&values);
2024 body_spec.extend_from_slice(&encode_size_pvd(0, false));
2025
2026 let pkt = build_monitor_packet(11, 0x00, &body_spec);
2027 let mut pva = PvaPacket::new(&pkt);
2028 let mut cmd = pva.decode_payload().expect("decoded");
2029 if let PvaPacketCommand::Op(ref mut op) = cmd {
2030 op.decode_with_field_desc(&desc, false);
2031 let decoded = op.decoded_value.as_ref().expect("decoded");
2032 let value = extract_nt_scalar_value(decoded).expect("value");
2033 match value {
2034 DecodedValue::Array(items) => {
2035 assert_eq!(items.len(), 4);
2036 assert!(matches!(items[0], DecodedValue::Float64(v) if (v - 1.0).abs() < 1e-6));
2037 assert!(matches!(items[3], DecodedValue::Float64(v) if (v - 4.0).abs() < 1e-6));
2038 }
2039 other => panic!("unexpected value {:?}", other),
2040 }
2041 } else {
2042 panic!("unexpected cmd");
2043 }
2044 }
2045
2046 #[test]
2047 fn pva_status_reports_error_state() {
2048 let ok = PvaStatus {
2049 code: 0,
2050 message: None,
2051 stack: None,
2052 };
2053 let err = PvaStatus {
2054 code: 2,
2055 message: Some("bad".to_string()),
2056 stack: None,
2057 };
2058 assert!(!ok.is_error());
2059 assert!(err.is_error());
2060 }
2061
2062 #[test]
2063 fn pva_status_display_includes_message_and_stack() {
2064 let status = PvaStatus {
2065 code: 2,
2066 message: Some("bad".to_string()),
2067 stack: Some("trace".to_string()),
2068 };
2069 assert_eq!(status.to_string(), "code=2 message=bad stack=trace");
2070 }
2071
2072 #[test]
2073 fn decode_op_response_status_reads_status_from_packet() {
2074 let raw = vec![
2075 0xCA, 0x02, 0x40, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x02,
2076 0x03, b'b', b'a', b'd', 0x00,
2077 ];
2078 let status = decode_op_response_status(&raw, false)
2079 .expect("status parse")
2080 .expect("status");
2081 assert!(status.is_error());
2082 assert_eq!(status.message.as_deref(), Some("bad"));
2083 }
2084}