cyclonedds 3.0.0-alpha.3

Safe Rust wrapper for Eclipse CycloneDDS
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
use crate::{
    error::{check, check_entity},
    statistics::Statistics,
    status::EntityStatus,
    xtypes::{SertypeHandle, TypeInfo, TypeObject},
    DdsError, DdsResult, Qos,
};
use cyclonedds_rust_sys::*;
use std::any::Any;
use std::sync::Mutex;

/// Read a string-valued entity property, growing the buffer if it did not fit.
///
/// `dds_get_name`/`dds_get_type_name` follow `snprintf` semantics: the return
/// value is the length the string *would* have needed, which can exceed the
/// buffer passed in. The previous code did `buf.truncate(n)` with that value —
/// and `Vec::truncate` only ever shrinks, so for a name of 256 bytes or more it
/// silently returned the whole raw buffer, embedded NUL padding included.
///
/// # Safety
/// `f` must be a CycloneDDS getter with `snprintf` return semantics that writes
/// a NUL-terminated string into the supplied buffer.
unsafe fn entity_string(
    entity: dds_entity_t,
    f: unsafe extern "C" fn(dds_entity_t, *mut i8, usize) -> dds_return_t,
) -> DdsResult<String> {
    let mut buf = vec![0u8; 256];
    let n = f(entity, buf.as_mut_ptr() as *mut i8, buf.len());
    if n < 0 {
        return Err(DdsError::from(n));
    }
    let needed = n as usize;
    if needed >= buf.len() {
        // Did not fit: retry once with an exactly-sized buffer (+1 for the NUL).
        buf = vec![0u8; needed + 1];
        let n = f(entity, buf.as_mut_ptr() as *mut i8, buf.len());
        if n < 0 {
            return Err(DdsError::from(n));
        }
    }
    buf.truncate(needed.min(buf.len()));
    Ok(String::from_utf8_lossy(&buf).into_owned())
}

/// Delete an entity from a `Drop`, surfacing failure instead of discarding it.
///
/// With [`OwnedEntity`] holding every ancestor up, a failure here no longer
/// means "my parent deleted me first" — that ordering is now impossible by
/// construction. It means something genuinely unexpected, so it is still worth
/// reporting rather than discarding.
///
/// The raw constructors (`Topic::from_entity`, `Data{Reader,Writer}::from_entities`,
/// `Publisher::from_entity`, …) are the remaining way to reach the old failure:
/// they adopt a handle whose lifetime this crate does not control.
///
/// Never panics: a panicking `Drop` during an unwind aborts.
pub(crate) fn delete_entity(entity: dds_entity_t, what: &str) {
    let ret = unsafe { dds_delete(entity) };
    if ret < 0 {
        #[cfg(feature = "tracing")]
        tracing::warn!(
            entity,
            what,
            code = ret,
            "dds_delete failed during Drop; the handle was likely already \
             deleted by a parent entity (check field declaration order)"
        );
        #[cfg(all(debug_assertions, not(feature = "tracing")))]
        eprintln!(
            "cyclonedds: dds_delete({entity}) failed for {what} during Drop (code {ret}); \
             the handle was likely already deleted by a parent entity \
             (check field declaration order)"
        );
        #[cfg(all(not(debug_assertions), not(feature = "tracing")))]
        let _ = (entity, what);
    }
}

/// A `dds_entity_t` together with everything that has to outlive it.
///
/// CycloneDDS deletes an entity's entire subtree when the entity is deleted, so
/// a child handle is only valid while its parent is. Storing the handle bare —
/// which is what every wrapper in this crate used to do — made that invariant
/// depend on the order the *user* happened to declare their struct fields, and
/// fields drop in declaration order:
///
/// ```text
/// struct App { participant, subscriber, reader }   // participant dies first
/// ```
///
/// Every entity now holds an `Arc<OwnedEntity>` for each of its ancestors
/// instead. The parent's `dds_delete` cannot run until the last child has
/// released it, which makes declaration order irrelevant by construction, and
/// lets a child outlive the binding it was created from.
///
/// The field order below is the load-bearing part. `Drop::drop` runs before any
/// field is dropped, so the sequence is always: delete this entity → drop the
/// listener it could still have invoked → release the ancestors.
pub(crate) struct OwnedEntity {
    entity: dds_entity_t,
    what: &'static str,
    /// Held so the C callback data outlives the entity that can invoke it.
    _listener: Option<crate::Listener>,
    /// Ancestors, released only once this entity is gone.
    _parents: Vec<std::sync::Arc<OwnedEntity>>,
    /// Callback data retained for as long as this DDS entity or any child is alive.
    _callback_state: Mutex<Option<Box<dyn Any + Send + Sync>>>,
}

