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