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}