impl OwnedEntity {
    /// Adopt `entity` as a child of `parents`.
    pub(crate) fn new(
        entity: dds_entity_t,
        what: &'static str,
        listener: Option<crate::Listener>,
        parents: Vec<std::sync::Arc<OwnedEntity>>,
    ) -> std::sync::Arc<Self> {
        std::sync::Arc::new(Self {
            entity,
            what,
            _listener: listener,
            _parents: parents,
            _callback_state: Mutex::new(None),
        })
    }

    /// Adopt a handle whose ancestors this crate does not hold.
    ///
    /// Only for the documented raw/FFI constructors: nothing keeps the parent
    /// alive, so the caller carries the invariant the rest of the crate now
    /// enforces.
    pub(crate) fn unowned(entity: dds_entity_t, what: &'static str) -> std::sync::Arc<Self> {
        Self::new(entity, what, None, Vec::new())
    }

    pub(crate) fn handle(&self) -> dds_entity_t {
        self.entity
    }

    /// The listener this entity is holding up, if any.
    #[cfg(test)]
    pub(crate) fn listener(&self) -> Option<&crate::Listener> {
        self._listener.as_ref()
    }

    /// Add an ancestor after construction.
    ///
    /// Only reachable through [`retaining`](crate::UntypedTopic::retaining),
    /// which requires the `Arc` to still be unique — i.e. the entity has not
    /// been handed to anyone yet.
    pub(crate) fn push_parent(&mut self, parent: std::sync::Arc<OwnedEntity>) {
        self._parents.push(parent);
    }

    pub(crate) fn replace_callback_state(&self, state: Option<Box<dyn Any + Send + Sync>>) {
        let mut current = self
            ._callback_state
            .lock()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        *current = state;
    }
}

/// Crate-internal access to a wrapper's owned handle, so a constructor can take
/// its parent's `Arc` without every type needing a bespoke accessor.
pub(crate) trait OwnedHandle {
    fn owned(&self) -> &std::sync::Arc<OwnedEntity>;
}

impl Drop for OwnedEntity {
    fn drop(&mut self) {
        delete_entity(self.entity, self.what);
    }
}

pub trait DdsEntity {
    /// The raw CycloneDDS handle behind this wrapper.
    ///
    /// **Deliberately public**, and the decision is recorded rather than
    /// revisited: a review proposed making it `pub(crate)`, on the grounds that
    /// a raw handle is an invitation to misuse it. It stays public because the
    /// alternative is worse. Interoperating with C — passing an entity to a
    /// CycloneDDS API this crate does not wrap, or adopting one created
    /// elsewhere — is a supported use, and closing this would force every such
    /// caller into `unsafe` transmutes to recover a number the wrapper already
    /// has. The raw `from_entity`/`from_entities` constructors depend on it too.
    ///
    /// What the handle does *not* carry is ownership. Every wrapper holds an
    /// `Arc<OwnedEntity>` that keeps its ancestors alive; the `dds_entity_t`
    /// returned here is a plain integer with none of that. Two rules follow:
    ///
    /// * It is valid only while the wrapper it came from is alive. Storing it
    ///   past that point is the pre-3.0 defect this crate spent a release
    ///   fixing — CycloneDDS deletes an entity's whole subtree, and handles are
    ///   redrawn, so a stale one usually errors and occasionally does worse.
    /// * Do not call `dds_delete` on it. The wrapper's `Drop` will.
    fn entity(&self) -> dds_entity_t;

    fn get_parent(&self) -> DdsResult<dds_entity_t> {
        unsafe { check_entity(dds_get_parent(self.entity())) }
    }

    fn get_participant(&self) -> DdsResult<dds_entity_t> {
        unsafe { check_entity(dds_get_participant(self.entity())) }
    }

    fn get_publisher(&self) -> DdsResult<dds_entity_t> {
        unsafe { check_entity(dds_get_publisher(self.entity())) }
    }

    fn get_subscriber(&self) -> DdsResult<dds_entity_t> {
        unsafe { check_entity(dds_get_subscriber(self.entity())) }
    }

