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