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