1extern crate alloc;
31use alloc::collections::{BTreeMap, BTreeSet};
32use alloc::string::String;
33use alloc::sync::Arc;
34use alloc::vec::Vec;
35
36#[cfg(feature = "std")]
37use std::sync::Mutex;
38
39use crate::builtin_subscriber::BuiltinSubscriber;
40use crate::builtin_topics::{ParticipantBuiltinTopicData, TopicBuiltinTopicData};
41use crate::dds_type::DdsType;
42use crate::entity::StatusMask;
43use crate::error::{DdsError, Result};
44use crate::instance_handle::InstanceHandle;
45use crate::listener::ArcDomainParticipantListener;
46use crate::publisher::Publisher;
47use crate::qos::{DomainParticipantQos, PublisherQos, SubscriberQos, TopicQos};
48use crate::subscriber::Subscriber;
49use crate::topic::{
50 ContentFilteredTopic, Topic, TopicDescription, TopicDescriptionHandle, TopicInner,
51};
52
53#[cfg(feature = "std")]
54use crate::runtime::{DcpsRuntime, RuntimeConfig};
55
56pub type DomainId = i32;
58
59#[derive(Debug, Default)]
68#[cfg(feature = "std")]
69pub(crate) struct IgnoreFilterInner {
70 pub(crate) participants: Mutex<BTreeSet<InstanceHandle>>,
71 pub(crate) topics: Mutex<BTreeSet<InstanceHandle>>,
72 pub(crate) publications: Mutex<BTreeSet<InstanceHandle>>,
73 pub(crate) subscriptions: Mutex<BTreeSet<InstanceHandle>>,
74}
75
76#[derive(Clone, Debug, Default)]
80#[cfg(feature = "std")]
81pub struct IgnoreFilter {
82 pub(crate) inner: Arc<IgnoreFilterInner>,
83}
84
85#[cfg(feature = "std")]
86impl IgnoreFilter {
87 #[must_use]
89 pub fn is_participant_ignored(&self, h: InstanceHandle) -> bool {
90 self.inner
91 .participants
92 .lock()
93 .map(|s| s.contains(&h))
94 .unwrap_or(false)
95 }
96
97 #[must_use]
99 pub fn is_topic_ignored(&self, h: InstanceHandle) -> bool {
100 self.inner
101 .topics
102 .lock()
103 .map(|s| s.contains(&h))
104 .unwrap_or(false)
105 }
106
107 #[must_use]
109 pub fn is_publication_ignored(&self, h: InstanceHandle) -> bool {
110 self.inner
111 .publications
112 .lock()
113 .map(|s| s.contains(&h))
114 .unwrap_or(false)
115 }
116
117 #[must_use]
119 pub fn is_subscription_ignored(&self, h: InstanceHandle) -> bool {
120 self.inner
121 .subscriptions
122 .lock()
123 .map(|s| s.contains(&h))
124 .unwrap_or(false)
125 }
126}
127
128#[cfg(feature = "std")]
145fn random_guid_prefix() -> zerodds_rtps::wire_types::GuidPrefix {
146 use std::sync::atomic::{AtomicU32, Ordering};
147 static COUNTER: AtomicU32 = AtomicU32::new(0);
148 let host_id = host_id_bytes();
149 let pid = std::process::id();
150 let t = std::time::SystemTime::now()
151 .duration_since(std::time::UNIX_EPOCH)
152 .map(|d| d.as_nanos() as u64)
153 .unwrap_or(0);
154 let c = COUNTER.fetch_add(1, Ordering::Relaxed);
155 let mut bytes = [0u8; 12];
156 bytes[0..4].copy_from_slice(&host_id);
157 bytes[4..8].copy_from_slice(&pid.to_le_bytes());
158 bytes[8..12].copy_from_slice(&(t as u32).to_le_bytes());
159 bytes[11] = bytes[11].wrapping_add(c as u8);
160 zerodds_rtps::wire_types::GuidPrefix::from_bytes(bytes)
161}
162
163#[cfg(feature = "std")]
178pub fn host_id_bytes() -> [u8; 4] {
179 use std::sync::OnceLock;
180 static HOST_ID: OnceLock<[u8; 4]> = OnceLock::new();
181 *HOST_ID.get_or_init(|| {
182 let hostname = gethostname_via_libc()
191 .or_else(|| std::env::var("HOSTNAME").ok())
192 .or_else(|| std::env::var("COMPUTERNAME").ok())
193 .or_else(read_etc_hostname);
194 let h = match hostname {
195 Some(s) if !s.is_empty() => fnv1a_32(s.as_bytes()),
196 _ => {
197 let pid = std::process::id();
201 let t = std::time::SystemTime::now()
202 .duration_since(std::time::UNIX_EPOCH)
203 .map(|d| d.as_nanos() as u32)
204 .unwrap_or(0);
205 pid.wrapping_mul(0x9E37_79B1).wrapping_add(t)
206 }
207 };
208 h.to_le_bytes()
209 })
210}
211
212#[cfg(all(feature = "std", unix))]
213#[allow(unsafe_code)]
214fn gethostname_via_libc() -> Option<String> {
215 let mut buf = [0u8; 256];
218 let rc = unsafe { libc::gethostname(buf.as_mut_ptr().cast::<libc::c_char>(), buf.len()) };
221 if rc != 0 {
222 return None;
223 }
224 let len = buf.iter().position(|b| *b == 0).unwrap_or(buf.len());
226 if len == 0 {
227 return None;
228 }
229 core::str::from_utf8(&buf[..len]).ok().map(|s| s.to_owned())
230}
231
232#[cfg(all(feature = "std", not(unix)))]
233fn gethostname_via_libc() -> Option<String> {
234 None
235}
236
237#[cfg(feature = "std")]
238fn read_etc_hostname() -> Option<String> {
239 std::fs::read_to_string("/etc/hostname")
240 .ok()
241 .map(|s| s.trim().to_owned())
242}
243
244#[cfg(feature = "std")]
245fn fnv1a_32(data: &[u8]) -> u32 {
246 let mut h: u32 = 0x811C_9DC5;
247 for &b in data {
248 h ^= u32::from(b);
249 h = h.wrapping_mul(0x0100_0193);
250 }
251 h
252}
253
254#[derive(Clone)]
256pub struct DomainParticipant {
257 inner: Arc<ParticipantInner>,
258}
259
260impl core::fmt::Debug for DomainParticipant {
261 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
262 f.debug_struct("DomainParticipant")
263 .field("domain_id", &self.inner.domain_id)
264 .finish_non_exhaustive()
265 }
266}
267
268impl DomainParticipant {
269 pub(crate) fn downgrade(&self) -> alloc::sync::Weak<ParticipantInner> {
276 Arc::downgrade(&self.inner)
277 }
278
279 pub(crate) fn from_inner(inner: Arc<ParticipantInner>) -> Self {
282 Self { inner }
283 }
284}
285
286pub(crate) struct ParticipantInner {
287 pub(crate) domain_id: DomainId,
288 pub(crate) qos: Mutex<DomainParticipantQos>,
289 pub(crate) entity_state: Arc<crate::entity::EntityState>,
291 topics: Mutex<BTreeMap<String, Arc<TopicInner>>>,
295 #[cfg(feature = "std")]
299 pub(crate) runtime: Option<Arc<DcpsRuntime>>,
300 pub(crate) builtin_subscriber: Arc<BuiltinSubscriber>,
304 #[cfg(feature = "std")]
309 pub(crate) ignore_filter: IgnoreFilter,
310 publishers: Mutex<Vec<InstanceHandle>>,
317 subscribers: Mutex<Vec<InstanceHandle>>,
319 pub(crate) datawriters: Mutex<Vec<InstanceHandle>>,
323 pub(crate) datareaders: Mutex<Vec<InstanceHandle>>,
326 pub(crate) listener: Mutex<Option<(ArcDomainParticipantListener, StatusMask)>>,
330 #[cfg(feature = "std")]
336 pub(crate) type_registry: Mutex<BTreeMap<String, zerodds_types::dynamic::DynamicType>>,
337 #[cfg(feature = "std")]
341 pub(crate) type_lookup: Mutex<TypeLookupState>,
342}
343
344#[cfg(feature = "std")]
347#[derive(Debug, Default)]
348pub(crate) struct TypeLookupState {
349 pub attempts: BTreeMap<zerodds_types::EquivalenceHash, (std::time::Instant, u32)>,
351 pub outgoing: Vec<(zerodds_types::EquivalenceHash, u64)>,
356}
357
358#[cfg(feature = "std")]
359impl TypeLookupState {
360 pub const BACKOFF: std::time::Duration = std::time::Duration::from_secs(5);
362 pub const MAX_ATTEMPTS: u32 = 3;
364}
365
366impl DomainParticipant {
367 pub(crate) fn new(domain_id: DomainId, qos: DomainParticipantQos) -> Self {
371 let builtin = Arc::new(BuiltinSubscriber::new());
372 let participant = Self {
373 inner: Arc::new(ParticipantInner {
374 domain_id,
375 qos: Mutex::new(qos),
376 entity_state: crate::entity::EntityState::new(),
377 topics: Mutex::new(BTreeMap::new()),
378 #[cfg(feature = "std")]
379 runtime: None,
380 builtin_subscriber: builtin,
381 #[cfg(feature = "std")]
382 ignore_filter: IgnoreFilter::default(),
383 publishers: Mutex::new(Vec::new()),
384 subscribers: Mutex::new(Vec::new()),
385 datawriters: Mutex::new(Vec::new()),
386 datareaders: Mutex::new(Vec::new()),
387 listener: Mutex::new(None),
388 #[cfg(feature = "std")]
389 type_registry: Mutex::new(BTreeMap::new()),
390 #[cfg(feature = "std")]
391 type_lookup: Mutex::new(TypeLookupState::default()),
392 }),
393 };
394 #[cfg(feature = "std")]
396 participant.register_builtin_types();
397 participant
398 }
399
400 #[cfg(feature = "std")]
406 pub(crate) fn new_with_runtime(
407 domain_id: DomainId,
408 qos: DomainParticipantQos,
409 config: RuntimeConfig,
410 ) -> Result<Self> {
411 #[cfg(feature = "security")]
415 let config = config
416 .with_security_log_properties(&qos.property)
417 .map_err(|_| DdsError::PreconditionNotMet {
418 reason: "invalid dds.sec.log.* security logger configuration",
419 })?;
420 let runtime = DcpsRuntime::start(domain_id, random_guid_prefix(), config)?;
421 let builtin = Arc::new(BuiltinSubscriber::new());
422 runtime.attach_builtin_sinks(builtin.sinks());
425 let ignore_filter = IgnoreFilter::default();
429 runtime.attach_ignore_filter(ignore_filter.clone());
430 let participant = Self {
431 inner: Arc::new(ParticipantInner {
432 domain_id,
433 qos: Mutex::new(qos),
434 entity_state: crate::entity::EntityState::new(),
435 topics: Mutex::new(BTreeMap::new()),
436 runtime: Some(runtime),
437 builtin_subscriber: builtin,
438 ignore_filter,
439 publishers: Mutex::new(Vec::new()),
440 subscribers: Mutex::new(Vec::new()),
441 datawriters: Mutex::new(Vec::new()),
442 datareaders: Mutex::new(Vec::new()),
443 listener: Mutex::new(None),
444 type_registry: Mutex::new(BTreeMap::new()),
445 type_lookup: Mutex::new(TypeLookupState::default()),
446 }),
447 };
448 participant.register_builtin_types();
450 Ok(participant)
451 }
452
453 #[cfg(feature = "std")]
457 #[must_use]
458 pub fn runtime(&self) -> Option<&Arc<DcpsRuntime>> {
459 self.inner.runtime.as_ref()
460 }
461
462 #[must_use]
464 pub fn domain_id(&self) -> DomainId {
465 self.inner.domain_id
466 }
467
468 #[must_use]
471 pub fn qos(&self) -> DomainParticipantQos {
472 self.inner.qos.lock().map(|g| g.clone()).unwrap_or_default()
473 }
474
475 pub fn set_qos(&self, qos: DomainParticipantQos) -> Result<()> {
482 if let Ok(mut g) = self.inner.qos.lock() {
483 *g = qos;
484 }
485 Ok(())
486 }
487
488 #[cfg(feature = "std")]
497 pub fn register_builtin_types(&self) {
498 if let Ok(types) = zerodds_types::dynamic::all_builtin_types() {
499 if let Ok(mut reg) = self.inner.type_registry.lock() {
500 for (name, t) in types {
501 reg.insert(name, t);
502 }
503 }
504 }
505 }
506
507 #[cfg(feature = "std")]
510 pub fn unregister_builtin_types(&self) {
511 if let Ok(mut reg) = self.inner.type_registry.lock() {
512 reg.retain(|name, _| !zerodds_types::dynamic::is_builtin_type_name(name));
513 }
514 }
515
516 #[cfg(feature = "std")]
520 #[must_use]
521 pub fn find_builtin_type(&self, name: &str) -> Option<zerodds_types::dynamic::DynamicType> {
522 self.inner
523 .type_registry
524 .lock()
525 .ok()
526 .and_then(|reg| reg.get(name).cloned())
527 }
528
529 #[cfg(feature = "std")]
531 #[must_use]
532 pub fn registered_type_count(&self) -> usize {
533 self.inner
534 .type_registry
535 .lock()
536 .map(|r| r.len())
537 .unwrap_or(0)
538 }
539
540 #[cfg(feature = "std")]
547 pub fn enqueue_type_lookup(&self, hash: zerodds_types::EquivalenceHash) -> bool {
548 let mut state = match self.inner.type_lookup.lock() {
549 Ok(s) => s,
550 Err(_) => return false,
551 };
552 let now = std::time::Instant::now();
553 if let Some((last, retries)) = state.attempts.get(&hash).copied() {
554 if retries >= TypeLookupState::MAX_ATTEMPTS {
555 return false;
556 }
557 if now.duration_since(last) < TypeLookupState::BACKOFF {
558 return false;
559 }
560 state
561 .attempts
562 .insert(hash, (now, retries.saturating_add(1)));
563 } else {
564 state.attempts.insert(hash, (now, 1));
565 }
566 let seq = state.outgoing.len() as u64 + 1;
568 state.outgoing.push((hash, seq));
569 true
570 }
571
572 #[cfg(feature = "std")]
577 #[must_use]
578 pub fn drain_type_lookup_requests(&self) -> Vec<(zerodds_types::EquivalenceHash, u64)> {
579 self.inner
580 .type_lookup
581 .lock()
582 .map(|mut s| core::mem::take(&mut s.outgoing))
583 .unwrap_or_default()
584 }
585
586 #[cfg(feature = "std")]
592 pub fn ingest_type_lookup_reply(
593 &self,
594 types: Vec<(
595 zerodds_types::EquivalenceHash,
596 zerodds_types::MinimalTypeObject,
597 )>,
598 ) -> usize {
599 let mut count = 0;
600 if let Ok(mut state) = self.inner.type_lookup.lock() {
601 for (hash, _t) in &types {
602 state.attempts.remove(hash);
603 count += 1;
604 }
605 }
606 let _ = types;
610 count
611 }
612
613 #[cfg(feature = "std")]
626 pub fn on_remote_publication_discovered(&self, type_information_blob: Option<&[u8]>) -> usize {
627 self.on_remote_type_information(type_information_blob)
628 }
629
630 #[cfg(feature = "std")]
633 pub fn on_remote_subscription_discovered(&self, type_information_blob: Option<&[u8]>) -> usize {
634 self.on_remote_type_information(type_information_blob)
635 }
636
637 #[cfg(feature = "std")]
638 fn on_remote_type_information(&self, blob: Option<&[u8]>) -> usize {
639 let Some(bytes) = blob else {
640 return 0;
641 };
642 let Ok(ti) = zerodds_types::type_information::TypeInformation::from_bytes_le(bytes) else {
643 return 0;
644 };
645 let mut queued = 0;
646 if let Some(hash) = extract_equivalence_hash(&ti.minimal.typeid_with_size.type_id) {
648 if !self.has_type_for_hash(hash) && self.enqueue_type_lookup(hash) {
649 queued += 1;
650 }
651 }
652 if let Some(hash) = extract_equivalence_hash(&ti.complete.typeid_with_size.type_id) {
654 if !self.has_type_for_hash(hash) && self.enqueue_type_lookup(hash) {
655 queued += 1;
656 }
657 }
658 queued
659 }
660
661 #[cfg(feature = "std")]
667 fn has_type_for_hash(&self, hash: zerodds_types::EquivalenceHash) -> bool {
668 let Some(rt) = self.inner.runtime.as_ref() else {
669 return false;
670 };
671 let Ok(server) = rt.type_lookup_server.lock() else {
672 return false;
673 };
674 server.registry.get_minimal(&hash).is_some()
675 || server.registry.get_complete(&hash).is_some()
676 }
677
678 #[cfg(feature = "std")]
682 #[must_use]
683 pub fn type_lookup_exhausted(&self, hash: zerodds_types::EquivalenceHash) -> bool {
684 self.inner
685 .type_lookup
686 .lock()
687 .ok()
688 .and_then(|s| s.attempts.get(&hash).map(|(_, n)| *n))
689 .unwrap_or(0)
690 >= TypeLookupState::MAX_ATTEMPTS
691 }
692
693 pub fn create_topic<T: DdsType>(&self, name: &str, qos: TopicQos) -> Result<Topic<T>> {
701 if name.is_empty() {
702 return Err(DdsError::BadParameter { what: "topic name" });
703 }
704 let mut topics = self
705 .inner
706 .topics
707 .lock()
708 .map_err(|_| DdsError::PreconditionNotMet {
709 reason: "topic registry poisoned",
710 })?;
711 if let Some(existing) = topics.get(name) {
712 if existing.type_name != T::TYPE_NAME {
713 #[cfg(feature = "std")]
717 existing
718 .inconsistent_topic_count
719 .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
720 return Err(DdsError::InconsistentPolicy {
721 what: "topic name reused with different type",
722 });
723 }
724 return Ok(reconstruct_topic::<T>(existing.clone(), self.clone()));
726 }
727 let topic = Topic::<T>::new(name.into(), qos, self.clone());
728 topics.insert(name.into(), topic_inner(&topic));
729 Ok(topic)
730 }
731
732 #[must_use]
737 pub fn lookup_topicdescription(&self, name: &str) -> Option<TopicDescriptionHandle> {
738 let topics = self.inner.topics.lock().ok()?;
739 let inner = topics.get(name)?;
740 Some(TopicDescriptionHandle::new(
741 inner.name.clone(),
742 String::from(inner.type_name),
743 self.clone(),
744 ))
745 }
746
747 #[cfg(feature = "std")]
762 pub fn find_topic(
763 &self,
764 name: &str,
765 timeout: core::time::Duration,
766 ) -> Result<TopicDescriptionHandle> {
767 if name.is_empty() {
768 return Err(DdsError::BadParameter { what: "topic name" });
769 }
770 let deadline = std::time::Instant::now() + timeout;
771 if let Some(h) = self.lookup_topicdescription(name) {
774 return Ok(h);
775 }
776 let poll = core::time::Duration::from_millis(20);
779 loop {
780 if let Some(handle) = self.find_topic_in_sedp(name) {
781 return Ok(handle);
782 }
783 if std::time::Instant::now() >= deadline {
784 return Err(DdsError::Timeout);
785 }
786 std::thread::sleep(poll);
787 }
788 }
789
790 #[cfg(feature = "std")]
794 fn find_topic_in_sedp(&self, name: &str) -> Option<TopicDescriptionHandle> {
795 let rt = self.inner.runtime.as_ref()?;
796 let sedp = rt.sedp.lock().ok()?;
797 for p in sedp.cache().publications() {
799 if p.data.topic_name == name {
800 return Some(TopicDescriptionHandle::new(
801 p.data.topic_name.clone(),
802 p.data.type_name.clone(),
803 self.clone(),
804 ));
805 }
806 }
807 for s in sedp.cache().subscriptions() {
808 if s.data.topic_name == name {
809 return Some(TopicDescriptionHandle::new(
810 s.data.topic_name.clone(),
811 s.data.type_name.clone(),
812 self.clone(),
813 ));
814 }
815 }
816 None
817 }
818
819 pub fn create_contentfilteredtopic<T: DdsType>(
833 &self,
834 name: &str,
835 related_topic: &Topic<T>,
836 filter_expression: &str,
837 filter_parameters: alloc::vec::Vec<String>,
838 ) -> Result<ContentFilteredTopic<T>> {
839 if name.is_empty() {
840 return Err(DdsError::BadParameter {
841 what: "content-filtered-topic name",
842 });
843 }
844 if filter_expression.is_empty() {
845 return Err(DdsError::BadParameter {
846 what: "filter expression",
847 });
848 }
849 ContentFilteredTopic::<T>::new(
850 name.into(),
851 related_topic.clone(),
852 filter_expression.into(),
853 filter_parameters,
854 self.clone(),
855 )
856 }
857
858 pub fn create_multitopic<T: DdsType>(
870 &self,
871 name: &str,
872 type_name: &str,
873 related_topic_names: alloc::vec::Vec<String>,
874 subscription_expression: &str,
875 expression_parameters: alloc::vec::Vec<String>,
876 ) -> Result<crate::topic::MultiTopic<T>> {
877 if name.is_empty() {
878 return Err(DdsError::BadParameter {
879 what: "multitopic name",
880 });
881 }
882 if type_name.is_empty() {
883 return Err(DdsError::BadParameter {
884 what: "multitopic type_name",
885 });
886 }
887 if subscription_expression.is_empty() {
888 return Err(DdsError::BadParameter {
889 what: "multitopic subscription expression",
890 });
891 }
892 crate::topic::MultiTopic::<T>::new(
893 name.into(),
894 type_name.into(),
895 related_topic_names,
896 subscription_expression.into(),
897 expression_parameters,
898 self.clone(),
899 )
900 }
901
902 pub fn delete_multitopic<T: DdsType>(&self, mt: &crate::topic::MultiTopic<T>) -> Result<()> {
909 if mt.get_participant().inner_ptr() != self.inner_ptr() {
910 return Err(DdsError::BadParameter {
911 what: "multitopic belongs to different participant",
912 });
913 }
914 Ok(())
915 }
916
917 pub fn delete_contentfilteredtopic<T: DdsType>(
930 &self,
931 cft: &ContentFilteredTopic<T>,
932 ) -> Result<()> {
933 if cft.get_participant().inner_ptr() != self.inner_ptr() {
934 return Err(DdsError::BadParameter {
935 what: "cft belongs to different participant",
936 });
937 }
938 Ok(())
939 }
940
941 pub(crate) fn inner_ptr(&self) -> *const ParticipantInner {
944 Arc::as_ptr(&self.inner)
945 }
946
947 pub fn create_publisher(&self, qos: PublisherQos) -> Publisher {
950 #[cfg(feature = "std")]
951 let p = {
952 let p = Publisher::new(qos, self.inner.runtime.clone());
953 p.attach_participant(Arc::downgrade(&self.inner));
956 p
957 };
958 #[cfg(not(feature = "std"))]
959 let p = Publisher::new(qos);
960 if let Ok(mut list) = self.inner.publishers.lock() {
962 list.push(p.inner.entity_state.instance_handle());
963 }
964 p
965 }
966
967 pub fn create_subscriber(&self, qos: SubscriberQos) -> Subscriber {
969 #[cfg(feature = "std")]
970 let s = {
971 let s = Subscriber::new(qos, self.inner.runtime.clone());
972 s.attach_participant(Arc::downgrade(&self.inner));
974 s
975 };
976 #[cfg(not(feature = "std"))]
977 let s = Subscriber::new(qos);
978 if let Ok(mut list) = self.inner.subscribers.lock() {
979 list.push(s.inner.entity_state.instance_handle());
980 }
981 s
982 }
983
984 #[must_use]
986 pub fn topics_len(&self) -> usize {
987 self.inner.topics.lock().map(|t| t.len()).unwrap_or(0)
988 }
989
990 #[must_use]
994 pub fn discovered_participants_count(&self) -> usize {
995 #[cfg(feature = "std")]
996 if let Some(rt) = self.inner.runtime.as_ref() {
997 return rt.discovered_participants().len();
998 }
999 0
1000 }
1001
1002 #[must_use]
1005 pub fn discovered_publications_count(&self) -> usize {
1006 #[cfg(feature = "std")]
1007 if let Some(rt) = self.inner.runtime.as_ref() {
1008 return rt.discovered_publications_count();
1009 }
1010 0
1011 }
1012
1013 #[must_use]
1015 pub fn discovered_subscriptions_count(&self) -> usize {
1016 #[cfg(feature = "std")]
1017 if let Some(rt) = self.inner.runtime.as_ref() {
1018 return rt.discovered_subscriptions_count();
1019 }
1020 0
1021 }
1022
1023 pub fn ignore_participant(&self, handle: InstanceHandle) -> Result<()> {
1040 #[cfg(feature = "std")]
1041 if let Ok(mut s) = self.inner.ignore_filter.inner.participants.lock() {
1042 s.insert(handle);
1043 }
1044 Ok(())
1045 }
1046
1047 pub fn ignore_topic(&self, handle: InstanceHandle) -> Result<()> {
1052 #[cfg(feature = "std")]
1053 if let Ok(mut s) = self.inner.ignore_filter.inner.topics.lock() {
1054 s.insert(handle);
1055 }
1056 Ok(())
1057 }
1058
1059 pub fn ignore_publication(&self, handle: InstanceHandle) -> Result<()> {
1065 #[cfg(feature = "std")]
1066 if let Ok(mut s) = self.inner.ignore_filter.inner.publications.lock() {
1067 s.insert(handle);
1068 }
1069 Ok(())
1070 }
1071
1072 pub fn ignore_subscription(&self, handle: InstanceHandle) -> Result<()> {
1078 #[cfg(feature = "std")]
1079 if let Ok(mut s) = self.inner.ignore_filter.inner.subscriptions.lock() {
1080 s.insert(handle);
1081 }
1082 Ok(())
1083 }
1084
1085 #[must_use]
1087 pub fn is_participant_ignored(&self, handle: InstanceHandle) -> bool {
1088 #[cfg(feature = "std")]
1089 return self.inner.ignore_filter.is_participant_ignored(handle);
1090 #[cfg(not(feature = "std"))]
1091 {
1092 let _ = handle;
1093 false
1094 }
1095 }
1096
1097 #[must_use]
1099 pub fn is_topic_ignored(&self, handle: InstanceHandle) -> bool {
1100 #[cfg(feature = "std")]
1101 return self.inner.ignore_filter.is_topic_ignored(handle);
1102 #[cfg(not(feature = "std"))]
1103 {
1104 let _ = handle;
1105 false
1106 }
1107 }
1108
1109 #[must_use]
1111 pub fn is_publication_ignored(&self, handle: InstanceHandle) -> bool {
1112 #[cfg(feature = "std")]
1113 return self.inner.ignore_filter.is_publication_ignored(handle);
1114 #[cfg(not(feature = "std"))]
1115 {
1116 let _ = handle;
1117 false
1118 }
1119 }
1120
1121 #[must_use]
1123 pub fn is_subscription_ignored(&self, handle: InstanceHandle) -> bool {
1124 #[cfg(feature = "std")]
1125 return self.inner.ignore_filter.is_subscription_ignored(handle);
1126 #[cfg(not(feature = "std"))]
1127 {
1128 let _ = handle;
1129 false
1130 }
1131 }
1132
1133 #[cfg(feature = "std")]
1136 #[must_use]
1137 #[allow(dead_code)]
1138 pub(crate) fn ignore_filter(&self) -> IgnoreFilter {
1139 self.inner.ignore_filter.clone()
1140 }
1141
1142 pub fn delete_contained_entities(&self) -> Result<()> {
1164 {
1166 let mut topics =
1167 self.inner
1168 .topics
1169 .lock()
1170 .map_err(|_| DdsError::PreconditionNotMet {
1171 reason: "topic registry poisoned",
1172 })?;
1173 topics.clear();
1174 }
1175 if let Ok(mut p) = self.inner.publishers.lock() {
1177 p.clear();
1178 }
1179 if let Ok(mut s) = self.inner.subscribers.lock() {
1180 s.clear();
1181 }
1182 let sinks = self.inner.builtin_subscriber.sinks();
1187 if let Ok(mut g) = sinks.participant.lock() {
1188 g.clear();
1189 }
1190 if let Ok(mut g) = sinks.topic.lock() {
1191 g.clear();
1192 }
1193 if let Ok(mut g) = sinks.publication.lock() {
1194 g.clear();
1195 }
1196 if let Ok(mut g) = sinks.subscription.lock() {
1197 g.clear();
1198 }
1199 Ok(())
1200 }
1201
1202 #[must_use]
1205 pub fn publishers_len(&self) -> usize {
1206 self.inner.publishers.lock().map(|p| p.len()).unwrap_or(0)
1207 }
1208
1209 #[must_use]
1211 pub fn subscribers_len(&self) -> usize {
1212 self.inner.subscribers.lock().map(|s| s.len()).unwrap_or(0)
1213 }
1214
1215 #[must_use]
1218 pub fn instance_handle(&self) -> InstanceHandle {
1219 self.inner.entity_state.instance_handle()
1220 }
1221
1222 #[cfg(feature = "std")]
1231 #[must_use]
1232 pub fn participant_handle(&self) -> InstanceHandle {
1233 match self.inner.runtime.as_ref() {
1234 Some(rt) => {
1235 let guid = zerodds_rtps::wire_types::Guid::new(
1236 rt.guid_prefix,
1237 zerodds_rtps::wire_types::EntityId::PARTICIPANT,
1238 );
1239 crate::instance_handle::InstanceHandle::from_guid(guid)
1240 }
1241 None => crate::instance_handle::HANDLE_NIL,
1242 }
1243 }
1244
1245 #[must_use]
1257 pub fn contains_entity(&self, handle: InstanceHandle) -> bool {
1258 if self.instance_handle() == handle {
1259 return true;
1260 }
1261 if let Ok(topics) = self.inner.topics.lock() {
1262 for t in topics.values() {
1263 if t.entity_state.instance_handle() == handle {
1264 return true;
1265 }
1266 }
1267 }
1268 if let Ok(pubs) = self.inner.publishers.lock() {
1269 if pubs.contains(&handle) {
1270 return true;
1271 }
1272 }
1273 if let Ok(subs) = self.inner.subscribers.lock() {
1274 if subs.contains(&handle) {
1275 return true;
1276 }
1277 }
1278 if let Ok(dws) = self.inner.datawriters.lock() {
1279 if dws.contains(&handle) {
1280 return true;
1281 }
1282 }
1283 if let Ok(drs) = self.inner.datareaders.lock() {
1284 if drs.contains(&handle) {
1285 return true;
1286 }
1287 }
1288 false
1289 }
1290
1291 #[cfg(feature = "std")]
1299 #[must_use]
1300 pub fn get_discovered_participants(&self) -> Vec<InstanceHandle> {
1301 let Some(rt) = self.inner.runtime.as_ref() else {
1302 return Vec::new();
1303 };
1304 let mut out = Vec::new();
1305 for d in rt.discovered_participants() {
1306 let h = InstanceHandle::from_guid(d.data.guid);
1307 if self.is_participant_ignored(h) {
1308 continue;
1309 }
1310 out.push(h);
1311 }
1312 out
1313 }
1314
1315 #[cfg(not(feature = "std"))]
1317 #[must_use]
1318 pub fn get_discovered_participants(&self) -> Vec<InstanceHandle> {
1319 Vec::new()
1320 }
1321
1322 #[cfg(feature = "std")]
1329 pub fn get_discovered_participant_data(
1330 &self,
1331 handle: InstanceHandle,
1332 ) -> Result<ParticipantBuiltinTopicData> {
1333 if self.is_participant_ignored(handle) {
1334 return Err(DdsError::BadParameter {
1335 what: "participant handle is ignored",
1336 });
1337 }
1338 let Some(rt) = self.inner.runtime.as_ref() else {
1339 return Err(DdsError::BadParameter {
1340 what: "no runtime — offline participant",
1341 });
1342 };
1343 for d in rt.discovered_participants() {
1344 if InstanceHandle::from_guid(d.data.guid) == handle {
1345 return Ok(ParticipantBuiltinTopicData::from_wire(&d.data));
1346 }
1347 }
1348 Err(DdsError::BadParameter {
1349 what: "unknown participant handle",
1350 })
1351 }
1352
1353 #[cfg(not(feature = "std"))]
1355 pub fn get_discovered_participant_data(
1356 &self,
1357 _handle: InstanceHandle,
1358 ) -> Result<ParticipantBuiltinTopicData> {
1359 Err(DdsError::BadParameter {
1360 what: "no runtime — offline participant",
1361 })
1362 }
1363
1364 #[cfg(feature = "std")]
1372 #[must_use]
1373 pub fn get_discovered_topics(&self) -> Vec<InstanceHandle> {
1374 let Some(rt) = self.inner.runtime.as_ref() else {
1375 return Vec::new();
1376 };
1377 let Ok(sedp) = rt.sedp.lock() else {
1378 return Vec::new();
1379 };
1380 let mut seen = BTreeSet::new();
1381 for p in sedp.cache().publications() {
1382 let key = TopicBuiltinTopicData::synthesize_key(&p.data.topic_name, &p.data.type_name);
1383 let h = InstanceHandle::from_guid(key);
1384 if self.is_topic_ignored(h) {
1385 continue;
1386 }
1387 seen.insert(h);
1388 }
1389 for s in sedp.cache().subscriptions() {
1390 let key = TopicBuiltinTopicData::synthesize_key(&s.data.topic_name, &s.data.type_name);
1391 let h = InstanceHandle::from_guid(key);
1392 if self.is_topic_ignored(h) {
1393 continue;
1394 }
1395 seen.insert(h);
1396 }
1397 seen.into_iter().collect()
1398 }
1399
1400 #[cfg(not(feature = "std"))]
1402 #[must_use]
1403 pub fn get_discovered_topics(&self) -> Vec<InstanceHandle> {
1404 Vec::new()
1405 }
1406
1407 #[cfg(feature = "std")]
1414 pub fn get_discovered_topic_data(
1415 &self,
1416 handle: InstanceHandle,
1417 ) -> Result<TopicBuiltinTopicData> {
1418 if self.is_topic_ignored(handle) {
1419 return Err(DdsError::BadParameter {
1420 what: "topic handle is ignored",
1421 });
1422 }
1423 let Some(rt) = self.inner.runtime.as_ref() else {
1424 return Err(DdsError::BadParameter {
1425 what: "no runtime — offline participant",
1426 });
1427 };
1428 let Ok(sedp) = rt.sedp.lock() else {
1429 return Err(DdsError::PreconditionNotMet {
1430 reason: "sedp poisoned",
1431 });
1432 };
1433 for p in sedp.cache().publications() {
1435 let topic = TopicBuiltinTopicData::from_publication(&p.data);
1436 if InstanceHandle::from_guid(topic.key) == handle {
1437 return Ok(topic);
1438 }
1439 }
1440 for s in sedp.cache().subscriptions() {
1441 let topic = TopicBuiltinTopicData::from_subscription(&s.data);
1442 if InstanceHandle::from_guid(topic.key) == handle {
1443 return Ok(topic);
1444 }
1445 }
1446 Err(DdsError::BadParameter {
1447 what: "unknown topic handle",
1448 })
1449 }
1450
1451 #[cfg(not(feature = "std"))]
1453 pub fn get_discovered_topic_data(
1454 &self,
1455 _handle: InstanceHandle,
1456 ) -> Result<TopicBuiltinTopicData> {
1457 Err(DdsError::BadParameter {
1458 what: "no runtime — offline participant",
1459 })
1460 }
1461
1462 #[must_use]
1489 pub fn get_builtin_subscriber(&self) -> Arc<BuiltinSubscriber> {
1490 Arc::clone(&self.inner.builtin_subscriber)
1491 }
1492
1493 pub fn set_listener(&self, listener: Option<ArcDomainParticipantListener>, mask: StatusMask) {
1501 if let Ok(mut slot) = self.inner.listener.lock() {
1502 *slot = listener.map(|l| (l, mask));
1503 }
1504 self.inner.entity_state.set_listener_mask(mask);
1506 }
1507
1508 #[must_use]
1511 pub fn get_listener(&self) -> Option<ArcDomainParticipantListener> {
1512 self.inner
1513 .listener
1514 .lock()
1515 .ok()
1516 .and_then(|s| s.as_ref().map(|(l, _)| Arc::clone(l)))
1517 }
1518
1519 #[must_use]
1523 #[allow(dead_code)] pub(crate) fn snapshot_listener(&self) -> Option<(ArcDomainParticipantListener, StatusMask)> {
1525 self.inner
1526 .listener
1527 .lock()
1528 .ok()
1529 .and_then(|s| s.as_ref().map(|(l, m)| (Arc::clone(l), *m)))
1530 }
1531}
1532
1533impl crate::entity::Entity for DomainParticipant {
1538 type Qos = DomainParticipantQos;
1539
1540 fn get_qos(&self) -> Self::Qos {
1541 self.inner.qos.lock().map(|q| q.clone()).unwrap_or_default()
1542 }
1543
1544 fn set_qos(&self, qos: Self::Qos) -> Result<()> {
1545 if let Ok(mut current) = self.inner.qos.lock() {
1548 *current = qos;
1549 }
1550 Ok(())
1551 }
1552
1553 fn enable(&self) -> Result<()> {
1554 self.inner.entity_state.enable();
1555 Ok(())
1556 }
1557
1558 fn entity_state(&self) -> Arc<crate::entity::EntityState> {
1559 Arc::clone(&self.inner.entity_state)
1560 }
1561}
1562
1563fn topic_inner<T: DdsType>(t: &Topic<T>) -> Arc<TopicInner> {
1566 t.inner()
1567}
1568
1569#[cfg(feature = "std")]
1572fn extract_equivalence_hash(
1573 ti: &zerodds_types::TypeIdentifier,
1574) -> Option<zerodds_types::EquivalenceHash> {
1575 use zerodds_types::TypeIdentifier;
1576 match ti {
1577 TypeIdentifier::EquivalenceHashMinimal(h) | TypeIdentifier::EquivalenceHashComplete(h) => {
1578 Some(*h)
1579 }
1580 _ => None,
1581 }
1582}
1583
1584fn reconstruct_topic<T: DdsType>(
1585 inner: Arc<TopicInner>,
1586 participant: DomainParticipant,
1587) -> Topic<T> {
1588 Topic::<T>::from_inner(inner, participant)
1593}
1594
1595impl<T: DdsType> Topic<T> {
1597 pub(crate) fn from_inner(inner: Arc<TopicInner>, participant: DomainParticipant) -> Self {
1598 Self::_from_inner_impl(inner, participant)
1599 }
1600}
1601
1602#[cfg(test)]
1607#[allow(clippy::expect_used, clippy::unwrap_used)]
1608mod tests {
1609 use super::*;
1610 use crate::dds_type::RawBytes;
1611
1612 #[test]
1613 fn participant_created_with_domain_id() {
1614 let p = DomainParticipant::new(42, DomainParticipantQos::default());
1615 assert_eq!(p.domain_id(), 42);
1616 assert_eq!(p.topics_len(), 0);
1617 }
1618
1619 #[test]
1623 fn random_guid_prefixes_share_host_id_within_process() {
1624 let p1 = random_guid_prefix();
1625 let p2 = random_guid_prefix();
1626 assert_eq!(p1.host_id(), p2.host_id(), "same-host within process");
1627 assert!(p1.is_same_host(p2));
1628
1629 let pid_le = std::process::id().to_le_bytes();
1630 let bytes = p1.to_bytes();
1631 assert_eq!(&bytes[4..8], &pid_le, "PID bytes in prefix[4..8]");
1632
1633 assert_ne!(p1, p2, "two prefixes must be distinct");
1636 }
1637
1638 #[test]
1639 fn host_id_bytes_deterministic_within_process() {
1640 let a = host_id_bytes();
1641 let b = host_id_bytes();
1642 assert_eq!(a, b, "OnceLock-cached host-id must be stable");
1643 }
1644
1645 #[test]
1646 fn create_topic_stores_in_registry() {
1647 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1648 let t1 = p
1649 .create_topic::<RawBytes>("Chatter", TopicQos::default())
1650 .unwrap();
1651 let t2 = p
1652 .create_topic::<RawBytes>("Chatter", TopicQos::default())
1653 .unwrap();
1654 assert_eq!(t1.name(), t2.name());
1655 assert_eq!(p.topics_len(), 1);
1656 }
1657
1658 #[test]
1659 fn create_topic_rejects_type_conflict() {
1660 #[derive(Debug)]
1662 struct DummyU32(u32);
1663 impl DdsType for DummyU32 {
1664 const TYPE_NAME: &'static str = "test::DummyU32";
1665 fn encode(
1666 &self,
1667 out: &mut alloc::vec::Vec<u8>,
1668 ) -> core::result::Result<(), crate::dds_type::EncodeError> {
1669 out.extend_from_slice(&self.0.to_le_bytes());
1670 Ok(())
1671 }
1672 fn decode(bytes: &[u8]) -> core::result::Result<Self, crate::dds_type::DecodeError> {
1673 if bytes.len() != 4 {
1674 return Err(crate::dds_type::DecodeError::Invalid { what: "u32 len" });
1675 }
1676 let mut a = [0u8; 4];
1677 a.copy_from_slice(bytes);
1678 Ok(Self(u32::from_le_bytes(a)))
1679 }
1680 }
1681
1682 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1683 let _ = p
1684 .create_topic::<RawBytes>("X", TopicQos::default())
1685 .unwrap();
1686 let err = p
1687 .create_topic::<DummyU32>("X", TopicQos::default())
1688 .unwrap_err();
1689 assert!(matches!(err, DdsError::InconsistentPolicy { .. }));
1690 }
1691
1692 #[test]
1693 fn create_topic_rejects_empty_name() {
1694 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1695 let err = p
1696 .create_topic::<RawBytes>("", TopicQos::default())
1697 .unwrap_err();
1698 assert!(matches!(err, DdsError::BadParameter { .. }));
1699 }
1700
1701 #[test]
1702 fn lookup_topicdescription_returns_local_topics() {
1703 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1704 let _t = p
1705 .create_topic::<RawBytes>("Hello", TopicQos::default())
1706 .unwrap();
1707 let h = p.lookup_topicdescription("Hello").expect("local lookup");
1708 use crate::topic::TopicDescription as _;
1709 assert_eq!(h.get_name(), "Hello");
1710 assert_eq!(h.get_type_name(), RawBytes::TYPE_NAME);
1711 assert_eq!(h.get_participant().domain_id(), 0);
1712 }
1713
1714 #[test]
1715 fn lookup_topicdescription_none_for_unknown() {
1716 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1717 assert!(p.lookup_topicdescription("Unknown").is_none());
1718 }
1719
1720 #[test]
1723 fn contains_entity_returns_true_for_self_handle() {
1724 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1725 let h = p.instance_handle();
1726 assert!(p.contains_entity(h));
1727 }
1728
1729 #[test]
1730 fn contains_entity_returns_true_for_local_topic() {
1731 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1732 let t = p
1733 .create_topic::<RawBytes>("Hi", TopicQos::default())
1734 .unwrap();
1735 let topic_handle = t.inner().entity_state.instance_handle();
1736 assert!(p.contains_entity(topic_handle));
1737 }
1738
1739 #[test]
1740 fn contains_entity_returns_true_for_local_publisher() {
1741 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1742 let pub_ = p.create_publisher(PublisherQos::default());
1743 let h = pub_.inner.entity_state.instance_handle();
1744 assert!(p.contains_entity(h));
1745 }
1746
1747 #[test]
1748 fn contains_entity_returns_true_for_local_subscriber() {
1749 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1750 let s = p.create_subscriber(SubscriberQos::default());
1751 let h = s.inner.entity_state.instance_handle();
1752 assert!(p.contains_entity(h));
1753 }
1754
1755 #[test]
1756 fn contains_entity_returns_false_for_unknown_handle() {
1757 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1758 let other = DomainParticipant::new(0, DomainParticipantQos::default());
1760 let other_h = other.instance_handle();
1761 assert!(!p.contains_entity(other_h));
1762 }
1763
1764 #[test]
1765 fn contains_entity_returns_false_for_topic_after_delete() {
1766 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1767 let t = p
1768 .create_topic::<RawBytes>("Tmp", TopicQos::default())
1769 .unwrap();
1770 let topic_handle = t.inner().entity_state.instance_handle();
1771 assert!(p.contains_entity(topic_handle));
1772 p.delete_contained_entities().unwrap();
1773 assert!(!p.contains_entity(topic_handle));
1774 }
1775
1776 #[test]
1777 fn contains_entity_recursive_finds_local_datawriter() {
1778 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1781 let topic = p
1782 .create_topic::<RawBytes>("Hello", TopicQos::default())
1783 .unwrap();
1784 let pub_ = p.create_publisher(PublisherQos::default());
1785 let dw = pub_
1786 .create_datawriter(&topic, crate::qos::DataWriterQos::default())
1787 .unwrap();
1788 let dw_handle = dw.instance_handle();
1789 assert!(p.contains_entity(dw_handle));
1790 assert!(pub_.contains_writer(dw_handle));
1792 }
1793
1794 #[test]
1795 fn contains_entity_recursive_finds_local_datareader() {
1796 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1797 let topic = p
1798 .create_topic::<RawBytes>("Hello2", TopicQos::default())
1799 .unwrap();
1800 let sub = p.create_subscriber(SubscriberQos::default());
1801 let dr = sub
1802 .create_datareader(&topic, crate::qos::DataReaderQos::default())
1803 .unwrap();
1804 let dr_handle = dr.subscription_handle();
1805 assert!(p.contains_entity(dr_handle));
1806 assert!(sub.contains_reader(dr_handle));
1807 }
1808
1809 #[test]
1810 fn contains_entity_recursive_does_not_find_foreign_datawriter() {
1811 let p1 = DomainParticipant::new(0, DomainParticipantQos::default());
1814 let p2 = DomainParticipant::new(1, DomainParticipantQos::default());
1815 let topic = p2
1816 .create_topic::<RawBytes>("Foreign", TopicQos::default())
1817 .unwrap();
1818 let pub2 = p2.create_publisher(PublisherQos::default());
1819 let dw2 = pub2
1820 .create_datawriter(&topic, crate::qos::DataWriterQos::default())
1821 .unwrap();
1822 assert!(!p1.contains_entity(dw2.instance_handle()));
1823 assert!(p2.contains_entity(dw2.instance_handle()));
1824 }
1825
1826 #[cfg(feature = "std")]
1827 #[test]
1828 fn find_topic_returns_immediately_for_local() {
1829 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1830 let _t = p
1831 .create_topic::<RawBytes>("Local", TopicQos::default())
1832 .unwrap();
1833 let started = std::time::Instant::now();
1834 let h = p
1835 .find_topic("Local", core::time::Duration::from_secs(5))
1836 .expect("local find");
1837 assert!(started.elapsed() < core::time::Duration::from_millis(50));
1840 use crate::topic::TopicDescription as _;
1841 assert_eq!(h.get_name(), "Local");
1842 }
1843
1844 #[cfg(feature = "std")]
1845 #[test]
1846 fn find_topic_times_out_when_unknown() {
1847 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1848 let err = p
1849 .find_topic("NotExists", core::time::Duration::from_millis(80))
1850 .unwrap_err();
1851 assert!(matches!(err, DdsError::Timeout));
1852 }
1853
1854 #[cfg(feature = "std")]
1855 #[test]
1856 fn find_topic_rejects_empty_name() {
1857 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1858 let err = p
1859 .find_topic("", core::time::Duration::from_millis(10))
1860 .unwrap_err();
1861 assert!(matches!(err, DdsError::BadParameter { .. }));
1862 }
1863
1864 #[test]
1865 fn create_contentfilteredtopic_rejects_empty_name() {
1866 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1867 let topic = p
1868 .create_topic::<RawBytes>("Base", TopicQos::default())
1869 .unwrap();
1870 let err = p
1871 .create_contentfilteredtopic("", &topic, "x > 0", alloc::vec::Vec::new())
1872 .unwrap_err();
1873 assert!(matches!(err, DdsError::BadParameter { .. }));
1874 }
1875
1876 #[test]
1877 fn create_contentfilteredtopic_rejects_empty_expression() {
1878 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1879 let topic = p
1880 .create_topic::<RawBytes>("Base", TopicQos::default())
1881 .unwrap();
1882 let err = p
1883 .create_contentfilteredtopic("CF", &topic, "", alloc::vec::Vec::new())
1884 .unwrap_err();
1885 assert!(matches!(err, DdsError::BadParameter { .. }));
1886 }
1887
1888 #[test]
1889 fn delete_contentfilteredtopic_accepts_own() {
1890 let p = DomainParticipant::new(0, DomainParticipantQos::default());
1891 let topic = p
1892 .create_topic::<RawBytes>("Base", TopicQos::default())
1893 .unwrap();
1894 let cft = p
1895 .create_contentfilteredtopic("CF", &topic, "x > 0", alloc::vec::Vec::new())
1896 .unwrap();
1897 p.delete_contentfilteredtopic(&cft).unwrap();
1898 }
1899
1900 #[cfg(feature = "std")]
1901 #[test]
1902 fn find_topic_resolves_via_sedp_subscription() {
1903 use crate::factory::DomainParticipantFactory;
1907 use core::time::Duration as CoreDur;
1908 use zerodds_rtps::publication_data::{DurabilityKind, ReliabilityKind, ReliabilityQos};
1909 use zerodds_rtps::subscription_data::SubscriptionBuiltinTopicData;
1910 use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix};
1911
1912 let p = DomainParticipantFactory::instance()
1913 .create_participant_with_config(
1914 43,
1915 DomainParticipantQos::default(),
1916 crate::runtime::RuntimeConfig::default(),
1917 )
1918 .expect("runtime start");
1919
1920 let target_topic = "DiscoveredViaSubSedp";
1921 if let Some(rt) = p.runtime() {
1922 if let Ok(mut sedp) = rt.sedp.lock() {
1923 let prefix = GuidPrefix::from_bytes([0xCD; 12]);
1924 let subdata = SubscriptionBuiltinTopicData {
1925 key: Guid::new(prefix, EntityId::user_reader_with_key([4, 5, 6])),
1926 participant_key: Guid::new(prefix, EntityId::PARTICIPANT),
1927 topic_name: target_topic.into(),
1928 type_name: "test::SubT".into(),
1929 durability: DurabilityKind::Volatile,
1930 reliability: ReliabilityQos {
1931 kind: ReliabilityKind::Reliable,
1932 max_blocking_time: zerodds_rtps::participant_data::Duration::from_secs(1),
1933 },
1934 ownership: zerodds_qos::OwnershipKind::Shared,
1935 liveliness: zerodds_qos::LivelinessQosPolicy::default(),
1936 deadline: zerodds_qos::DeadlineQosPolicy::default(),
1937 partition: alloc::vec::Vec::new(),
1938 user_data: alloc::vec::Vec::new(),
1939 topic_data: alloc::vec::Vec::new(),
1940 group_data: alloc::vec::Vec::new(),
1941 type_information: None,
1942 data_representation: alloc::vec::Vec::new(),
1943 content_filter: None,
1944 security_info: None,
1945 service_instance_name: None,
1946 related_entity_guid: None,
1947 topic_aliases: None,
1948 type_identifier: zerodds_types::TypeIdentifier::None,
1949 unicast_locators: Vec::new(),
1950 multicast_locators: Vec::new(),
1951 };
1952 sedp.cache_mut().insert_subscription(subdata, CoreDur::ZERO);
1953 }
1954 }
1955
1956 let h = p
1957 .find_topic(target_topic, CoreDur::from_millis(200))
1958 .expect("find via subscription");
1959 use crate::topic::TopicDescription as _;
1960 assert_eq!(h.get_name(), target_topic);
1961 assert_eq!(h.get_type_name(), "test::SubT");
1962 }
1963
1964 #[cfg(feature = "std")]
1965 #[test]
1966 fn find_topic_resolves_after_sedp_publication() {
1967 use crate::factory::DomainParticipantFactory;
1973 use core::time::Duration as CoreDur;
1974 use zerodds_rtps::publication_data::{
1975 DurabilityKind, PublicationBuiltinTopicData, ReliabilityKind, ReliabilityQos,
1976 };
1977 use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix};
1978
1979 let p = DomainParticipantFactory::instance()
1980 .create_participant_with_config(
1981 42,
1982 DomainParticipantQos::default(),
1983 crate::runtime::RuntimeConfig::default(),
1984 )
1985 .expect("runtime start");
1986
1987 let target_topic = "DiscoveredViaSedp";
1988 let target_type = "test::Discovered";
1989
1990 let p_inject = p.clone();
1993 let topic_name = String::from(target_topic);
1994 let type_name = String::from(target_type);
1995 let join = std::thread::spawn(move || {
1996 std::thread::sleep(CoreDur::from_millis(50));
1997 if let Some(rt) = p_inject.runtime() {
1998 if let Ok(mut sedp) = rt.sedp.lock() {
1999 let prefix = GuidPrefix::from_bytes([0xAB; 12]);
2000 let pubdata = PublicationBuiltinTopicData {
2001 key: Guid::new(prefix, EntityId::user_writer_with_key([1, 2, 3])),
2002 participant_key: Guid::new(prefix, EntityId::PARTICIPANT),
2003 topic_name,
2004 type_name,
2005 durability: DurabilityKind::Volatile,
2006 reliability: ReliabilityQos {
2007 kind: ReliabilityKind::Reliable,
2008 max_blocking_time: zerodds_rtps::participant_data::Duration::from_secs(
2009 1,
2010 ),
2011 },
2012 ownership: zerodds_qos::OwnershipKind::Shared,
2013 ownership_strength: 0,
2014 liveliness: zerodds_qos::LivelinessQosPolicy::default(),
2015 deadline: zerodds_qos::DeadlineQosPolicy::default(),
2016 lifespan: zerodds_qos::LifespanQosPolicy::default(),
2017 partition: alloc::vec::Vec::new(),
2018 user_data: alloc::vec::Vec::new(),
2019 topic_data: alloc::vec::Vec::new(),
2020 group_data: alloc::vec::Vec::new(),
2021 type_information: None,
2022 data_representation: alloc::vec::Vec::new(),
2023 security_info: None,
2024 service_instance_name: None,
2025 related_entity_guid: None,
2026 topic_aliases: None,
2027 type_identifier: zerodds_types::TypeIdentifier::None,
2028 unicast_locators: Vec::new(),
2029 multicast_locators: Vec::new(),
2030 };
2031 sedp.cache_mut().insert_publication(pubdata, CoreDur::ZERO);
2032 }
2033 }
2034 });
2035
2036 let result = p.find_topic(target_topic, CoreDur::from_secs(2));
2037 join.join().expect("inject thread");
2038 let h = result.expect("find_topic should resolve via SEDP");
2039 use crate::topic::TopicDescription as _;
2040 assert_eq!(h.get_name(), target_topic);
2041 assert_eq!(h.get_type_name(), target_type);
2042 }
2043
2044 #[test]
2049 fn ignore_participant_records_handle() {
2050 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2051 let h = InstanceHandle::from_raw(0xAA);
2052 assert!(!p.is_participant_ignored(h));
2053 p.ignore_participant(h).unwrap();
2054 assert!(p.is_participant_ignored(h));
2055 }
2056
2057 #[test]
2058 fn ignore_topic_records_handle() {
2059 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2060 let h = InstanceHandle::from_raw(0xBB);
2061 assert!(!p.is_topic_ignored(h));
2062 p.ignore_topic(h).unwrap();
2063 assert!(p.is_topic_ignored(h));
2064 }
2065
2066 #[test]
2067 fn ignore_publication_records_handle() {
2068 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2069 let h = InstanceHandle::from_raw(0xCC);
2070 assert!(!p.is_publication_ignored(h));
2071 p.ignore_publication(h).unwrap();
2072 assert!(p.is_publication_ignored(h));
2073 }
2074
2075 #[test]
2076 fn ignore_subscription_records_handle() {
2077 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2078 let h = InstanceHandle::from_raw(0xDD);
2079 assert!(!p.is_subscription_ignored(h));
2080 p.ignore_subscription(h).unwrap();
2081 assert!(p.is_subscription_ignored(h));
2082 }
2083
2084 #[test]
2085 fn ignore_lists_are_independent() {
2086 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2090 let h = InstanceHandle::from_raw(0xEE);
2091 p.ignore_topic(h).unwrap();
2092 assert!(p.is_topic_ignored(h));
2093 assert!(!p.is_participant_ignored(h));
2094 assert!(!p.is_publication_ignored(h));
2095 assert!(!p.is_subscription_ignored(h));
2096 }
2097
2098 #[test]
2099 fn ignore_is_monotonic_and_idempotent() {
2100 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2103 let h = InstanceHandle::from_raw(0x42);
2104 p.ignore_participant(h).unwrap();
2105 p.ignore_participant(h).unwrap();
2106 assert!(p.is_participant_ignored(h));
2107 }
2108
2109 #[test]
2110 fn delete_contained_entities_clears_topics_and_groups() {
2111 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2112 let _t = p
2113 .create_topic::<RawBytes>("ToBeRemoved", TopicQos::default())
2114 .unwrap();
2115 let _pub_ = p.create_publisher(PublisherQos::default());
2116 let _sub_ = p.create_subscriber(SubscriberQos::default());
2117 assert_eq!(p.topics_len(), 1);
2118 assert_eq!(p.publishers_len(), 1);
2119 assert_eq!(p.subscribers_len(), 1);
2120 p.delete_contained_entities().unwrap();
2121 assert_eq!(p.topics_len(), 0);
2122 assert_eq!(p.publishers_len(), 0);
2123 assert_eq!(p.subscribers_len(), 0);
2124 }
2125
2126 #[test]
2127 fn delete_contained_entities_clears_builtin_reader_inboxes() {
2128 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2129 use crate::builtin_topics::ParticipantBuiltinTopicData as DcpsP;
2132 use zerodds_rtps::wire_types::Guid;
2133 let bs = p.get_builtin_subscriber();
2134 bs.sinks()
2135 .push_participant(&DcpsP {
2136 key: Guid::from_bytes([7u8; 16]),
2137 user_data: alloc::vec::Vec::new(),
2138 })
2139 .unwrap();
2140 let r = bs.participant_reader();
2141 assert_eq!(r.read().unwrap().len(), 1);
2142 p.delete_contained_entities().unwrap();
2143 assert_eq!(r.read().unwrap().len(), 0);
2144 }
2145
2146 #[cfg(feature = "std")]
2147 #[test]
2148 fn get_discovered_participants_offline_is_empty() {
2149 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2152 assert!(p.get_discovered_participants().is_empty());
2153 }
2154
2155 #[cfg(feature = "std")]
2156 #[test]
2157 fn get_discovered_participant_data_offline_errors() {
2158 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2159 let err = p
2160 .get_discovered_participant_data(InstanceHandle::from_raw(1))
2161 .unwrap_err();
2162 assert!(matches!(err, DdsError::BadParameter { .. }));
2163 }
2164
2165 #[cfg(feature = "std")]
2166 #[test]
2167 fn get_discovered_topics_offline_is_empty() {
2168 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2169 assert!(p.get_discovered_topics().is_empty());
2170 }
2171
2172 #[cfg(feature = "std")]
2173 #[test]
2174 fn get_discovered_topic_data_offline_errors() {
2175 let p = DomainParticipant::new(0, DomainParticipantQos::default());
2176 let err = p
2177 .get_discovered_topic_data(InstanceHandle::from_raw(1))
2178 .unwrap_err();
2179 assert!(matches!(err, DdsError::BadParameter { .. }));
2180 }
2181
2182 #[cfg(feature = "std")]
2183 #[test]
2184 fn get_discovered_participants_lists_after_spdp_inject() {
2185 use crate::factory::DomainParticipantFactory;
2190 let p = DomainParticipantFactory::instance()
2191 .create_participant_with_config(
2192 30,
2193 DomainParticipantQos::default(),
2194 crate::runtime::RuntimeConfig::default(),
2195 )
2196 .expect("rt start");
2197
2198 use zerodds_rtps::participant_data::ParticipantBuiltinTopicData as WirePart;
2202 use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix, ProtocolVersion, VendorId};
2203 let remote = GuidPrefix::from_bytes([0xCA; 12]);
2204 let wire = WirePart {
2205 guid: Guid::new(remote, EntityId::PARTICIPANT),
2206 protocol_version: ProtocolVersion::V2_5,
2207 vendor_id: VendorId::ZERODDS,
2208 default_unicast_locator: None,
2209 default_multicast_locator: None,
2210 metatraffic_unicast_locator: None,
2211 metatraffic_multicast_locator: None,
2212 domain_id: Some(30),
2213 builtin_endpoint_set: 0,
2214 lease_duration: zerodds_rtps::participant_data::Duration::from_secs(100),
2215 user_data: alloc::vec::Vec::new(),
2216 properties: Default::default(),
2217 identity_token: None,
2218 permissions_token: None,
2219 identity_status_token: None,
2220 sig_algo_info: None,
2221 kx_algo_info: None,
2222 sym_cipher_algo_info: None,
2223 participant_security_info: None,
2224 };
2225 let beacon = zerodds_discovery::spdp::SpdpBeacon::new(wire.clone())
2226 .serialize()
2227 .expect("serialize");
2228 if let Some(rt) = p.runtime() {
2229 crate::runtime::handle_spdp_datagram_for_test(rt, &beacon);
2230 }
2231
2232 let handles = p.get_discovered_participants();
2233 assert_eq!(handles.len(), 1);
2234 let data = p
2235 .get_discovered_participant_data(handles[0])
2236 .expect("data lookup");
2237 assert_eq!(data.key, wire.guid);
2238 p.ignore_participant(handles[0]).unwrap();
2240 assert!(p.get_discovered_participants().is_empty());
2241 let err = p.get_discovered_participant_data(handles[0]).unwrap_err();
2242 assert!(matches!(err, DdsError::BadParameter { .. }));
2243 }
2244
2245 #[cfg(feature = "std")]
2246 #[test]
2247 fn get_discovered_topics_lists_unique_handles_for_pub_and_sub() {
2248 use crate::factory::DomainParticipantFactory;
2250 use core::time::Duration as CoreDur;
2251 use zerodds_rtps::publication_data::{
2252 DurabilityKind, PublicationBuiltinTopicData, ReliabilityKind, ReliabilityQos,
2253 };
2254 use zerodds_rtps::subscription_data::SubscriptionBuiltinTopicData;
2255 use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix};
2256
2257 let p = DomainParticipantFactory::instance()
2258 .create_participant_with_config(
2259 21,
2260 DomainParticipantQos::default(),
2261 crate::runtime::RuntimeConfig::default(),
2262 )
2263 .expect("rt start");
2264 if let Some(rt) = p.runtime() {
2265 if let Ok(mut sedp) = rt.sedp.lock() {
2266 let prefix = GuidPrefix::from_bytes([0x77; 12]);
2267 let pubdata = PublicationBuiltinTopicData {
2268 key: Guid::new(prefix, EntityId::user_writer_with_key([1, 2, 3])),
2269 participant_key: Guid::new(prefix, EntityId::PARTICIPANT),
2270 topic_name: "SharedTopic".into(),
2271 type_name: "SharedType".into(),
2272 durability: DurabilityKind::Volatile,
2273 reliability: ReliabilityQos {
2274 kind: ReliabilityKind::Reliable,
2275 max_blocking_time: zerodds_rtps::participant_data::Duration::from_secs(1),
2276 },
2277 ownership: zerodds_qos::OwnershipKind::Shared,
2278 ownership_strength: 0,
2279 liveliness: zerodds_qos::LivelinessQosPolicy::default(),
2280 deadline: zerodds_qos::DeadlineQosPolicy::default(),
2281 lifespan: zerodds_qos::LifespanQosPolicy::default(),
2282 partition: alloc::vec::Vec::new(),
2283 user_data: alloc::vec::Vec::new(),
2284 topic_data: alloc::vec::Vec::new(),
2285 group_data: alloc::vec::Vec::new(),
2286 type_information: None,
2287 data_representation: alloc::vec::Vec::new(),
2288 security_info: None,
2289 service_instance_name: None,
2290 related_entity_guid: None,
2291 topic_aliases: None,
2292 type_identifier: zerodds_types::TypeIdentifier::None,
2293 unicast_locators: Vec::new(),
2294 multicast_locators: Vec::new(),
2295 };
2296 let subdata = SubscriptionBuiltinTopicData {
2297 key: Guid::new(prefix, EntityId::user_reader_with_key([4, 5, 6])),
2298 participant_key: Guid::new(prefix, EntityId::PARTICIPANT),
2299 topic_name: "SharedTopic".into(),
2300 type_name: "SharedType".into(),
2301 durability: DurabilityKind::Volatile,
2302 reliability: ReliabilityQos {
2303 kind: ReliabilityKind::Reliable,
2304 max_blocking_time: zerodds_rtps::participant_data::Duration::from_secs(1),
2305 },
2306 ownership: zerodds_qos::OwnershipKind::Shared,
2307 liveliness: zerodds_qos::LivelinessQosPolicy::default(),
2308 deadline: zerodds_qos::DeadlineQosPolicy::default(),
2309 partition: alloc::vec::Vec::new(),
2310 user_data: alloc::vec::Vec::new(),
2311 topic_data: alloc::vec::Vec::new(),
2312 group_data: alloc::vec::Vec::new(),
2313 type_information: None,
2314 data_representation: alloc::vec::Vec::new(),
2315 content_filter: None,
2316 security_info: None,
2317 service_instance_name: None,
2318 related_entity_guid: None,
2319 topic_aliases: None,
2320 type_identifier: zerodds_types::TypeIdentifier::None,
2321 unicast_locators: Vec::new(),
2322 multicast_locators: Vec::new(),
2323 };
2324 sedp.cache_mut().insert_publication(pubdata, CoreDur::ZERO);
2325 sedp.cache_mut().insert_subscription(subdata, CoreDur::ZERO);
2326 }
2327 }
2328 let topics = p.get_discovered_topics();
2329 assert_eq!(topics.len(), 1, "Pub+Sub on same topic -> 1 handle");
2330 let data = p.get_discovered_topic_data(topics[0]).expect("topic data");
2331 assert_eq!(data.name, "SharedTopic");
2332 assert_eq!(data.type_name, "SharedType");
2333 }
2334
2335 #[cfg(feature = "std")]
2336 #[test]
2337 fn get_discovered_topic_data_filters_ignored() {
2338 use crate::factory::DomainParticipantFactory;
2339 use core::time::Duration as CoreDur;
2340 use zerodds_rtps::publication_data::{
2341 DurabilityKind, PublicationBuiltinTopicData, ReliabilityKind, ReliabilityQos,
2342 };
2343 use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix};
2344
2345 let p = DomainParticipantFactory::instance()
2346 .create_participant_with_config(
2347 22,
2348 DomainParticipantQos::default(),
2349 crate::runtime::RuntimeConfig::default(),
2350 )
2351 .expect("rt start");
2352 if let Some(rt) = p.runtime() {
2353 if let Ok(mut sedp) = rt.sedp.lock() {
2354 let prefix = GuidPrefix::from_bytes([0x55; 12]);
2355 let pubdata = PublicationBuiltinTopicData {
2356 key: Guid::new(prefix, EntityId::user_writer_with_key([1, 2, 3])),
2357 participant_key: Guid::new(prefix, EntityId::PARTICIPANT),
2358 topic_name: "ToIgnore".into(),
2359 type_name: "T".into(),
2360 durability: DurabilityKind::Volatile,
2361 reliability: ReliabilityQos {
2362 kind: ReliabilityKind::Reliable,
2363 max_blocking_time: zerodds_rtps::participant_data::Duration::from_secs(1),
2364 },
2365 ownership: zerodds_qos::OwnershipKind::Shared,
2366 ownership_strength: 0,
2367 liveliness: zerodds_qos::LivelinessQosPolicy::default(),
2368 deadline: zerodds_qos::DeadlineQosPolicy::default(),
2369 lifespan: zerodds_qos::LifespanQosPolicy::default(),
2370 partition: alloc::vec::Vec::new(),
2371 user_data: alloc::vec::Vec::new(),
2372 topic_data: alloc::vec::Vec::new(),
2373 group_data: alloc::vec::Vec::new(),
2374 type_information: None,
2375 data_representation: alloc::vec::Vec::new(),
2376 security_info: None,
2377 service_instance_name: None,
2378 related_entity_guid: None,
2379 topic_aliases: None,
2380 type_identifier: zerodds_types::TypeIdentifier::None,
2381 unicast_locators: Vec::new(),
2382 multicast_locators: Vec::new(),
2383 };
2384 sedp.cache_mut().insert_publication(pubdata, CoreDur::ZERO);
2385 }
2386 }
2387 let topics_before = p.get_discovered_topics();
2388 assert_eq!(topics_before.len(), 1);
2389 p.ignore_topic(topics_before[0]).unwrap();
2392 assert!(p.get_discovered_topics().is_empty());
2393 let err = p.get_discovered_topic_data(topics_before[0]).unwrap_err();
2394 assert!(matches!(err, DdsError::BadParameter { .. }));
2395 }
2396
2397 #[test]
2398 fn delete_contentfilteredtopic_rejects_foreign() {
2399 let p1 = DomainParticipant::new(0, DomainParticipantQos::default());
2400 let p2 = DomainParticipant::new(1, DomainParticipantQos::default());
2401 let topic = p1
2402 .create_topic::<RawBytes>("Base", TopicQos::default())
2403 .unwrap();
2404 let cft = p1
2405 .create_contentfilteredtopic("CF", &topic, "x > 0", alloc::vec::Vec::new())
2406 .unwrap();
2407 let err = p2.delete_contentfilteredtopic(&cft).unwrap_err();
2408 assert!(matches!(err, DdsError::BadParameter { .. }));
2409 }
2410}