Skip to main content

hdds_c/
qos.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4//! QoS C FFI bindings for HDDS.
5//!
6//! Provides C-compatible functions to create, configure, and load QoS profiles.
7
8use std::ffi::CStr;
9use std::os::raw::c_char;
10use std::ptr;
11
12use hdds::api::QoS;
13
14use crate::HddsError;
15
16/// Opaque handle to a QoS profile.
17#[repr(C)]
18pub struct HddsQoS {
19    _private: [u8; 0],
20}
21
22// =============================================================================
23// QoS Creation
24// =============================================================================
25
26/// Create a default QoS profile (best-effort, volatile).
27///
28/// # Safety
29/// The returned pointer must be freed with `hdds_qos_destroy`.
30#[no_mangle]
31pub unsafe extern "C" fn hdds_qos_default() -> *mut HddsQoS {
32    let qos = Box::new(QoS::default());
33    Box::into_raw(qos).cast::<HddsQoS>()
34}
35
36/// Create a best-effort QoS profile.
37///
38/// Best-effort QoS does not guarantee delivery but has lower overhead.
39///
40/// # Safety
41/// The returned pointer must be freed with `hdds_qos_destroy`.
42#[no_mangle]
43pub unsafe extern "C" fn hdds_qos_best_effort() -> *mut HddsQoS {
44    let qos = Box::new(QoS::best_effort());
45    Box::into_raw(qos).cast::<HddsQoS>()
46}
47
48/// Create a reliable QoS profile.
49///
50/// Reliable QoS guarantees delivery with NACK-driven retransmission.
51///
52/// # Safety
53/// The returned pointer must be freed with `hdds_qos_destroy`.
54#[no_mangle]
55pub unsafe extern "C" fn hdds_qos_reliable() -> *mut HddsQoS {
56    let qos = Box::new(QoS::reliable());
57    Box::into_raw(qos).cast::<HddsQoS>()
58}
59
60/// Create an RTI Connext-compatible QoS profile.
61///
62/// Uses RTI Connext DDS 6.x defaults for interoperability.
63///
64/// # Safety
65/// The returned pointer must be freed with `hdds_qos_destroy`.
66#[no_mangle]
67pub unsafe extern "C" fn hdds_qos_rti_defaults() -> *mut HddsQoS {
68    let qos = Box::new(QoS::rti_defaults());
69    Box::into_raw(qos).cast::<HddsQoS>()
70}
71
72/// Destroy a QoS profile.
73///
74/// # Safety
75/// - `qos` must be a valid pointer returned from a `hdds_qos_*` creation function.
76/// - Must not be called more than once with the same pointer.
77#[no_mangle]
78pub unsafe extern "C" fn hdds_qos_destroy(qos: *mut HddsQoS) {
79    if !qos.is_null() {
80        let _ = Box::from_raw(qos.cast::<QoS>());
81    }
82}
83
84// =============================================================================
85// QoS Loading from XML
86// =============================================================================
87
88/// Load QoS from a FastDDS XML profile file.
89///
90/// Parses the XML file and extracts the default profile's QoS settings.
91/// Supports all 22 DDS QoS policies.
92///
93/// # Arguments
94/// - `path`: Path to the FastDDS XML profile file (null-terminated C string).
95///
96/// # Returns
97/// - Valid pointer on success.
98/// - NULL if the file cannot be read or parsed.
99///
100/// # Safety
101/// - `path` must be a valid null-terminated C string.
102/// - The returned pointer must be freed with `hdds_qos_destroy`.
103#[cfg(feature = "qos-loaders")]
104#[no_mangle]
105pub unsafe extern "C" fn hdds_qos_load_fastdds_xml(path: *const c_char) -> *mut HddsQoS {
106    if path.is_null() {
107        return ptr::null_mut();
108    }
109
110    let Ok(path_str) = CStr::from_ptr(path).to_str() else {
111        return ptr::null_mut();
112    };
113
114    match QoS::load_fastdds(path_str) {
115        Ok(qos) => Box::into_raw(Box::new(qos)).cast::<HddsQoS>(),
116        Err(err) => {
117            eprintln!("[hdds_c] Failed to load FastDDS XML: {}", err);
118            ptr::null_mut()
119        }
120    }
121}
122
123/// Load QoS from a vendor XML file (auto-detect vendor).
124///
125/// Automatically detects the vendor format and parses accordingly.
126/// Currently supports: FastDDS (eProsima).
127///
128/// # Arguments
129/// - `path`: Path to the XML profile file (null-terminated C string).
130///
131/// # Returns
132/// - Valid pointer on success.
133/// - NULL if the file cannot be read or parsed.
134///
135/// # Safety
136/// - `path` must be a valid null-terminated C string.
137/// - The returned pointer must be freed with `hdds_qos_destroy`.
138#[cfg(feature = "qos-loaders")]
139#[no_mangle]
140pub unsafe extern "C" fn hdds_qos_from_xml(path: *const c_char) -> *mut HddsQoS {
141    if path.is_null() {
142        return ptr::null_mut();
143    }
144
145    let Ok(path_str) = CStr::from_ptr(path).to_str() else {
146        return ptr::null_mut();
147    };
148
149    match QoS::from_xml(path_str) {
150        Ok(qos) => Box::into_raw(Box::new(qos)).cast::<HddsQoS>(),
151        Err(err) => {
152            eprintln!("[hdds_c] Failed to load QoS from XML: {}", err);
153            ptr::null_mut()
154        }
155    }
156}
157
158// =============================================================================
159// QoS Builder Methods (Fluent API via mutation)
160// =============================================================================
161
162/// Set history depth (KEEP_LAST) on a QoS profile.
163///
164/// # Safety
165/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
166#[no_mangle]
167pub unsafe extern "C" fn hdds_qos_set_history_depth(qos: *mut HddsQoS, depth: u32) -> HddsError {
168    if qos.is_null() {
169        return HddsError::HddsInvalidArgument;
170    }
171
172    let qos_ref = &mut *qos.cast::<QoS>();
173    qos_ref.history = hdds::dds::qos::History::KeepLast(depth);
174    HddsError::HddsOk
175}
176
177/// Set history policy to KEEP_ALL.
178///
179/// # Safety
180/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
181#[no_mangle]
182pub unsafe extern "C" fn hdds_qos_set_history_keep_all(qos: *mut HddsQoS) -> HddsError {
183    if qos.is_null() {
184        return HddsError::HddsInvalidArgument;
185    }
186
187    let qos_ref = &mut *qos.cast::<QoS>();
188    qos_ref.history = hdds::dds::qos::History::KeepAll;
189    HddsError::HddsOk
190}
191
192/// Set durability to VOLATILE.
193///
194/// # Safety
195/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
196#[no_mangle]
197pub unsafe extern "C" fn hdds_qos_set_volatile(qos: *mut HddsQoS) -> HddsError {
198    if qos.is_null() {
199        return HddsError::HddsInvalidArgument;
200    }
201
202    let qos_ref = &mut *qos.cast::<QoS>();
203    qos_ref.durability = hdds::dds::qos::Durability::Volatile;
204    HddsError::HddsOk
205}
206
207/// Set durability to TRANSIENT_LOCAL.
208///
209/// # Safety
210/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
211#[no_mangle]
212pub unsafe extern "C" fn hdds_qos_set_transient_local(qos: *mut HddsQoS) -> HddsError {
213    if qos.is_null() {
214        return HddsError::HddsInvalidArgument;
215    }
216
217    let qos_ref = &mut *qos.cast::<QoS>();
218    qos_ref.durability = hdds::dds::qos::Durability::TransientLocal;
219    HddsError::HddsOk
220}
221
222/// Set durability to PERSISTENT.
223///
224/// # Safety
225/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
226#[no_mangle]
227pub unsafe extern "C" fn hdds_qos_set_persistent(qos: *mut HddsQoS) -> HddsError {
228    if qos.is_null() {
229        return HddsError::HddsInvalidArgument;
230    }
231
232    let qos_ref = &mut *qos.cast::<QoS>();
233    qos_ref.durability = hdds::dds::qos::Durability::Persistent;
234    HddsError::HddsOk
235}
236
237/// Set reliability to RELIABLE.
238///
239/// # Safety
240/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
241#[no_mangle]
242pub unsafe extern "C" fn hdds_qos_set_reliable(qos: *mut HddsQoS) -> HddsError {
243    if qos.is_null() {
244        return HddsError::HddsInvalidArgument;
245    }
246
247    let qos_ref = &mut *qos.cast::<QoS>();
248    qos_ref.reliability = hdds::dds::qos::Reliability::Reliable;
249    HddsError::HddsOk
250}
251
252/// Set reliability to BEST_EFFORT.
253///
254/// # Safety
255/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
256#[no_mangle]
257pub unsafe extern "C" fn hdds_qos_set_best_effort(qos: *mut HddsQoS) -> HddsError {
258    if qos.is_null() {
259        return HddsError::HddsInvalidArgument;
260    }
261
262    let qos_ref = &mut *qos.cast::<QoS>();
263    qos_ref.reliability = hdds::dds::qos::Reliability::BestEffort;
264    HddsError::HddsOk
265}
266
267/// Set deadline period in nanoseconds.
268///
269/// # Safety
270/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
271#[no_mangle]
272pub unsafe extern "C" fn hdds_qos_set_deadline_ns(qos: *mut HddsQoS, period_ns: u64) -> HddsError {
273    if qos.is_null() {
274        return HddsError::HddsInvalidArgument;
275    }
276
277    let qos_ref = &mut *qos.cast::<QoS>();
278    qos_ref.deadline = hdds::dds::qos::Deadline::new(std::time::Duration::from_nanos(period_ns));
279    HddsError::HddsOk
280}
281
282/// Set lifespan duration in nanoseconds.
283///
284/// # Safety
285/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
286#[no_mangle]
287pub unsafe extern "C" fn hdds_qos_set_lifespan_ns(
288    qos: *mut HddsQoS,
289    duration_ns: u64,
290) -> HddsError {
291    if qos.is_null() {
292        return HddsError::HddsInvalidArgument;
293    }
294
295    let qos_ref = &mut *qos.cast::<QoS>();
296    qos_ref.lifespan = hdds::dds::qos::Lifespan::new(std::time::Duration::from_nanos(duration_ns));
297    HddsError::HddsOk
298}
299
300/// Set ownership to SHARED.
301///
302/// # Safety
303/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
304#[no_mangle]
305pub unsafe extern "C" fn hdds_qos_set_ownership_shared(qos: *mut HddsQoS) -> HddsError {
306    if qos.is_null() {
307        return HddsError::HddsInvalidArgument;
308    }
309
310    let qos_ref = &mut *qos.cast::<QoS>();
311    qos_ref.ownership = hdds::dds::qos::Ownership::shared();
312    HddsError::HddsOk
313}
314
315/// Set ownership to EXCLUSIVE with given strength.
316///
317/// # Safety
318/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
319#[no_mangle]
320pub unsafe extern "C" fn hdds_qos_set_ownership_exclusive(
321    qos: *mut HddsQoS,
322    strength: i32,
323) -> HddsError {
324    if qos.is_null() {
325        return HddsError::HddsInvalidArgument;
326    }
327
328    let qos_ref = &mut *qos.cast::<QoS>();
329    qos_ref.ownership = hdds::dds::qos::Ownership::exclusive();
330    qos_ref.ownership_strength = hdds::dds::qos::OwnershipStrength::new(strength);
331    HddsError::HddsOk
332}
333
334/// Add a partition name to the QoS.
335///
336/// # Safety
337/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
338/// - `partition` must be a valid null-terminated C string.
339#[no_mangle]
340pub unsafe extern "C" fn hdds_qos_add_partition(
341    qos: *mut HddsQoS,
342    partition: *const c_char,
343) -> HddsError {
344    if qos.is_null() || partition.is_null() {
345        return HddsError::HddsInvalidArgument;
346    }
347
348    let Ok(part_str) = CStr::from_ptr(partition).to_str() else {
349        return HddsError::HddsInvalidArgument;
350    };
351
352    let qos_ref = &mut *qos.cast::<QoS>();
353
354    // Add new partition using the add() method
355    qos_ref.partition.add(part_str);
356
357    HddsError::HddsOk
358}
359
360// =============================================================================
361// QoS Getters (for inspection/debugging)
362// =============================================================================
363
364/// Check if QoS is reliable.
365///
366/// # Safety
367/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
368#[no_mangle]
369pub unsafe extern "C" fn hdds_qos_is_reliable(qos: *const HddsQoS) -> bool {
370    if qos.is_null() {
371        return false;
372    }
373
374    let qos_ref = &*qos.cast::<QoS>();
375    matches!(qos_ref.reliability, hdds::dds::qos::Reliability::Reliable)
376}
377
378/// Check if QoS is transient-local.
379///
380/// # Safety
381/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
382#[no_mangle]
383pub unsafe extern "C" fn hdds_qos_is_transient_local(qos: *const HddsQoS) -> bool {
384    if qos.is_null() {
385        return false;
386    }
387
388    let qos_ref = &*qos.cast::<QoS>();
389    matches!(
390        qos_ref.durability,
391        hdds::dds::qos::Durability::TransientLocal
392    )
393}
394
395/// Get history depth.
396///
397/// # Safety
398/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
399#[no_mangle]
400pub unsafe extern "C" fn hdds_qos_get_history_depth(qos: *const HddsQoS) -> u32 {
401    if qos.is_null() {
402        return 0;
403    }
404
405    let qos_ref = &*qos.cast::<QoS>();
406    match qos_ref.history {
407        hdds::dds::qos::History::KeepLast(depth) => depth,
408        hdds::dds::qos::History::KeepAll => 0,
409    }
410}
411
412/// Get deadline period in nanoseconds.
413///
414/// Returns `u64::MAX` if infinite.
415///
416/// # Safety
417/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
418#[no_mangle]
419pub unsafe extern "C" fn hdds_qos_get_deadline_ns(qos: *const HddsQoS) -> u64 {
420    if qos.is_null() {
421        return u64::MAX;
422    }
423
424    let qos_ref = &*qos.cast::<QoS>();
425    qos_ref.deadline.period.as_nanos() as u64
426}
427
428/// Get lifespan duration in nanoseconds.
429///
430/// Returns `u64::MAX` if infinite.
431///
432/// # Safety
433/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
434#[no_mangle]
435pub unsafe extern "C" fn hdds_qos_get_lifespan_ns(qos: *const HddsQoS) -> u64 {
436    if qos.is_null() {
437        return u64::MAX;
438    }
439
440    let qos_ref = &*qos.cast::<QoS>();
441    qos_ref.lifespan.duration.as_nanos() as u64
442}
443
444/// Check if ownership is exclusive.
445///
446/// # Safety
447/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
448#[no_mangle]
449pub unsafe extern "C" fn hdds_qos_is_ownership_exclusive(qos: *const HddsQoS) -> bool {
450    if qos.is_null() {
451        return false;
452    }
453
454    let qos_ref = &*qos.cast::<QoS>();
455    matches!(
456        qos_ref.ownership.kind,
457        hdds::dds::qos::OwnershipKind::Exclusive
458    )
459}
460
461/// Get ownership strength value.
462///
463/// # Safety
464/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
465#[no_mangle]
466pub unsafe extern "C" fn hdds_qos_get_ownership_strength(qos: *const HddsQoS) -> i32 {
467    if qos.is_null() {
468        return 0;
469    }
470
471    let qos_ref = &*qos.cast::<QoS>();
472    qos_ref.ownership_strength.value
473}
474
475/// Liveliness kind enumeration for C FFI.
476#[repr(C)]
477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
478#[allow(clippy::enum_variant_names)] // C FFI: prefix required (no namespaces in C)
479pub enum HddsLivelinessKind {
480    /// DDS infrastructure automatically asserts liveliness.
481    HddsLivelinessAutomatic = 0,
482    /// Application must assert per participant.
483    HddsLivelinessManualByParticipant = 1,
484    /// Application must assert per writer/topic.
485    HddsLivelinessManualByTopic = 2,
486}
487
488/// Get liveliness kind.
489///
490/// # Safety
491/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
492#[no_mangle]
493pub unsafe extern "C" fn hdds_qos_get_liveliness_kind(qos: *const HddsQoS) -> HddsLivelinessKind {
494    if qos.is_null() {
495        return HddsLivelinessKind::HddsLivelinessAutomatic;
496    }
497
498    let qos_ref = &*qos.cast::<QoS>();
499    match qos_ref.liveliness.kind {
500        hdds::dds::qos::LivelinessKind::Automatic => HddsLivelinessKind::HddsLivelinessAutomatic,
501        hdds::dds::qos::LivelinessKind::ManualByParticipant => {
502            HddsLivelinessKind::HddsLivelinessManualByParticipant
503        }
504        hdds::dds::qos::LivelinessKind::ManualByTopic => {
505            HddsLivelinessKind::HddsLivelinessManualByTopic
506        }
507    }
508}
509
510/// Get liveliness lease duration in nanoseconds.
511///
512/// Returns `u64::MAX` if infinite.
513///
514/// # Safety
515/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
516#[no_mangle]
517pub unsafe extern "C" fn hdds_qos_get_liveliness_lease_ns(qos: *const HddsQoS) -> u64 {
518    if qos.is_null() {
519        return u64::MAX;
520    }
521
522    let qos_ref = &*qos.cast::<QoS>();
523    qos_ref.liveliness.lease_duration.as_nanos() as u64
524}
525
526/// Get time-based filter minimum separation in nanoseconds.
527///
528/// Returns 0 if no filtering (all samples delivered).
529///
530/// # Safety
531/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
532#[no_mangle]
533pub unsafe extern "C" fn hdds_qos_get_time_based_filter_ns(qos: *const HddsQoS) -> u64 {
534    if qos.is_null() {
535        return 0;
536    }
537
538    let qos_ref = &*qos.cast::<QoS>();
539    qos_ref.time_based_filter.minimum_separation.as_nanos() as u64
540}
541
542/// Get latency budget in nanoseconds.
543///
544/// Returns 0 if no latency budget (best effort delivery).
545///
546/// # Safety
547/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
548#[no_mangle]
549pub unsafe extern "C" fn hdds_qos_get_latency_budget_ns(qos: *const HddsQoS) -> u64 {
550    if qos.is_null() {
551        return 0;
552    }
553
554    let qos_ref = &*qos.cast::<QoS>();
555    qos_ref.latency_budget.duration.as_nanos() as u64
556}
557
558/// Get transport priority.
559///
560/// # Safety
561/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
562#[no_mangle]
563pub unsafe extern "C" fn hdds_qos_get_transport_priority(qos: *const HddsQoS) -> i32 {
564    if qos.is_null() {
565        return 0;
566    }
567
568    let qos_ref = &*qos.cast::<QoS>();
569    qos_ref.transport_priority.value
570}
571
572/// Get max samples resource limit.
573///
574/// Returns `usize::MAX` for unlimited.
575///
576/// # Safety
577/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
578#[no_mangle]
579pub unsafe extern "C" fn hdds_qos_get_max_samples(qos: *const HddsQoS) -> usize {
580    if qos.is_null() {
581        return usize::MAX;
582    }
583
584    let qos_ref = &*qos.cast::<QoS>();
585    qos_ref.resource_limits.max_samples
586}
587
588/// Get max instances resource limit.
589///
590/// Returns `usize::MAX` for unlimited.
591///
592/// # Safety
593/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
594#[no_mangle]
595pub unsafe extern "C" fn hdds_qos_get_max_instances(qos: *const HddsQoS) -> usize {
596    if qos.is_null() {
597        return usize::MAX;
598    }
599
600    let qos_ref = &*qos.cast::<QoS>();
601    qos_ref.resource_limits.max_instances
602}
603
604/// Get max samples per instance resource limit.
605///
606/// Returns `usize::MAX` for unlimited.
607///
608/// # Safety
609/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
610#[no_mangle]
611pub unsafe extern "C" fn hdds_qos_get_max_samples_per_instance(qos: *const HddsQoS) -> usize {
612    if qos.is_null() {
613        return usize::MAX;
614    }
615
616    let qos_ref = &*qos.cast::<QoS>();
617    qos_ref.resource_limits.max_samples_per_instance
618}
619
620// =============================================================================
621// QoS Setters (additional)
622// =============================================================================
623
624/// Set liveliness to automatic with given lease duration in nanoseconds.
625///
626/// # Safety
627/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
628#[no_mangle]
629pub unsafe extern "C" fn hdds_qos_set_liveliness_automatic_ns(
630    qos: *mut HddsQoS,
631    lease_ns: u64,
632) -> HddsError {
633    if qos.is_null() {
634        return HddsError::HddsInvalidArgument;
635    }
636
637    let qos_ref = &mut *qos.cast::<QoS>();
638    qos_ref.liveliness =
639        hdds::dds::qos::Liveliness::automatic(std::time::Duration::from_nanos(lease_ns));
640    HddsError::HddsOk
641}
642
643/// Set liveliness to manual-by-participant with given lease duration in nanoseconds.
644///
645/// # Safety
646/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
647#[no_mangle]
648pub unsafe extern "C" fn hdds_qos_set_liveliness_manual_participant_ns(
649    qos: *mut HddsQoS,
650    lease_ns: u64,
651) -> HddsError {
652    if qos.is_null() {
653        return HddsError::HddsInvalidArgument;
654    }
655
656    let qos_ref = &mut *qos.cast::<QoS>();
657    qos_ref.liveliness = hdds::dds::qos::Liveliness::manual_by_participant(
658        std::time::Duration::from_nanos(lease_ns),
659    );
660    HddsError::HddsOk
661}
662
663/// Set liveliness to manual-by-topic with given lease duration in nanoseconds.
664///
665/// # Safety
666/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
667#[no_mangle]
668pub unsafe extern "C" fn hdds_qos_set_liveliness_manual_topic_ns(
669    qos: *mut HddsQoS,
670    lease_ns: u64,
671) -> HddsError {
672    if qos.is_null() {
673        return HddsError::HddsInvalidArgument;
674    }
675
676    let qos_ref = &mut *qos.cast::<QoS>();
677    qos_ref.liveliness =
678        hdds::dds::qos::Liveliness::manual_by_topic(std::time::Duration::from_nanos(lease_ns));
679    HddsError::HddsOk
680}
681
682/// Set time-based filter minimum separation in nanoseconds.
683///
684/// # Safety
685/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
686#[no_mangle]
687pub unsafe extern "C" fn hdds_qos_set_time_based_filter_ns(
688    qos: *mut HddsQoS,
689    min_separation_ns: u64,
690) -> HddsError {
691    if qos.is_null() {
692        return HddsError::HddsInvalidArgument;
693    }
694
695    let qos_ref = &mut *qos.cast::<QoS>();
696    qos_ref.time_based_filter =
697        hdds::dds::qos::TimeBasedFilter::new(std::time::Duration::from_nanos(min_separation_ns));
698    HddsError::HddsOk
699}
700
701/// Set latency budget in nanoseconds.
702///
703/// # Safety
704/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
705#[no_mangle]
706pub unsafe extern "C" fn hdds_qos_set_latency_budget_ns(
707    qos: *mut HddsQoS,
708    budget_ns: u64,
709) -> HddsError {
710    if qos.is_null() {
711        return HddsError::HddsInvalidArgument;
712    }
713
714    let qos_ref = &mut *qos.cast::<QoS>();
715    qos_ref.latency_budget =
716        hdds::dds::qos::LatencyBudget::new(std::time::Duration::from_nanos(budget_ns));
717    HddsError::HddsOk
718}
719
720/// Set transport priority.
721///
722/// # Safety
723/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
724#[no_mangle]
725pub unsafe extern "C" fn hdds_qos_set_transport_priority(
726    qos: *mut HddsQoS,
727    priority: i32,
728) -> HddsError {
729    if qos.is_null() {
730        return HddsError::HddsInvalidArgument;
731    }
732
733    let qos_ref = &mut *qos.cast::<QoS>();
734    qos_ref.transport_priority.value = priority;
735    HddsError::HddsOk
736}
737
738/// Set resource limits.
739///
740/// Use `usize::MAX` for any value to indicate unlimited.
741///
742/// # Safety
743/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
744#[no_mangle]
745pub unsafe extern "C" fn hdds_qos_set_resource_limits(
746    qos: *mut HddsQoS,
747    max_samples: usize,
748    max_instances: usize,
749    max_samples_per_instance: usize,
750) -> HddsError {
751    if qos.is_null() {
752        return HddsError::HddsInvalidArgument;
753    }
754
755    let qos_ref = &mut *qos.cast::<QoS>();
756    qos_ref.resource_limits.max_samples = max_samples;
757    qos_ref.resource_limits.max_instances = max_instances;
758    qos_ref.resource_limits.max_samples_per_instance = max_samples_per_instance;
759    HddsError::HddsOk
760}
761
762// =============================================================================
763// Clone QoS
764// =============================================================================
765
766/// Clone a QoS profile.
767///
768/// # Safety
769/// - `qos` must be a valid pointer from `hdds_qos_*` functions.
770/// - The returned pointer must be freed with `hdds_qos_destroy`.
771#[no_mangle]
772pub unsafe extern "C" fn hdds_qos_clone(qos: *const HddsQoS) -> *mut HddsQoS {
773    if qos.is_null() {
774        return ptr::null_mut();
775    }
776
777    let qos_ref = &*qos.cast::<QoS>();
778    let cloned = Box::new(qos_ref.clone());
779    Box::into_raw(cloned).cast::<HddsQoS>()
780}
781
782#[cfg(test)]
783mod tests {
784    use super::*;
785    use std::ffi::CString;
786
787    #[test]
788    fn test_qos_creation_and_destroy() {
789        unsafe {
790            let qos = hdds_qos_default();
791            assert!(!qos.is_null());
792            hdds_qos_destroy(qos);
793
794            let qos = hdds_qos_reliable();
795            assert!(!qos.is_null());
796            assert!(hdds_qos_is_reliable(qos));
797            hdds_qos_destroy(qos);
798
799            let qos = hdds_qos_best_effort();
800            assert!(!qos.is_null());
801            assert!(!hdds_qos_is_reliable(qos));
802            hdds_qos_destroy(qos);
803        }
804    }
805
806    #[test]
807    fn test_qos_setters() {
808        unsafe {
809            let qos = hdds_qos_default();
810
811            // Test history depth
812            assert_eq!(hdds_qos_set_history_depth(qos, 50), HddsError::HddsOk);
813            assert_eq!(hdds_qos_get_history_depth(qos), 50);
814
815            // Test transient local
816            assert_eq!(hdds_qos_set_transient_local(qos), HddsError::HddsOk);
817            assert!(hdds_qos_is_transient_local(qos));
818
819            // Test reliable
820            assert_eq!(hdds_qos_set_reliable(qos), HddsError::HddsOk);
821            assert!(hdds_qos_is_reliable(qos));
822
823            // Test partition
824            let part = CString::new("test_partition").unwrap();
825            assert_eq!(
826                hdds_qos_add_partition(qos, part.as_ptr()),
827                HddsError::HddsOk
828            );
829
830            hdds_qos_destroy(qos);
831        }
832    }
833
834    #[test]
835    fn test_qos_clone() {
836        unsafe {
837            let qos = hdds_qos_reliable();
838            hdds_qos_set_history_depth(qos, 42);
839            hdds_qos_set_transient_local(qos);
840
841            let cloned = hdds_qos_clone(qos);
842            assert!(!cloned.is_null());
843            assert!(hdds_qos_is_reliable(cloned));
844            assert!(hdds_qos_is_transient_local(cloned));
845            assert_eq!(hdds_qos_get_history_depth(cloned), 42);
846
847            hdds_qos_destroy(qos);
848            hdds_qos_destroy(cloned);
849        }
850    }
851}