    fn get_datareader(&self) -> DdsResult<dds_entity_t> {
        unsafe { check_entity(dds_get_datareader(self.entity())) }
    }

    fn get_topic(&self) -> DdsResult<dds_entity_t> {
        unsafe { check_entity(dds_get_topic(self.entity())) }
    }

    fn get_children(&self) -> DdsResult<Vec<dds_entity_t>> {
        unsafe {
            let mut buf: Vec<dds_entity_t> = vec![0; 64];
            let n = dds_get_children(self.entity(), buf.as_mut_ptr(), buf.len());
            if n < 0 {
                return Err(DdsError::from(n));
            }
            buf.truncate(n as usize);
            Ok(buf)
        }
    }

    fn get_name(&self) -> DdsResult<String> {
        unsafe { entity_string(self.entity(), dds_get_name) }
    }

    fn get_type_name(&self) -> DdsResult<String> {
        unsafe { entity_string(self.entity(), dds_get_type_name) }
    }

    fn get_domain_id(&self) -> DdsResult<u32> {
        unsafe {
            let mut id: dds_domainid_t = 0;
            check(dds_get_domainid(self.entity(), &mut id))?;
            Ok(id)
        }
    }

    fn get_instance_handle(&self) -> DdsResult<dds_instance_handle_t> {
        unsafe {
            let mut handle: dds_instance_handle_t = 0;
            check(dds_get_instance_handle(self.entity(), &mut handle))?;
            Ok(handle)
        }
    }

    fn get_guid(&self) -> DdsResult<dds_guid_t> {
        unsafe {
            let mut guid: dds_guid_t = std::mem::zeroed();
            check(dds_get_guid(self.entity(), &mut guid))?;
            Ok(guid)
        }
    }

    /// Convenience alias for [`Self::get_guid`].
    fn guid(&self) -> DdsResult<dds_guid_t> {
        self.get_guid()
    }

    /// Read a consolidated status snapshot for this entity.
    ///
    /// Each field is `Some` only when the corresponding status is supported
    /// by the entity type (e.g. a `DataWriter` will have
    /// `liveliness_lost` and `publication_matched`, but not `sample_lost`).
    fn status(&self) -> DdsResult<EntityStatus> {
        let mut s = EntityStatus::default();
        unsafe {
            let mut raw_it: dds_inconsistent_topic_status_t = std::mem::zeroed();
            if dds_get_inconsistent_topic_status(self.entity(), &mut raw_it) == 0 {
                s.inconsistent_topic = Some(raw_it.into());
            }
            let mut raw_ll: dds_liveliness_lost_status_t = std::mem::zeroed();
            if dds_get_liveliness_lost_status(self.entity(), &mut raw_ll) == 0 {
                s.liveliness_lost = Some(raw_ll.into());
            }
            let mut raw_lc: dds_liveliness_changed_status_t = std::mem::zeroed();
            if dds_get_liveliness_changed_status(self.entity(), &mut raw_lc) == 0 {
                s.liveliness_changed = Some(raw_lc.into());
            }
            let mut raw_odm: dds_offered_deadline_missed_status_t = std::mem::zeroed();
            if dds_get_offered_deadline_missed_status(self.entity(), &mut raw_odm) == 0 {
                s.offered_deadline_missed = Some(raw_odm.into());
            }
            let mut raw_oiq: dds_offered_incompatible_qos_status_t = std::mem::zeroed();
            if dds_get_offered_incompatible_qos_status(self.entity(), &mut raw_oiq) == 0 {
                s.offered_incompatible_qos = Some(raw_oiq.into());
            }
            let mut raw_rdm: dds_requested_deadline_missed_status_t = std::mem::zeroed();
            if dds_get_requested_deadline_missed_status(self.entity(), &mut raw_rdm) == 0 {
                s.requested_deadline_missed = Some(raw_rdm.into());
            }
            let mut raw_riq: dds_requested_incompatible_qos_status_t = std::mem::zeroed();
            if dds_get_requested_incompatible_qos_status(self.entity(), &mut raw_riq) == 0 {
                s.requested_incompatible_qos = Some(raw_riq.into());
            }
            let mut raw_sl: dds_sample_lost_status_t = std::mem::zeroed();
            if dds_get_sample_lost_status(self.entity(), &mut raw_sl) == 0 {
                s.sample_lost = Some(raw_sl.into());
            }
            let mut raw_sr: dds_sample_rejected_status_t = std::mem::zeroed();
            if dds_get_sample_rejected_status(self.entity(), &mut raw_sr) == 0 {
                s.sample_rejected = Some(raw_sr.into());
            }
            let mut raw_pm: dds_publication_matched_status_t = std::mem::zeroed();
            if dds_get_publication_matched_status(self.entity(), &mut raw_pm) == 0 {
                s.publication_matched = Some(raw_pm.into());
            }
            let mut raw_sm: dds_subscription_matched_status_t = std::mem::zeroed();
            if dds_get_subscription_matched_status(self.entity(), &mut raw_sm) == 0 {
                s.subscription_matched = Some(raw_sm.into());
            }
        }
        Ok(s)
    }

