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 pub user: Option<String>,
494 pub host: Option<String>,
495}
496
497impl PvaConnectionValidationPayload {
498 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
499 if raw.len() < 6 {
500 debug!(
501 "PvaConnectionValidationPayload::new: raw too short {}",
502 raw.len()
503 );
504 return None;
505 }
506 let buffer_size = if is_be {
507 u32::from_be_bytes(raw[0..4].try_into().ok()?)
508 } else {
509 u32::from_le_bytes(raw[0..4].try_into().ok()?)
510 };
511 let introspection_registry_size = if is_be {
512 u16::from_be_bytes(raw[4..6].try_into().ok()?)
513 } else {
514 u16::from_le_bytes(raw[4..6].try_into().ok()?)
515 };
516
517 if is_server {
518 let mut offset = 6;
521 let authz = if offset < raw.len() {
522 if let Some((count, consumed)) = decode_size(&raw[offset..], is_be) {
523 offset += consumed;
524 let mut first_method = None;
525 for _ in 0..count {
526 if let Some((s, c)) = decode_string(&raw[offset..], is_be) {
527 if first_method.is_none() && !s.is_empty() {
528 first_method = Some(s);
529 }
530 offset += c;
531 }
532 }
533 first_method
534 } else {
535 decode_string(&raw[offset..], is_be).map(|(s, _)| s)
537 }
538 } else {
539 None
540 };
541
542 Some(Self {
543 is_server,
544 buffer_size,
545 introspection_registry_size,
546 qos: 0,
547 authz,
548 user: None,
549 host: None,
550 })
551 } else {
552 if raw.len() < 8 {
554 return None;
555 }
556 let qos = if is_be {
557 u16::from_be_bytes(raw[6..8].try_into().ok()?)
558 } else {
559 u16::from_le_bytes(raw[6..8].try_into().ok()?)
560 };
561 let (authz, user, host) = if raw.len() > 8 {
562 if let Some((s, consumed)) = decode_string(&raw[8..], is_be) {
563 let off = 8 + consumed;
564 let (user, host) = if off < raw.len() {
565 decode_ca_credentials(&raw[off..], is_be)
566 } else {
567 (None, None)
568 };
569 (Some(s), user, host)
570 } else {
571 (None, None, None)
572 }
573 } else {
574 (None, None, None)
575 };
576
577 Some(Self {
578 is_server,
579 buffer_size,
580 introspection_registry_size,
581 qos,
582 authz,
583 user,
584 host,
585 })
586 }
587 }
588}
589
590fn decode_ca_credentials(bytes: &[u8], is_be: bool) -> (Option<String>, Option<String>) {
596 let dec = PvdDecoder::new(is_be);
597 let (desc, consumed) = match dec.parse_introspection_with_len(bytes) {
599 Ok(v) => v,
600 Err(_) => return (None, None),
601 };
602 let value_bytes = &bytes[consumed..];
603 match dec.decode_structure(value_bytes, &desc) {
604 Ok((decoded, _)) => (
605 decoded_string_field(&decoded, "user"),
606 decoded_string_field(&decoded, "host"),
607 ),
608 Err(_) => (None, None),
609 }
610}
611
612fn decoded_string_field(decoded: &DecodedValue, name: &str) -> Option<String> {
615 if let DecodedValue::Structure(fields) = decoded {
616 for (field_name, value) in fields {
617 if field_name == name
618 && let DecodedValue::String(s) = value
619 {
620 return Some(s.clone());
621 }
622 }
623 }
624 None
625}
626
627#[derive(Debug)]
628pub struct PvaConnectionValidatedPayload {
629 pub status: Option<PvaStatus>,
630}
631
632impl PvaConnectionValidatedPayload {
633 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
634 let (status, _consumed) = decode_status(raw, is_be);
635 Some(Self { status })
636 }
637}
638
639#[derive(Debug)]
640pub struct PvaAuthNzPayload {
641 pub raw: Vec<u8>,
642 pub strings: Vec<String>,
643}
644
645impl PvaAuthNzPayload {
646 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
647 let mut strings = vec![];
648 if let Some((count, consumed)) = decode_size(raw, is_be) {
649 let mut offset = consumed;
650 for _ in 0..count {
651 if let Some((s, len)) = decode_string(&raw[offset..], is_be) {
652 strings.push(s);
653 offset += len;
654 } else {
655 break;
656 }
657 }
658 }
659 Some(Self {
660 raw: raw.to_vec(),
661 strings,
662 })
663 }
664}
665
666#[derive(Debug)]
667pub struct PvaAclChangePayload {
668 pub status: Option<PvaStatus>,
669 pub raw: Vec<u8>,
670}
671
672impl PvaAclChangePayload {
673 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
674 let (status, consumed) = decode_status(raw, is_be);
675 let raw_rem = if raw.len() > consumed {
676 raw[consumed..].to_vec()
677 } else {
678 vec![]
679 };
680 Some(Self {
681 status,
682 raw: raw_rem,
683 })
684 }
685}
686
687#[derive(Debug)]
688pub struct PvaGetFieldPayload {
689 pub is_server: bool,
690 pub cid: u32,
691 pub sid: Option<u32>,
692 pub ioid: Option<u32>,
693 pub field_name: Option<String>,
694 pub status: Option<PvaStatus>,
695 pub introspection: Option<StructureDesc>,
696 pub raw: Vec<u8>,
697}
698
699impl PvaGetFieldPayload {
700 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
701 if !is_server {
702 if raw.len() < 4 {
703 debug!(
704 "PvaGetFieldPayload::new (client): raw too short {}",
705 raw.len()
706 );
707 return None;
708 }
709 let cid = if is_be {
710 u32::from_be_bytes(raw[0..4].try_into().ok()?)
711 } else {
712 u32::from_le_bytes(raw[0..4].try_into().ok()?)
713 };
714
715 let legacy_field = if raw.len() > 4 {
719 decode_string(&raw[4..], is_be)
720 .and_then(|(s, consumed)| (4 + consumed == raw.len()).then_some(s))
721 } else {
722 None
723 };
724
725 let epics_variant = if raw.len() >= 9 {
726 let ioid = if is_be {
727 u32::from_be_bytes(raw[4..8].try_into().ok()?)
728 } else {
729 u32::from_le_bytes(raw[4..8].try_into().ok()?)
730 };
731 decode_string(&raw[8..], is_be)
732 .and_then(|(s, consumed)| (8 + consumed == raw.len()).then_some((ioid, s)))
733 } else {
734 None
735 };
736
737 let (sid, ioid, field_name) = if let Some((ioid, field)) = epics_variant {
738 (Some(cid), Some(ioid), Some(field))
739 } else {
740 (None, None, legacy_field)
741 };
742
743 return Some(Self {
744 is_server,
745 cid,
746 sid,
747 ioid,
748 field_name,
749 status: None,
750 introspection: None,
751 raw: vec![],
752 });
753 }
754
755 let parse_status_then_intro = |bytes: &[u8]| {
756 let (status, consumed) = decode_status(bytes, is_be);
757 let pvd_raw = if bytes.len() > consumed {
758 bytes[consumed..].to_vec()
759 } else {
760 vec![]
761 };
762 let introspection = if !pvd_raw.is_empty() {
763 let decoder = PvdDecoder::new(is_be);
764 decoder.parse_introspection(&pvd_raw).ok()
765 } else {
766 None
767 };
768 (status, pvd_raw, introspection)
769 };
770
771 let (cid, status, pvd_raw, introspection) = if raw.len() >= 4 {
775 let parsed_cid = if is_be {
776 u32::from_be_bytes(raw[0..4].try_into().ok()?)
777 } else {
778 u32::from_le_bytes(raw[0..4].try_into().ok()?)
779 };
780 let (status, pvd_raw, introspection) = parse_status_then_intro(&raw[4..]);
781 (parsed_cid, status, pvd_raw, introspection)
782 } else {
783 let (status, pvd_raw, introspection) = parse_status_then_intro(raw);
784 (0, status, pvd_raw, introspection)
785 };
786
787 Some(Self {
788 is_server,
789 cid,
790 sid: None,
791 ioid: None,
792 field_name: None,
793 status,
794 introspection,
795 raw: pvd_raw,
796 })
797 }
798}
799
800#[derive(Debug)]
801pub struct PvaMessagePayload {
802 pub ioid: u32,
803 pub message_type: u8,
804 pub message: Option<String>,
805 pub status: Option<PvaStatus>,
807 pub raw: Vec<u8>,
808}
809
810impl PvaMessagePayload {
811 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
812 if raw.len() >= 5 {
814 let ioid = if is_be {
815 u32::from_be_bytes(raw[0..4].try_into().ok()?)
816 } else {
817 u32::from_le_bytes(raw[0..4].try_into().ok()?)
818 };
819 let message_type = raw[4];
820 let message = if raw.len() > 5 {
821 decode_string(&raw[5..], is_be).map(|(s, _)| s)
822 } else {
823 None
824 };
825 let code = match message_type {
827 0 => 0xFF, 1 => 0x01, 2 => 0x02, _ => 0x03, };
832 let status = Some(PvaStatus {
833 code,
834 message: message.clone(),
835 stack: None,
836 });
837 Some(Self {
838 ioid,
839 message_type,
840 message,
841 status,
842 raw: raw.to_vec(),
843 })
844 } else {
845 Some(Self {
847 ioid: 0,
848 message_type: 0,
849 message: None,
850 status: None,
851 raw: raw.to_vec(),
852 })
853 }
854 }
855}
856
857#[derive(Debug)]
858pub struct PvaMultipleDataEntry {
859 pub ioid: u32,
860 pub subcmd: u8,
861}
862
863#[derive(Debug)]
864pub struct PvaMultipleDataPayload {
865 pub entries: Vec<PvaMultipleDataEntry>,
866 pub raw: Vec<u8>,
867}
868
869impl PvaMultipleDataPayload {
870 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
871 let mut entries: Vec<PvaMultipleDataEntry> = vec![];
872 if let Some((count, consumed)) = decode_size(raw, is_be) {
873 let mut offset = consumed;
874 for _ in 0..count {
875 if raw.len() < offset + 5 {
876 break;
877 }
878 let ioid = if is_be {
879 u32::from_be_bytes(raw[offset..offset + 4].try_into().ok()?)
880 } else {
881 u32::from_le_bytes(raw[offset..offset + 4].try_into().ok()?)
882 };
883 let subcmd = raw[offset + 4];
884 entries.push(PvaMultipleDataEntry { ioid, subcmd });
885 offset += 5;
886 }
887 }
888 Some(Self {
889 entries,
890 raw: raw.to_vec(),
891 })
892 }
893}
894
895#[derive(Debug)]
896pub struct PvaCancelRequestPayload {
897 pub request_id: u32,
898 pub status: Option<PvaStatus>,
899}
900
901impl PvaCancelRequestPayload {
902 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
903 if raw.len() < 4 {
904 debug!("PvaCancelRequestPayload::new: raw too short {}", raw.len());
905 return None;
906 }
907 let request_id = if is_be {
908 u32::from_be_bytes(raw[0..4].try_into().ok()?)
909 } else {
910 u32::from_le_bytes(raw[0..4].try_into().ok()?)
911 };
912 let (status, _) = if raw.len() > 4 {
913 decode_status(&raw[4..], is_be)
914 } else {
915 (None, 0)
916 };
917 Some(Self { request_id, status })
918 }
919}
920
921#[derive(Debug)]
922pub struct PvaDestroyRequestPayload {
923 pub sid: u32,
924 pub request_id: u32,
925}
926
927impl PvaDestroyRequestPayload {
928 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
934 let word = |range: std::ops::Range<usize>| -> Option<u32> {
935 let bytes = raw.get(range)?.try_into().ok()?;
936 Some(if is_be {
937 u32::from_be_bytes(bytes)
938 } else {
939 u32::from_le_bytes(bytes)
940 })
941 };
942 if raw.len() >= 8 {
943 Some(Self {
944 sid: word(0..4)?,
945 request_id: word(4..8)?,
946 })
947 } else if raw.len() >= 4 {
948 Some(Self {
949 sid: 0,
950 request_id: word(0..4)?,
951 })
952 } else {
953 debug!("PvaDestroyRequestPayload::new: raw too short {}", raw.len());
954 None
955 }
956 }
957}
958
959#[derive(Debug)]
960pub struct PvaOriginTagPayload {
961 pub address: [u8; 16],
962}
963
964impl PvaOriginTagPayload {
965 pub fn new(raw: &[u8]) -> Option<Self> {
966 if raw.len() < 16 {
967 debug!("PvaOriginTagPayload::new: raw too short {}", raw.len());
968 return None;
969 }
970 let address: [u8; 16] = raw[0..16].try_into().ok()?;
971 Some(Self { address })
972 }
973}
974
975#[derive(Debug)]
976pub struct PvaUnknownPayload {
977 pub command: u8,
978 pub is_control: bool,
979 pub raw_len: usize,
980}
981
982impl PvaUnknownPayload {
983 pub fn new(command: u8, is_control: bool, raw_len: usize) -> Self {
984 Self {
985 command,
986 is_control,
987 raw_len,
988 }
989 }
990}
991
992#[derive(Debug)]
995pub struct PvaSearchPayload {
996 pub seq: u32,
997 pub mask: u8,
998 pub addr: [u8; 16],
999 pub port: u16,
1000 pub protocols: Vec<String>,
1001 pub pv_requests: Vec<(u32, String)>,
1002 pub pv_names: Vec<String>,
1003}
1004
1005impl PvaSearchPayload {
1006 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
1007 if raw.is_empty() {
1008 debug!("PvaSearchPayload::new received an empty raw slice.");
1009 return None;
1010 }
1011 const MIN_FIXED_SEARCH_PAYLOAD_SIZE: usize = 26;
1012 if raw.len() < MIN_FIXED_SEARCH_PAYLOAD_SIZE {
1013 debug!(
1014 "PvaSearchPayload::new: raw slice length {} is less than min fixed size {}.",
1015 raw.len(),
1016 MIN_FIXED_SEARCH_PAYLOAD_SIZE
1017 );
1018 return None;
1019 }
1020
1021 let seq = if is_be {
1022 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1023 } else {
1024 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1025 };
1026
1027 let mask = raw[4];
1028 let addr: [u8; 16] = raw[8..24].try_into().unwrap();
1029 let port = if is_be {
1030 u16::from_be_bytes(raw[24..26].try_into().unwrap())
1031 } else {
1032 u16::from_le_bytes(raw[24..26].try_into().unwrap())
1033 };
1034
1035 let mut offset = 26;
1036
1037 let (protocol_count, consumed) = decode_size(&raw[offset..], is_be)?;
1038 offset += consumed;
1039
1040 let mut protocols = vec![];
1041 for _ in 0..protocol_count {
1042 let (protocol, len) = decode_string(&raw[offset..], is_be)?;
1043 protocols.push(protocol);
1044 offset += len;
1045 }
1046
1047 if raw.len() < offset + 2 {
1049 return None;
1050 }
1051 let pv_count = if is_be {
1052 u16::from_be_bytes(raw[offset..offset + 2].try_into().unwrap())
1053 } else {
1054 u16::from_le_bytes(raw[offset..offset + 2].try_into().unwrap())
1055 };
1056 offset += 2;
1057
1058 let mut pv_names = vec![];
1059 let mut pv_requests = vec![];
1060 for _ in 0..pv_count {
1061 if raw.len() < offset + 4 {
1062 debug!(
1063 "PvaSearchPayload::new: not enough data for PV CID at offset {}. Raw len: {}",
1064 offset,
1065 raw.len()
1066 );
1067 return None;
1068 }
1069 let cid = if is_be {
1070 u32::from_be_bytes(raw[offset..offset + 4].try_into().unwrap())
1071 } else {
1072 u32::from_le_bytes(raw[offset..offset + 4].try_into().unwrap())
1073 };
1074 offset += 4;
1075 let (pv_name, len) = decode_string(&raw[offset..], is_be)?;
1076 pv_names.push(pv_name.clone());
1077 pv_requests.push((cid, pv_name));
1078 offset += len;
1079 }
1080
1081 Some(Self {
1082 seq,
1083 mask,
1084 addr,
1085 port,
1086 protocols,
1087 pv_requests,
1088 pv_names,
1089 })
1090 }
1091}
1092
1093#[derive(Debug)]
1095pub struct PvaBeaconPayload {
1096 pub guid: [u8; 12],
1097 pub flags: u8,
1098 pub beacon_sequence_id: u8,
1099 pub change_count: u16,
1100 pub server_address: [u8; 16],
1101 pub server_port: u16,
1102 pub protocol: String,
1103 pub server_status_if: String,
1104}
1105
1106impl PvaBeaconPayload {
1107 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
1108 const MIN_FIXED_BEACON_PAYLOAD_SIZE: usize = 12 + 1 + 1 + 2 + 16 + 2;
1110
1111 if raw.len() < MIN_FIXED_BEACON_PAYLOAD_SIZE {
1112 debug!(
1113 "PvaBeaconPayload::new: raw slice length {} is less than min fixed size {}.",
1114 raw.len(),
1115 MIN_FIXED_BEACON_PAYLOAD_SIZE
1116 );
1117 return None;
1118 }
1119
1120 let guid: [u8; 12] = raw[0..12].try_into().unwrap();
1121 let flags = raw[12];
1122 let beacon_sequence_id = raw[13];
1123 let change_count = if is_be {
1124 u16::from_be_bytes(raw[14..16].try_into().unwrap())
1125 } else {
1126 u16::from_le_bytes(raw[14..16].try_into().unwrap())
1127 };
1128 let server_address: [u8; 16] = raw[16..32].try_into().unwrap();
1129 let server_port = if is_be {
1130 u16::from_be_bytes(raw[32..34].try_into().unwrap())
1131 } else {
1132 u16::from_le_bytes(raw[32..34].try_into().unwrap())
1133 };
1134 let (protocol, len) = decode_string(&raw[34..], is_be)?;
1135 let protocol = protocol;
1136 let server_status_if = if len > 0 {
1137 let (server_status_if, _server_status_len) = decode_string(&raw[34 + len..], is_be)?;
1138 server_status_if
1139 } else {
1140 String::new()
1141 };
1142
1143 Some(Self {
1144 guid,
1145 flags,
1146 beacon_sequence_id,
1147 change_count,
1148 server_address,
1149 server_port,
1150 protocol,
1151 server_status_if,
1152 })
1153 }
1154}
1155
1156#[derive(Debug)]
1160pub struct PvaCreateChannelPayload {
1161 pub is_server: bool,
1163 pub channels: Vec<(u32, String)>,
1165 pub cid: u32,
1167 pub sid: u32,
1169 pub status: Option<PvaStatus>,
1171}
1172
1173impl PvaCreateChannelPayload {
1174 pub fn new(raw: &[u8], is_be: bool, is_server: bool) -> Option<Self> {
1175 if raw.is_empty() {
1176 debug!("PvaCreateChannelPayload::new received an empty raw slice.");
1177 return None;
1178 }
1179
1180 if is_server {
1181 if raw.len() < 8 {
1183 debug!("CREATE_CHANNEL server response too short: {}", raw.len());
1184 return None;
1185 }
1186
1187 let cid = if is_be {
1188 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1189 } else {
1190 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1191 };
1192
1193 let sid = if is_be {
1194 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1195 } else {
1196 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1197 };
1198
1199 let status = if raw.len() > 8 {
1201 let code = raw[8];
1202 if code == 0xff {
1203 None } else {
1205 let mut idx = 9;
1206 let message = if idx < raw.len() {
1207 decode_string(&raw[idx..], is_be).map(|(msg, consumed)| {
1208 idx += consumed;
1209 msg
1210 })
1211 } else {
1212 None
1213 };
1214 let stack = if idx < raw.len() {
1215 decode_string(&raw[idx..], is_be).map(|(s, _)| s)
1216 } else {
1217 None
1218 };
1219 Some(PvaStatus {
1220 code,
1221 message,
1222 stack,
1223 })
1224 }
1225 } else {
1226 None
1227 };
1228
1229 Some(Self {
1230 is_server: true,
1231 channels: vec![],
1232 cid,
1233 sid,
1234 status,
1235 })
1236 } else {
1237 if raw.len() < 2 {
1239 debug!("CREATE_CHANNEL client request too short: {}", raw.len());
1240 return None;
1241 }
1242
1243 let count = if is_be {
1244 u16::from_be_bytes(raw[0..2].try_into().unwrap())
1245 } else {
1246 u16::from_le_bytes(raw[0..2].try_into().unwrap())
1247 };
1248
1249 let mut offset = 2;
1250 let mut channels = Vec::with_capacity(count as usize);
1251
1252 for _ in 0..count {
1253 if raw.len() < offset + 4 {
1254 debug!(
1255 "CREATE_CHANNEL: not enough data for CID at offset {}",
1256 offset
1257 );
1258 break;
1259 }
1260
1261 let cid = if is_be {
1262 u32::from_be_bytes(raw[offset..offset + 4].try_into().unwrap())
1263 } else {
1264 u32::from_le_bytes(raw[offset..offset + 4].try_into().unwrap())
1265 };
1266 offset += 4;
1267
1268 if let Some((pv_name, consumed)) = decode_string(&raw[offset..], is_be) {
1269 offset += consumed;
1270 channels.push((cid, pv_name));
1271 } else {
1272 debug!(
1273 "CREATE_CHANNEL: failed to decode PV name at offset {}",
1274 offset
1275 );
1276 break;
1277 }
1278 }
1279
1280 Some(Self {
1281 is_server: false,
1282 channels,
1283 cid: 0,
1284 sid: 0,
1285 status: None,
1286 })
1287 }
1288 }
1289}
1290
1291impl fmt::Display for PvaCreateChannelPayload {
1292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1293 if self.is_server {
1294 let status_text = if let Some(s) = &self.status {
1295 format!(" status={}", s.code)
1296 } else {
1297 String::new()
1298 };
1299 write!(
1300 f,
1301 "CREATE_CHANNEL(cid={}, sid={}{})",
1302 self.cid, self.sid, status_text
1303 )
1304 } else {
1305 let pv_list: Vec<String> = self
1306 .channels
1307 .iter()
1308 .map(|(cid, name)| format!("{}:'{}'", cid, name))
1309 .collect();
1310 write!(f, "CREATE_CHANNEL({})", pv_list.join(", "))
1311 }
1312 }
1313}
1314
1315#[derive(Debug)]
1318pub struct PvaDestroyChannelPayload {
1319 pub sid: u32,
1321 pub cid: u32,
1323}
1324
1325impl PvaDestroyChannelPayload {
1326 pub fn new(raw: &[u8], is_be: bool) -> Option<Self> {
1327 if raw.len() < 8 {
1328 debug!("DESTROY_CHANNEL payload too short: {}", raw.len());
1329 return None;
1330 }
1331
1332 let sid = if is_be {
1333 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1334 } else {
1335 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1336 };
1337
1338 let cid = if is_be {
1339 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1340 } else {
1341 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1342 };
1343
1344 Some(Self { sid, cid })
1345 }
1346}
1347
1348impl fmt::Display for PvaDestroyChannelPayload {
1349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1350 write!(f, "DESTROY_CHANNEL(sid={}, cid={})", self.sid, self.cid)
1351 }
1352}
1353
1354#[derive(Debug)]
1356pub struct PvaOpPayload {
1357 pub sid_or_cid: u32,
1358 pub ioid: u32,
1359 pub subcmd: u8,
1360 pub body: Vec<u8>,
1361 pub command: u8,
1362 pub is_server: bool,
1363 pub status: Option<PvaStatus>,
1364 pub pv_names: Vec<String>,
1365 pub introspection: Option<StructureDesc>,
1367 pub decoded_value: Option<DecodedValue>,
1369}
1370
1371fn extract_pv_names(raw: &[u8]) -> Vec<String> {
1373 let mut names: Vec<String> = Vec::new();
1374 let mut i = 0usize;
1375 while i < raw.len() {
1376 if raw[i].is_ascii_alphanumeric() {
1378 let start = i;
1379 i += 1;
1380 while i < raw.len() {
1381 let b = raw[i];
1382 if b.is_ascii_alphanumeric()
1383 || b == b':'
1384 || b == b'.'
1385 || b == b'_'
1386 || b == b'-'
1387 || b == b'/'
1388 {
1389 i += 1;
1390 } else {
1391 break;
1392 }
1393 }
1394 let len = i - start;
1395 if len >= 3 && len <= 128 {
1396 if let Ok(s) = std::str::from_utf8(&raw[start..start + len]) {
1397 if s.chars().any(|c| c.is_ascii_alphabetic()) {
1399 if !names.contains(&s.to_string()) {
1400 names.push(s.to_string());
1401 if names.len() >= 8 {
1402 break;
1403 }
1404 }
1405 }
1406 }
1407 }
1408 } else {
1409 i += 1;
1410 }
1411 }
1412 names
1413}
1414
1415impl PvaOpPayload {
1416 pub fn new(raw: &[u8], is_be: bool, is_server: bool, command: u8) -> Option<Self> {
1417 if raw.len() < 5 {
1419 debug!("PvaOpPayload::new: raw too short {}", raw.len());
1420 return None;
1421 }
1422
1423 let (sid_or_cid, ioid, subcmd, offset) = if is_server {
1424 if raw.len() < 5 {
1426 return None;
1427 }
1428 let ioid = if is_be {
1429 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1430 } else {
1431 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1432 };
1433 let subcmd = raw[4];
1434 (0, ioid, subcmd, 5)
1435 } else {
1436 if raw.len() < 9 {
1438 return None;
1439 }
1440 let sid = if is_be {
1441 u32::from_be_bytes(raw[0..4].try_into().unwrap())
1442 } else {
1443 u32::from_le_bytes(raw[0..4].try_into().unwrap())
1444 };
1445 let ioid = if is_be {
1446 u32::from_be_bytes(raw[4..8].try_into().unwrap())
1447 } else {
1448 u32::from_le_bytes(raw[4..8].try_into().unwrap())
1449 };
1450 let subcmd = raw[8];
1451 (sid, ioid, subcmd, 9)
1452 };
1453
1454 let body = if raw.len() > offset {
1455 raw[offset..].to_vec()
1456 } else {
1457 vec![]
1458 };
1459
1460 let mut status: Option<PvaStatus> = None;
1468 let mut pvd_raw: Vec<u8> = vec![];
1469
1470 let has_status = is_server && ((subcmd & 0x08) != 0 || (command != 13 && command != 14));
1471
1472 if !body.is_empty() {
1473 if has_status {
1474 let (parsed, consumed) = decode_status(&body, is_be);
1475 status = parsed;
1476 pvd_raw = if body.len() > consumed {
1477 body[consumed..].to_vec()
1478 } else {
1479 vec![]
1480 };
1481 } else {
1482 pvd_raw = body.clone();
1483 }
1484 }
1485
1486 let pv_names = extract_pv_names(&pvd_raw);
1487
1488 let introspection = if is_server && (subcmd & 0x08) != 0 && !pvd_raw.is_empty() {
1490 let decoder = PvdDecoder::new(is_be);
1491 decoder.parse_introspection(&pvd_raw).ok()
1492 } else {
1493 None
1494 };
1495
1496 let result = Some(Self {
1497 sid_or_cid,
1498 ioid,
1499 subcmd,
1500 body: pvd_raw,
1501 command,
1502 is_server,
1503 status: status.clone(),
1504 pv_names,
1505 introspection,
1506 decoded_value: None, });
1508
1509 result
1510 }
1511
1512 pub fn decode_with_field_desc(
1518 &mut self,
1519 field_desc: &StructureDesc,
1520 is_be: bool,
1521 mode: DecodeMode,
1522 ) -> DecodeResult<()> {
1523 if self.body.is_empty() {
1524 return Ok(());
1525 }
1526
1527 let decoder = PvdDecoder::new(is_be);
1528
1529 if self.subcmd == 0x00 || (self.subcmd & 0x40) != 0 {
1531 if self.command == 13 {
1532 let update = match mode {
1533 DecodeMode::Strict => decoder.decode_monitor_update(&self.body, field_desc)?,
1534 DecodeMode::Lenient => {
1535 decoder
1536 .decode_monitor_update_lenient(&self.body, field_desc)?
1537 .0
1538 }
1539 };
1540 self.decoded_value = Some(update.value);
1541 } else {
1542 let (value, _) = decoder.decode_structure_with_bitset(&self.body, field_desc)?;
1543 self.decoded_value = Some(value);
1544 }
1545 } else {
1546 let (value, _) = decoder.decode_structure(&self.body, field_desc)?;
1548 self.decoded_value = Some(value);
1549 }
1550 Ok(())
1551 }
1552}
1553
1554#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1556pub enum DecodeMode {
1557 Strict,
1560 Lenient,
1567}
1568
1569#[derive(Debug, Clone)]
1570pub struct PvaStatus {
1571 pub code: u8,
1572 pub message: Option<String>,
1573 pub stack: Option<String>,
1574}
1575
1576impl PvaStatus {
1577 pub fn is_error(&self) -> bool {
1578 self.code != 0
1579 }
1580}
1581
1582impl fmt::Display for PvaBeaconPayload {
1585 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1586 write!(
1587 f,
1588 "Beacon:GUID=[{}],Flags=[{}],SeqId=[{}],ChangeCount=[{}],ServerAddress=[{}],ServerPort=[{}],Protocol=[{}]",
1589 hex::encode(self.guid),
1590 self.flags,
1591 self.beacon_sequence_id,
1592 self.change_count,
1593 format_pva_address(&self.server_address),
1594 self.server_port,
1595 self.protocol
1596 )
1597 }
1598}
1599
1600impl fmt::Display for PvaSearchPayload {
1602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1603 write!(f, "Search:PVs=[{}]", self.pv_names.join(","))
1604 }
1605}
1606
1607impl fmt::Display for PvaControlPayload {
1608 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1609 let name = match self.command {
1610 0 => "MARK_TOTAL_BYTES_SENT",
1611 1 => "ACK_TOTAL_BYTES_RECEIVED",
1612 2 => "SET_BYTE_ORDER",
1613 3 => "ECHO_REQUEST",
1614 4 => "ECHO_RESPONSE",
1615 _ => "CONTROL",
1616 };
1617 write!(f, "{}(data={})", name, self.data)
1618 }
1619}
1620
1621impl fmt::Display for PvaSearchResponsePayload {
1622 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1623 let found_text = if self.found { "true" } else { "false" };
1624 if self.cids.is_empty() {
1625 write!(
1626 f,
1627 "SearchResponse(found={}, proto={})",
1628 found_text, self.protocol
1629 )
1630 } else {
1631 write!(
1632 f,
1633 "SearchResponse(found={}, proto={}, cids=[{}])",
1634 found_text,
1635 self.protocol,
1636 self.cids
1637 .iter()
1638 .map(|c| c.to_string())
1639 .collect::<Vec<String>>()
1640 .join(",")
1641 )
1642 }
1643 }
1644}
1645
1646impl fmt::Display for PvaConnectionValidationPayload {
1647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1648 let dir = if self.is_server { "server" } else { "client" };
1649 let authz = self.authz.as_deref().unwrap_or("");
1650 if authz.is_empty() {
1651 write!(
1652 f,
1653 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x})",
1654 dir, self.buffer_size, self.introspection_registry_size, self.qos
1655 )
1656 } else {
1657 write!(
1658 f,
1659 "ConnectionValidation(dir={}, qsize={}, isize={}, qos=0x{:04x}, authz={})",
1660 dir, self.buffer_size, self.introspection_registry_size, self.qos, authz
1661 )
1662 }
1663 }
1664}
1665
1666impl fmt::Display for PvaStatus {
1667 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1668 write!(
1669 f,
1670 "code={} message={} stack={}",
1671 self.code,
1672 self.message.as_deref().unwrap_or(""),
1673 self.stack.as_deref().unwrap_or("")
1674 )
1675 }
1676}
1677
1678impl fmt::Display for PvaConnectionValidatedPayload {
1679 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1680 match &self.status {
1681 Some(s) => write!(f, "ConnectionValidated(status={})", s.code),
1682 None => write!(f, "ConnectionValidated(status=OK)"),
1683 }
1684 }
1685}
1686
1687impl fmt::Display for PvaAuthNzPayload {
1688 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1689 if !self.strings.is_empty() {
1690 write!(f, "AuthNZ(strings=[{}])", self.strings.join(","))
1691 } else {
1692 write!(f, "AuthNZ(raw_len={})", self.raw.len())
1693 }
1694 }
1695}
1696
1697impl fmt::Display for PvaAclChangePayload {
1698 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1699 match &self.status {
1700 Some(s) => write!(f, "ACL_CHANGE(status={})", s.code),
1701 None => write!(f, "ACL_CHANGE(status=OK)"),
1702 }
1703 }
1704}
1705
1706impl fmt::Display for PvaGetFieldPayload {
1707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1708 if self.is_server {
1709 let status = self.status.as_ref().map(|s| s.code).unwrap_or(0xff);
1710 write!(f, "GET_FIELD(status={})", status)
1711 } else {
1712 let field = self.field_name.as_deref().unwrap_or("");
1713 if field.is_empty() {
1714 write!(f, "GET_FIELD(cid={})", self.cid)
1715 } else {
1716 write!(f, "GET_FIELD(cid={}, field={})", self.cid, field)
1717 }
1718 }
1719 }
1720}
1721
1722impl fmt::Display for PvaMessagePayload {
1723 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1724 match &self.status {
1725 Some(s) => {
1726 if let Some(msg) = &s.message {
1727 write!(f, "MESSAGE(status={}, msg='{}')", s.code, msg)
1728 } else {
1729 write!(f, "MESSAGE(status={})", s.code)
1730 }
1731 }
1732 None => write!(f, "MESSAGE(status=OK)"),
1733 }
1734 }
1735}
1736
1737impl fmt::Display for PvaMultipleDataPayload {
1738 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1739 if self.entries.is_empty() {
1740 write!(f, "MULTIPLE_DATA(raw_len={})", self.raw.len())
1741 } else {
1742 write!(f, "MULTIPLE_DATA(entries={})", self.entries.len())
1743 }
1744 }
1745}
1746
1747impl fmt::Display for PvaCancelRequestPayload {
1748 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1749 let status = self.status.as_ref().map(|s| s.code);
1750 match status {
1751 Some(code) => write!(f, "CANCEL_REQUEST(id={}, status={})", self.request_id, code),
1752 None => write!(f, "CANCEL_REQUEST(id={})", self.request_id),
1753 }
1754 }
1755}
1756
1757impl fmt::Display for PvaDestroyRequestPayload {
1758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1759 write!(
1760 f,
1761 "DESTROY_REQUEST(sid={}, id={})",
1762 self.sid, self.request_id
1763 )
1764 }
1765}
1766
1767impl fmt::Display for PvaOriginTagPayload {
1768 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1769 write!(f, "ORIGIN_TAG(addr={})", format_pva_address(&self.address))
1770 }
1771}
1772
1773impl fmt::Display for PvaUnknownPayload {
1774 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1775 let kind = if self.is_control {
1776 "CONTROL"
1777 } else {
1778 "APPLICATION"
1779 };
1780 write!(
1781 f,
1782 "UNKNOWN(cmd={}, type={}, raw_len={})",
1783 self.command, kind, self.raw_len
1784 )
1785 }
1786}
1787
1788impl fmt::Display for PvaPacketCommand {
1790 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1791 match self {
1792 PvaPacketCommand::Control(payload) => write!(f, "{}", payload),
1793 PvaPacketCommand::Search(payload) => write!(f, "{}", payload),
1794 PvaPacketCommand::SearchResponse(payload) => write!(f, "{}", payload),
1795 PvaPacketCommand::Beacon(payload) => write!(f, "{}", payload),
1796 PvaPacketCommand::ConnectionValidation(payload) => write!(f, "{}", payload),
1797 PvaPacketCommand::ConnectionValidated(payload) => write!(f, "{}", payload),
1798 PvaPacketCommand::AuthNZ(payload) => write!(f, "{}", payload),
1799 PvaPacketCommand::AclChange(payload) => write!(f, "{}", payload),
1800 PvaPacketCommand::Op(payload) => write!(f, "{}", payload),
1801 PvaPacketCommand::CreateChannel(payload) => write!(f, "{}", payload),
1802 PvaPacketCommand::DestroyChannel(payload) => write!(f, "{}", payload),
1803 PvaPacketCommand::GetField(payload) => write!(f, "{}", payload),
1804 PvaPacketCommand::Message(payload) => write!(f, "{}", payload),
1805 PvaPacketCommand::MultipleData(payload) => write!(f, "{}", payload),
1806 PvaPacketCommand::CancelRequest(payload) => write!(f, "{}", payload),
1807 PvaPacketCommand::DestroyRequest(payload) => write!(f, "{}", payload),
1808 PvaPacketCommand::OriginTag(payload) => write!(f, "{}", payload),
1809 PvaPacketCommand::Echo(bytes) => write!(f, "ECHO ({} bytes)", bytes.len()),
1810 PvaPacketCommand::Unknown(payload) => write!(f, "{}", payload),
1811 }
1812 }
1813}
1814
1815impl fmt::Display for PvaOpPayload {
1816 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1817 let cmd_name = match self.command {
1818 10 => "GET",
1819 11 => "PUT",
1820 12 => "PUT_GET",
1821 13 => "MONITOR",
1822 14 => "ARRAY",
1823 16 => "PROCESS",
1824 20 => "RPC",
1825 _ => "OP",
1826 };
1827
1828 let status_text = if let Some(s) = &self.status {
1829 match &s.message {
1830 Some(m) if !m.is_empty() => format!(" status={} msg='{}'", s.code, m),
1831 _ => format!(" status={}", s.code),
1832 }
1833 } else {
1834 String::new()
1835 };
1836
1837 let value_text = if let Some(ref decoded) = self.decoded_value {
1839 let formatted = format_compact_value(decoded);
1840 if formatted.is_empty() || formatted == "{}" {
1841 String::new()
1842 } else {
1843 format!(" [{}]", formatted)
1844 }
1845 } else if !self.pv_names.is_empty() {
1846 format!(" data=[{}]", self.pv_names.join(","))
1847 } else {
1848 String::new()
1849 };
1850
1851 if self.is_server {
1852 write!(
1853 f,
1854 "{}(ioid={}, sub=0x{:02x}{}{})",
1855 cmd_name, self.ioid, self.subcmd, status_text, value_text
1856 )
1857 } else {
1858 write!(
1859 f,
1860 "{}(sid={}, ioid={}, sub=0x{:02x}{}{})",
1861 cmd_name, self.sid_or_cid, self.ioid, self.subcmd, status_text, value_text
1862 )
1863 }
1864 }
1865}
1866
1867#[cfg(test)]
1868mod tests {
1869 use super::*;
1870 use crate::spvd_decode::extract_nt_scalar_value;
1871
1872 #[test]
1873 fn destroy_request_decodes_spec_and_legacy_forms() {
1874 let mut spec = Vec::new();
1876 spec.extend_from_slice(&7u32.to_le_bytes());
1877 spec.extend_from_slice(&42u32.to_le_bytes());
1878 let p = PvaDestroyRequestPayload::new(&spec, false).unwrap();
1879 assert_eq!((p.sid, p.request_id), (7, 42));
1880
1881 let legacy = 42u32.to_le_bytes();
1883 let p = PvaDestroyRequestPayload::new(&legacy, false).unwrap();
1884 assert_eq!((p.sid, p.request_id), (0, 42));
1885
1886 assert!(PvaDestroyRequestPayload::new(&[0u8; 3], false).is_none());
1887 }
1888 use crate::spvd_encode::{
1889 encode_nt_payload_bitset_parts, encode_nt_scalar_bitset_parts, encode_size_pvd,
1890 nt_payload_desc, nt_scalar_desc,
1891 };
1892 use crate::spvirit_encode::encode_header;
1893 use spvirit_types::{NtPayload, NtScalar, NtScalarArray, ScalarArrayValue, ScalarValue};
1894
1895 #[test]
1896 fn test_decode_status_ok() {
1897 let raw = [0xff];
1898 let (status, consumed) = decode_status(&raw, false);
1899 assert!(status.is_none());
1900 assert_eq!(consumed, 1);
1901 }
1902
1903 #[test]
1904 fn test_decode_status_message() {
1905 let raw = [1u8, 2, b'h', b'i', 2, b's', b't'];
1906 let (status, consumed) = decode_status(&raw, false);
1907 assert_eq!(consumed, 7);
1908 let status = status.unwrap();
1909 assert_eq!(status.code, 1);
1910 assert_eq!(status.message.as_deref(), Some("hi"));
1911 assert_eq!(status.stack.as_deref(), Some("st"));
1912 }
1913
1914 #[test]
1915 fn test_search_response_decode() {
1916 let mut raw: Vec<u8> = vec![];
1917 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");
1923 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();
1928 assert!(decoded.found);
1929 assert_eq!(decoded.protocol, "tcp");
1930 assert_eq!(decoded.cids, vec![42u32]);
1931 }
1932
1933 fn build_monitor_packet(ioid: u32, subcmd: u8, body: &[u8]) -> Vec<u8> {
1934 let mut payload = Vec::new();
1935 payload.extend_from_slice(&ioid.to_le_bytes());
1936 payload.push(subcmd);
1937 payload.extend_from_slice(body);
1938 let mut out = encode_header(true, false, false, 2, 13, payload.len() as u32);
1939 out.extend_from_slice(&payload);
1940 out
1941 }
1942
1943 #[test]
1946 fn test_monitor_decode_spec_order_strict() {
1947 let nt = NtScalar::from_value(ScalarValue::F64(3.5));
1948 let desc = nt_scalar_desc(&nt.value);
1949 let (changed_bitset, values) = encode_nt_scalar_bitset_parts(&nt, false);
1950
1951 let mut body_spec = Vec::new();
1952 body_spec.extend_from_slice(&changed_bitset);
1953 body_spec.extend_from_slice(&values);
1954 body_spec.extend_from_slice(&encode_size_pvd(0, false));
1955
1956 let pkt = build_monitor_packet(1, 0x00, &body_spec);
1957 let mut pva = PvaPacket::new(&pkt);
1958 let mut cmd = pva.decode_payload().expect("decoded");
1959 if let PvaPacketCommand::Op(ref mut op) = cmd {
1960 op.decode_with_field_desc(&desc, false, DecodeMode::Strict)
1961 .expect("spec-order body decodes");
1962 let decoded = op.decoded_value.as_ref().expect("decoded");
1963 let value = extract_nt_scalar_value(decoded).expect("value");
1964 match value {
1965 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1966 other => panic!("unexpected value {:?}", other),
1967 }
1968 } else {
1969 panic!("unexpected cmd");
1970 }
1971 }
1972
1973 #[test]
1978 fn test_monitor_decode_non_spec_layouts_via_lenient() {
1979 use crate::monitor::MonitorLayout;
1980 use crate::spvd_decode::PvdDecoder;
1981
1982 let nt = NtScalar::from_value(ScalarValue::F64(3.5));
1983 let desc = nt_scalar_desc(&nt.value);
1984 let (changed_bitset, values) = encode_nt_scalar_bitset_parts(&nt, false);
1985 let decoder = PvdDecoder::new(false);
1986
1987 let mut body_overrun = Vec::new();
1989 body_overrun.extend_from_slice(&changed_bitset);
1990 body_overrun.extend_from_slice(&encode_size_pvd(0, false));
1991 body_overrun.extend_from_slice(&values);
1992
1993 let (update, layout) = decoder
1994 .decode_monitor_update_lenient(&body_overrun, &desc)
1995 .expect("overrun-before-data body decodes");
1996 assert_eq!(layout, MonitorLayout::OverrunBeforeData);
1997 match extract_nt_scalar_value(&update.value).expect("value") {
1998 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
1999 other => panic!("unexpected value {:?}", other),
2000 }
2001
2002 let mut body_legacy = Vec::new();
2004 body_legacy.extend_from_slice(&changed_bitset);
2005 body_legacy.extend_from_slice(&values);
2006
2007 let (update, layout) = decoder
2008 .decode_monitor_update_lenient(&body_legacy, &desc)
2009 .expect("changed-only body decodes");
2010 assert_eq!(layout, MonitorLayout::ChangedOnly);
2011 match extract_nt_scalar_value(&update.value).expect("value") {
2012 DecodedValue::Float64(v) => assert!((*v - 3.5).abs() < 1e-6),
2013 other => panic!("unexpected value {:?}", other),
2014 }
2015 }
2016
2017 #[test]
2018 fn test_monitor_decode_prefers_spec_order_for_array_payload() {
2019 let payload_value =
2020 NtPayload::ScalarArray(NtScalarArray::from_value(ScalarArrayValue::F64(vec![
2021 1.0, 2.0, 3.0, 4.0,
2022 ])));
2023 let desc = nt_payload_desc(&payload_value);
2024 let (changed_bitset, values) = encode_nt_payload_bitset_parts(&payload_value, false);
2025
2026 let mut body_spec = Vec::new();
2027 body_spec.extend_from_slice(&changed_bitset);
2028 body_spec.extend_from_slice(&values);
2029 body_spec.extend_from_slice(&encode_size_pvd(0, false));
2030
2031 let pkt = build_monitor_packet(11, 0x00, &body_spec);
2032 let mut pva = PvaPacket::new(&pkt);
2033 let mut cmd = pva.decode_payload().expect("decoded");
2034 if let PvaPacketCommand::Op(ref mut op) = cmd {
2035 op.decode_with_field_desc(&desc, false, DecodeMode::Strict)
2036 .expect("spec-order body decodes");
2037 let decoded = op.decoded_value.as_ref().expect("decoded");
2038 let value = extract_nt_scalar_value(decoded).expect("value");
2039 match value {
2040 DecodedValue::Array(items) => {
2041 assert_eq!(items.len(), 4);
2042 assert!(matches!(items[0], DecodedValue::Float64(v) if (v - 1.0).abs() < 1e-6));
2043 assert!(matches!(items[3], DecodedValue::Float64(v) if (v - 4.0).abs() < 1e-6));
2044 }
2045 other => panic!("unexpected value {:?}", other),
2046 }
2047 } else {
2048 panic!("unexpected cmd");
2049 }
2050 }
2051
2052 #[test]
2053 fn pva_status_reports_error_state() {
2054 let ok = PvaStatus {
2055 code: 0,
2056 message: None,
2057 stack: None,
2058 };
2059 let err = PvaStatus {
2060 code: 2,
2061 message: Some("bad".to_string()),
2062 stack: None,
2063 };
2064 assert!(!ok.is_error());
2065 assert!(err.is_error());
2066 }
2067
2068 #[test]
2069 fn pva_status_display_includes_message_and_stack() {
2070 let status = PvaStatus {
2071 code: 2,
2072 message: Some("bad".to_string()),
2073 stack: Some("trace".to_string()),
2074 };
2075 assert_eq!(status.to_string(), "code=2 message=bad stack=trace");
2076 }
2077
2078 #[test]
2079 fn decode_op_response_status_reads_status_from_packet() {
2080 let raw = vec![
2081 0xCA, 0x02, 0x40, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x02,
2082 0x03, b'b', b'a', b'd', 0x00,
2083 ];
2084 let status = decode_op_response_status(&raw, false)
2085 .expect("status parse")
2086 .expect("status");
2087 assert!(status.is_error());
2088 assert_eq!(status.message.as_deref(), Some("bad"));
2089 }
2090
2091 #[test]
2092 fn connection_validation_decodes_ca_user_and_host() {
2093 let raw = crate::spvirit_encode::encode_connection_validation_client_ca(
2097 0x10000, 1, 0, "ca", "operator1",
2102 "ws-42.lab",
2103 true,
2104 );
2105 let p = PvaConnectionValidationPayload::new(&raw, true, false).expect("decode");
2106 assert_eq!(p.authz.as_deref(), Some("ca"));
2107 assert_eq!(p.user.as_deref(), Some("operator1"));
2108 assert_eq!(p.host.as_deref(), Some("ws-42.lab"));
2109 }
2110
2111 #[test]
2112 fn connection_validation_anonymous_has_no_user() {
2113 let raw = crate::spvirit_encode::encode_connection_validation_client_anon(
2114 0x10000, 1, 0, "anonymous", true,
2115 );
2116 let p = PvaConnectionValidationPayload::new(&raw, true, false).expect("decode");
2117 assert_eq!(p.authz.as_deref(), Some("anonymous"));
2118 assert_eq!(p.user, None);
2119 assert_eq!(p.host, None);
2120 }
2121}