Skip to main content

hdds_c/
listener.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4//! DDS Listener FFI Bindings
5//!
6//! C-compatible callback-based listener API for DataReader and DataWriter events.
7//! These structs and functions wrap the Rust listener traits defined in
8//! `hdds::dds::listener` for consumption by C, C++, and Python SDKs.
9//!
10//! # Usage from C
11//!
12//! ```c
13//! void my_on_data(const uint8_t* data, size_t len, void* user_data) {
14//!     // process data
15//! }
16//!
17//! HddsReaderListener listener = {0};
18//! listener.on_data_available = my_on_data;
19//! listener.user_data = my_context;
20//! hdds_reader_set_listener(reader, &listener);
21//! ```
22//!
23//! # Thread Safety
24//!
25//! The C caller is responsible for ensuring that callback functions and
26//! user_data pointers remain valid for the lifetime of the listener.
27
28use std::os::raw::{c_char, c_void};
29
30use super::{HddsDataReader, HddsDataWriter, HddsError};
31
32// =============================================================================
33// C-compatible status structs
34// =============================================================================
35
36/// Subscription matched status (C-compatible mirror of Rust SubscriptionMatchedStatus).
37///
38/// Reports the number of publications matched with this reader.
39#[repr(C)]
40#[derive(Debug, Clone, Copy, Default)]
41pub struct HddsSubscriptionMatchedStatus {
42    /// Total cumulative count of matched publications.
43    pub total_count: u32,
44    /// Change in total_count since last callback.
45    pub total_count_change: i32,
46    /// Current number of matched publications.
47    pub current_count: u32,
48    /// Change in current_count since last callback.
49    pub current_count_change: i32,
50}
51
52/// Publication matched status (C-compatible mirror of Rust PublicationMatchedStatus).
53///
54/// Reports the number of subscriptions matched with this writer.
55#[repr(C)]
56#[derive(Debug, Clone, Copy, Default)]
57pub struct HddsPublicationMatchedStatus {
58    /// Total cumulative count of matched subscriptions.
59    pub total_count: u32,
60    /// Change in total_count since last callback.
61    pub total_count_change: i32,
62    /// Current number of matched subscriptions.
63    pub current_count: u32,
64    /// Change in current_count since last callback.
65    pub current_count_change: i32,
66}
67
68/// Liveliness changed status (C-compatible mirror of Rust LivelinessChangedStatus).
69///
70/// Reports changes in liveliness of matched writers.
71#[repr(C)]
72#[derive(Debug, Clone, Copy, Default)]
73pub struct HddsLivelinessChangedStatus {
74    /// Number of publications currently asserting liveliness.
75    pub alive_count: u32,
76    /// Change in alive_count since last callback.
77    pub alive_count_change: i32,
78    /// Number of publications that have lost liveliness.
79    pub not_alive_count: u32,
80    /// Change in not_alive_count since last callback.
81    pub not_alive_count_change: i32,
82}
83
84/// Sample lost status (C-compatible mirror of Rust SampleLostStatus).
85///
86/// Reports samples lost due to gaps in sequence numbers.
87#[repr(C)]
88#[derive(Debug, Clone, Copy, Default)]
89pub struct HddsSampleLostStatus {
90    /// Total cumulative count of lost samples.
91    pub total_count: u32,
92    /// Change in total_count since last callback.
93    pub total_count_change: i32,
94}
95
96/// Sample rejected status (C-compatible mirror of Rust SampleRejectedStatus).
97///
98/// Reports samples rejected due to resource limits.
99/// `last_reason` values: 0=NotRejected, 1=ResourceLimit, 2=InstanceLimit,
100/// 3=SamplesPerInstanceLimit.
101#[repr(C)]
102#[derive(Debug, Clone, Copy, Default)]
103pub struct HddsSampleRejectedStatus {
104    /// Total cumulative count of rejected samples.
105    pub total_count: u32,
106    /// Change in total_count since last callback.
107    pub total_count_change: i32,
108    /// Reason for rejection (0=NotRejected, 1=ResourceLimit, 2=InstanceLimit, 3=SamplesPerInstanceLimit).
109    pub last_reason: u32,
110}
111
112/// Deadline missed status (C-compatible mirror of Rust RequestedDeadlineMissedStatus).
113///
114/// Reports missed deadlines on a reader or writer.
115#[repr(C)]
116#[derive(Debug, Clone, Copy, Default)]
117pub struct HddsDeadlineMissedStatus {
118    /// Total cumulative count of missed deadlines.
119    pub total_count: u32,
120    /// Change in total_count since last callback.
121    pub total_count_change: i32,
122}
123
124/// Incompatible QoS status (C-compatible mirror of Rust RequestedIncompatibleQosStatus).
125///
126/// Reports QoS incompatibility between matched endpoints.
127#[repr(C)]
128#[derive(Debug, Clone, Copy, Default)]
129pub struct HddsIncompatibleQosStatus {
130    /// Total cumulative count of incompatible QoS offers.
131    pub total_count: u32,
132    /// Change in total_count since last callback.
133    pub total_count_change: i32,
134    /// ID of the last incompatible QoS policy.
135    pub last_policy_id: u32,
136}
137
138// =============================================================================
139// Callback type aliases (C function pointers)
140// =============================================================================
141
142/// Callback for data available events.
143///
144/// # Parameters
145/// - `data`: Pointer to serialized sample bytes
146/// - `len`: Length of the serialized data in bytes
147/// - `user_data`: User-provided context pointer
148pub type HddsOnDataAvailable =
149    Option<unsafe extern "C" fn(data: *const u8, len: usize, user_data: *mut c_void)>;
150
151/// Callback for subscription matched events.
152pub type HddsOnSubscriptionMatched = Option<
153    unsafe extern "C" fn(status: *const HddsSubscriptionMatchedStatus, user_data: *mut c_void),
154>;
155
156/// Callback for publication matched events.
157pub type HddsOnPublicationMatched = Option<
158    unsafe extern "C" fn(status: *const HddsPublicationMatchedStatus, user_data: *mut c_void),
159>;
160
161/// Callback for liveliness changed events.
162pub type HddsOnLivelinessChanged = Option<
163    unsafe extern "C" fn(status: *const HddsLivelinessChangedStatus, user_data: *mut c_void),
164>;
165
166/// Callback for sample lost events.
167pub type HddsOnSampleLost =
168    Option<unsafe extern "C" fn(status: *const HddsSampleLostStatus, user_data: *mut c_void)>;
169
170/// Callback for sample rejected events.
171pub type HddsOnSampleRejected =
172    Option<unsafe extern "C" fn(status: *const HddsSampleRejectedStatus, user_data: *mut c_void)>;
173
174/// Callback for deadline missed events (reader side).
175pub type HddsOnDeadlineMissed =
176    Option<unsafe extern "C" fn(status: *const HddsDeadlineMissedStatus, user_data: *mut c_void)>;
177
178/// Callback for incompatible QoS events (reader side).
179pub type HddsOnIncompatibleQos =
180    Option<unsafe extern "C" fn(status: *const HddsIncompatibleQosStatus, user_data: *mut c_void)>;
181
182/// Callback for sample written events (writer confirmation).
183///
184/// # Parameters
185/// - `data`: Pointer to serialized sample bytes
186/// - `len`: Length of the serialized data in bytes
187/// - `sequence_number`: Assigned RTPS sequence number
188/// - `user_data`: User-provided context pointer
189pub type HddsOnSampleWritten = Option<
190    unsafe extern "C" fn(data: *const u8, len: usize, sequence_number: u64, user_data: *mut c_void),
191>;
192
193/// Callback for offered deadline missed events (writer side).
194///
195/// # Parameters
196/// - `instance_handle`: Handle of the instance that missed the deadline (0 if none)
197/// - `user_data`: User-provided context pointer
198pub type HddsOnOfferedDeadlineMissed =
199    Option<unsafe extern "C" fn(instance_handle: u64, user_data: *mut c_void)>;
200
201/// Callback for offered incompatible QoS events (writer side).
202///
203/// # Parameters
204/// - `policy_id`: ID of the incompatible QoS policy
205/// - `policy_name`: Null-terminated policy name string (e.g., "RELIABILITY")
206/// - `user_data`: User-provided context pointer
207pub type HddsOnOfferedIncompatibleQos = Option<
208    unsafe extern "C" fn(policy_id: u32, policy_name: *const c_char, user_data: *mut c_void),
209>;
210
211/// Callback for liveliness lost events (writer side).
212pub type HddsOnLivelinessLost = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
213
214// =============================================================================
215// Listener structs
216// =============================================================================
217
218/// C-compatible DataReader listener.
219///
220/// Set callback fields to receive events. Any callback set to `None` (NULL)
221/// will be silently ignored. The `user_data` pointer is passed through to
222/// every callback invocation.
223///
224/// # Example (C)
225///
226/// ```c
227/// HddsReaderListener listener = {0};
228/// listener.on_data_available = my_data_callback;
229/// listener.on_subscription_matched = my_match_callback;
230/// listener.user_data = my_context;
231/// hdds_reader_set_listener(reader, &listener);
232/// ```
233#[repr(C)]
234pub struct HddsReaderListener {
235    /// Called when new data is available to read.
236    pub on_data_available: HddsOnDataAvailable,
237    /// Called when the reader matches/unmatches with a writer.
238    pub on_subscription_matched: HddsOnSubscriptionMatched,
239    /// Called when liveliness of a matched writer changes.
240    pub on_liveliness_changed: HddsOnLivelinessChanged,
241    /// Called when samples are lost (gap in sequence numbers).
242    pub on_sample_lost: HddsOnSampleLost,
243    /// Called when samples are rejected due to resource limits.
244    pub on_sample_rejected: HddsOnSampleRejected,
245    /// Called when the requested deadline is missed.
246    pub on_deadline_missed: HddsOnDeadlineMissed,
247    /// Called when QoS is incompatible with a matched writer.
248    pub on_incompatible_qos: HddsOnIncompatibleQos,
249    /// User-provided context pointer, passed to all callbacks.
250    pub user_data: *mut c_void,
251}
252
253// Safety: The C caller is responsible for thread safety of user_data and callbacks.
254// Listeners are stored internally and invoked from background threads.
255unsafe impl Send for HddsReaderListener {}
256unsafe impl Sync for HddsReaderListener {}
257
258/// C-compatible DataWriter listener.
259///
260/// Set callback fields to receive events. Any callback set to `None` (NULL)
261/// will be silently ignored. The `user_data` pointer is passed through to
262/// every callback invocation.
263///
264/// # Example (C)
265///
266/// ```c
267/// HddsWriterListener listener = {0};
268/// listener.on_publication_matched = my_match_callback;
269/// listener.user_data = my_context;
270/// hdds_writer_set_listener(writer, &listener);
271/// ```
272#[repr(C)]
273pub struct HddsWriterListener {
274    /// Called after a sample is successfully written.
275    pub on_sample_written: HddsOnSampleWritten,
276    /// Called when the writer matches/unmatches with a reader.
277    pub on_publication_matched: HddsOnPublicationMatched,
278    /// Called when an offered deadline is missed.
279    pub on_offered_deadline_missed: HddsOnOfferedDeadlineMissed,
280    /// Called when QoS is incompatible with a matched reader.
281    pub on_offered_incompatible_qos: HddsOnOfferedIncompatibleQos,
282    /// Called when liveliness is lost (MANUAL_BY_* only).
283    pub on_liveliness_lost: HddsOnLivelinessLost,
284    /// User-provided context pointer, passed to all callbacks.
285    pub user_data: *mut c_void,
286}
287
288// Safety: The C caller is responsible for thread safety of user_data and callbacks.
289unsafe impl Send for HddsWriterListener {}
290unsafe impl Sync for HddsWriterListener {}
291
292// =============================================================================
293// FFI functions
294// =============================================================================
295
296/// Install a listener on a DataReader.
297///
298/// The listener struct is copied internally. The caller must ensure that
299/// any `user_data` pointer and callback functions remain valid until the
300/// listener is cleared or the reader is destroyed.
301///
302/// # Safety
303///
304/// - `reader` must be a valid pointer returned from `hdds_reader_create` or similar.
305/// - `listener` must be a valid pointer to a properly initialized `HddsReaderListener`.
306///
307/// # Returns
308///
309/// `HddsOk` on success, `HddsInvalidArgument` if either pointer is null.
310#[no_mangle]
311pub unsafe extern "C" fn hdds_reader_set_listener(
312    reader: *mut HddsDataReader,
313    listener: *const HddsReaderListener,
314) -> HddsError {
315    if reader.is_null() || listener.is_null() {
316        return HddsError::HddsInvalidArgument;
317    }
318
319    // Listener bridging not yet implemented -- core DataReader does not
320    // expose set_listener(). Return Unsupported so callers know this is
321    // a no-op rather than silently ignoring callbacks.
322    let _ = listener;
323    log::warn!("hdds_reader_set_listener: not yet implemented");
324    HddsError::HddsUnsupported
325}
326
327/// Remove the listener from a DataReader.
328///
329/// After this call, no more callbacks will be invoked for this reader.
330///
331/// # Safety
332///
333/// - `reader` must be a valid pointer returned from `hdds_reader_create` or similar.
334///
335/// # Returns
336///
337/// `HddsOk` on success, `HddsInvalidArgument` if the pointer is null.
338#[no_mangle]
339pub unsafe extern "C" fn hdds_reader_clear_listener(reader: *mut HddsDataReader) -> HddsError {
340    if reader.is_null() {
341        return HddsError::HddsInvalidArgument;
342    }
343
344    // No-op until listener bridging is implemented.
345    log::warn!("hdds_reader_clear_listener: not yet implemented");
346    HddsError::HddsUnsupported
347}
348
349/// Install a listener on a DataWriter.
350///
351/// The listener struct is copied internally. The caller must ensure that
352/// any `user_data` pointer and callback functions remain valid until the
353/// listener is cleared or the writer is destroyed.
354///
355/// # Safety
356///
357/// - `writer` must be a valid pointer returned from `hdds_writer_create` or similar.
358/// - `listener` must be a valid pointer to a properly initialized `HddsWriterListener`.
359///
360/// # Returns
361///
362/// `HddsOk` on success, `HddsInvalidArgument` if either pointer is null.
363#[no_mangle]
364pub unsafe extern "C" fn hdds_writer_set_listener(
365    writer: *mut HddsDataWriter,
366    listener: *const HddsWriterListener,
367) -> HddsError {
368    if writer.is_null() || listener.is_null() {
369        return HddsError::HddsInvalidArgument;
370    }
371
372    // Listener bridging not yet implemented -- core DataWriter does not
373    // expose set_listener(). Return Unsupported so callers know.
374    let _ = listener;
375    log::warn!("hdds_writer_set_listener: not yet implemented");
376    HddsError::HddsUnsupported
377}
378
379/// Remove the listener from a DataWriter.
380///
381/// After this call, no more callbacks will be invoked for this writer.
382///
383/// # Safety
384///
385/// - `writer` must be a valid pointer returned from `hdds_writer_create` or similar.
386///
387/// # Returns
388///
389/// `HddsOk` on success, `HddsInvalidArgument` if the pointer is null.
390#[no_mangle]
391pub unsafe extern "C" fn hdds_writer_clear_listener(writer: *mut HddsDataWriter) -> HddsError {
392    if writer.is_null() {
393        return HddsError::HddsInvalidArgument;
394    }
395
396    // No-op until listener bridging is implemented.
397    log::warn!("hdds_writer_clear_listener: not yet implemented");
398    HddsError::HddsUnsupported
399}