Skip to main content

cyclonedds/
builtin.rs

1use crate::{
2    error::check,
3    qos::Qos,
4    xtypes::{FindScope, TopicDescriptor, TypeInfo, TypeObject},
5    DdsError, DdsResult, DdsType, UntypedTopic,
6};
7use cyclonedds_rust_sys::*;
8use std::ffi::{c_char, CStr};
9
10pub const DDS_MIN_PSEUDO_HANDLE: dds_entity_t = 0x7fff0000u32 as dds_entity_t;
11pub const BUILTIN_TOPIC_DCPSPARTICIPANT: dds_entity_t = DDS_MIN_PSEUDO_HANDLE + 1;
12pub const BUILTIN_TOPIC_DCPSTOPIC: dds_entity_t = DDS_MIN_PSEUDO_HANDLE + 2;
13pub const BUILTIN_TOPIC_DCPSPUBLICATION: dds_entity_t = DDS_MIN_PSEUDO_HANDLE + 3;
14pub const BUILTIN_TOPIC_DCPSSUBSCRIPTION: dds_entity_t = DDS_MIN_PSEUDO_HANDLE + 4;
15
16fn dup_cstr(ptr: *const c_char) -> *mut c_char {
17    if ptr.is_null() {
18        std::ptr::null_mut()
19    } else {
20        unsafe { dds_string_dup(ptr) }
21    }
22}
23
24#[repr(C)]
25pub struct BuiltinParticipantSample {
26    key: dds_guid_t,
27    qos: *mut dds_qos_t,
28}
29
30unsafe impl Send for BuiltinParticipantSample {}
31
32impl BuiltinParticipantSample {
33    pub fn key(&self) -> dds_guid_t {
34        self.key
35    }
36
37    pub fn qos(&self) -> DdsResult<Option<Qos>> {
38        Qos::from_raw_clone(self.qos)
39    }
40
41    /// Returns the participant name if set (via the QoS entity_name policy).
42    ///
43    /// In CycloneDDS, the participant name is not a direct field of
44    /// `dds_builtintopic_participant_t` but is carried as the entity name
45    /// in the participant's QoS.
46    pub fn participant_name(&self) -> Option<String> {
47        self.qos()
48            .ok()
49            .flatten()
50            .and_then(|q| q.entity_name().ok().flatten())
51    }
52}
53
54impl Clone for BuiltinParticipantSample {
55    fn clone(&self) -> Self {
56        Self {
57            key: self.key,
58            qos: Qos::from_raw_clone(self.qos)
59                .ok()
60                .flatten()
61                .map_or(std::ptr::null_mut(), |q| {
62                    let ptr = q.as_ptr() as *mut dds_qos_t;
63                    std::mem::forget(q);
64                    ptr
65                }),
66        }
67    }
68}
69
70impl Drop for BuiltinParticipantSample {
71    fn drop(&mut self) {
72        if !self.qos.is_null() {
73            unsafe { dds_delete_qos(self.qos) };
74        }
75    }
76}
77
78impl DdsType for BuiltinParticipantSample {
79    fn type_name() -> &'static str {
80        "DCPSParticipant"
81    }
82
83    fn ops() -> Vec<u32> {
84        Vec::new()
85    }
86
87    unsafe fn clone_out(ptr: *const Self) -> Self {
88        let src = &*ptr;
89        src.clone()
90    }
91}
92
93#[repr(C)]
94pub struct BuiltinTopicSample {
95    key: dds_builtintopic_topic_key_t,
96    topic_name: *mut c_char,
97    type_name: *mut c_char,
98    qos: *mut dds_qos_t,
99}
100
101unsafe impl Send for BuiltinTopicSample {}
102
103impl BuiltinTopicSample {
104    pub fn key(&self) -> dds_builtintopic_topic_key_t {
105        self.key
106    }
107
108    pub fn topic_name(&self) -> String {
109        if self.topic_name.is_null() {
110            String::new()
111        } else {
112            unsafe { CStr::from_ptr(self.topic_name) }
113                .to_string_lossy()
114                .into_owned()
115        }
116    }
117
118    pub fn type_name_value(&self) -> String {
119        if self.type_name.is_null() {
120            String::new()
121        } else {
122            unsafe { CStr::from_ptr(self.type_name) }
123                .to_string_lossy()
124                .into_owned()
125        }
126    }
127
128    pub fn qos(&self) -> DdsResult<Option<Qos>> {
129        Qos::from_raw_clone(self.qos)
130    }
131
132    pub fn find_topic(
133        &self,
134        participant: dds_entity_t,
135        scope: FindScope,
136        timeout: dds_duration_t,
137    ) -> DdsResult<Option<UntypedTopic>> {
138        let name = std::ffi::CString::new(self.topic_name())
139            .map_err(|_| DdsError::BadParameter("topic name contains null".into()))?;
140        let handle = unsafe {
141            dds_find_topic(
142                scope.as_raw(),
143                participant,
144                name.as_ptr(),
145                std::ptr::null(),
146                timeout,
147            )
148        };
149        if handle == 0 {
150            return Ok(None);
151        }
152        crate::error::check_entity(handle).map(|entity| Some(UntypedTopic::from_entity(entity)))
153    }
154}
155
156impl Clone for BuiltinTopicSample {
157    fn clone(&self) -> Self {
158        Self {
159            key: self.key,
160            topic_name: dup_cstr(self.topic_name),
161            type_name: dup_cstr(self.type_name),
162            qos: Qos::from_raw_clone(self.qos)
163                .ok()
164                .flatten()
165                .map_or(std::ptr::null_mut(), |q| {
166                    let ptr = q.as_ptr() as *mut dds_qos_t;
167                    std::mem::forget(q);
168                    ptr
169                }),
170        }
171    }
172}
173
174impl Drop for BuiltinTopicSample {
175    fn drop(&mut self) {
176        if !self.topic_name.is_null() {
177            unsafe { dds_string_free(self.topic_name) };
178        }
179        if !self.type_name.is_null() {
180            unsafe { dds_string_free(self.type_name) };
181        }
182        if !self.qos.is_null() {
183            unsafe { dds_delete_qos(self.qos) };
184        }
185    }
186}
187
188impl DdsType for BuiltinTopicSample {
189    fn type_name() -> &'static str {
190        "DCPSTopic"
191    }
192
193    fn ops() -> Vec<u32> {
194        Vec::new()
195    }
196
197    unsafe fn clone_out(ptr: *const Self) -> Self {
198        let src = &*ptr;
199        src.clone()
200    }
201}
202
203impl std::fmt::Debug for BuiltinParticipantSample {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        f.debug_struct("BuiltinParticipantSample")
206            .field("key", &self.key)
207            .field("participant_name", &self.participant_name())
208            .finish()
209    }
210}
211
212impl std::fmt::Debug for BuiltinTopicSample {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        f.debug_struct("BuiltinTopicSample")
215            .field("topic_name", &self.topic_name())
216            .field("type_name", &self.type_name_value())
217            .finish()
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use std::ffi::CString;
225
226    #[test]
227    fn builtin_topic_sample_clone_preserves_strings_and_qos() {
228        let qos = Qos::builder()
229            .reliable()
230            .entity_name("builtin-topic")
231            .build()
232            .unwrap();
233        let topic_name = CString::new("topic-a").unwrap();
234        let type_name = CString::new("type-a").unwrap();
235
236        let sample = BuiltinTopicSample {
237            key: dds_builtintopic_topic_key_t { d: [1; 16] },
238            topic_name: unsafe { dds_string_dup(topic_name.as_ptr()) },
239            type_name: unsafe { dds_string_dup(type_name.as_ptr()) },
240            qos: {
241                let ptr = qos.as_ptr() as *mut dds_qos_t;
242                std::mem::forget(qos);
243                ptr
244            },
245        };
246
247        let cloned = sample.clone();
248        assert_eq!(cloned.topic_name(), "topic-a");
249        assert_eq!(cloned.type_name_value(), "type-a");
250        let cloned_qos = cloned.qos().unwrap().unwrap();
251        assert_eq!(cloned_qos.entity_name().unwrap().unwrap(), "builtin-topic");
252    }
253
254    #[test]
255    fn builtin_participant_sample_clone_preserves_qos() {
256        let qos = Qos::builder()
257            .entity_name("builtin-participant")
258            .build()
259            .unwrap();
260        let sample = BuiltinParticipantSample {
261            key: dds_guid_t { v: [9; 16] },
262            qos: {
263                let ptr = qos.as_ptr() as *mut dds_qos_t;
264                std::mem::forget(qos);
265                ptr
266            },
267        };
268
269        let cloned = sample.clone();
270        let cloned_qos = cloned.qos().unwrap().unwrap();
271        assert_eq!(
272            cloned_qos.entity_name().unwrap().unwrap(),
273            "builtin-participant"
274        );
275        assert_eq!(cloned.key().v, [9; 16]);
276    }
277}
278
279#[repr(C)]
280pub struct BuiltinEndpointSample {
281    key: dds_guid_t,
282    participant_key: dds_guid_t,
283    participant_instance_handle: dds_instance_handle_t,
284    topic_name: *mut c_char,
285    type_name: *mut c_char,
286    qos: *mut dds_qos_t,
287}
288
289unsafe impl Send for BuiltinEndpointSample {}
290
291impl BuiltinEndpointSample {
292    fn as_native_ptr(&self) -> *mut dds_builtintopic_endpoint_t {
293        self as *const Self as *mut dds_builtintopic_endpoint_t
294    }
295
296    pub fn key(&self) -> dds_guid_t {
297        self.key
298    }
299
300    pub fn participant_key(&self) -> dds_guid_t {
301        self.participant_key
302    }
303
304    pub fn participant_instance_handle(&self) -> dds_instance_handle_t {
305        self.participant_instance_handle
306    }
307
308    pub fn topic_name(&self) -> String {
309        if self.topic_name.is_null() {
310            String::new()
311        } else {
312            unsafe { CStr::from_ptr(self.topic_name) }
313                .to_string_lossy()
314                .into_owned()
315        }
316    }
317
318    pub fn type_name_value(&self) -> String {
319        if self.type_name.is_null() {
320            String::new()
321        } else {
322            unsafe { CStr::from_ptr(self.type_name) }
323                .to_string_lossy()
324                .into_owned()
325        }
326    }
327
328    /// Alias for [`type_name_value()`](Self::type_name_value) for ergonomic API
329    /// consistency with the cyclonedds-python binding.
330    pub fn type_name(&self) -> String {
331        self.type_name_value()
332    }
333
334    pub fn qos(&self) -> DdsResult<Option<Qos>> {
335        Qos::from_raw_clone(self.qos)
336    }
337
338    pub fn type_info(&self) -> DdsResult<TypeInfo> {
339        let mut ptr = std::ptr::null();
340        unsafe {
341            check(dds_builtintopic_get_endpoint_type_info(
342                self.as_native_ptr(),
343                &mut ptr,
344            ))?;
345        }
346        if ptr.is_null() {
347            return Err(DdsError::Other(
348                "CycloneDDS returned null endpoint typeinfo".into(),
349            ));
350        }
351        Ok(TypeInfo::from_raw(ptr.cast_mut()))
352    }
353
354    pub fn create_topic_descriptor(
355        &self,
356        participant: dds_entity_t,
357        scope: FindScope,
358        timeout: dds_duration_t,
359    ) -> DdsResult<TopicDescriptor> {
360        self.type_info()?
361            .create_topic_descriptor(participant, scope, timeout)
362    }
363
364    pub fn create_topic(
365        &self,
366        participant: dds_entity_t,
367        scope: FindScope,
368        timeout: dds_duration_t,
369    ) -> DdsResult<UntypedTopic> {
370        let descriptor = self.create_topic_descriptor(participant, scope, timeout)?;
371        descriptor.create_topic(participant, &self.topic_name())
372    }
373
374    pub fn create_topic_with_qos(
375        &self,
376        participant: dds_entity_t,
377        scope: FindScope,
378        timeout: dds_duration_t,
379        qos: &Qos,
380    ) -> DdsResult<UntypedTopic> {
381        let descriptor = self.create_topic_descriptor(participant, scope, timeout)?;
382        descriptor.create_topic_with_qos(participant, &self.topic_name(), qos)
383    }
384
385    pub fn find_topic(
386        &self,
387        participant: dds_entity_t,
388        scope: FindScope,
389        timeout: dds_duration_t,
390    ) -> DdsResult<Option<UntypedTopic>> {
391        let type_info = self.type_info()?;
392        let name = std::ffi::CString::new(self.topic_name())
393            .map_err(|_| DdsError::BadParameter("topic name contains null".into()))?;
394        let handle = unsafe {
395            dds_find_topic(
396                scope.as_raw(),
397                participant,
398                name.as_ptr(),
399                type_info.as_ptr(),
400                timeout,
401            )
402        };
403        if handle == 0 {
404            return Ok(None);
405        }
406        crate::error::check_entity(handle).map(|entity| Some(UntypedTopic::from_entity(entity)))
407    }
408
409    pub fn minimal_type_object(
410        &self,
411        participant: dds_entity_t,
412        timeout: dds_duration_t,
413    ) -> DdsResult<Option<TypeObject>> {
414        self.type_info()?.minimal_type_object(participant, timeout)
415    }
416
417    pub fn complete_type_object(
418        &self,
419        participant: dds_entity_t,
420        timeout: dds_duration_t,
421    ) -> DdsResult<Option<TypeObject>> {
422        self.type_info()?.complete_type_object(participant, timeout)
423    }
424}
425
426impl Clone for BuiltinEndpointSample {
427    fn clone(&self) -> Self {
428        Self {
429            key: self.key,
430            participant_key: self.participant_key,
431            participant_instance_handle: self.participant_instance_handle,
432            topic_name: dup_cstr(self.topic_name),
433            type_name: dup_cstr(self.type_name),
434            qos: Qos::from_raw_clone(self.qos)
435                .ok()
436                .flatten()
437                .map_or(std::ptr::null_mut(), |q| {
438                    let ptr = q.as_ptr() as *mut dds_qos_t;
439                    std::mem::forget(q);
440                    ptr
441                }),
442        }
443    }
444}
445
446impl Drop for BuiltinEndpointSample {
447    fn drop(&mut self) {
448        if !self.topic_name.is_null() {
449            unsafe { dds_string_free(self.topic_name) };
450        }
451        if !self.type_name.is_null() {
452            unsafe { dds_string_free(self.type_name) };
453        }
454        if !self.qos.is_null() {
455            unsafe { dds_delete_qos(self.qos) };
456        }
457    }
458}
459
460impl DdsType for BuiltinEndpointSample {
461    fn type_name() -> &'static str {
462        "BuiltinEndpointSample"
463    }
464
465    fn ops() -> Vec<u32> {
466        Vec::new()
467    }
468
469    unsafe fn clone_out(ptr: *const Self) -> Self {
470        let src = &*ptr;
471        src.clone()
472    }
473}
474
475impl std::fmt::Debug for BuiltinEndpointSample {
476    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477        f.debug_struct("BuiltinEndpointSample")
478            .field("topic_name", &self.topic_name())
479            .field("type_name", &self.type_name_value())
480            .finish()
481    }
482}
483
484#[cfg(test)]
485mod endpoint_tests {
486    use super::*;
487    use std::ffi::CString;
488
489    #[test]
490    fn builtin_endpoint_sample_clone_preserves_strings_and_qos() {
491        let qos = Qos::builder()
492            .reliable()
493            .entity_name("builtin-endpoint")
494            .build()
495            .unwrap();
496        let topic_name = CString::new("topic-endpoint").unwrap();
497        let type_name = CString::new("type-endpoint").unwrap();
498
499        let sample = BuiltinEndpointSample {
500            key: dds_guid_t { v: [1; 16] },
501            participant_key: dds_guid_t { v: [2; 16] },
502            participant_instance_handle: 42,
503            topic_name: unsafe { dds_string_dup(topic_name.as_ptr()) },
504            type_name: unsafe { dds_string_dup(type_name.as_ptr()) },
505            qos: {
506                let ptr = qos.as_ptr() as *mut dds_qos_t;
507                std::mem::forget(qos);
508                ptr
509            },
510        };
511
512        let cloned = sample.clone();
513        assert_eq!(cloned.topic_name(), "topic-endpoint");
514        assert_eq!(cloned.type_name_value(), "type-endpoint");
515        assert_eq!(cloned.participant_instance_handle(), 42);
516        let cloned_qos = cloned.qos().unwrap().unwrap();
517        assert_eq!(
518            cloned_qos.entity_name().unwrap().unwrap(),
519            "builtin-endpoint"
520        );
521    }
522}