1use alloc::vec::Vec;
12
13use crate::policies::{
14 DeadlineQosPolicy, DestinationOrderQosPolicy, DurabilityQosPolicy, LatencyBudgetQosPolicy,
15 LivelinessQosPolicy, OwnershipQosPolicy, PartitionQosPolicy, PresentationQosPolicy, ReaderQos,
16 ReliabilityQosPolicy, WriterQos,
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[non_exhaustive]
28pub enum IncompatibleReason {
29 Durability,
31 Reliability,
33 Deadline,
35 LatencyBudget,
37 Liveliness,
40 DestinationOrder,
42 Presentation,
45 Ownership,
47 Partition,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum CompatibilityResult {
54 Compatible,
56 Incompatible(Vec<IncompatibleReason>),
58}
59
60impl CompatibilityResult {
61 #[must_use]
63 pub fn is_compatible(&self) -> bool {
64 matches!(self, Self::Compatible)
65 }
66
67 #[must_use]
72 pub fn from_reasons(mut reasons: Vec<IncompatibleReason>) -> Self {
73 if reasons.is_empty() {
74 return Self::Compatible;
75 }
76 reasons.sort();
79 reasons.dedup();
80 Self::Incompatible(reasons)
81 }
82}
83
84impl DurabilityQosPolicy {
89 #[must_use]
91 pub fn is_compatible_with(self, requested: Self) -> bool {
92 self.kind >= requested.kind
93 }
94}
95
96impl ReliabilityQosPolicy {
97 #[must_use]
100 pub fn is_compatible_with(self, requested: Self) -> bool {
101 self.kind >= requested.kind
102 }
103}
104
105impl DeadlineQosPolicy {
106 #[must_use]
109 pub fn is_compatible_with(self, requested: Self) -> bool {
110 self.period <= requested.period
111 }
112}
113
114impl LatencyBudgetQosPolicy {
115 #[must_use]
118 pub fn is_compatible_with(self, requested: Self) -> bool {
119 self.duration <= requested.duration
120 }
121}
122
123impl LivelinessQosPolicy {
124 #[must_use]
128 pub fn is_compatible_with(self, requested: Self) -> bool {
129 self.kind >= requested.kind && self.lease_duration <= requested.lease_duration
130 }
131}
132
133impl DestinationOrderQosPolicy {
134 #[must_use]
136 pub fn is_compatible_with(self, requested: Self) -> bool {
137 self.kind >= requested.kind
138 }
139}
140
141impl OwnershipQosPolicy {
142 #[must_use]
144 pub fn is_compatible_with(self, requested: Self) -> bool {
145 self.kind == requested.kind
146 }
147}
148
149impl PresentationQosPolicy {
150 #[must_use]
157 pub fn is_compatible_with(self, requested: Self) -> bool {
158 self.access_scope >= requested.access_scope
159 && (self.coherent_access || !requested.coherent_access)
160 && (self.ordered_access || !requested.ordered_access)
161 }
162}
163
164#[must_use]
180pub fn compute_compatibility(offered: &WriterQos, requested: &ReaderQos) -> CompatibilityResult {
181 let mut reasons = Vec::new();
182 if !offered.durability.is_compatible_with(requested.durability) {
183 reasons.push(IncompatibleReason::Durability);
184 }
185 if !offered
186 .reliability
187 .is_compatible_with(requested.reliability)
188 {
189 reasons.push(IncompatibleReason::Reliability);
190 }
191 if !offered.deadline.is_compatible_with(requested.deadline) {
192 reasons.push(IncompatibleReason::Deadline);
193 }
194 if !offered
195 .latency_budget
196 .is_compatible_with(requested.latency_budget)
197 {
198 reasons.push(IncompatibleReason::LatencyBudget);
199 }
200 if !offered.liveliness.is_compatible_with(requested.liveliness) {
201 reasons.push(IncompatibleReason::Liveliness);
202 }
203 if !offered
204 .destination_order
205 .is_compatible_with(requested.destination_order)
206 {
207 reasons.push(IncompatibleReason::DestinationOrder);
208 }
209 if !offered
210 .presentation
211 .is_compatible_with(requested.presentation)
212 {
213 reasons.push(IncompatibleReason::Presentation);
214 }
215 if !offered.ownership.is_compatible_with(requested.ownership) {
216 reasons.push(IncompatibleReason::Ownership);
217 }
218 if !offered.partition.is_compatible_with(&requested.partition) {
219 reasons.push(IncompatibleReason::Partition);
220 }
221 CompatibilityResult::from_reasons(reasons)
222}
223
224impl PartitionQosPolicy {
225 #[must_use]
233 pub fn is_compatible_with(&self, requested: &Self) -> bool {
234 if self.names.is_empty() && requested.names.is_empty() {
235 return true;
236 }
237 if self.names.is_empty() || requested.names.is_empty() {
238 return false;
239 }
240 self.names.iter().any(|o| {
243 requested.names.iter().any(|rq| {
244 super::policies::partition::fnmatch(o, rq)
245 || super::policies::partition::fnmatch(rq, o)
246 })
247 })
248 }
249}
250
251#[cfg(test)]
252#[allow(clippy::bool_assert_comparison, clippy::panic, clippy::unwrap_used)]
253mod tests {
254 use super::*;
255 use crate::duration::Duration;
256 use crate::policies::{
257 DestinationOrderKind, DurabilityKind, LivelinessKind, OwnershipKind,
258 PresentationAccessScope, ReliabilityKind,
259 };
260
261 #[test]
262 fn empty_reasons_is_compatible() {
263 let r = CompatibilityResult::from_reasons(Vec::new());
264 assert_eq!(r, CompatibilityResult::Compatible);
265 assert!(r.is_compatible());
266 }
267
268 #[test]
269 fn non_empty_reasons_is_incompatible() {
270 let r = CompatibilityResult::from_reasons(alloc::vec![IncompatibleReason::Durability]);
271 assert!(!r.is_compatible());
272 }
273
274 #[test]
275 fn durability_offered_ge_requested() {
276 let offered = DurabilityQosPolicy {
277 kind: DurabilityKind::Transient,
278 };
279 let req = DurabilityQosPolicy {
280 kind: DurabilityKind::TransientLocal,
281 };
282 assert!(offered.is_compatible_with(req));
283 assert!(!req.is_compatible_with(offered));
285 }
286
287 #[test]
288 fn reliability_reliable_offered_besteffort_requested_ok() {
289 let offered = ReliabilityQosPolicy {
290 kind: ReliabilityKind::Reliable,
291 max_blocking_time: Duration::ZERO,
292 };
293 let req = ReliabilityQosPolicy {
294 kind: ReliabilityKind::BestEffort,
295 max_blocking_time: Duration::ZERO,
296 };
297 assert!(offered.is_compatible_with(req));
298 assert!(!req.is_compatible_with(offered));
299 }
300
301 #[test]
302 fn deadline_offered_le_requested() {
303 let offered = DeadlineQosPolicy {
304 period: Duration::from_secs(1),
305 };
306 let req = DeadlineQosPolicy {
307 period: Duration::from_secs(5),
308 };
309 assert!(offered.is_compatible_with(req));
310 assert!(!req.is_compatible_with(offered));
311 }
312
313 #[test]
314 fn latency_budget_offered_le_requested() {
315 let offered = LatencyBudgetQosPolicy {
316 duration: Duration::from_millis(10),
317 };
318 let req = LatencyBudgetQosPolicy {
319 duration: Duration::from_millis(100),
320 };
321 assert!(offered.is_compatible_with(req));
322 assert!(!req.is_compatible_with(offered));
323 }
324
325 #[test]
326 fn liveliness_kind_and_lease_checked() {
327 let offered = LivelinessQosPolicy {
328 kind: LivelinessKind::ManualByTopic,
329 lease_duration: Duration::from_secs(1),
330 };
331 let req = LivelinessQosPolicy {
332 kind: LivelinessKind::ManualByParticipant,
333 lease_duration: Duration::from_secs(5),
334 };
335 assert!(offered.is_compatible_with(req));
336
337 let req_strict = LivelinessQosPolicy {
339 kind: LivelinessKind::Automatic,
340 lease_duration: Duration::ZERO,
341 };
342 assert!(!offered.is_compatible_with(req_strict));
343 }
344
345 #[test]
346 fn destination_order_offered_ge_requested() {
347 let offered = DestinationOrderQosPolicy {
348 kind: DestinationOrderKind::BySourceTimestamp,
349 };
350 let req = DestinationOrderQosPolicy {
351 kind: DestinationOrderKind::ByReceptionTimestamp,
352 };
353 assert!(offered.is_compatible_with(req));
354 assert!(!req.is_compatible_with(offered));
355 }
356
357 #[test]
358 fn ownership_must_match_exactly() {
359 let a = OwnershipQosPolicy {
360 kind: OwnershipKind::Shared,
361 };
362 let b = OwnershipQosPolicy {
363 kind: OwnershipKind::Exclusive,
364 };
365 assert!(a.is_compatible_with(a));
366 assert!(!a.is_compatible_with(b));
367 }
368
369 #[test]
370 fn presentation_scope_and_flags() {
371 let offered = PresentationQosPolicy {
372 access_scope: PresentationAccessScope::Group,
373 coherent_access: true,
374 ordered_access: true,
375 };
376 let req = PresentationQosPolicy {
377 access_scope: PresentationAccessScope::Topic,
378 coherent_access: false,
379 ordered_access: true,
380 };
381 assert!(offered.is_compatible_with(req));
382
383 let offered_weak = PresentationQosPolicy {
385 access_scope: PresentationAccessScope::Group,
386 coherent_access: false,
387 ordered_access: true,
388 };
389 let req_coherent = PresentationQosPolicy {
390 access_scope: PresentationAccessScope::Instance,
391 coherent_access: true,
392 ordered_access: false,
393 };
394 assert!(!offered_weak.is_compatible_with(req_coherent));
395 }
396
397 #[test]
398 fn partition_exact_match() {
399 use alloc::string::String;
400 let offered = PartitionQosPolicy {
401 names: alloc::vec![String::from("a"), String::from("b")],
402 };
403 let req = PartitionQosPolicy {
404 names: alloc::vec![String::from("c"), String::from("b")],
405 };
406 assert!(offered.is_compatible_with(&req));
407
408 let req_disjoint = PartitionQosPolicy {
409 names: alloc::vec![String::from("c"), String::from("d")],
410 };
411 assert!(!offered.is_compatible_with(&req_disjoint));
412 }
413
414 #[test]
415 fn partition_empty_match_empty() {
416 let a = PartitionQosPolicy::default();
417 let b = PartitionQosPolicy::default();
418 assert!(a.is_compatible_with(&b));
419 }
420
421 #[test]
426 fn compute_compatibility_default_writer_reader_is_compatible() {
427 let w = crate::policies::WriterQos::default();
428 let r = crate::policies::ReaderQos::default();
429 let result = compute_compatibility(&w, &r);
430 assert!(result.is_compatible(), "got {result:?}");
432 }
433
434 #[test]
435 fn compute_compatibility_reports_durability_mismatch() {
436 let mut w = crate::policies::WriterQos::default();
437 let mut r = crate::policies::ReaderQos::default();
438 w.durability = DurabilityQosPolicy {
439 kind: DurabilityKind::Volatile,
440 };
441 r.durability = DurabilityQosPolicy {
442 kind: DurabilityKind::Transient,
443 };
444 let result = compute_compatibility(&w, &r);
445 assert!(!result.is_compatible());
446 if let CompatibilityResult::Incompatible(reasons) = result {
447 assert!(reasons.contains(&IncompatibleReason::Durability));
448 }
449 }
450
451 #[test]
452 fn compute_compatibility_reports_multiple_mismatches() {
453 let mut w = crate::policies::WriterQos::default();
454 let mut r = crate::policies::ReaderQos::default();
455 w.reliability.kind = ReliabilityKind::BestEffort;
457 r.reliability.kind = ReliabilityKind::Reliable;
458 w.durability = DurabilityQosPolicy {
460 kind: DurabilityKind::Volatile,
461 };
462 r.durability = DurabilityQosPolicy {
463 kind: DurabilityKind::Transient,
464 };
465 let result = compute_compatibility(&w, &r);
466 if let CompatibilityResult::Incompatible(reasons) = result {
467 assert!(reasons.contains(&IncompatibleReason::Reliability));
468 assert!(reasons.contains(&IncompatibleReason::Durability));
469 assert!(reasons.len() >= 2);
470 } else {
471 panic!("expected incompatible");
472 }
473 }
474
475 #[test]
476 fn compute_compatibility_partition_disjoint_fails() {
477 use alloc::string::String;
478 let mut w = crate::policies::WriterQos::default();
479 let mut r = crate::policies::ReaderQos::default();
480 w.partition = PartitionQosPolicy {
481 names: alloc::vec![String::from("alpha")],
482 };
483 r.partition = PartitionQosPolicy {
484 names: alloc::vec![String::from("beta")],
485 };
486 let result = compute_compatibility(&w, &r);
487 if let CompatibilityResult::Incompatible(reasons) = result {
488 assert!(reasons.contains(&IncompatibleReason::Partition));
489 } else {
490 panic!("expected partition mismatch");
491 }
492 }
493}