Skip to main content

cyclonedds/qos/
policy.rs

1//! [`QoS`](crate::QoS) policy types for entities.
2//!
3//! Each type in this module corresponds to a [`QoS`](crate::QoS) policy defined
4//! in the DCPS specification. Policies are set on a [`QoS`](crate::QoS)
5//! instance via its
6//! `with_*` methods and applied to entities through their builders.
7//!
8//! Not all policies apply to all entity types. Refer to the DDS specification
9//! or Cyclone DDS documentation for the applicability of each policy.
10
11// TODO add the details on what QoS policies apply to what types here too.
12
13use crate::Duration;
14use crate::internal::traits::AsFfi;
15
16/// Attaches arbitrary application-specific data to an entity.
17///
18/// The value is propagated during discovery and made available to remote
19/// participants, allowing applications to embed metadata such as version
20/// information or node identity in the entity itself.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct UserData {
23    /// The raw byte payload.
24    pub value: Vec<u8>,
25}
26
27impl AsFfi for UserData {
28    type Target<'a> = &'a [u8];
29
30    fn as_ffi(&self) -> Self::Target<'_> {
31        &self.value
32    }
33}
34
35/// Attaches arbitrary application-specific data to a topic.
36///
37/// Propagated during discovery alongside the topic description, allowing
38/// applications to embed metadata in the topic itself.
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct TopicData {
41    /// The raw byte payload.
42    pub value: Vec<u8>,
43}
44
45impl AsFfi for TopicData {
46    type Target<'a> = &'a [u8];
47
48    #[inline]
49    fn as_ffi(&self) -> Self::Target<'_> {
50        &self.value
51    }
52}
53
54/// Attaches arbitrary application-specific data to a publisher or subscriber.
55///
56/// Propagated during discovery, allowing applications to embed metadata at
57/// the publisher or subscriber level.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct GroupData {
60    /// The raw byte payload.
61    pub value: Vec<u8>,
62}
63
64impl AsFfi for GroupData {
65    type Target<'a> = &'a [u8];
66
67    #[inline]
68    fn as_ffi(&self) -> Self::Target<'_> {
69        &self.value
70    }
71}
72
73/// Controls whether samples are stored for late-joining readers.
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub enum Durability {
76    /// Samples are not stored. Late-joining readers receive only new samples.
77    Volatile,
78    /// Samples are stored in the writer. Late-joining readers on the same node
79    /// receive historical samples.
80    TransientLocal,
81    /// Samples are stored in a separate durability service. Late-joining
82    /// readers anywhere in the domain receive historical samples.
83    Transient,
84    /// Like [`Transient`](Durability::Transient) but samples survive process
85    /// restarts.
86    Persistent,
87}
88
89impl AsFfi for Durability {
90    type Target<'a> = cyclonedds_sys::dds_durability_kind_t;
91
92    #[inline]
93    fn as_ffi(&self) -> Self::Target<'_> {
94        match self {
95            Durability::Volatile => cyclonedds_sys::dds_durability_kind_DDS_DURABILITY_VOLATILE,
96            Durability::TransientLocal => {
97                cyclonedds_sys::dds_durability_kind_DDS_DURABILITY_TRANSIENT_LOCAL
98            }
99            Durability::Transient => cyclonedds_sys::dds_durability_kind_DDS_DURABILITY_TRANSIENT,
100            Durability::Persistent => cyclonedds_sys::dds_durability_kind_DDS_DURABILITY_PERSISTENT,
101        }
102    }
103}
104
105/// Configures the history and resource limits of the durability service.
106///
107/// Only relevant when [`Durability`] is [`Transient`](Durability::Transient) or
108/// [`Persistent`](Durability::Persistent). Controls how the durability service
109/// stores and purges historical samples.
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111pub struct DurabilityService {
112    /// How long the service retains historical data after all matching readers
113    /// have been removed.
114    pub service_cleanup_delay: Duration,
115    /// History depth to be applied within the durability service.
116    pub history: History,
117    /// Resource limits applied within the durability service.
118    pub resource_limits: ResourceLimits,
119}
120
121impl AsFfi for DurabilityService {
122    type Target<'a> = (
123        cyclonedds_sys::dds_duration_t,
124        cyclonedds_sys::dds_history_kind_t,
125        i32,
126        i32,
127        i32,
128        i32,
129    );
130
131    #[inline]
132    fn as_ffi(&self) -> Self::Target<'_> {
133        let (history_kind, history_depth) = self.history.as_ffi();
134        let service_cleanup_delay = self.service_cleanup_delay.inner;
135
136        (
137            service_cleanup_delay,
138            history_kind,
139            history_depth,
140            self.resource_limits.max_samples.as_ffi(),
141            self.resource_limits.max_instances.as_ffi(),
142            self.resource_limits.max_samples_per_instance.as_ffi(),
143        )
144    }
145}
146
147/// Controls the scope and ordering of sample presentation to subscribers.
148///
149/// The access scope determines the boundary within which `coherent_access` and
150/// `ordered_access` are applied.
151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152pub enum Presentation {
153    /// Coherence and ordering are applied per instance.
154    Instance {
155        /// Whether changes within a transaction are delivered atomically.
156        coherent_access: bool,
157        /// Whether samples are delivered in order within the scope.
158        ordered_access: bool,
159    },
160    /// Coherence and ordering are applied across all instances of a topic.
161    Topic {
162        /// Whether changes within a transaction are delivered atomically.
163        coherent_access: bool,
164        /// Whether samples are delivered in order within the scope.
165        ordered_access: bool,
166    },
167    /// Coherence and ordering are applied across all topics within a publisher
168    /// or subscriber group.
169    Group {
170        /// Whether changes within a transaction are delivered atomically.
171        coherent_access: bool,
172        /// Whether samples are delivered in order within the scope.
173        ordered_access: bool,
174    },
175}
176
177impl AsFfi for Presentation {
178    type Target<'a> = (
179        cyclonedds_sys::dds_presentation_access_scope_kind,
180        bool,
181        bool,
182    );
183
184    #[inline]
185    fn as_ffi(&self) -> Self::Target<'_> {
186        match self {
187            Presentation::Instance {
188                coherent_access,
189                ordered_access,
190            } => (
191                cyclonedds_sys::dds_presentation_access_scope_kind_DDS_PRESENTATION_INSTANCE,
192                *coherent_access,
193                *ordered_access,
194            ),
195            Presentation::Topic {
196                coherent_access,
197                ordered_access,
198            } => (
199                cyclonedds_sys::dds_presentation_access_scope_kind_DDS_PRESENTATION_TOPIC,
200                *coherent_access,
201                *ordered_access,
202            ),
203            Presentation::Group {
204                coherent_access,
205                ordered_access,
206            } => (
207                cyclonedds_sys::dds_presentation_access_scope_kind_DDS_PRESENTATION_GROUP,
208                *coherent_access,
209                *ordered_access,
210            ),
211        }
212    }
213}
214
215/// The maximum time between successive writes for a given instance.
216///
217/// Writers and readers negotiate a compatible deadline. If a writer does not
218/// write within the deadline period, the
219/// [`OfferedDeadlineMissed`](crate::status::OfferedDeadlineMissed) event fires.
220/// If a reader does not receive a sample within the period, the
221/// [`RequestedDeadlineMissed`](crate::status::RequestedDeadlineMissed) event
222/// fires.
223#[derive(Clone, Copy, Debug, PartialEq, Eq)]
224pub struct Deadline {
225    /// The maximum interval between writes for a given instance.
226    pub period: Duration,
227}
228
229impl AsFfi for Deadline {
230    type Target<'a> = cyclonedds_sys::dds_duration_t;
231
232    #[inline]
233    fn as_ffi(&self) -> Self::Target<'_> {
234        self.period.inner
235    }
236}
237
238/// The acceptable delay between writing and delivering a sample.
239///
240/// NOTE: this does not enforce any timing guarantees but is rather a
241/// configuration hint that allows the middleware to batch samples that arrive
242/// within the budget window.
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
244pub struct LatencyBudget {
245    /// The maximum duration to allow batched results to be transmitted within.
246    pub duration: Duration,
247}
248
249impl AsFfi for LatencyBudget {
250    type Target<'a> = cyclonedds_sys::dds_duration_t;
251
252    #[inline]
253    fn as_ffi(&self) -> Self::Target<'_> {
254        self.duration.inner
255    }
256}
257
258/// Controls whether ownership of an instance is shared or exclusive among
259/// writers.
260///
261/// With exclusive ownership, only the writer with the highest
262/// [`strength`](Ownership::Exclusive::strength) value delivers samples for a
263/// given instance. Other writers are silently ignored by readers.
264#[derive(Clone, Copy, Debug, PartialEq, Eq)]
265pub enum Ownership {
266    /// Multiple writers may deliver samples for the same instance.
267    Shared,
268    /// Only the writer with the highest strength delivers samples for a given
269    /// instance.
270    Exclusive {
271        /// The ownership strength of this writer. Higher values take
272        /// precedence.
273        strength: i32,
274    },
275}
276
277impl AsFfi for Ownership {
278    type Target<'a> = (cyclonedds_sys::dds_ownership_kind_t, Option<i32>);
279
280    #[inline]
281    fn as_ffi(&self) -> Self::Target<'_> {
282        match self {
283            Ownership::Shared => (
284                cyclonedds_sys::dds_ownership_kind_DDS_OWNERSHIP_SHARED,
285                None,
286            ),
287            Ownership::Exclusive { strength } => (
288                cyclonedds_sys::dds_ownership_kind_DDS_OWNERSHIP_EXCLUSIVE,
289                Some(*strength),
290            ),
291        }
292    }
293}
294
295/// Controls how the system determines whether a writer is still active.
296///
297/// Readers use the liveliness policy to detect when a matched writer has
298/// stopped publishing. When a writer's liveliness is lost, the
299/// [`LivelinessChanged`](crate::status::LivelinessChanged) event fires on
300/// matched readers, and the [`LivelinessLost`](crate::status::LivelinessLost)
301/// event fires on the writer.
302#[derive(Clone, Copy, Debug, PartialEq, Eq)]
303pub enum Liveliness {
304    /// The middleware asserts liveliness automatically on behalf of the writer.
305    Automatic {
306        /// The duration within which liveliness must be asserted.
307        lease_duration: Duration,
308    },
309    /// Liveliness is asserted by any write activity from the participant.
310    ManualByParticipant {
311        /// The duration within which liveliness must be asserted.
312        lease_duration: Duration,
313    },
314    /// Liveliness must be asserted explicitly per writer via a write or
315    /// liveliness assertion call.
316    ManualByTopic {
317        /// The duration within which liveliness must be asserted.
318        lease_duration: Duration,
319    },
320}
321
322impl AsFfi for Liveliness {
323    type Target<'a> = (
324        cyclonedds_sys::dds_liveliness_kind_t,
325        cyclonedds_sys::dds_duration_t,
326    );
327
328    #[inline]
329    fn as_ffi(&self) -> Self::Target<'_> {
330        match self {
331            Liveliness::Automatic { lease_duration } => (
332                cyclonedds_sys::dds_liveliness_kind_DDS_LIVELINESS_AUTOMATIC,
333                lease_duration.inner,
334            ),
335            Liveliness::ManualByParticipant { lease_duration } => (
336                cyclonedds_sys::dds_liveliness_kind_DDS_LIVELINESS_MANUAL_BY_PARTICIPANT,
337                lease_duration.inner,
338            ),
339            Liveliness::ManualByTopic { lease_duration } => (
340                cyclonedds_sys::dds_liveliness_kind_DDS_LIVELINESS_MANUAL_BY_TOPIC,
341                lease_duration.inner,
342            ),
343        }
344    }
345}
346
347/// The minimum time between sample deliveries to a reader for a given instance.
348///
349/// Samples arriving faster than the minimum separation are dropped. Useful for
350/// throttling high-frequency writers at the reader side without changing the
351/// writer's publish rate.
352#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353pub struct TimeBasedFilter {
354    /// The minimum interval between delivered samples for a given instance.
355    pub minimum_separation: Duration,
356}
357
358impl AsFfi for TimeBasedFilter {
359    type Target<'a> = cyclonedds_sys::dds_duration_t;
360
361    #[inline]
362    fn as_ffi(&self) -> Self::Target<'_> {
363        self.minimum_separation.inner
364    }
365}
366
367/// Restricts communication to named logical partitions within a domain.
368///
369/// A writer and reader only match if they share at least one partition name.
370/// Partition names support wildcards as defined by the DCPS specification. The
371/// default partition (empty string) is used when no partition is set.
372#[derive(Clone, Debug, PartialEq, Eq)]
373pub struct Partition {
374    /// The list of partition names.
375    pub partitions: Vec<String>,
376}
377
378impl AsFfi for Partition {
379    type Target<'a> = Vec<std::ffi::CString>;
380
381    #[inline]
382    fn as_ffi(&self) -> Self::Target<'_> {
383        self.partitions
384            .iter()
385            .map(|partition| {
386                std::ffi::CString::new(partition.as_str()).expect(
387                    "TODO should this be moved to the construction of the partition policy?",
388                )
389            })
390            .collect()
391    }
392}
393
394/// The delivery guarantee for samples.
395#[derive(Clone, Copy, Debug, PartialEq, Eq)]
396pub enum Reliability {
397    /// Samples may be dropped. No retransmission is attempted.
398    BestEffort,
399    /// Samples are retransmitted until acknowledged or the blocking time
400    /// elapses.
401    Reliable {
402        /// The maximum time a write call blocks when the writer's resource
403        /// limits are reached.
404        max_blocking_time: Duration,
405    },
406}
407
408impl AsFfi for Reliability {
409    type Target<'a> = (
410        cyclonedds_sys::dds_reliability_kind_t,
411        cyclonedds_sys::dds_duration_t,
412    );
413
414    #[inline]
415    fn as_ffi(&self) -> Self::Target<'_> {
416        match self {
417            Reliability::BestEffort => (
418                cyclonedds_sys::dds_reliability_kind_DDS_RELIABILITY_BEST_EFFORT,
419                0,
420            ),
421            Reliability::Reliable { max_blocking_time } => (
422                cyclonedds_sys::dds_reliability_kind_DDS_RELIABILITY_RELIABLE,
423                max_blocking_time.inner,
424            ),
425        }
426    }
427}
428
429/// A hint to the transport layer about the relative send priority of this
430/// entity.
431///
432/// Higher values indicate higher priority. The interpretation is
433/// transport-dependent and not guaranteed to be honored.
434#[derive(Clone, Copy, Debug, PartialEq, Eq)]
435pub struct TransportPriority {
436    /// The priority value. Higher values indicate higher priority.
437    pub priority: i32,
438}
439
440impl AsFfi for TransportPriority {
441    type Target<'a> = i32;
442
443    #[inline]
444    fn as_ffi(&self) -> Self::Target<'_> {
445        self.priority
446    }
447}
448
449/// The maximum duration a sample remains valid after being written.
450///
451/// Samples that have not been delivered within their lifespan are silently
452/// expired.
453#[derive(Clone, Copy, Debug, PartialEq, Eq)]
454pub struct Lifespan {
455    /// The maximum age of a sample before it is considered expired.
456    pub duration: Duration,
457}
458
459impl AsFfi for Lifespan {
460    type Target<'a> = cyclonedds_sys::dds_duration_t;
461
462    #[inline]
463    fn as_ffi(&self) -> Self::Target<'_> {
464        self.duration.inner
465    }
466}
467
468/// Controls the order in which samples are delivered to a reader when multiple
469/// writers produce samples for the same instance.
470#[derive(Clone, Copy, Debug, PartialEq, Eq)]
471pub enum DestinationOrder {
472    /// Samples are ordered by the time they were received by the reader.
473    ByReceptionTimestamp,
474    /// Samples are ordered by the timestamp set by the writer at publication
475    /// time.
476    BySourceTimestamp,
477}
478
479impl AsFfi for DestinationOrder {
480    type Target<'a> = cyclonedds_sys::dds_destination_order_kind_t;
481
482    #[inline]
483    fn as_ffi(&self) -> Self::Target<'_> {
484        match self {
485            DestinationOrder::ByReceptionTimestamp =>
486                cyclonedds_sys::dds_destination_order_kind_DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP,
487            DestinationOrder::BySourceTimestamp =>
488                cyclonedds_sys::dds_destination_order_kind_DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP,
489        }
490    }
491}
492
493/// Controls how many samples are stored per instance.
494#[derive(Clone, Copy, Debug, PartialEq, Eq)]
495pub enum History {
496    /// All samples are retained, subject to [`ResourceLimits`].
497    KeepAll,
498    /// Only the `depth` most recent samples per instance are retained.
499    KeepLast {
500        /// The number of samples to retain per instance.
501        depth: i32,
502    },
503}
504
505impl AsFfi for History {
506    type Target<'a> = (cyclonedds_sys::dds_history_kind_t, i32);
507
508    #[inline]
509    fn as_ffi(&self) -> Self::Target<'_> {
510        match self {
511            History::KeepAll => (cyclonedds_sys::dds_history_kind_DDS_HISTORY_KEEP_ALL, 0),
512            History::KeepLast { depth } => (
513                cyclonedds_sys::dds_history_kind_DDS_HISTORY_KEEP_LAST,
514                *depth,
515            ),
516        }
517    }
518}
519
520/// Caps on the number of instances, samples, and samples per instance.
521///
522/// When a limit is reached, incoming samples are rejected and the
523/// [`SampleRejected`](crate::status::SampleRejected) event fires. Use
524/// [`ResourceLimit::Unlimited`] to impose no cap.
525#[derive(Clone, Copy, Debug, PartialEq, Eq)]
526pub struct ResourceLimits {
527    /// Maximum total number of samples across all instances.
528    pub max_samples: ResourceLimit,
529    /// Maximum number of instances.
530    pub max_instances: ResourceLimit,
531    /// Maximum number of samples per instance.
532    pub max_samples_per_instance: ResourceLimit,
533}
534
535/// A resource limit value, either bounded or unlimited.
536#[derive(Clone, Copy, Debug, PartialEq, Eq)]
537pub enum ResourceLimit {
538    /// No limit is imposed.
539    Unlimited,
540    /// The resource is capped at the given value.
541    Limited(u32),
542}
543
544impl ResourceLimit {
545    #[must_use]
546    fn as_ffi(self) -> i32 {
547        /// This is an invalid value on the Cyclone C side and will defer the
548        /// failure of the resource limit down to the later calls which are able
549        /// to correctly propagate an error out.
550        const INVALID_LIMIT_IN_CYCLONE_C_LIB: i32 = 0;
551        match self {
552            ResourceLimit::Unlimited => cyclonedds_sys::DDS_LENGTH_UNLIMITED,
553            ResourceLimit::Limited(limit) => {
554                i32::try_from(limit).unwrap_or(INVALID_LIMIT_IN_CYCLONE_C_LIB)
555            }
556        }
557    }
558}
559
560impl AsFfi for ResourceLimits {
561    type Target<'a> = (i32, i32, i32);
562
563    #[inline]
564    fn as_ffi(&self) -> Self::Target<'_> {
565        (
566            self.max_samples.as_ffi(),
567            self.max_instances.as_ffi(),
568            self.max_samples_per_instance.as_ffi(),
569        )
570    }
571}
572
573/// Controls whether child entities are automatically enabled on creation.
574///
575/// When `autoenable_created_entities` is `false`, entities must be explicitly
576/// enabled before they can communicate.
577#[derive(Clone, Copy, Debug, PartialEq, Eq)]
578pub struct EntityFactory {
579    /// If `true`, entities are enabled immediately on creation.
580    pub autoenable_created_entities: bool,
581}
582
583impl AsFfi for EntityFactory {
584    type Target<'a> = bool;
585
586    #[inline]
587    fn as_ffi(&self) -> Self::Target<'_> {
588        self.autoenable_created_entities
589    }
590}
591
592/// Controls how the writer handles instances when it is deleted.
593///
594/// When `autodispose_unregistered_instances` is `true`, the writer
595/// automatically disposes all instances it owns on deletion, notifying readers
596/// that the data is no longer available.
597#[derive(Clone, Copy, Debug, PartialEq, Eq)]
598pub struct WriterDataLifecycle {
599    /// If `true`, all owned instances are disposed when the writer is deleted.
600    pub autodispose_unregistered_instances: bool,
601}
602
603impl AsFfi for WriterDataLifecycle {
604    type Target<'a> = bool;
605
606    #[inline]
607    fn as_ffi(&self) -> Self::Target<'_> {
608        self.autodispose_unregistered_instances
609    }
610}
611
612/// Controls how the reader handles stale instance data after writers disappear.
613#[derive(Clone, Copy, Debug, PartialEq, Eq)]
614pub struct ReaderDataLifecycle {
615    /// How long samples for an instance are retained after all matching writers
616    /// have gone away.
617    pub autopurge_nowriter_samples_delay: Duration,
618    /// How long samples for a disposed instance are retained before being
619    /// purged from the reader cache.
620    pub autopurge_disposed_samples_delay: Duration,
621}
622
623impl AsFfi for ReaderDataLifecycle {
624    type Target<'a> = (
625        cyclonedds_sys::dds_duration_t,
626        cyclonedds_sys::dds_duration_t,
627    );
628
629    #[inline]
630    fn as_ffi(&self) -> Self::Target<'_> {
631        (
632            self.autopurge_nowriter_samples_delay.inner,
633            self.autopurge_disposed_samples_delay.inner,
634        )
635    }
636}
637
638// TODO validate the following QoS
639// ///
640// pub enum IgnoreLocal {
641//     ///
642//     Nothing,
643//     ///
644//     Participant,
645//     ///
646//     Process,
647// }
648
649// ///
650// pub enum TypeConsistency {
651//     ///
652//     DisallowTypeCoercion {
653//         ///
654//         force_type_validation: bool,
655//     },
656//     ///
657//     AllowTypeCoercion {
658//         ///
659//         ignore_sequence_bounds: bool,
660//         ///
661//         ignore_string_bounds: bool,
662//         ///
663//         ignore_member_names: bool,
664//         ///
665//         prevent_type_widening: bool,
666//         ///
667//         force_type_validation: bool,
668//     },
669// }
670
671// ///
672// pub struct WriterBatching {
673//     ///
674//     pub batch_updates: bool,
675// }
676
677// ///
678// pub struct PsmxInstances {
679//     ///
680//     pub instances: Vec<String>,
681// }
682
683// ///
684// pub enum DataRepresentationKind {
685//     ///
686//     Xcdr1,
687//     ///
688//     Xml,
689//     ///
690//     Xcdr2,
691// }
692
693// ///
694// pub struct DataRepresentation {
695//     ///
696//     pub representations: std::collections::HashSet<DataRepresentationKind>,
697// }
698
699/// Assigns a human-readable name to an entity.
700///
701/// Used in diagnostics, logging, and monitoring tools to identify entities
702/// by name rather than by handle.
703#[derive(Clone, Debug, PartialEq, Eq)]
704pub struct EntityName {
705    /// The name to assign to the entity.
706    pub name: String,
707}
708
709impl AsFfi for EntityName {
710    type Target<'a> = std::ffi::CString;
711
712    #[inline]
713    fn as_ffi(&self) -> Self::Target<'_> {
714        // TODO should this be moved to the construction of the name policy or deferred
715        // to the set_qos + construction of the objects with the QoS?O
716        std::ffi::CString::new(self.name.as_str())
717            .expect("unable to safely create std::ffi::CString from entity name")
718    }
719}