1extern crate alloc;
27use alloc::string::{String, ToString};
28use alloc::sync::Arc;
29use alloc::vec::Vec;
30
31#[cfg(feature = "std")]
32use std::sync::Mutex;
33
34use zerodds_qos::{
35 DurabilityKind, DurabilityQosPolicy, HistoryKind, HistoryQosPolicy, ReliabilityKind,
36 ReliabilityQosPolicy,
37};
38
39use crate::builtin_topics::{
40 ParticipantBuiltinTopicData, PublicationBuiltinTopicData, SubscriptionBuiltinTopicData,
41 TOPIC_NAME_DCPS_PARTICIPANT, TOPIC_NAME_DCPS_PUBLICATION, TOPIC_NAME_DCPS_SUBSCRIPTION,
42 TOPIC_NAME_DCPS_TOPIC, TopicBuiltinTopicData,
43};
44use crate::dds_type::DdsType;
45use crate::error::{DdsError, Result};
46use crate::qos::{DataReaderQos, SubscriberQos, TopicQos};
47use crate::subscriber::{DataReader, Subscriber, SubscriberInner};
48use crate::topic::Topic;
49
50#[must_use]
53pub fn builtin_reader_qos() -> DataReaderQos {
54 let mut qos = DataReaderQos::default();
55 qos.reliability = ReliabilityQosPolicy {
56 kind: ReliabilityKind::Reliable,
57 max_blocking_time: qos.reliability.max_blocking_time,
58 };
59 qos.durability = DurabilityQosPolicy {
60 kind: DurabilityKind::TransientLocal,
61 };
62 qos.history = HistoryQosPolicy {
63 kind: HistoryKind::KeepLast,
64 depth: 1,
65 };
66 qos
67}
68
69#[derive(Debug)]
74pub struct BuiltinSubscriber {
75 subscriber: Subscriber,
78 participant_reader: DataReader<ParticipantBuiltinTopicData>,
80 topic_reader: DataReader<TopicBuiltinTopicData>,
82 publication_reader: DataReader<PublicationBuiltinTopicData>,
84 subscription_reader: DataReader<SubscriptionBuiltinTopicData>,
86 sinks: BuiltinSinks,
89}
90
91#[derive(Debug, Clone)]
94pub struct BuiltinSinks {
95 pub participant: Arc<Mutex<Vec<crate::runtime::UserSample>>>,
97 pub topic: Arc<Mutex<Vec<crate::runtime::UserSample>>>,
99 pub publication: Arc<Mutex<Vec<crate::runtime::UserSample>>>,
101 pub subscription: Arc<Mutex<Vec<crate::runtime::UserSample>>>,
103}
104
105impl BuiltinSinks {
106 pub fn push_participant(&self, sample: &ParticipantBuiltinTopicData) -> Result<()> {
113 push_into(&self.participant, sample)
114 }
115
116 pub fn push_topic(&self, sample: &TopicBuiltinTopicData) -> Result<()> {
121 push_into(&self.topic, sample)
122 }
123
124 pub fn push_publication(&self, sample: &PublicationBuiltinTopicData) -> Result<()> {
129 push_into(&self.publication, sample)
130 }
131
132 pub fn push_subscription(&self, sample: &SubscriptionBuiltinTopicData) -> Result<()> {
137 push_into(&self.subscription, sample)
138 }
139
140 pub fn dispose_publication(&self, guid: zerodds_rtps::wire_types::Guid) {
148 push_lifecycle(&self.publication, guid);
149 }
150
151 pub fn dispose_subscription(&self, guid: zerodds_rtps::wire_types::Guid) {
153 push_lifecycle(&self.subscription, guid);
154 }
155}
156
157fn push_lifecycle(
161 sink: &Arc<Mutex<Vec<crate::runtime::UserSample>>>,
162 guid: zerodds_rtps::wire_types::Guid,
163) {
164 if let Ok(mut guard) = sink.lock() {
165 guard.push(crate::runtime::UserSample::Lifecycle {
166 key_hash: guid.to_bytes(),
167 kind: zerodds_rtps::history_cache::ChangeKind::NotAliveDisposed,
168 });
169 }
170}
171
172fn push_into<T: DdsType>(
173 sink: &Arc<Mutex<Vec<crate::runtime::UserSample>>>,
174 sample: &T,
175) -> Result<()> {
176 let mut buf = Vec::new();
177 sample.encode(&mut buf).map_err(|e| DdsError::WireError {
178 message: format_err(&e),
179 })?;
180 let mut guard = sink.lock().map_err(|_| DdsError::PreconditionNotMet {
181 reason: "builtin sink mutex poisoned",
182 })?;
183 guard.push(crate::runtime::UserSample::Alive {
188 payload: crate::sample_bytes::SampleBytes::from_vec(buf),
189 writer_guid: [0u8; 16],
190 writer_strength: 0,
191 representation: 0,
194 big_endian: false,
195 source_timestamp: None,
196 source_sequence_number: -1,
199 });
200 Ok(())
201}
202
203fn format_err(e: &crate::dds_type::EncodeError) -> String {
204 use core::fmt::Write;
205 let mut s = String::new();
206 let _ = write!(s, "{e}");
207 s
208}
209
210impl BuiltinSubscriber {
211 #[must_use]
215 pub fn new() -> Self {
216 let subscriber = Subscriber::new(SubscriberQos::default(), None);
219 let inner = subscriber.inner.clone();
220
221 let qos = builtin_reader_qos();
222
223 let part_inbox: Arc<Mutex<Vec<crate::runtime::UserSample>>> =
226 Arc::new(Mutex::new(Vec::new()));
227 let topic_inbox: Arc<Mutex<Vec<crate::runtime::UserSample>>> =
228 Arc::new(Mutex::new(Vec::new()));
229 let pub_inbox: Arc<Mutex<Vec<crate::runtime::UserSample>>> =
230 Arc::new(Mutex::new(Vec::new()));
231 let sub_inbox: Arc<Mutex<Vec<crate::runtime::UserSample>>> =
232 Arc::new(Mutex::new(Vec::new()));
233
234 let participant_reader = DataReader::new_builtin(
235 Topic::<ParticipantBuiltinTopicData>::new_orphan(
236 TOPIC_NAME_DCPS_PARTICIPANT.to_string(),
237 TopicQos::default(),
238 ),
239 qos.clone(),
240 inner.clone(),
241 part_inbox.clone(),
242 );
243 let topic_reader = DataReader::new_builtin(
244 Topic::<TopicBuiltinTopicData>::new_orphan(
245 TOPIC_NAME_DCPS_TOPIC.to_string(),
246 TopicQos::default(),
247 ),
248 qos.clone(),
249 inner.clone(),
250 topic_inbox.clone(),
251 );
252 let publication_reader = DataReader::new_builtin(
253 Topic::<PublicationBuiltinTopicData>::new_orphan(
254 TOPIC_NAME_DCPS_PUBLICATION.to_string(),
255 TopicQos::default(),
256 ),
257 qos.clone(),
258 inner.clone(),
259 pub_inbox.clone(),
260 );
261 let subscription_reader = DataReader::new_builtin(
262 Topic::<SubscriptionBuiltinTopicData>::new_orphan(
263 TOPIC_NAME_DCPS_SUBSCRIPTION.to_string(),
264 TopicQos::default(),
265 ),
266 qos,
267 inner,
268 sub_inbox.clone(),
269 );
270
271 Self {
272 subscriber,
273 participant_reader,
274 topic_reader,
275 publication_reader,
276 subscription_reader,
277 sinks: BuiltinSinks {
278 participant: part_inbox,
279 topic: topic_inbox,
280 publication: pub_inbox,
281 subscription: sub_inbox,
282 },
283 }
284 }
285
286 #[must_use]
288 pub fn subscriber(&self) -> &Subscriber {
289 &self.subscriber
290 }
291
292 #[must_use]
295 pub fn sinks(&self) -> BuiltinSinks {
296 self.sinks.clone()
297 }
298
299 pub fn lookup_datareader<T: BuiltinTopic>(&self, topic_name: &str) -> Result<DataReader<T>> {
311 T::lookup(self, topic_name)
312 }
313
314 #[must_use]
317 pub fn participant_reader(&self) -> DataReader<ParticipantBuiltinTopicData> {
318 clone_reader(&self.participant_reader)
319 }
320
321 #[must_use]
323 pub fn topic_reader(&self) -> DataReader<TopicBuiltinTopicData> {
324 clone_reader(&self.topic_reader)
325 }
326
327 #[must_use]
329 pub fn publication_reader(&self) -> DataReader<PublicationBuiltinTopicData> {
330 clone_reader(&self.publication_reader)
331 }
332
333 #[must_use]
335 pub fn subscription_reader(&self) -> DataReader<SubscriptionBuiltinTopicData> {
336 clone_reader(&self.subscription_reader)
337 }
338}
339
340impl Default for BuiltinSubscriber {
341 fn default() -> Self {
342 Self::new()
343 }
344}
345
346pub trait BuiltinTopic: DdsType + private::Sealed + Sized {
352 const TOPIC_NAME: &'static str;
354 #[doc(hidden)]
355 fn lookup(sub: &BuiltinSubscriber, topic_name: &str) -> Result<DataReader<Self>>;
356}
357
358mod private {
359 pub trait Sealed {}
360 impl Sealed for crate::builtin_topics::ParticipantBuiltinTopicData {}
361 impl Sealed for crate::builtin_topics::TopicBuiltinTopicData {}
362 impl Sealed for crate::builtin_topics::PublicationBuiltinTopicData {}
363 impl Sealed for crate::builtin_topics::SubscriptionBuiltinTopicData {}
364}
365
366impl BuiltinTopic for ParticipantBuiltinTopicData {
367 const TOPIC_NAME: &'static str = TOPIC_NAME_DCPS_PARTICIPANT;
368 fn lookup(sub: &BuiltinSubscriber, topic_name: &str) -> Result<DataReader<Self>> {
369 if topic_name != Self::TOPIC_NAME {
370 return Err(DdsError::BadParameter {
371 what: "builtin topic_name does not match type parameter",
372 });
373 }
374 Ok(sub.participant_reader())
375 }
376}
377
378impl BuiltinTopic for TopicBuiltinTopicData {
379 const TOPIC_NAME: &'static str = TOPIC_NAME_DCPS_TOPIC;
380 fn lookup(sub: &BuiltinSubscriber, topic_name: &str) -> Result<DataReader<Self>> {
381 if topic_name != Self::TOPIC_NAME {
382 return Err(DdsError::BadParameter {
383 what: "builtin topic_name does not match type parameter",
384 });
385 }
386 Ok(sub.topic_reader())
387 }
388}
389
390impl BuiltinTopic for PublicationBuiltinTopicData {
391 const TOPIC_NAME: &'static str = TOPIC_NAME_DCPS_PUBLICATION;
392 fn lookup(sub: &BuiltinSubscriber, topic_name: &str) -> Result<DataReader<Self>> {
393 if topic_name != Self::TOPIC_NAME {
394 return Err(DdsError::BadParameter {
395 what: "builtin topic_name does not match type parameter",
396 });
397 }
398 Ok(sub.publication_reader())
399 }
400}
401
402impl BuiltinTopic for SubscriptionBuiltinTopicData {
403 const TOPIC_NAME: &'static str = TOPIC_NAME_DCPS_SUBSCRIPTION;
404 fn lookup(sub: &BuiltinSubscriber, topic_name: &str) -> Result<DataReader<Self>> {
405 if topic_name != Self::TOPIC_NAME {
406 return Err(DdsError::BadParameter {
407 what: "builtin topic_name does not match type parameter",
408 });
409 }
410 Ok(sub.subscription_reader())
411 }
412}
413
414fn clone_reader<T: DdsType + Send + Sync + 'static>(r: &DataReader<T>) -> DataReader<T> {
420 DataReader::<T>::new_builtin(
421 r.topic().clone(),
422 r.qos().clone(),
423 builtin_clone_subscriber_inner(r),
424 r.__inbox_handle(),
425 )
426}
427
428fn builtin_clone_subscriber_inner<T: DdsType>(_r: &DataReader<T>) -> Arc<SubscriberInner> {
429 Arc::new(SubscriberInner {
432 qos: std::sync::Mutex::new(SubscriberQos::default()),
433 entity_state: crate::entity::EntityState::new(),
434 runtime: None,
435 listener: std::sync::Mutex::new(None),
436 participant: std::sync::Mutex::new(None),
437 access_scope: crate::coherent_set::GroupAccessScope::new(),
438 datareaders: std::sync::Mutex::new(alloc::vec::Vec::new()),
439 })
440}
441
442#[cfg(test)]
443#[allow(clippy::expect_used, clippy::unwrap_used)]
444mod tests {
445 use super::*;
446 use crate::builtin_topics::{
447 ParticipantBuiltinTopicData, PublicationBuiltinTopicData, SubscriptionBuiltinTopicData,
448 TopicBuiltinTopicData,
449 };
450 use zerodds_rtps::wire_types::Guid;
451
452 fn mk_guid(seed: u8) -> Guid {
453 let mut b = [0u8; 16];
454 for (i, slot) in b.iter_mut().enumerate() {
455 *slot = seed.wrapping_add(i as u8);
456 }
457 Guid::from_bytes(b)
458 }
459
460 #[test]
461 fn builtin_subscriber_has_four_readers() {
462 let bs = BuiltinSubscriber::new();
463 assert_eq!(bs.participant_reader().topic().name(), "DCPSParticipant");
464 assert_eq!(bs.topic_reader().topic().name(), "DCPSTopic");
465 assert_eq!(bs.publication_reader().topic().name(), "DCPSPublication");
466 assert_eq!(bs.subscription_reader().topic().name(), "DCPSSubscription");
467 }
468
469 #[test]
470 fn builtin_reader_qos_is_spec_default() {
471 let q = builtin_reader_qos();
472 assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
473 assert_eq!(q.durability.kind, DurabilityKind::TransientLocal);
474 assert_eq!(q.history.kind, HistoryKind::KeepLast);
475 assert_eq!(q.history.depth, 1);
476 }
477
478 #[test]
479 fn lookup_datareader_routes_by_type() {
480 let bs = BuiltinSubscriber::new();
481 let r = bs
482 .lookup_datareader::<ParticipantBuiltinTopicData>("DCPSParticipant")
483 .unwrap();
484 assert_eq!(r.topic().name(), "DCPSParticipant");
485
486 let r = bs
487 .lookup_datareader::<TopicBuiltinTopicData>("DCPSTopic")
488 .unwrap();
489 assert_eq!(r.topic().name(), "DCPSTopic");
490
491 let r = bs
492 .lookup_datareader::<PublicationBuiltinTopicData>("DCPSPublication")
493 .unwrap();
494 assert_eq!(r.topic().name(), "DCPSPublication");
495
496 let r = bs
497 .lookup_datareader::<SubscriptionBuiltinTopicData>("DCPSSubscription")
498 .unwrap();
499 assert_eq!(r.topic().name(), "DCPSSubscription");
500 }
501
502 #[test]
503 fn lookup_datareader_rejects_wrong_topic_name() {
504 let bs = BuiltinSubscriber::new();
505 let err = bs
506 .lookup_datareader::<ParticipantBuiltinTopicData>("DCPSPublication")
507 .unwrap_err();
508 assert!(matches!(err, DdsError::BadParameter { .. }));
509 }
510
511 #[test]
512 fn sinks_push_participant_lands_in_reader() {
513 let bs = BuiltinSubscriber::new();
514 let sample = ParticipantBuiltinTopicData {
515 key: mk_guid(0xA0),
516 user_data: alloc::vec![],
517 };
518 bs.sinks().push_participant(&sample).unwrap();
519 let reader = bs
520 .lookup_datareader::<ParticipantBuiltinTopicData>("DCPSParticipant")
521 .unwrap();
522 let samples = reader.take().unwrap();
523 assert_eq!(samples.len(), 1);
524 assert_eq!(samples[0].key, sample.key);
525 }
526
527 #[test]
528 fn sinks_push_topic_lands_in_reader() {
529 let bs = BuiltinSubscriber::new();
530 let sample = TopicBuiltinTopicData {
531 key: TopicBuiltinTopicData::synthesize_key("MyT", "MyType"),
532 name: "MyT".to_string(),
533 type_name: "MyType".to_string(),
534 durability: DurabilityKind::Volatile,
535 reliability: ReliabilityKind::Reliable,
536 };
537 bs.sinks().push_topic(&sample).unwrap();
538 let reader = bs
539 .lookup_datareader::<TopicBuiltinTopicData>("DCPSTopic")
540 .unwrap();
541 let samples = reader.take().unwrap();
542 assert_eq!(samples.len(), 1);
543 assert_eq!(samples[0].name, "MyT");
544 }
545
546 #[test]
547 fn sinks_push_publication_lands_in_reader() {
548 let bs = BuiltinSubscriber::new();
549 let sample = PublicationBuiltinTopicData {
550 key: mk_guid(0xB0),
551 participant_key: mk_guid(0xC0),
552 topic_name: "T".to_string(),
553 type_name: "T".to_string(),
554 durability: DurabilityKind::Volatile,
555 reliability: ReliabilityKind::BestEffort,
556 ownership: zerodds_qos::OwnershipKind::Shared,
557 ownership_strength: 0,
558 liveliness_lease_seconds: 0,
559 deadline_seconds: 0,
560 lifespan_seconds: 0,
561 partition: alloc::vec![],
562 };
563 bs.sinks().push_publication(&sample).unwrap();
564 let reader = bs
565 .lookup_datareader::<PublicationBuiltinTopicData>("DCPSPublication")
566 .unwrap();
567 let samples = reader.take().unwrap();
568 assert_eq!(samples.len(), 1);
569 assert_eq!(samples[0].key, sample.key);
570 }
571
572 #[test]
573 fn sinks_push_subscription_lands_in_reader() {
574 let bs = BuiltinSubscriber::new();
575 let sample = SubscriptionBuiltinTopicData {
576 key: mk_guid(0xD0),
577 participant_key: mk_guid(0xE0),
578 topic_name: "T".to_string(),
579 type_name: "T".to_string(),
580 durability: DurabilityKind::Volatile,
581 reliability: ReliabilityKind::Reliable,
582 ownership: zerodds_qos::OwnershipKind::Shared,
583 liveliness_lease_seconds: 0,
584 deadline_seconds: 0,
585 partition: alloc::vec![],
586 };
587 bs.sinks().push_subscription(&sample).unwrap();
588 let reader = bs
589 .lookup_datareader::<SubscriptionBuiltinTopicData>("DCPSSubscription")
590 .unwrap();
591 let samples = reader.take().unwrap();
592 assert_eq!(samples.len(), 1);
593 assert_eq!(samples[0].topic_name, "T");
594 }
595
596 #[test]
597 fn subscriber_handle_is_accessible() {
598 let bs = BuiltinSubscriber::new();
599 let _: &Subscriber = bs.subscriber();
602 }
603
604 #[test]
605 fn default_constructs_via_new() {
606 let bs: BuiltinSubscriber = Default::default();
607 assert_eq!(bs.participant_reader().topic().name(), "DCPSParticipant");
608 }
609
610 #[test]
611 fn lookup_topic_with_wrong_topic_name_for_topic_type() {
612 let bs = BuiltinSubscriber::new();
613 let err = bs
614 .lookup_datareader::<TopicBuiltinTopicData>("DCPSParticipant")
615 .unwrap_err();
616 assert!(matches!(err, DdsError::BadParameter { .. }));
617 }
618
619 #[test]
620 fn lookup_publication_with_wrong_topic_name() {
621 let bs = BuiltinSubscriber::new();
622 let err = bs
623 .lookup_datareader::<PublicationBuiltinTopicData>("DCPSTopic")
624 .unwrap_err();
625 assert!(matches!(err, DdsError::BadParameter { .. }));
626 }
627
628 #[test]
629 fn lookup_subscription_with_wrong_topic_name() {
630 let bs = BuiltinSubscriber::new();
631 let err = bs
632 .lookup_datareader::<SubscriptionBuiltinTopicData>("DCPSTopic")
633 .unwrap_err();
634 assert!(matches!(err, DdsError::BadParameter { .. }));
635 }
636
637 #[test]
638 fn read_does_not_remove_samples() {
639 let bs = BuiltinSubscriber::new();
640 let sample = ParticipantBuiltinTopicData {
641 key: mk_guid(0x33),
642 user_data: alloc::vec![],
643 };
644 bs.sinks().push_participant(&sample).unwrap();
645 let reader = bs.participant_reader();
646 let s1 = reader.read().unwrap();
647 let s2 = reader.read().unwrap();
648 assert_eq!(s1.len(), 1);
649 assert_eq!(s2.len(), 1);
650 }
651}