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