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