1extern crate alloc;
19use alloc::string::String;
20use alloc::vec::Vec;
21
22use crate::endpoint_security_info::EndpointSecurityInfo;
23use crate::error::WireError;
24use crate::parameter_list::{Parameter, ParameterList, pid};
25use crate::participant_data::{Duration, ENCAPSULATION_PL_CDR_LE};
26use crate::wire_types::{Guid, Locator};
27
28pub use zerodds_qos::DurabilityKind;
33
34pub use zerodds_qos::ReliabilityKind;
38
39pub use zerodds_qos::ReliabilityQosPolicy as ReliabilityQos;
44
45pub mod data_representation {
51 pub const XCDR: i16 = 0;
54 pub const XML: i16 = 1;
56 pub const XCDR2: i16 = 2;
59
60 pub const DEFAULT_OFFER: [i16; 1] = [XCDR2];
79
80 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
92 pub enum DataRepMatchMode {
93 Strict,
95 #[default]
98 Tolerant,
99 }
100
101 #[must_use]
111 pub fn negotiate(
112 writer_offered: &[i16],
113 reader_accepted: &[i16],
114 mode: DataRepMatchMode,
115 ) -> Option<i16> {
116 let w_default = [XCDR];
118 let r_default = [XCDR];
119 let w: &[i16] = if writer_offered.is_empty() {
120 &w_default
121 } else {
122 writer_offered
123 };
124 let r: &[i16] = if reader_accepted.is_empty() {
125 &r_default
126 } else {
127 reader_accepted
128 };
129
130 match mode {
131 DataRepMatchMode::Strict => {
132 let first = w.first().copied()?;
134 if r.contains(&first) {
135 Some(first)
136 } else {
137 None
138 }
139 }
140 DataRepMatchMode::Tolerant => {
141 w.iter().copied().find(|id| r.contains(id))
145 }
146 }
147 }
148
149 #[must_use]
156 pub fn encap_for_final_le(id: i16) -> [u8; 4] {
157 match id {
158 XCDR2 => [0x00, 0x07, 0x00, 0x00], _ => [0x00, 0x01, 0x00, 0x00], }
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct PublicationBuiltinTopicData {
167 pub key: Guid,
169 pub participant_key: Guid,
171 pub topic_name: String,
173 pub type_name: String,
175 pub durability: DurabilityKind,
177 pub reliability: ReliabilityQos,
179 pub ownership: zerodds_qos::OwnershipKind,
181 pub ownership_strength: i32,
184 pub liveliness: zerodds_qos::LivelinessQosPolicy,
186 pub deadline: zerodds_qos::DeadlineQosPolicy,
188 pub lifespan: zerodds_qos::LifespanQosPolicy,
190 pub partition: Vec<String>,
192 pub user_data: Vec<u8>,
195 pub topic_data: Vec<u8>,
198 pub group_data: Vec<u8>,
201 pub type_information: Option<Vec<u8>>,
206 pub data_representation: Vec<i16>,
212 pub security_info: Option<EndpointSecurityInfo>,
217 pub service_instance_name: Option<String>,
221 pub related_entity_guid: Option<Guid>,
226 pub topic_aliases: Option<Vec<String>>,
229 pub type_identifier: zerodds_types::TypeIdentifier,
233 pub unicast_locators: Vec<Locator>,
241 pub multicast_locators: Vec<Locator>,
243}
244
245pub fn encode_locator_params(params: &mut ParameterList, pid_id: u16, locators: &[Locator]) {
248 for loc in locators {
249 params.push(Parameter::new(pid_id, loc.to_bytes_le().to_vec()));
250 }
251}
252
253#[must_use]
257pub fn collect_locator_params(
258 pl: &ParameterList,
259 pid_id: u16,
260 little_endian: bool,
261) -> Vec<Locator> {
262 if !little_endian {
263 return Vec::new();
264 }
265 pl.parameters
266 .iter()
267 .filter(|p| p.id == pid_id && p.value.len() == Locator::WIRE_SIZE)
268 .filter_map(|p| {
269 let mut b = [0u8; 24];
270 b.copy_from_slice(&p.value);
271 Locator::from_bytes_le(b).ok()
272 })
273 .collect()
274}
275
276impl PublicationBuiltinTopicData {
277 pub fn to_pl_cdr_le(&self) -> Result<Vec<u8>, WireError> {
284 let mut params = ParameterList::new();
285
286 params.push(Parameter::new(
288 pid::PARTICIPANT_GUID,
289 self.participant_key.to_bytes().to_vec(),
290 ));
291
292 params.push(Parameter::new(
294 pid::ENDPOINT_GUID,
295 self.key.to_bytes().to_vec(),
296 ));
297
298 params.push(Parameter::new(
300 pid::TOPIC_NAME,
301 encode_cdr_string_le(&self.topic_name)?,
302 ));
303
304 params.push(Parameter::new(
306 pid::TYPE_NAME,
307 encode_cdr_string_le(&self.type_name)?,
308 ));
309
310 params.push(Parameter::new(
312 pid::DURABILITY,
313 (self.durability as u32).to_le_bytes().to_vec(),
314 ));
315
316 let mut rel = Vec::with_capacity(12);
318 rel.extend_from_slice(&(self.reliability.kind as u32).to_le_bytes());
319 rel.extend_from_slice(&self.reliability.max_blocking_time.to_bytes_le());
320 params.push(Parameter::new(pid::RELIABILITY, rel));
321
322 params.push(Parameter::new(
324 pid::OWNERSHIP,
325 encode_u32_le(self.ownership as u32).to_vec(),
326 ));
327
328 params.push(Parameter::new(
331 pid::OWNERSHIP_STRENGTH,
332 encode_u32_le(self.ownership_strength as u32).to_vec(),
333 ));
334
335 params.push(Parameter::new(
337 pid::LIVELINESS,
338 encode_liveliness_le(self.liveliness),
339 ));
340
341 params.push(Parameter::new(
343 pid::DEADLINE,
344 encode_duration_le(self.deadline.period).to_vec(),
345 ));
346
347 params.push(Parameter::new(
349 pid::LIFESPAN,
350 encode_duration_le(self.lifespan.duration).to_vec(),
351 ));
352
353 if !self.partition.is_empty() {
355 params.push(Parameter::new(
356 pid::PARTITION,
357 encode_partition_le(&self.partition)?,
358 ));
359 }
360
361 if !self.user_data.is_empty() {
365 params.push(Parameter::new(
366 pid::USER_DATA,
367 encode_octet_seq_le(&self.user_data)?,
368 ));
369 }
370 if !self.topic_data.is_empty() {
371 params.push(Parameter::new(
372 pid::TOPIC_DATA,
373 encode_octet_seq_le(&self.topic_data)?,
374 ));
375 }
376 if !self.group_data.is_empty() {
377 params.push(Parameter::new(
378 pid::GROUP_DATA,
379 encode_octet_seq_le(&self.group_data)?,
380 ));
381 }
382
383 if let Some(ti) = &self.type_information {
386 params.push(Parameter::new(pid::TYPE_INFORMATION, ti.clone()));
387 }
388
389 if let Some(info) = self.security_info {
393 params.push(Parameter::new(
394 pid::ENDPOINT_SECURITY_INFO,
395 info.to_bytes(true).to_vec(),
396 ));
397 }
398
399 if let Some(name) = &self.service_instance_name {
403 params.push(Parameter::new(
404 pid::SERVICE_INSTANCE_NAME,
405 encode_cdr_string_le(name)?,
406 ));
407 }
408 if let Some(guid) = self.related_entity_guid {
409 params.push(Parameter::new(
410 pid::RELATED_ENTITY_GUID,
411 guid.to_bytes().to_vec(),
412 ));
413 }
414 if let Some(aliases) = &self.topic_aliases {
415 params.push(Parameter::new(
416 pid::TOPIC_ALIASES,
417 encode_partition_le(aliases)?,
418 ));
419 }
420
421 if self.type_identifier != zerodds_types::TypeIdentifier::None {
423 let mut w = zerodds_cdr::BufferWriter::new(zerodds_cdr::Endianness::Little);
424 self.type_identifier
425 .encode_into(&mut w)
426 .map_err(|_| WireError::ValueOutOfRange {
427 message: "type_identifier encoding failed",
428 })?;
429 params.push(Parameter::new(pid::ZERODDS_TYPE_ID, w.into_bytes()));
430 }
431
432 if !self.data_representation.is_empty() {
434 let mut dr = Vec::with_capacity(4 + 2 * self.data_representation.len());
435 let len = u32::try_from(self.data_representation.len()).map_err(|_| {
436 WireError::ValueOutOfRange {
437 message: "data_representation length exceeds u32::MAX",
438 }
439 })?;
440 dr.extend_from_slice(&len.to_le_bytes());
441 for rep in &self.data_representation {
442 dr.extend_from_slice(&rep.to_le_bytes());
443 }
444 params.push(Parameter::new(pid::DATA_REPRESENTATION, dr));
445 }
446
447 encode_locator_params(&mut params, pid::UNICAST_LOCATOR, &self.unicast_locators);
449 encode_locator_params(
450 &mut params,
451 pid::MULTICAST_LOCATOR,
452 &self.multicast_locators,
453 );
454
455 let mut out = Vec::with_capacity(params.parameters.len() * 24 + 16);
456 out.extend_from_slice(&ENCAPSULATION_PL_CDR_LE);
457 out.extend_from_slice(&[0, 0]); out.extend_from_slice(¶ms.to_bytes(true));
459 Ok(out)
460 }
461
462 pub fn from_pl_cdr_le(bytes: &[u8]) -> Result<Self, WireError> {
470 if bytes.len() < 4 {
471 return Err(WireError::UnexpectedEof {
472 needed: 4,
473 offset: 0,
474 });
475 }
476 let little_endian = match &bytes[..2] {
477 b if b == ENCAPSULATION_PL_CDR_LE => true,
478 [0x00, 0x02] => false,
479 other => {
480 return Err(WireError::UnsupportedEncapsulation {
481 kind: [other[0], other[1]],
482 });
483 }
484 };
485 let pl = ParameterList::from_bytes(&bytes[4..], little_endian)?;
486
487 let key = pl
488 .find(pid::ENDPOINT_GUID)
489 .and_then(guid_from_param)
490 .ok_or(WireError::ValueOutOfRange {
491 message: "ENDPOINT_GUID missing or wrong length",
492 })?;
493
494 let participant_key = pl
497 .find(pid::PARTICIPANT_GUID)
498 .and_then(guid_from_param)
499 .unwrap_or_else(|| {
500 Guid::new(key.prefix, crate::wire_types::EntityId::PARTICIPANT)
502 });
503
504 let topic_name = pl
505 .find(pid::TOPIC_NAME)
506 .map(|p| decode_cdr_string(&p.value, little_endian))
507 .transpose()?
508 .ok_or(WireError::ValueOutOfRange {
509 message: "TOPIC_NAME missing",
510 })?;
511
512 let type_name = pl
513 .find(pid::TYPE_NAME)
514 .map(|p| decode_cdr_string(&p.value, little_endian))
515 .transpose()?
516 .ok_or(WireError::ValueOutOfRange {
517 message: "TYPE_NAME missing",
518 })?;
519
520 let durability = pl
521 .find(pid::DURABILITY)
522 .and_then(|p| {
523 if p.value.len() >= 4 {
524 let mut b = [0u8; 4];
525 b.copy_from_slice(&p.value[..4]);
526 Some(DurabilityKind::from_u32(if little_endian {
527 u32::from_le_bytes(b)
528 } else {
529 u32::from_be_bytes(b)
530 }))
531 } else {
532 None
533 }
534 })
535 .unwrap_or_default();
536
537 let reliability = pl
538 .find(pid::RELIABILITY)
539 .and_then(|p| {
540 if p.value.len() >= 12 {
541 let mut k = [0u8; 4];
542 k.copy_from_slice(&p.value[..4]);
543 let kind = ReliabilityKind::from_u32(if little_endian {
544 u32::from_le_bytes(k)
545 } else {
546 u32::from_be_bytes(k)
547 });
548 let mut d = [0u8; 8];
549 d.copy_from_slice(&p.value[4..12]);
550 let max_blocking_time = if little_endian {
551 Duration::from_bytes_le(d)
552 } else {
553 let mut s = [0u8; 4];
555 s.copy_from_slice(&d[..4]);
556 let mut f = [0u8; 4];
557 f.copy_from_slice(&d[4..]);
558 Duration {
559 seconds: i32::from_be_bytes(s),
560 fraction: u32::from_be_bytes(f),
561 }
562 };
563 Some(ReliabilityQos {
564 kind,
565 max_blocking_time,
566 })
567 } else {
568 None
569 }
570 })
571 .unwrap_or_default();
572
573 let ownership = pl
574 .find(pid::OWNERSHIP)
575 .and_then(|p| decode_u32(&p.value, little_endian))
576 .map(zerodds_qos::OwnershipKind::from_u32)
577 .unwrap_or_default();
578
579 let ownership_strength = pl
580 .find(pid::OWNERSHIP_STRENGTH)
581 .and_then(|p| decode_i32(&p.value, little_endian))
582 .unwrap_or(0);
583
584 let liveliness = pl
585 .find(pid::LIVELINESS)
586 .and_then(|p| decode_liveliness(&p.value, little_endian))
587 .unwrap_or_default();
588
589 let deadline = pl
590 .find(pid::DEADLINE)
591 .and_then(|p| decode_duration(&p.value, little_endian))
592 .map(|period| zerodds_qos::DeadlineQosPolicy { period })
593 .unwrap_or_default();
594
595 let lifespan = pl
596 .find(pid::LIFESPAN)
597 .and_then(|p| decode_duration(&p.value, little_endian))
598 .map(|duration| zerodds_qos::LifespanQosPolicy { duration })
599 .unwrap_or_default();
600
601 let partition = pl
602 .find(pid::PARTITION)
603 .and_then(|p| decode_partition(&p.value, little_endian))
604 .unwrap_or_default();
605
606 let user_data = pl
607 .find(pid::USER_DATA)
608 .and_then(|p| decode_octet_seq(&p.value, little_endian))
609 .unwrap_or_default();
610 let topic_data = pl
611 .find(pid::TOPIC_DATA)
612 .and_then(|p| decode_octet_seq(&p.value, little_endian))
613 .unwrap_or_default();
614 let group_data = pl
615 .find(pid::GROUP_DATA)
616 .and_then(|p| decode_octet_seq(&p.value, little_endian))
617 .unwrap_or_default();
618
619 let type_information = pl.find(pid::TYPE_INFORMATION).map(|p| p.value.clone());
620
621 let security_info = pl
622 .find(pid::ENDPOINT_SECURITY_INFO)
623 .and_then(|p| EndpointSecurityInfo::from_bytes(&p.value, little_endian).ok());
624
625 let service_instance_name = pl
626 .find(pid::SERVICE_INSTANCE_NAME)
627 .map(|p| decode_cdr_string(&p.value, little_endian))
628 .transpose()
629 .ok()
630 .flatten();
631 let related_entity_guid = pl.find(pid::RELATED_ENTITY_GUID).and_then(guid_from_param);
632 let topic_aliases = pl
633 .find(pid::TOPIC_ALIASES)
634 .and_then(|p| decode_partition(&p.value, little_endian));
635
636 let type_identifier = pl
637 .find(pid::ZERODDS_TYPE_ID)
638 .and_then(|p| {
639 let mut r =
640 zerodds_cdr::BufferReader::new(&p.value, zerodds_cdr::Endianness::Little);
641 zerodds_types::TypeIdentifier::decode_from(&mut r).ok()
642 })
643 .unwrap_or_default();
644
645 let data_representation = pl
646 .find(pid::DATA_REPRESENTATION)
647 .map(|p| {
648 let v = &p.value;
649 if v.len() < 4 {
650 return Vec::new();
651 }
652 let mut n_bytes = [0u8; 4];
653 n_bytes.copy_from_slice(&v[..4]);
654 let n = if little_endian {
655 u32::from_le_bytes(n_bytes)
656 } else {
657 u32::from_be_bytes(n_bytes)
658 } as usize;
659 let cap = n.min(v.len().saturating_sub(4) / 2);
663 let mut reps = Vec::with_capacity(cap);
664 for i in 0..n {
665 let off = 4 + i * 2;
666 if off + 2 > v.len() {
667 break;
668 }
669 let mut b = [0u8; 2];
670 b.copy_from_slice(&v[off..off + 2]);
671 reps.push(if little_endian {
672 i16::from_le_bytes(b)
673 } else {
674 i16::from_be_bytes(b)
675 });
676 }
677 reps
678 })
679 .unwrap_or_default();
680
681 Ok(Self {
682 key,
683 participant_key,
684 topic_name,
685 type_name,
686 durability,
687 reliability,
688 ownership,
689 ownership_strength,
690 liveliness,
691 deadline,
692 lifespan,
693 partition,
694 user_data,
695 topic_data,
696 group_data,
697 type_information,
698 data_representation,
699 security_info,
700 service_instance_name,
701 related_entity_guid,
702 topic_aliases,
703 type_identifier,
704 unicast_locators: collect_locator_params(&pl, pid::UNICAST_LOCATOR, little_endian),
705 multicast_locators: collect_locator_params(&pl, pid::MULTICAST_LOCATOR, little_endian),
706 })
707 }
708}
709
710pub fn inject_pid_shm_locator(bytes: &mut Vec<u8>, locator_bytes: &[u8]) -> Result<(), WireError> {
735 use crate::parameter_list::pid;
736 if bytes.len() < 4 {
737 return Err(WireError::ValueOutOfRange {
738 message: "inject_pid_shm_locator: bytes too short",
739 });
740 }
741 let sentinel_pos = bytes.len() - 4;
742 if bytes[sentinel_pos..] != [0x01, 0x00, 0x00, 0x00] {
744 return Err(WireError::ValueOutOfRange {
745 message: "inject_pid_shm_locator: missing PID_SENTINEL trailer",
746 });
747 }
748 let padded_len = (locator_bytes.len() + 3) & !3;
750 if padded_len > u16::MAX as usize {
751 return Err(WireError::ValueOutOfRange {
752 message: "inject_pid_shm_locator: locator > u16::MAX",
753 });
754 }
755 let mut inject = Vec::with_capacity(4 + padded_len + 4);
756 inject.extend_from_slice(&pid::SHM_LOCATOR.to_le_bytes());
757 inject.extend_from_slice(&(padded_len as u16).to_le_bytes());
758 inject.extend_from_slice(locator_bytes);
759 inject.resize(inject.len() + (padded_len - locator_bytes.len()), 0);
761 inject.extend_from_slice(&bytes[sentinel_pos..]);
763 bytes.truncate(sentinel_pos);
764 bytes.extend_from_slice(&inject);
765 Ok(())
766}
767
768pub(crate) fn guid_from_param(p: &Parameter) -> Option<Guid> {
769 if p.value.len() == 16 {
770 let mut g = [0u8; 16];
771 g.copy_from_slice(&p.value);
772 Some(Guid::from_bytes(g))
773 } else {
774 None
775 }
776}
777
778pub(crate) fn encode_duration_le(d: Duration) -> [u8; 8] {
784 let mut out = [0u8; 8];
785 out[..4].copy_from_slice(&d.seconds.to_le_bytes());
786 out[4..].copy_from_slice(&d.fraction.to_le_bytes());
787 out
788}
789
790pub(crate) fn decode_duration(value: &[u8], little_endian: bool) -> Option<Duration> {
791 if value.len() < 8 {
792 return None;
793 }
794 let mut s = [0u8; 4];
795 s.copy_from_slice(&value[..4]);
796 let mut f = [0u8; 4];
797 f.copy_from_slice(&value[4..8]);
798 if little_endian {
799 Some(Duration {
800 seconds: i32::from_le_bytes(s),
801 fraction: u32::from_le_bytes(f),
802 })
803 } else {
804 Some(Duration {
805 seconds: i32::from_be_bytes(s),
806 fraction: u32::from_be_bytes(f),
807 })
808 }
809}
810
811pub(crate) fn encode_u32_le(v: u32) -> [u8; 4] {
813 v.to_le_bytes()
814}
815
816pub(crate) fn decode_u32(value: &[u8], little_endian: bool) -> Option<u32> {
817 if value.len() < 4 {
818 return None;
819 }
820 let mut b = [0u8; 4];
821 b.copy_from_slice(&value[..4]);
822 if little_endian {
823 Some(u32::from_le_bytes(b))
824 } else {
825 Some(u32::from_be_bytes(b))
826 }
827}
828
829pub(crate) fn decode_i32(value: &[u8], little_endian: bool) -> Option<i32> {
830 decode_u32(value, little_endian).map(|u| u as i32)
831}
832
833pub(crate) fn encode_liveliness_le(l: zerodds_qos::LivelinessQosPolicy) -> Vec<u8> {
835 let mut out = Vec::with_capacity(12);
836 out.extend_from_slice(&(l.kind as u32).to_le_bytes());
837 out.extend_from_slice(&encode_duration_le(l.lease_duration));
838 out
839}
840
841pub(crate) fn decode_liveliness(
842 value: &[u8],
843 little_endian: bool,
844) -> Option<zerodds_qos::LivelinessQosPolicy> {
845 if value.len() < 12 {
846 return None;
847 }
848 let kind_u = decode_u32(&value[..4], little_endian)?;
849 let lease = decode_duration(&value[4..12], little_endian)?;
850 Some(zerodds_qos::LivelinessQosPolicy {
851 kind: zerodds_qos::LivelinessKind::from_u32(kind_u),
852 lease_duration: lease,
853 })
854}
855
856pub fn encode_octet_seq_le(data: &[u8]) -> Result<Vec<u8>, WireError> {
861 let len = u32::try_from(data.len()).map_err(|_| WireError::ValueOutOfRange {
862 message: "octet sequence length exceeds u32::MAX",
863 })?;
864 let mut out = Vec::with_capacity(4 + data.len() + 3);
865 out.extend_from_slice(&len.to_le_bytes());
866 out.extend_from_slice(data);
867 while out.len() % 4 != 0 {
868 out.push(0);
869 }
870 Ok(out)
871}
872
873pub fn decode_octet_seq(value: &[u8], little_endian: bool) -> Option<Vec<u8>> {
875 let n = decode_u32(value, little_endian)? as usize;
876 if 4 + n > value.len() {
877 return None;
878 }
879 Some(value[4..4 + n].to_vec())
880}
881
882pub(crate) fn encode_partition_le(partitions: &[String]) -> Result<Vec<u8>, WireError> {
883 let mut out = Vec::new();
884 let len = u32::try_from(partitions.len()).map_err(|_| WireError::ValueOutOfRange {
885 message: "partition count exceeds u32::MAX",
886 })?;
887 out.extend_from_slice(&len.to_le_bytes());
888 for p in partitions {
889 out.extend_from_slice(&encode_cdr_string_le(p)?);
894 }
895 Ok(out)
896}
897
898pub(crate) fn decode_partition(value: &[u8], little_endian: bool) -> Option<Vec<String>> {
899 let n = decode_u32(value, little_endian)? as usize;
900 let cap = n.min(value.len().saturating_sub(4) / 5);
904 let mut out = Vec::with_capacity(cap);
905 let mut pos = 4;
906 for _ in 0..n {
907 if pos + 4 > value.len() {
908 return None;
909 }
910 let mut lb = [0u8; 4];
911 lb.copy_from_slice(&value[pos..pos + 4]);
912 let slen = if little_endian {
913 u32::from_le_bytes(lb)
914 } else {
915 u32::from_be_bytes(lb)
916 } as usize;
917 let next_raw_end = pos + 4 + slen;
918 if next_raw_end > value.len() {
919 return None;
920 }
921 let s =
922 decode_cdr_string(&value[pos..next_raw_end.min(value.len())], little_endian).ok()?;
923 out.push(s);
924 let padded_end = (next_raw_end + 3) & !3;
926 pos = padded_end;
927 }
928 Some(out)
929}
930
931pub(crate) fn encode_cdr_string_le(s: &str) -> Result<Vec<u8>, WireError> {
932 let bytes = s.as_bytes();
933 let len =
934 u32::try_from(bytes.len().saturating_add(1)).map_err(|_| WireError::ValueOutOfRange {
935 message: "CDR string length exceeds u32::MAX",
936 })?;
937 let mut out = Vec::with_capacity(4 + bytes.len() + 4);
938 out.extend_from_slice(&len.to_le_bytes());
939 out.extend_from_slice(bytes);
940 out.push(0); while out.len() % 4 != 0 {
943 out.push(0);
944 }
945 Ok(out)
946}
947
948pub(crate) fn decode_cdr_string(value: &[u8], little_endian: bool) -> Result<String, WireError> {
950 if value.len() < 4 {
951 return Err(WireError::UnexpectedEof {
952 needed: 4,
953 offset: 0,
954 });
955 }
956 let mut lb = [0u8; 4];
957 lb.copy_from_slice(&value[..4]);
958 let len = if little_endian {
959 u32::from_le_bytes(lb)
960 } else {
961 u32::from_be_bytes(lb)
962 } as usize;
963 if len == 0 {
964 return Err(WireError::ValueOutOfRange {
965 message: "CDR string length 0 (missing null terminator)",
966 });
967 }
968 if value.len() < 4 + len {
969 return Err(WireError::UnexpectedEof {
970 needed: 4 + len,
971 offset: 0,
972 });
973 }
974 let raw = &value[4..4 + len];
975 if raw[len - 1] != 0 {
976 return Err(WireError::ValueOutOfRange {
977 message: "CDR string missing null terminator",
978 });
979 }
980 String::from_utf8(raw[..len - 1].to_vec()).map_err(|_| WireError::ValueOutOfRange {
981 message: "CDR string is not valid UTF-8",
982 })
983}
984
985#[cfg(test)]
986#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
987mod tests {
988 use super::*;
989
990 #[test]
991 fn durability_try_from_u32_rejects_unknown() {
992 assert_eq!(
993 DurabilityKind::try_from_u32(0),
994 Some(DurabilityKind::Volatile)
995 );
996 assert_eq!(
997 DurabilityKind::try_from_u32(1),
998 Some(DurabilityKind::TransientLocal)
999 );
1000 assert_eq!(
1001 DurabilityKind::try_from_u32(3),
1002 Some(DurabilityKind::Persistent)
1003 );
1004 assert_eq!(DurabilityKind::try_from_u32(99), None);
1005 }
1006
1007 #[test]
1008 fn reliability_try_from_u32_rejects_unknown() {
1009 assert_eq!(
1010 ReliabilityKind::try_from_u32(1),
1011 Some(ReliabilityKind::BestEffort)
1012 );
1013 assert_eq!(
1014 ReliabilityKind::try_from_u32(2),
1015 Some(ReliabilityKind::Reliable)
1016 );
1017 assert_eq!(ReliabilityKind::try_from_u32(0), None);
1019 assert_eq!(ReliabilityKind::try_from_u32(42), None);
1020 }
1021
1022 #[test]
1023 fn legacy_from_u32_still_defaults_for_sedp_forward_compat() {
1024 assert_eq!(DurabilityKind::from_u32(99), DurabilityKind::Volatile);
1026 assert_eq!(ReliabilityKind::from_u32(99), ReliabilityKind::BestEffort);
1027 }
1028 use crate::wire_types::{EntityId, GuidPrefix};
1029 use alloc::vec;
1030
1031 fn sample_data() -> PublicationBuiltinTopicData {
1032 PublicationBuiltinTopicData {
1033 key: Guid::new(
1034 GuidPrefix::from_bytes([1; 12]),
1035 EntityId::user_writer_with_key([0x10, 0x20, 0x30]),
1036 ),
1037 participant_key: Guid::new(GuidPrefix::from_bytes([1; 12]), EntityId::PARTICIPANT),
1038 topic_name: "ChatterTopic".into(),
1039 type_name: "std_msgs::String".into(),
1040 durability: DurabilityKind::Volatile,
1041 reliability: ReliabilityQos {
1042 kind: ReliabilityKind::Reliable,
1043 max_blocking_time: Duration::from_secs(10),
1044 },
1045 ownership: zerodds_qos::OwnershipKind::Shared,
1046 ownership_strength: 0,
1047 liveliness: zerodds_qos::LivelinessQosPolicy::default(),
1048 deadline: zerodds_qos::DeadlineQosPolicy::default(),
1049 lifespan: zerodds_qos::LifespanQosPolicy::default(),
1050 partition: alloc::vec::Vec::new(),
1051 user_data: alloc::vec::Vec::new(),
1052 topic_data: alloc::vec::Vec::new(),
1053 group_data: alloc::vec::Vec::new(),
1054 type_information: None,
1055 data_representation: alloc::vec::Vec::new(),
1056 security_info: None,
1057 service_instance_name: None,
1058 related_entity_guid: None,
1059 topic_aliases: None,
1060 type_identifier: zerodds_types::TypeIdentifier::None,
1061 unicast_locators: alloc::vec::Vec::new(),
1062 multicast_locators: alloc::vec::Vec::new(),
1063 }
1064 }
1065
1066 #[test]
1067 fn endpoint_locators_roundtrip_le() {
1068 let mut d = sample_data();
1069 d.unicast_locators = alloc::vec![
1070 Locator::udp_v4([192, 168, 1, 5], 7411),
1071 Locator::udp_v4([10, 0, 0, 9], 7411),
1072 ];
1073 d.multicast_locators = alloc::vec![Locator::udp_v4([239, 255, 0, 2], 7401)];
1074 let bytes = d.to_pl_cdr_le().unwrap();
1075 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1076 assert_eq!(decoded.unicast_locators, d.unicast_locators);
1077 assert_eq!(decoded.multicast_locators, d.multicast_locators);
1078 }
1079
1080 #[test]
1081 fn roundtrip_le() {
1082 let d = sample_data();
1083 let bytes = d.to_pl_cdr_le().unwrap();
1084 assert_eq!(&bytes[..2], &[0x00, 0x03]); let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1086 assert_eq!(decoded, d);
1087 }
1088
1089 #[test]
1090 fn security_info_roundtrip() {
1091 use crate::endpoint_security_info::{EndpointSecurityInfo, attrs, plugin_attrs};
1092 let mut d = sample_data();
1093 d.security_info = Some(EndpointSecurityInfo {
1094 endpoint_security_attributes: attrs::IS_VALID | attrs::IS_SUBMESSAGE_PROTECTED,
1095 plugin_endpoint_security_attributes: plugin_attrs::IS_VALID
1096 | plugin_attrs::IS_SUBMESSAGE_ENCRYPTED,
1097 });
1098 let bytes = d.to_pl_cdr_le().unwrap();
1099 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1100 assert_eq!(decoded.security_info, d.security_info);
1101 }
1102
1103 #[test]
1104 fn legacy_peer_without_security_info_parses_ok() {
1105 let d = sample_data();
1106 assert!(d.security_info.is_none());
1107 let bytes = d.to_pl_cdr_le().unwrap();
1108 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1109 assert!(decoded.security_info.is_none());
1110 }
1111
1112 #[test]
1113 fn roundtrip_utf8_topic_name() {
1114 let mut d = sample_data();
1115 d.topic_name = "Zählung".into();
1116 let bytes = d.to_pl_cdr_le().unwrap();
1117 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1118 assert_eq!(decoded.topic_name, "Zählung");
1119 }
1120
1121 #[test]
1122 fn decode_rejects_unknown_encapsulation() {
1123 let mut bytes = vec![0xFF, 0xFF, 0x00, 0x00];
1124 bytes.extend_from_slice(&[0u8; 16]);
1125 let res = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes);
1126 assert!(matches!(
1127 res,
1128 Err(WireError::UnsupportedEncapsulation { .. })
1129 ));
1130 }
1131
1132 #[test]
1133 fn decode_rejects_missing_topic_name() {
1134 let mut pl = ParameterList::new();
1135 pl.push(Parameter::new(pid::ENDPOINT_GUID, vec![0u8; 16]));
1136 pl.push(Parameter::new(
1137 pid::TYPE_NAME,
1138 encode_cdr_string_le("T").unwrap(),
1139 ));
1140 let mut bytes = Vec::new();
1141 bytes.extend_from_slice(&ENCAPSULATION_PL_CDR_LE);
1142 bytes.extend_from_slice(&[0, 0]);
1143 bytes.extend_from_slice(&pl.to_bytes(true));
1144 let res = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes);
1145 assert!(
1146 matches!(res, Err(WireError::ValueOutOfRange { message }) if message.contains("TOPIC_NAME"))
1147 );
1148 }
1149
1150 #[test]
1151 fn decode_rejects_missing_type_name() {
1152 let mut pl = ParameterList::new();
1153 pl.push(Parameter::new(pid::ENDPOINT_GUID, vec![0u8; 16]));
1154 pl.push(Parameter::new(
1155 pid::TOPIC_NAME,
1156 encode_cdr_string_le("T").unwrap(),
1157 ));
1158 let mut bytes = Vec::new();
1159 bytes.extend_from_slice(&ENCAPSULATION_PL_CDR_LE);
1160 bytes.extend_from_slice(&[0, 0]);
1161 bytes.extend_from_slice(&pl.to_bytes(true));
1162 let res = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes);
1163 assert!(
1164 matches!(res, Err(WireError::ValueOutOfRange { message }) if message.contains("TYPE_NAME"))
1165 );
1166 }
1167
1168 #[test]
1169 fn decode_rejects_missing_endpoint_guid() {
1170 let mut pl = ParameterList::new();
1171 pl.push(Parameter::new(
1172 pid::TOPIC_NAME,
1173 encode_cdr_string_le("T").unwrap(),
1174 ));
1175 pl.push(Parameter::new(
1176 pid::TYPE_NAME,
1177 encode_cdr_string_le("U").unwrap(),
1178 ));
1179 let mut bytes = Vec::new();
1180 bytes.extend_from_slice(&ENCAPSULATION_PL_CDR_LE);
1181 bytes.extend_from_slice(&[0, 0]);
1182 bytes.extend_from_slice(&pl.to_bytes(true));
1183 let res = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes);
1184 assert!(
1185 matches!(res, Err(WireError::ValueOutOfRange { message }) if message.contains("ENDPOINT_GUID"))
1186 );
1187 }
1188
1189 #[test]
1190 fn unknown_pids_are_skipped() {
1191 let mut bytes = sample_data().to_pl_cdr_le().unwrap();
1192 let sentinel_pos = bytes.len() - 4;
1195 let mut inject = vec![0xFFu8, 0x7F, 4, 0, 0xDE, 0xAD, 0xBE, 0xEF];
1196 inject.extend_from_slice(&bytes[sentinel_pos..]);
1197 bytes.truncate(sentinel_pos);
1198 bytes.extend_from_slice(&inject);
1199 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1200 assert_eq!(decoded, sample_data());
1201 }
1202
1203 #[test]
1204 fn inject_pid_shm_locator_appends_before_sentinel() {
1205 let mut locator = Vec::new();
1208 locator.extend_from_slice(&0xDEAD_BEEFu32.to_le_bytes()); locator.extend_from_slice(&1000u32.to_le_bytes()); locator.extend_from_slice(&64u32.to_le_bytes()); locator.extend_from_slice(&4096u32.to_le_bytes()); let path = "/dev/shm/zd-1";
1214 locator.extend_from_slice(&((path.len() as u32) + 1).to_le_bytes());
1215 locator.extend_from_slice(path.as_bytes());
1216 locator.push(0);
1217 let pad = (4 - locator.len() % 4) % 4;
1219 locator.resize(locator.len() + pad, 0);
1220
1221 let mut bytes = sample_data().to_pl_cdr_le().unwrap();
1222 let len_before = bytes.len();
1223 super::inject_pid_shm_locator(&mut bytes, &locator).unwrap();
1224 assert!(bytes.len() > len_before);
1226 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1229 assert_eq!(decoded, sample_data());
1230 let pid_found = bytes.windows(2).any(|w| w == 0x8001u16.to_le_bytes());
1232 assert!(pid_found, "PID_SHM_LOCATOR should appear in bytes");
1233 }
1234
1235 #[test]
1236 fn inject_pid_shm_locator_rejects_missing_sentinel() {
1237 let mut bytes = vec![0u8; 8];
1238 let res = super::inject_pid_shm_locator(&mut bytes, &[0u8; 16]);
1239 assert!(res.is_err());
1240 }
1241
1242 #[test]
1243 fn inject_pid_shm_locator_rejects_too_short() {
1244 let mut bytes = vec![0u8, 1u8];
1245 let res = super::inject_pid_shm_locator(&mut bytes, &[0u8; 16]);
1246 assert!(res.is_err());
1247 }
1248
1249 #[test]
1250 fn participant_key_fallback_when_pid_missing() {
1251 let d = sample_data();
1254 let mut pl = ParameterList::new();
1255 pl.push(Parameter::new(
1256 pid::ENDPOINT_GUID,
1257 d.key.to_bytes().to_vec(),
1258 ));
1259 pl.push(Parameter::new(
1260 pid::TOPIC_NAME,
1261 encode_cdr_string_le(&d.topic_name).unwrap(),
1262 ));
1263 pl.push(Parameter::new(
1264 pid::TYPE_NAME,
1265 encode_cdr_string_le(&d.type_name).unwrap(),
1266 ));
1267 let mut bytes = Vec::new();
1268 bytes.extend_from_slice(&ENCAPSULATION_PL_CDR_LE);
1269 bytes.extend_from_slice(&[0, 0]);
1270 bytes.extend_from_slice(&pl.to_bytes(true));
1271 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1272 assert_eq!(decoded.participant_key.prefix, d.key.prefix);
1273 assert_eq!(decoded.participant_key.entity_id, EntityId::PARTICIPANT);
1274 }
1275
1276 #[test]
1277 fn durability_kind_from_u32_unknown_defaults_volatile() {
1278 assert_eq!(DurabilityKind::from_u32(0), DurabilityKind::Volatile);
1279 assert_eq!(DurabilityKind::from_u32(1), DurabilityKind::TransientLocal);
1280 assert_eq!(DurabilityKind::from_u32(999), DurabilityKind::Volatile);
1281 }
1282
1283 #[test]
1284 fn rpc_discovery_pids_roundtrip() {
1285 let mut d = sample_data();
1286 d.service_instance_name = Some("CalcInstance-1".into());
1287 d.related_entity_guid = Some(Guid::new(
1288 crate::wire_types::GuidPrefix::from_bytes([7; 12]),
1289 crate::wire_types::EntityId::user_reader_with_key([0xAA, 0xBB, 0xCC]),
1290 ));
1291 d.topic_aliases = Some(alloc::vec!["LegacyCalc_Request".into(), "v2_Req".into()]);
1292
1293 let bytes = d.to_pl_cdr_le().unwrap();
1294 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1295 assert_eq!(decoded.service_instance_name, d.service_instance_name);
1296 assert_eq!(decoded.related_entity_guid, d.related_entity_guid);
1297 assert_eq!(decoded.topic_aliases, d.topic_aliases);
1298 }
1299
1300 #[test]
1301 fn rpc_pids_optional_legacy_peer_parses_ok() {
1302 let d = sample_data();
1303 assert!(d.service_instance_name.is_none());
1304 assert!(d.related_entity_guid.is_none());
1305 assert!(d.topic_aliases.is_none());
1306 let bytes = d.to_pl_cdr_le().unwrap();
1307 let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
1308 assert!(decoded.service_instance_name.is_none());
1309 assert!(decoded.related_entity_guid.is_none());
1310 assert!(decoded.topic_aliases.is_none());
1311 }
1312
1313 #[test]
1314 fn rpc_pid_constants_in_emitted_bytes() {
1315 let mut d = sample_data();
1319 d.service_instance_name = Some("X".into());
1320 d.related_entity_guid = Some(Guid::new(
1321 crate::wire_types::GuidPrefix::from_bytes([1; 12]),
1322 crate::wire_types::EntityId::PARTICIPANT,
1323 ));
1324 d.topic_aliases = Some(alloc::vec!["A".into()]);
1325 let bytes = d.to_pl_cdr_le().unwrap();
1326 let mut found_080 = false;
1328 let mut found_081 = false;
1329 let mut found_082 = false;
1330 for w in bytes.windows(2) {
1331 if w == [0x80, 0x00] {
1332 found_080 = true;
1333 }
1334 if w == [0x81, 0x00] {
1335 found_081 = true;
1336 }
1337 if w == [0x82, 0x00] {
1338 found_082 = true;
1339 }
1340 }
1341 assert!(found_080 && found_081 && found_082);
1342 }
1343
1344 #[test]
1345 fn reliability_kind_from_u32_unknown_defaults_best_effort() {
1346 assert_eq!(ReliabilityKind::from_u32(1), ReliabilityKind::BestEffort);
1347 assert_eq!(ReliabilityKind::from_u32(2), ReliabilityKind::Reliable);
1348 assert_eq!(ReliabilityKind::from_u32(999), ReliabilityKind::BestEffort);
1349 }
1350}