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}