1use hex;
7use std::fmt;
8use tracing::debug;
9
10use crate::spvirit_encode::format_pva_address;
11use crate::spvd_decode::{format_compact_value, DecodedValue, PvdDecoder, StructureDesc};
12
13const PVA_COMMAND_NAMES: &[&str] = &[
17 "BEACON", "CONNECTION_VALIDATION", "ECHO", "SEARCH", "SEARCH_RESPONSE", "AUTHNZ", "ACL_CHANGE", "CREATE_CHANNEL", "DESTROY_CHANNEL", "CONNECTION_VALIDATED", "GET", "PUT", "PUT_GET", "MONITOR", "ARRAY", "DESTROY_REQUEST", "PROCESS", "GET_FIELD", "MESSAGE", "MULTIPLE_DATA", "RPC", "CANCEL_REQUEST", "ORIGIN_TAG", ];
41
42pub fn command_name(code: u8) -> &'static str {
44 PVA_COMMAND_NAMES
45 .get(code as usize)
46 .copied()
47 .unwrap_or("Unknown")
48}
49
50pub fn command_to_integer(command: &str) -> u8 {
52 PVA_COMMAND_NAMES
53 .iter()
54 .position(|&name| name == command)
55 .map(|i| i as u8)
56 .unwrap_or(255)
57}
58
59#[derive(Debug)]
62pub struct PvaCommands;
63
64impl PvaCommands {
65 pub fn new() -> Self {
66 Self
67 }
68
69 pub fn get_command(&self, code: u8) -> &'static str {
70 command_name(code)
71 }
72}
73#[derive(Debug)]
74pub struct PvaControlFlags {
75 pub raw: u8,
76 pub is_application: bool,
82 pub is_control: bool,
83 pub is_segmented: u8,
84 pub is_first_segment: bool,
85 pub is_last_segment: bool,
86 pub is_middle_segment: bool,
87 pub is_client: bool,
88 pub is_server: bool,
89 pub is_lsb: bool,
90 pub is_msb: bool,
91 pub is_valid: bool,
92}
93
94impl PvaControlFlags {
95 pub fn new(raw: u8) -> Self {
96 let is_application = (raw & 0x01) == 0; let is_control = (raw & 0x01) != 0; let is_segmented = (raw & 0x30) >> 4; let is_first_segment = is_segmented == 0x01; let is_last_segment = is_segmented == 0x02; let is_middle_segment = is_segmented == 0x03; let is_client = (raw & 0x40) == 0; let is_server = (raw & 0x40) != 0; let is_lsb = (raw & 0x80) == 0; let is_msb = (raw & 0x80) != 0; let is_valid = (raw & 0x0E) == 0; Self {
109 raw,
110 is_application,
111 is_control,
112 is_segmented,
113 is_first_segment,
114 is_last_segment,
115 is_middle_segment,
116 is_client,
117 is_server,
118 is_lsb,
119 is_msb,
120 is_valid,
121 }
122 }
123 fn is_valid(&self) -> bool {
124 self.is_valid
125 }
126}
127#[derive(Debug)]
128pub struct PvaHeader {
129 pub magic: u8,
130 pub version: u8,
131 pub flags: PvaControlFlags,
132 pub command: u8,
133 pub payload_length: u32,
134}
135
136impl PvaHeader {
137 pub fn new(raw: &[u8]) -> Self {
138 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() < 8 {
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 let qos = if is_be {
514 u16::from_be_bytes(raw[6..8].try_into().ok()?)
515 } else {
516 u16::from_le_bytes(raw[6..8].try_into().ok()?)
517 };
518 let authz = if raw.len() > 8 {
519 if let Some((s, consumed)) = decode_string(&raw[8..], is_be) {
521 if 8 + consumed == raw.len() {
522 Some(s)
523 } else {
524 let mut offset = 9; let name = decode_string(&raw[offset..], is_be).map(|(s, c)| {
527 offset += c;
528 s
529 });
530 let method = decode_string(&raw[offset..], is_be).map(|(s, _)| s);
531 match (name, method) {
532 (Some(n), _) if !n.is_empty() => Some(n),
533 (_, Some(m)) if !m.is_empty() => Some(m),
534 _ => None,
535 }
536 }
537 } else {
538 None
539 }
540 } else {
541 None
542 };
543
544 Some(Self {
545 is_server,
546 buffer_size,
547 introspection_registry_size,
548 qos,
549 authz,
550 })
551 }
552}
553
554#[derive(Debug)]
555pub struct PvaConnectionValidatedPayload {
556 pub status: Option<PvaStatus>,
557}
558
559impl PvaConnectionValidatedPayload {
560 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
561 let (status, _consumed) = decode_status(raw, is_be);
562 Some(Self { status })
563 }
564}
565
566#[derive(Debug)]
567pub struct PvaAuthNzPayload {
568 pub raw: Vec<u8>,
569 pub strings: Vec<String>,
570}
571
572impl PvaAuthNzPayload {
573 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
574 let mut strings = vec![];
575 if let Some((count, consumed)) = decode_size(raw, is_be) {
576 let mut offset = consumed;
577 for _ in 0..count {
578 if let Some((s, len)) = decode_string(&raw[offset..], is_be) {
579 strings.push(s);
580 offset += len;
581 } else {
582 break;
583 }
584 }
585 }
586 Some(Self {
587 raw: raw.to_vec(),
588 strings,
589 })
590 }
591}
592
593#[derive(Debug)]
594pub struct PvaAclChangePayload {
595 pub status: Option<PvaStatus>,
596 pub raw: Vec<u8>,
597}
598
599impl PvaAclChangePayload {
600 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
601 let (status, consumed) = decode_status(raw, is_be);
602 let raw_rem = if raw.len() > consumed {
603 raw[consumed..].to_vec()
604 } else {
605 vec![]
606 };
607 Some(Self {
608 status,
609 raw: raw_rem,
610 })
611 }
612}
613
614#[derive(Debug)]
615pub struct PvaGetFieldPayload {
616 pub is_server: bool,
617 pub cid: u32,
618 pub sid: Option<u32>,
619 pub ioid: Option<u32>,
620 pub field_name: Option<String>,
621 pub status: Option<PvaStatus>,
622 pub introspection: Option<StructureDesc>,
623 pub raw: Vec<u8>,
624}
625
626impl PvaGetFieldPayload {
627 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
628 if !is_server {
629 if raw.len() < 4 {
630 debug!(
631 "PvaGetFieldPayload::new (client): raw too short {}",
632 raw.len()
633 );
634 return None;
635 }
636 let cid = if is_be {
637 u32::from_be_bytes(raw[0..4].try_into().ok()?)
638 } else {
639 u32::from_le_bytes(raw[0..4].try_into().ok()?)
640 };
641
642 let legacy_field = if raw.len() > 4 {
646 decode_string(&raw[4..], is_be).and_then(|(s, consumed)| {
647 (4 + consumed == raw.len()).then_some(s)
648 })
649 } else {
650 None
651 };
652
653 let epics_variant = if raw.len() >= 9 {
654 let ioid = if is_be {
655 u32::from_be_bytes(raw[4..8].try_into().ok()?)
656 } else {
657 u32::from_le_bytes(raw[4..8].try_into().ok()?)
658 };
659 decode_string(&raw[8..], is_be).and_then(|(s, consumed)| {
660 (8 + consumed == raw.len()).then_some((ioid, s))
661 })
662 } else {
663 None
664 };
665
666 let (sid, ioid, field_name) = if let Some((ioid, field)) = epics_variant {
667 (Some(cid), Some(ioid), Some(field))
668 } else {
669 (None, None, legacy_field)
670 };
671
672 return Some(Self {
673 is_server,
674 cid,
675 sid,
676 ioid,
677 field_name,
678 status: None,
679 introspection: None,
680 raw: vec![],
681 });
682 }
683
684 let parse_status_then_intro = |bytes: &[u8]| {
685 let (status, consumed) = decode_status(bytes, is_be);
686 let pvd_raw = if bytes.len() > consumed {
687 bytes[consumed..].to_vec()
688 } else {
689 vec![]
690 };
691 let introspection = if !pvd_raw.is_empty() {
692 let decoder = PvdDecoder::new(is_be);
693 decoder.parse_introspection(&pvd_raw)
694 } else {
695 None
696 };
697 (status, pvd_raw, introspection)
698 };
699
700 let (cid, status, pvd_raw, introspection) = if raw.len() >= 4 {
704 let parsed_cid = if is_be {
705 u32::from_be_bytes(raw[0..4].try_into().ok()?)
706 } else {
707 u32::from_le_bytes(raw[0..4].try_into().ok()?)
708 };
709 let (status, pvd_raw, introspection) = parse_status_then_intro(&raw[4..]);
710 (parsed_cid, status, pvd_raw, introspection)
711 } else {
712 let (status, pvd_raw, introspection) = parse_status_then_intro(raw);
713 (0, status, pvd_raw, introspection)
714 };
715
716 Some(Self {
717 is_server,
718 cid,
719 sid: None,
720 ioid: None,
721 field_name: None,
722 status,
723 introspection,
724 raw: pvd_raw,
725 })
726 }
727}
728
729#[derive(Debug)]
730pub struct PvaMessagePayload {
731 pub status: Option<PvaStatus>,
732 pub raw: Vec<u8>,
733}
734
735impl PvaMessagePayload {
736 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
737 let (status, consumed) = decode_status(raw, is_be);
738 let remainder = if raw.len() > consumed {
739 raw[consumed..].to_vec()
740 } else {
741 vec![]
742 };
743 Some(Self {
744 status,
745 raw: remainder,
746 })
747 }
748}
749
750#[derive(Debug)]
751pub struct PvaMultipleDataEntry {
752 pub ioid: u32,
753 pub subcmd: u8,
754}
755
756#[derive(Debug)]
757pub struct PvaMultipleDataPayload {
758 pub entries: Vec<PvaMultipleDataEntry>,
759 pub raw: Vec<u8>,
760}
761
762impl PvaMultipleDataPayload {
763 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
764 let mut entries: Vec<PvaMultipleDataEntry> = vec![];
765 if let Some((count, consumed)) = decode_size(raw, is_be) {
766 let mut offset = consumed;
767 for _ in 0..count {
768 if raw.len() < offset + 5 {
769 break;
770 }
771 let ioid = if is_be {
772 u32::from_be_bytes(raw[offset..offset + 4].try_into().ok()?)
773 } else {
774 u32::from_le_bytes(raw[offset..offset + 4].try_into().ok()?)
775 };
776 let subcmd = raw[offset + 4];
777 entries.push(PvaMultipleDataEntry { ioid, subcmd });
778 offset += 5;
779 }
780 }
781 Some(Self {
782 entries,
783 raw: raw.to_vec(),
784 })
785 }
786}
787
788#[derive(Debug)]
789pub struct PvaCancelRequestPayload {
790 pub request_id: u32,
791 pub status: Option<PvaStatus>,
792}
793
794impl PvaCancelRequestPayload {
795 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
796 if raw.len() < 4 {
797 debug!("PvaCancelRequestPayload::new: raw too short {}", raw.len());
798 return None;
799 }
800 let request_id = if is_be {
801 u32::from_be_bytes(raw[0..4].try_into().ok()?)
802 } else {
803 u32::from_le_bytes(raw[0..4].try_into().ok()?)
804 };
805 let (status, _) = if raw.len() > 4 {
806 decode_status(&raw[4..], is_be)
807 } else {
808 (None, 0)
809 };
810 Some(Self { request_id, status })
811 }
812}
813
814#[derive(Debug)]
815pub struct PvaDestroyRequestPayload {
816 pub request_id: u32,
817 pub status: Option<PvaStatus>,
818}
819
820impl PvaDestroyRequestPayload {
821 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
822 if raw.len() < 4 {
823 debug!("PvaDestroyRequestPayload::new: raw too short {}", raw.len());
824 return None;
825 }
826 let request_id = if is_be {
827 u32::from_be_bytes(raw[0..4].try_into().ok()?)
828 } else {
829 u32::from_le_bytes(raw[0..4].try_into().ok()?)
830 };
831 let (status, _) = if raw.len() > 4 {
832 decode_status(&raw[4..], is_be)
833 } else {
834 (None, 0)
835 };
836 Some(Self { request_id, status })
837 }
838}
839
840#[derive(Debug)]
841pub struct PvaOriginTagPayload {
842 pub address: [u8; 16],
843}
844
845impl PvaOriginTagPayload {
846 pub fn new(raw: &[u8]) -> Option<Self> {
847 if raw.len() < 16 {
848 debug!("PvaOriginTagPayload::new: raw too short {}", raw.len());
849 return None;
850 }
851 let address: [u8; 16] = raw[0..16].try_into().ok()?;
852 Some(Self { address })
853 }
854}
855
856#[derive(Debug)]
857pub struct PvaUnknownPayload {
858 pub command: u8,
859 pub is_control: bool,
860 pub raw_len: usize,
861}
862
863impl PvaUnknownPayload {
864 pub fn new(command: u8, is_control: bool, raw_len: usize) -> Self {
865 Self {
866 command,
867 is_control,
868 raw_len,
869 }
870 }
871}
872
873#[derive(Debug)]
876pub struct PvaSearchPayload {
877 pub seq: u32,
878 pub mask: u8,
879 pub addr: [u8; 16],
880 pub port: u16,
881 pub protocols: Vec<String>,
882 pub pv_requests: Vec<(u32, String)>,
883 pub pv_names: Vec<String>,
884}
885
886impl PvaSearchPayload {
887 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
888 if raw.is_empty() {
889 debug!("PvaSearchPayload::new received an empty raw slice.");
890 return None;
891 }
892 const MIN_FIXED_SEARCH_PAYLOAD_SIZE: usize = 26;
893 if raw.len() < MIN_FIXED_SEARCH_PAYLOAD_SIZE {
894 debug!(
895 "PvaSearchPayload::new: raw slice length {} is less than min fixed size {}.",
896 raw.len(),
897 MIN_FIXED_SEARCH_PAYLOAD_SIZE
898 );
899 return None;
900 }
901
902 let seq = if is_be {
903 u32::from_be_bytes(raw[0..4].try_into().unwrap())
904 } else {
905 u32::from_le_bytes(raw[0..4].try_into().unwrap())
906 };
907
908 let mask = raw[4];
909 let addr: [u8; 16] = raw[8..24].try_into().unwrap();
910 let port = if is_be {
911 u16::from_be_bytes(raw[24..26].try_into().unwrap())
912 } else {
913 u16::from_le_bytes(raw[24..26].try_into().unwrap())
914 };
915
916 let mut offset = 26;
917
918 let (protocol_count, consumed) = decode_size(&raw[offset..], is_be)?;
919 offset += consumed;
920
921 let mut protocols = vec![];
922 for _ in 0..protocol_count {
923 let (protocol, len) = decode_string(&raw[offset..], is_be)?;
924 protocols.push(protocol);
925 offset += len;
926 }
927
928 if raw.len() < offset + 2 {
930 return None;
931 }
932 let pv_count = if is_be {
933 u16::from_be_bytes(raw[offset..offset + 2].try_into().unwrap())
934 } else {
935 u16::from_le_bytes(raw[offset..offset + 2].try_into().unwrap())
936 };
937 offset += 2;
938
939 let mut pv_names = vec![];
940 let mut pv_requests = vec![];
941 for _ in 0..pv_count {
942 if raw.len() < offset + 4 {
943 debug!(
944 "PvaSearchPayload::new: not enough data for PV CID at offset {}. Raw len: {}",
945 offset,
946 raw.len()
947 );
948 return None;
949 }
950 let cid = if is_be {
951 u32::from_be_bytes(raw[offset..offset + 4].try_into().unwrap())
952 } else {
953 u32::from_le_bytes(raw[offset..offset + 4].try_into().unwrap())
954 };
955 offset += 4;
956 let (pv_name, len) = decode_string(&raw[offset..], is_be)?;
957 pv_names.push(pv_name.clone());
958 pv_requests.push((cid, pv_name));
959 offset += len;
960 }
961
962 Some(Self {
963 seq,
964 mask,
965 addr,
966 port,
967 protocols,
968 pv_requests,
969 pv_names,
970 })
971 }
972}
973
974#[derive(Debug)]
976pub struct PvaBeaconPayload {
977 pub guid: [u8; 12],
978 pub flags: u8,
979 pub beacon_sequence_id: u8,
980 pub change_count: u16,
981 pub server_address: [u8; 16],
982 pub server_port: u16,
983 pub protocol: String,
984 pub server_status_if: String,
985}
986
987impl PvaBeaconPayload {
988 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
989 const MIN_FIXED_BEACON_PAYLOAD_SIZE: usize = 12 + 1 + 1 + 2 + 16 + 2;
991
992 if raw.len() < MIN_FIXED_BEACON_PAYLOAD_SIZE {
993 debug!(
994 "PvaBeaconPayload::new: raw slice length {} is less than min fixed size {}.",
995 raw.len(),
996 MIN_FIXED_BEACON_PAYLOAD_SIZE
997 );
998 return None;
999 }
1000
1001 let guid: [u8; 12] = raw[0..12].try_into().unwrap();
1002 let flags = raw[12];
1003 let beacon_sequence_id = raw[13];
1004 let change_count = if is_be {
1005 u16::from_be_bytes(raw[14..16].try_into().unwrap())
1006 } else {
1007 u16::from_le_bytes(raw[14..16].try_into().unwrap())
1008 };
1009 let server_address: [u8; 16] = raw[16..32].try_into().unwrap();
1010 let server_port = if is_be {
1011 u16::from_be_bytes(raw[32..34].try_into().unwrap())
1012 } else {
1013 u16::from_le_bytes(raw[32..34].try_into().unwrap())
1014 };
1015 let (protocol, len) = decode_string(&raw[34..], is_be)?;
1016 let protocol = protocol;
1017 let server_status_if = if len > 0 {
1018 let (server_status_if, _server_status_len) = decode_string(&raw[34 + len..], is_be)?;
1019 server_status_if
1020 } else {
1021 String::new()
1022 };
1023
1024 Some(Self {
1025 guid,
1026 flags,
1027 beacon_sequence_id,
1028 change_count,
1029 server_address,
1030 server_port,
1031 protocol,
1032 server_status_if,
1033 })
1034 }
1035}
1036
1037#[derive(Debug)]
1041pub struct PvaCreateChannelPayload {
1042 pub is_server: bool,
1044 pub channels: Vec<(u32, String)>,
1046 pub cid: u32,
1048 pub sid: u32,
1050 pub status: Option<PvaStatus>,
1052}
1053
1054impl PvaCreateChannelPayload {
1055 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
1056 if raw.is_empty() {
1057 debug!("PvaCreateChannelPayload::new received an empty raw slice.");
1058 return None;
1059 }
1060
1061 if is_server {
1062 if raw.len() < 8 {
1064 debug!("CREATE_CHANNEL server response too short: {}", raw.len());
1065 return None;
1066 }
1067
1068 let cid = if is_be {
1069 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1070 } else {
1071 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1072 };
1073
1074 let sid = if is_be {
1075 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1076 } else {
1077 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1078 };
1079
1080 let status = if raw.len() > 8 {
1082 let code = raw[8];
1083 if code == 0xff {
1084 None } else {
1086 let mut idx = 9;
1087 let message = if idx < raw.len() {
1088 decode_string(&raw[idx..], is_be).map(|(msg, consumed)| {
1089 idx += consumed;
1090 msg
1091 })
1092 } else {
1093 None
1094 };
1095 let stack = if idx < raw.len() {
1096 decode_string(&raw[idx..], is_be).map(|(s, _)| s)
1097 } else {
1098 None
1099 };
1100 Some(PvaStatus {
1101 code,
1102 message,
1103 stack,
1104 })
1105 }
1106 } else {
1107 None
1108 };
1109
1110 Some(Self {
1111 is_server: true,
1112 channels: vec![],
1113 cid,
1114 sid,
1115 status,
1116 })
1117 } else {
1118 if raw.len() < 2 {
1120 debug!("CREATE_CHANNEL client request too short: {}", raw.len());
1121 return None;
1122 }
1123
1124 let count = if is_be {
1125 u16::from_be_bytes(raw[0..2].try_into().unwrap())
1126 } else {
1127 u16::from_le_bytes(raw[0..2].try_into().unwrap())
1128 };
1129
1130 let mut offset = 2;
1131 let mut channels = Vec::with_capacity(count as usize);
1132
1133 for _ in 0..count {
1134 if raw.len() < offset + 4 {
1135 debug!(
1136 "CREATE_CHANNEL: not enough data for CID at offset {}",
1137 offset
1138 );
1139 break;
1140 }
1141
1142 let cid = if is_be {
1143 u32::from_be_bytes(raw[offset..offset + 4].try_into().unwrap())
1144 } else {
1145 u32::from_le_bytes(raw[offset..offset + 4].try_into().unwrap())
1146 };
1147 offset += 4;
1148
1149 if let Some((pv_name, consumed)) = decode_string(&raw[offset..], is_be) {
1150 offset += consumed;
1151 channels.push((cid, pv_name));
1152 } else {
1153 debug!(
1154 "CREATE_CHANNEL: failed to decode PV name at offset {}",
1155 offset
1156 );
1157 break;
1158 }
1159 }
1160
1161 Some(Self {
1162 is_server: false,
1163 channels,
1164 cid: 0,
1165 sid: 0,
1166 status: None,
1167 })
1168 }
1169 }
1170}
1171
1172impl fmt::Display for PvaCreateChannelPayload {
1173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1174 if self.is_server {
1175 let status_text = if let Some(s) = &self.status {
1176 format!(" status={}", s.code)
1177 } else {
1178 String::new()
1179 };
1180 write!(
1181 f,
1182 "CREATE_CHANNEL(cid={}, sid={}{})",
1183 self.cid, self.sid, status_text
1184 )
1185 } else {
1186 let pv_list: Vec<String> = self
1187 .channels
1188 .iter()
1189 .map(|(cid, name)| format!("{}:'{}'", cid, name))
1190 .collect();
1191 write!(f, "CREATE_CHANNEL({})", pv_list.join(", "))
1192 }
1193 }
1194}
1195
1196#[derive(Debug)]
1199pub struct PvaDestroyChannelPayload {
1200 pub sid: u32,
1202 pub cid: u32,
1204}
1205
1206impl PvaDestroyChannelPayload {
1207 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
1208 if raw.len() < 8 {
1209 debug!("DESTROY_CHANNEL payload too short: {}", raw.len());
1210 return None;
1211 }
1212
1213 let sid = if is_be {
1214 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1215 } else {
1216 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1217 };
1218
1219 let cid = if is_be {
1220 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1221 } else {
1222 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1223 };
1224
1225 Some(Self { sid, cid })
1226 }
1227}
1228
1229impl fmt::Display for PvaDestroyChannelPayload {
1230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1231 write!(f, "DESTROY_CHANNEL(sid={}, cid={})", self.sid, self.cid)
1232 }
1233}
1234
1235#[derive(Debug)]
1237pub struct PvaOpPayload {
1238 pub sid_or_cid: u32,
1239 pub ioid: u32,
1240 pub subcmd: u8,
1241 pub body: Vec<u8>,
1242 pub command: u8,
1243 pub is_server: bool,
1244 pub status: Option<PvaStatus>,
1245 pub pv_names: Vec<String>,
1246 pub introspection: Option<StructureDesc>,
1248 pub decoded_value: Option<DecodedValue>,
1250}
1251
1252fn extract_pv_names(raw: &[u8]) -> Vec<String> {
1254 let mut names: Vec<String> = Vec::new();
1255 let mut i = 0usize;
1256 while i < raw.len() {
1257 if raw[i].is_ascii_alphanumeric() {
1259 let start = i;
1260 i += 1;
1261 while i < raw.len() {
1262 let b = raw[i];
1263 if b.is_ascii_alphanumeric()
1264 || b == b':'
1265 || b == b'.'
1266 || b == b'_'
1267 || b == b'-'
1268 || b == b'/'
1269 {
1270 i += 1;
1271 } else {
1272 break;
1273 }
1274 }
1275 let len = i - start;
1276 if len >= 3 && len <= 128 {
1277 if let Ok(s) = std::str::from_utf8(&raw[start..start + len]) {
1278 if s.chars().any(|c| c.is_ascii_alphabetic()) {
1280 if !names.contains(&s.to_string()) {
1281 names.push(s.to_string());
1282 if names.len() >= 8 {
1283 break;
1284 }
1285 }
1286 }
1287 }
1288 }
1289 } else {
1290 i += 1;
1291 }
1292 }
1293 names
1294}
1295
1296impl PvaOpPayload {
1297 pub fn new(raw: &[u8], is_be: bool, is_server: bool, command: u8) -> Option<Self> {
1298 if raw.len() < 5 {
1300 debug!("PvaOpPayload::new: raw too short {}", raw.len());
1301 return None;
1302 }
1303
1304 let (sid_or_cid, ioid, subcmd, offset) = if is_server {
1305 if raw.len() < 5 {
1307 return None;
1308 }
1309 let ioid = if is_be {
1310 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1311 } else {
1312 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1313 };
1314 let subcmd = raw[4];
1315 (0, ioid, subcmd, 5)
1316 } else {
1317 if raw.len() < 9 {
1319 return None;
1320 }
1321 let sid = if is_be {
1322 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1323 } else {
1324 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1325 };
1326 let ioid = if is_be {
1327 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1328 } else {
1329 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1330 };
1331 let subcmd = raw[8];
1332 (sid, ioid, subcmd, 9)
1333 };
1334
1335 let body = if raw.len() > offset {
1336 raw[offset..].to_vec()
1337 } else {
1338 vec![]
1339 };
1340
1341 let mut status: Option<PvaStatus> = None;
1347 let mut pvd_raw: Vec<u8> = vec![];
1348
1349 let has_status = is_server && (subcmd & 0x08) != 0;
1352
1353 if !body.is_empty() {
1354 if has_status {
1355 let (parsed, consumed) = decode_status(&body, is_be);
1356 status = parsed;
1357 pvd_raw = if body.len() > consumed {
1358 body[consumed..].to_vec()
1359 } else {
1360 vec![]
1361 };
1362 } else {
1363 if body[0] == 0xFF {
1366 pvd_raw = body[1..].to_vec();
1367 } else {
1368 pvd_raw = body.clone();
1369 }
1370 }
1371 }
1372
1373 let pv_names = extract_pv_names(&pvd_raw);
1374
1375 let introspection = if is_server && (subcmd & 0x08) != 0 && !pvd_raw.is_empty() {
1377 let decoder = PvdDecoder::new(is_be);
1378 decoder.parse_introspection(&pvd_raw)
1379 } else {
1380 None
1381 };
1382
1383 let result = Some(Self {
1384 sid_or_cid,
1385 ioid,
1386 subcmd,
1387 body: pvd_raw,
1388 command,
1389 is_server,
1390 status: status.clone(),
1391 pv_names,
1392 introspection,
1393 decoded_value: None, });
1395
1396 result
1397 }
1398
1399 pub fn decode_with_field_desc(&mut self, field_desc: &StructureDesc, is_be: bool) {
1401 if self.body.is_empty() {
1402 return;
1403 }
1404
1405 let decoder = PvdDecoder::new(is_be);
1406
1407 if self.subcmd == 0x00 || (self.subcmd & 0x40) != 0 {
1409 if self.command == 13 {
1410 let cand_overrun_pre =
1411 decoder.decode_structure_with_bitset_and_overrun(&self.body, field_desc);
1412 let cand_overrun_post =
1413 decoder.decode_structure_with_bitset_then_overrun(&self.body, field_desc);
1414 let cand_legacy = decoder.decode_structure_with_bitset(&self.body, field_desc);
1415 self.decoded_value =
1416 choose_best_decoded_multi([cand_overrun_pre, cand_overrun_post, cand_legacy]);
1417 } else if let Some((value, _)) =
1418 decoder.decode_structure_with_bitset(&self.body, field_desc)
1419 {
1420 self.decoded_value = Some(value);
1421 }
1422 } else {
1423 if let Some((value, _)) = decoder.decode_structure(&self.body, field_desc) {
1425 self.decoded_value = Some(value);
1426 }
1427 }
1428 }
1429}
1430
1431fn choose_best_decoded_multi(cands: [Option<(DecodedValue, usize)>; 3]) -> Option<DecodedValue> {
1432 let mut best_value: Option<DecodedValue> = None;
1433 let mut best_score = i32::MIN;
1434 let mut best_consumed = 0usize;
1435 let mut best_idx = 0usize;
1436
1437 for (idx, cand) in cands.into_iter().enumerate() {
1438 let Some((value, consumed)) = cand else {
1439 continue;
1440 };
1441 let score = score_decoded(&value);
1442 let better = score > best_score
1443 || (score == best_score && consumed > best_consumed)
1444 || (score == best_score && consumed == best_consumed && idx > best_idx);
1445 if better {
1446 best_score = score;
1447 best_consumed = consumed;
1448 best_idx = idx;
1449 best_value = Some(value);
1450 }
1451 }
1452
1453 best_value
1454}
1455
1456fn score_decoded(value: &DecodedValue) -> i32 {
1457 let DecodedValue::Structure(fields) = value else {
1458 return -1;
1459 };
1460
1461 let mut score = fields.len() as i32;
1462
1463 let mut has_value = false;
1464 let mut has_alarm = false;
1465 let mut has_ts = false;
1466
1467 for (name, val) in fields {
1468 match name.as_str() {
1469 "value" => {
1470 has_value = true;
1471 score += 4;
1472 match val {
1473 DecodedValue::Array(items) => {
1474 if items.is_empty() {
1475 score -= 2;
1476 } else {
1477 score += 6 + (items.len().min(8) as i32);
1478 }
1479 }
1480 DecodedValue::Structure(_) => score += 1,
1481 _ => score += 2,
1482 }
1483 }
1484 "alarm" => {
1485 has_alarm = true;
1486 score += 2;
1487 }
1488 "timeStamp" => {
1489 has_ts = true;
1490 score += 2;
1491 if let DecodedValue::Structure(ts_fields) = val {
1492 if let Some(secs) = ts_fields.iter().find_map(|(n, v)| {
1493 if n == "secondsPastEpoch" {
1494 if let DecodedValue::Int64(s) = v {
1495 return Some(*s);
1496 }
1497 }
1498 None
1499 }) {
1500 if (0..=4_000_000_000i64).contains(&secs) {
1501 score += 2;
1502 } else if secs.abs() > 10_000_000_000i64 {
1503 score -= 2;
1504 }
1505 }
1506 }
1507 }
1508 "display" | "control" => {
1509 score += 1;
1510 }
1511 _ => {}
1512 }
1513 }
1514
1515 if !has_value {
1516 score -= 2;
1517 }
1518 if !has_alarm {
1519 score -= 1;
1520 }
1521 if !has_ts {
1522 score -= 1;
1523 }
1524
1525 score
1526}
1527
1528#[derive(Debug, Clone)]
1529pub struct PvaStatus {
1530 pub code: u8,
1531 pub message: Option<String>,
1532 pub stack: Option<String>,
1533}
1534
1535impl PvaStatus {
1536 pub fn is_error(&self) -> bool {
1537 self.code != 0
1538 }
1539}
1540
1541impl fmt::Display for PvaBeaconPayload {
1544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1545 write!(
1546 f,
1547 "Beacon:GUID=[{}],Flags=[{}],SeqId=[{}],ChangeCount=[{}],ServerAddress=[{}],ServerPort=[{}],Protocol=[{}]",
1548 hex::encode(self.guid),
1549 self.flags,
1550 self.beacon_sequence_id,
1551 self.change_count,
1552 format_pva_address(&self.server_address),
1553 self.server_port,
1554 self.protocol
1555 )
1556 }
1557}
1558
1559impl fmt::Display for PvaSearchPayload {
1561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1562 write!(f, "Search:PVs=[{}]", self.pv_names.join(","))
1563 }
1564}
1565
1566impl fmt::Display for PvaControlPayload {
1567 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1568 let name = match self.command {
1569 0 => "MARK_TOTAL_BYTES_SENT",
1570 1 => "ACK_TOTAL_BYTES_RECEIVED",
1571 2 => "SET_BYTE_ORDER",
1572 3 => "ECHO_REQUEST",
1573 4 => "ECHO_RESPONSE",
1574 _ => "CONTROL",
1575 };
1576 write!(f, "{}(data={})", name, self.data)
1577 }
1578}
1579
1580impl fmt::Display for PvaSearchResponsePayload {
1581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1582 let found_text = if self.found { "true" } else { "false" };
1583 if self.cids.is_empty() {
1584 write!(
1585 f,
1586 "SearchResponse(found={}, proto={})",
1587 found_text, self.protocol
1588 )
1589 } else {
1590 write!(
1591 f,
1592 "SearchResponse(found={}, proto={}, cids=[{}])",
1593 found_text,
1594 self.protocol,
1595 self.cids
1596 .iter()
1597 .map(|c| c.to_string())
1598 .collect::<Vec<String>>()
1599 .join(",")
1600 )
1601 }
1602 }
1603}
1604
1605impl fmt::Display for PvaConnectionValidationPayload {
1606 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1607 let dir = if self.is_server { "server" } else { "client" };
1608 let authz = self.authz.as_deref().unwrap_or("");
1609 if authz.is_empty() {
1610 write!(
1611 f,
1612 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x})",
1613 dir, self.buffer_size, self.introspection_registry_size, self.qos
1614 )
1615 } else {
1616 write!(
1617 f,
1618 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x}, authz={})",
1619 dir, self.buffer_size, self.introspection_registry_size, self.qos, authz
1620 )
1621 }
1622 }
1623}
1624
1625impl fmt::Display for PvaStatus {
1626 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1627 write!(
1628 f,
1629 "code={} message={} stack={}",
1630 self.code,
1631 self.message.as_deref().unwrap_or(""),
1632 self.stack.as_deref().unwrap_or("")
1633 )
1634 }
1635}
1636
1637impl fmt::Display for PvaConnectionValidatedPayload {
1638 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1639 match &self.status {
1640 Some(s) => write!(f, "ConnectionValidated(status={})", s.code),
1641 None => write!(f, "ConnectionValidated(status=OK)"),
1642 }
1643 }
1644}
1645
1646impl fmt::Display for PvaAuthNzPayload {
1647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1648 if !self.strings.is_empty() {
1649 write!(f, "AuthNZ(strings=[{}])", self.strings.join(","))
1650 } else {
1651 write!(f, "AuthNZ(raw_len={})", self.raw.len())
1652 }
1653 }
1654}
1655
1656impl fmt::Display for PvaAclChangePayload {
1657 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1658 match &self.status {
1659 Some(s) => write!(f, "ACL_CHANGE(status={})", s.code),
1660 None => write!(f, "ACL_CHANGE(status=OK)"),
1661 }
1662 }
1663}
1664
1665impl fmt::Display for PvaGetFieldPayload {
1666 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1667 if self.is_server {
1668 let status = self.status.as_ref().map(|s| s.code).unwrap_or(0xff);
1669 write!(f, "GET_FIELD(status={})", status)
1670 } else {
1671 let field = self.field_name.as_deref().unwrap_or("");
1672 if field.is_empty() {
1673 write!(f, "GET_FIELD(cid={})", self.cid)
1674 } else {
1675 write!(f, "GET_FIELD(cid={}, field={})", self.cid, field)
1676 }
1677 }
1678 }
1679}
1680
1681impl fmt::Display for PvaMessagePayload {
1682 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1683 match &self.status {
1684 Some(s) => {
1685 if let Some(msg) = &s.message {
1686 write!(f, "MESSAGE(status={}, msg='{}')", s.code, msg)
1687 } else {
1688 write!(f, "MESSAGE(status={})", s.code)
1689 }
1690 }
1691 None => write!(f, "MESSAGE(status=OK)"),
1692 }
1693 }
1694}
1695
1696impl fmt::Display for PvaMultipleDataPayload {
1697 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1698 if self.entries.is_empty() {
1699 write!(f, "MULTIPLE_DATA(raw_len={})", self.raw.len())
1700 } else {
1701 write!(f, "MULTIPLE_DATA(entries={})", self.entries.len())
1702 }
1703 }
1704}
1705
1706impl fmt::Display for PvaCancelRequestPayload {
1707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1708 let status = self.status.as_ref().map(|s| s.code);
1709 match status {
1710 Some(code) => write!(f, "CANCEL_REQUEST(id={}, status={})", self.request_id, code),
1711 None => write!(f, "CANCEL_REQUEST(id={})", self.request_id),
1712 }
1713 }
1714}
1715
1716impl fmt::Display for PvaDestroyRequestPayload {
1717 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1718 let status = self.status.as_ref().map(|s| s.code);
1719 match status {
1720 Some(code) => write!(
1721 f,
1722 "DESTROY_REQUEST(id={}, status={})",
1723 self.request_id, code
1724 ),
1725 None => write!(f, "DESTROY_REQUEST(id={})", self.request_id),
1726 }
1727 }
1728}
1729
1730impl fmt::Display for PvaOriginTagPayload {
1731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1732 write!(
1733 f,
1734 "ORIGIN_TAG(addr={})",
1735 format_pva_address(&self.address)
1736 )
1737 }
1738}
1739
1740impl fmt::Display for PvaUnknownPayload {
1741 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1742 let kind = if self.is_control {
1743 "CONTROL"
1744 } else {
1745 "APPLICATION"
1746 };
1747 write!(
1748 f,
1749 "UNKNOWN(cmd={}, type={}, raw_len={})",
1750 self.command, kind, self.raw_len
1751 )
1752 }
1753}
1754
1755impl fmt::Display for PvaPacketCommand {
1757 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1758 match self {
1759 PvaPacketCommand::Control(payload) => write!(f, "{}", payload),
1760 PvaPacketCommand::Search(payload) => write!(f, "{}", payload),
1761 PvaPacketCommand::SearchResponse(payload) => write!(f, "{}", payload),
1762 PvaPacketCommand::Beacon(payload) => write!(f, "{}", payload),
1763 PvaPacketCommand::ConnectionValidation(payload) => write!(f, "{}", payload),
1764 PvaPacketCommand::ConnectionValidated(payload) => write!(f, "{}", payload),
1765 PvaPacketCommand::AuthNZ(payload) => write!(f, "{}", payload),
1766 PvaPacketCommand::AclChange(payload) => write!(f, "{}", payload),
1767 PvaPacketCommand::Op(payload) => write!(f, "{}", payload),
1768 PvaPacketCommand::CreateChannel(payload) => write!(f, "{}", payload),
1769 PvaPacketCommand::DestroyChannel(payload) => write!(f, "{}", payload),
1770 PvaPacketCommand::GetField(payload) => write!(f, "{}", payload),
1771 PvaPacketCommand::Message(payload) => write!(f, "{}", payload),
1772 PvaPacketCommand::MultipleData(payload) => write!(f, "{}", payload),
1773 PvaPacketCommand::CancelRequest(payload) => write!(f, "{}", payload),
1774 PvaPacketCommand::DestroyRequest(payload) => write!(f, "{}", payload),
1775 PvaPacketCommand::OriginTag(payload) => write!(f, "{}", payload),
1776 PvaPacketCommand::Echo(bytes) => write!(f, "ECHO ({} bytes)", bytes.len()),
1777 PvaPacketCommand::Unknown(payload) => write!(f, "{}", payload),
1778 }
1779 }
1780}
1781
1782impl fmt::Display for PvaOpPayload {
1783 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1784 let cmd_name = match self.command {
1785 10 => "GET",
1786 11 => "PUT",
1787 12 => "PUT_GET",
1788 13 => "MONITOR",
1789 14 => "ARRAY",
1790 16 => "PROCESS",
1791 20 => "RPC",
1792 _ => "OP",
1793 };
1794
1795 let status_text = if let Some(s) = &self.status {
1796 match &s.message {
1797 Some(m) if !m.is_empty() => format!(" status={} msg='{}'", s.code, m),
1798 _ => format!(" status={}", s.code),
1799 }
1800 } else {
1801 String::new()
1802 };
1803
1804 let value_text = if let Some(ref decoded) = self.decoded_value {
1806 let formatted = format_compact_value(decoded);
1807 if formatted.is_empty() || formatted == "{}" {
1808 String::new()
1809 } else {
1810 format!(" [{}]", formatted)
1811 }
1812 } else if !self.pv_names.is_empty() {
1813 format!(" data=[{}]", self.pv_names.join(","))
1814 } else {
1815 String::new()
1816 };
1817
1818 if self.is_server {
1819 write!(
1820 f,
1821 "{}(ioid={}, sub=0x{:02x}{}{})",
1822 cmd_name, self.ioid, self.subcmd, status_text, value_text
1823 )
1824 } else {
1825 write!(
1826 f,
1827 "{}(sid={}, ioid={}, sub=0x{:02x}{}{})",
1828 cmd_name, self.sid_or_cid, self.ioid, self.subcmd, status_text, value_text
1829 )
1830 }
1831 }
1832}
1833
1834#[cfg(test)]
1835mod tests {
1836 use super::*;
1837 use spvirit_types::{
1838 NtPayload, NtScalar, NtScalarArray, ScalarArrayValue, ScalarValue,
1839 };
1840 use crate::spvirit_encode::encode_header;
1841 use crate::spvd_decode::extract_nt_scalar_value;
1842 use crate::spvd_encode::{
1843 encode_nt_payload_bitset_parts, encode_nt_scalar_bitset_parts, encode_size_pvd,
1844 nt_payload_desc, nt_scalar_desc,
1845 };
1846
1847 #[test]
1848 fn test_decode_status_ok() {
1849 let raw = [0xff];
1850 let (status, consumed) = decode_status(&raw, false);
1851 assert!(status.is_none());
1852 assert_eq!(consumed, 1);
1853 }
1854
1855 #[test]
1856 fn test_decode_status_message() {
1857 let raw = [1u8, 2, b'h', b'i', 2, b's', b't'];
1858 let (status, consumed) = decode_status(&raw, false);
1859 assert_eq!(consumed, 7);
1860 let status = status.unwrap();
1861 assert_eq!(status.code, 1);
1862 assert_eq!(status.message.as_deref(), Some("hi"));
1863 assert_eq!(status.stack.as_deref(), Some("st"));
1864 }
1865
1866 #[test]
1867 fn test_search_response_decode() {
1868 let mut raw: Vec<u8> = vec![];
1869 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");
1875 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();
1880 assert!(decoded.found);
1881 assert_eq!(decoded.protocol, "tcp");
1882 assert_eq!(decoded.cids, vec![42u32]);
1883 }
1884
1885 fn build_monitor_packet(ioid: u32, subcmd: u8, body: &[u8]) -> Vec<u8> {
1886 let mut payload = Vec::new();
1887 payload.extend_from_slice(&ioid.to_le_bytes());
1888 payload.push(subcmd);
1889 payload.extend_from_slice(body);
1890 let mut out = encode_header(true, false, false, 2, 13, payload.len() as u32);
1891 out.extend_from_slice(&payload);
1892 out
1893 }
1894
1895 #[test]
1896 fn test_monitor_decode_overrun_and_legacy() {
1897 let nt = NtScalar::from_value(ScalarValue::F64(3.5));
1898 let desc = nt_scalar_desc(&nt.value);
1899 let (changed_bitset, values) = encode_nt_scalar_bitset_parts(&nt, false);
1900
1901 let mut body_overrun = Vec::new();
1902 body_overrun.extend_from_slice(&changed_bitset);
1903 body_overrun.extend_from_slice(&encode_size_pvd(0, false));
1904 body_overrun.extend_from_slice(&values);
1905
1906 let pkt = build_monitor_packet(1, 0x00, &body_overrun);
1907 let mut pva = PvaPacket::new(&pkt);
1908 let mut cmd = pva.decode_payload().expect("decoded");
1909 if let PvaPacketCommand::Op(ref mut op) = cmd {
1910 op.decode_with_field_desc(&desc, false);
1911 let decoded = op.decoded_value.as_ref().expect("decoded");
1912 let value = extract_nt_scalar_value(decoded).expect("value");
1913 match value {
1914 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1915 other => panic!("unexpected value {:?}", other),
1916 }
1917 } else {
1918 panic!("unexpected cmd");
1919 }
1920
1921 let mut body_legacy = Vec::new();
1922 body_legacy.extend_from_slice(&changed_bitset);
1923 body_legacy.extend_from_slice(&values);
1924
1925 let pkt = build_monitor_packet(1, 0x00, &body_legacy);
1926 let mut pva = PvaPacket::new(&pkt);
1927 let mut cmd = pva.decode_payload().expect("decoded");
1928 if let PvaPacketCommand::Op(ref mut op) = cmd {
1929 op.decode_with_field_desc(&desc, false);
1930 let decoded = op.decoded_value.as_ref().expect("decoded");
1931 let value = extract_nt_scalar_value(decoded).expect("value");
1932 match value {
1933 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1934 other => panic!("unexpected value {:?}", other),
1935 }
1936 } else {
1937 panic!("unexpected cmd");
1938 }
1939
1940 let mut body_spec = Vec::new();
1941 body_spec.extend_from_slice(&changed_bitset);
1942 body_spec.extend_from_slice(&values);
1943 body_spec.extend_from_slice(&encode_size_pvd(0, false));
1944
1945 let pkt = build_monitor_packet(1, 0x00, &body_spec);
1946 let mut pva = PvaPacket::new(&pkt);
1947 let mut cmd = pva.decode_payload().expect("decoded");
1948 if let PvaPacketCommand::Op(ref mut op) = cmd {
1949 op.decode_with_field_desc(&desc, false);
1950 let decoded = op.decoded_value.as_ref().expect("decoded");
1951 let value = extract_nt_scalar_value(decoded).expect("value");
1952 match value {
1953 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1954 other => panic!("unexpected value {:?}", other),
1955 }
1956 } else {
1957 panic!("unexpected cmd");
1958 }
1959 }
1960
1961 #[test]
1962 fn test_monitor_decode_prefers_spec_order_for_array_payload() {
1963 let payload_value =
1964 NtPayload::ScalarArray(NtScalarArray::from_value(ScalarArrayValue::F64(vec![
1965 1.0, 2.0, 3.0, 4.0,
1966 ])));
1967 let desc = nt_payload_desc(&payload_value);
1968 let (changed_bitset, values) = encode_nt_payload_bitset_parts(&payload_value, false);
1969
1970 let mut body_spec = Vec::new();
1971 body_spec.extend_from_slice(&changed_bitset);
1972 body_spec.extend_from_slice(&values);
1973 body_spec.extend_from_slice(&encode_size_pvd(0, false));
1974
1975 let pkt = build_monitor_packet(11, 0x00, &body_spec);
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::Array(items) => {
1984 assert_eq!(items.len(), 4);
1985 assert!(matches!(items[0], DecodedValue::Float64(v) if (v - 1.0).abs() < 1e-6));
1986 assert!(matches!(items[3], DecodedValue::Float64(v) if (v - 4.0).abs() < 1e-6));
1987 }
1988 other => panic!("unexpected value {:?}", other),
1989 }
1990 } else {
1991 panic!("unexpected cmd");
1992 }
1993 }
1994
1995 #[test]
1996 fn pva_status_reports_error_state() {
1997 let ok = PvaStatus {
1998 code: 0,
1999 message: None,
2000 stack: None,
2001 };
2002 let err = PvaStatus {
2003 code: 2,
2004 message: Some("bad".to_string()),
2005 stack: None,
2006 };
2007 assert!(!ok.is_error());
2008 assert!(err.is_error());
2009 }
2010
2011 #[test]
2012 fn pva_status_display_includes_message_and_stack() {
2013 let status = PvaStatus {
2014 code: 2,
2015 message: Some("bad".to_string()),
2016 stack: Some("trace".to_string()),
2017 };
2018 assert_eq!(status.to_string(), "code=2 message=bad stack=trace");
2019 }
2020
2021 #[test]
2022 fn decode_op_response_status_reads_status_from_packet() {
2023 let raw = vec![
2024 0xCA, 0x02, 0x40, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x02,
2025 0x03, b'b', b'a', b'd', 0x00,
2026 ];
2027 let status = decode_op_response_status(&raw, false)
2028 .expect("status parse")
2029 .expect("status");
2030 assert!(status.is_error());
2031 assert_eq!(status.message.as_deref(), Some("bad"));
2032 }
2033}