    fn enable(&self) -> DdsResult<()> {
        unsafe { check(dds_enable(self.entity())) }
    }

    fn get_status_mask(&self) -> DdsResult<u32> {
        unsafe {
            let mut mask: u32 = 0;
            check(dds_get_status_mask(self.entity(), &mut mask))?;
            Ok(mask)
        }
    }

    fn set_status_mask(&self, mask: u32) -> DdsResult<()> {
        unsafe { check(dds_set_status_mask(self.entity(), mask)) }
    }

    fn get_status_changes(&self) -> DdsResult<u32> {
        unsafe {
            let mut status: u32 = 0;
            check(dds_get_status_changes(self.entity(), &mut status))?;
            Ok(status)
        }
    }

    fn read_status(&self, mask: u32) -> DdsResult<u32> {
        unsafe {
            let mut status: u32 = 0;
            check(dds_read_status(self.entity(), &mut status, mask))?;
            Ok(status)
        }
    }

    fn take_status(&self, mask: u32) -> DdsResult<u32> {
        unsafe {
            let mut status: u32 = 0;
            check(dds_take_status(self.entity(), &mut status, mask))?;
            Ok(status)
        }
    }

    /// Get the set of triggered status bits that have changed since the
    /// last time they were read.
    fn status_changes(&self) -> DdsResult<u32> {
        unsafe {
            let mut changes: u32 = 0;
            check(dds_get_status_changes(self.entity(), &mut changes))?;
            Ok(changes)
        }
    }

    fn triggered(&self) -> DdsResult<bool> {
        unsafe {
            let ret = dds_triggered(self.entity());
            if ret < 0 {
                Err(DdsError::from(ret))
            } else {
                Ok(ret != 0)
            }
        }
    }

    fn is_shared_memory_available(&self) -> bool {
        unsafe { dds_is_shared_memory_available(self.entity()) }
    }

    fn create_statistics(&self) -> DdsResult<Statistics> {
        Statistics::new(self.entity())
    }

    fn get_qos(&self) -> DdsResult<Qos> {
        let mut qos = Qos::create()?;
        unsafe {
            check(dds_get_qos(self.entity(), qos.as_mut_ptr()))?;
        }
        Ok(qos)
    }

    fn get_sertype(&self) -> DdsResult<SertypeHandle> {
        unsafe {
            let mut ptr = std::ptr::null();
            check(dds_get_entity_sertype(self.entity(), &mut ptr))?;
            SertypeHandle::from_raw(ptr)
        }
    }

    fn get_type_info(&self) -> DdsResult<TypeInfo> {
        TypeInfo::from_entity(self.entity())
    }

    fn get_type_object(
        &self,
        type_id: *const dds_typeid_t,
        timeout: dds_duration_t,
    ) -> DdsResult<TypeObject> {
        TypeObject::from_entity_type_id(self.entity(), type_id, timeout)
    }

    fn get_minimal_type_object(&self, timeout: dds_duration_t) -> DdsResult<Option<TypeObject>> {
        self.get_type_info()?
            .minimal_type_object_from_entity(self.entity(), timeout)
    }

    fn get_complete_type_object(&self, timeout: dds_duration_t) -> DdsResult<Option<TypeObject>> {
        self.get_type_info()?
            .complete_type_object_from_entity(self.entity(), timeout)
    }

    fn matches_entity_type_info<E: DdsEntity>(&self, other: &E) -> DdsResult<bool> {
        Ok(self.get_type_info()?.matches(&other.get_type_info()?))
    }
}