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}