Skip to main content

hdds_c/
pubsub.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4//! Publisher and Subscriber APIs for HDDS C FFI
5//!
6//! These provide the standard DDS entity hierarchy:
7//! Participant -> Publisher -> DataWriter
8//! Participant -> Subscriber -> DataReader
9
10use std::ffi::CStr;
11use std::os::raw::c_char;
12use std::ptr;
13use std::sync::Arc;
14
15use hdds::api::{Participant, Publisher, QoS, Subscriber};
16
17use super::{BytePayload, HddsDataReader, HddsDataWriter, HddsParticipant, HddsQoS};
18
19/// Opaque handle to a Publisher
20#[repr(C)]
21pub struct HddsPublisher {
22    _private: [u8; 0],
23}
24
25/// Opaque handle to a Subscriber
26#[repr(C)]
27pub struct HddsSubscriber {
28    _private: [u8; 0],
29}
30
31// =============================================================================
32// Publisher
33// =============================================================================
34
35/// Create a Publisher with default QoS
36///
37/// # Safety
38/// - `participant` must be a valid pointer
39///
40/// # Returns
41/// Publisher handle, or NULL on error
42#[no_mangle]
43pub unsafe extern "C" fn hdds_publisher_create(
44    participant: *mut HddsParticipant,
45) -> *mut HddsPublisher {
46    hdds_publisher_create_with_qos(participant, ptr::null())
47}
48
49/// Create a Publisher with custom QoS
50///
51/// # Safety
52/// - `participant` must be a valid pointer
53/// - `qos` can be NULL for default QoS
54///
55/// # Returns
56/// Publisher handle, or NULL on error
57#[no_mangle]
58pub unsafe extern "C" fn hdds_publisher_create_with_qos(
59    participant: *mut HddsParticipant,
60    qos: *const HddsQoS,
61) -> *mut HddsPublisher {
62    if participant.is_null() {
63        return ptr::null_mut();
64    }
65
66    let participant_ref = &*participant.cast::<Arc<Participant>>();
67
68    let qos_value = if qos.is_null() {
69        QoS::default()
70    } else {
71        (*qos.cast::<QoS>()).clone()
72    };
73
74    match participant_ref.create_publisher(qos_value) {
75        Ok(publisher) => Box::into_raw(Box::new(publisher)).cast::<HddsPublisher>(),
76        Err(e) => {
77            log::error!("Failed to create publisher: {:?}", e);
78            ptr::null_mut()
79        }
80    }
81}
82
83/// Destroy a Publisher
84///
85/// # Safety
86/// - `publisher` must be a valid pointer or NULL
87#[no_mangle]
88pub unsafe extern "C" fn hdds_publisher_destroy(publisher: *mut HddsPublisher) {
89    if !publisher.is_null() {
90        let _ = Box::from_raw(publisher.cast::<Publisher>());
91    }
92}
93
94// =============================================================================
95// Subscriber
96// =============================================================================
97
98/// Create a Subscriber with default QoS
99///
100/// # Safety
101/// - `participant` must be a valid pointer
102///
103/// # Returns
104/// Subscriber handle, or NULL on error
105#[no_mangle]
106pub unsafe extern "C" fn hdds_subscriber_create(
107    participant: *mut HddsParticipant,
108) -> *mut HddsSubscriber {
109    hdds_subscriber_create_with_qos(participant, ptr::null())
110}
111
112/// Create a Subscriber with custom QoS
113///
114/// # Safety
115/// - `participant` must be a valid pointer
116/// - `qos` can be NULL for default QoS
117///
118/// # Returns
119/// Subscriber handle, or NULL on error
120#[no_mangle]
121pub unsafe extern "C" fn hdds_subscriber_create_with_qos(
122    participant: *mut HddsParticipant,
123    qos: *const HddsQoS,
124) -> *mut HddsSubscriber {
125    if participant.is_null() {
126        return ptr::null_mut();
127    }
128
129    let participant_ref = &*participant.cast::<Arc<Participant>>();
130
131    let qos_value = if qos.is_null() {
132        QoS::default()
133    } else {
134        (*qos.cast::<QoS>()).clone()
135    };
136
137    match participant_ref.create_subscriber(qos_value) {
138        Ok(subscriber) => Box::into_raw(Box::new(subscriber)).cast::<HddsSubscriber>(),
139        Err(e) => {
140            log::error!("Failed to create subscriber: {:?}", e);
141            ptr::null_mut()
142        }
143    }
144}
145
146/// Destroy a Subscriber
147///
148/// # Safety
149/// - `subscriber` must be a valid pointer or NULL
150#[no_mangle]
151pub unsafe extern "C" fn hdds_subscriber_destroy(subscriber: *mut HddsSubscriber) {
152    if !subscriber.is_null() {
153        let _ = Box::from_raw(subscriber.cast::<Subscriber>());
154    }
155}
156
157// =============================================================================
158// Publisher -> DataWriter
159// =============================================================================
160
161/// Create a DataWriter from a Publisher with default QoS
162///
163/// # Safety
164/// - `publisher` must be a valid pointer returned from `hdds_publisher_create`
165/// - `topic_name` must be a valid null-terminated C string
166///
167/// # Returns
168/// DataWriter handle, or NULL on error
169#[no_mangle]
170pub unsafe extern "C" fn hdds_publisher_create_writer(
171    publisher: *mut HddsPublisher,
172    topic_name: *const c_char,
173) -> *mut HddsDataWriter {
174    hdds_publisher_create_writer_with_qos(publisher, topic_name, ptr::null())
175}
176
177/// Create a DataWriter from a Publisher with custom QoS
178///
179/// # Safety
180/// - `publisher` must be a valid pointer returned from `hdds_publisher_create`
181/// - `topic_name` must be a valid null-terminated C string
182/// - `qos` can be NULL for default QoS
183///
184/// # Returns
185/// DataWriter handle, or NULL on error
186#[no_mangle]
187pub unsafe extern "C" fn hdds_publisher_create_writer_with_qos(
188    publisher: *mut HddsPublisher,
189    topic_name: *const c_char,
190    qos: *const HddsQoS,
191) -> *mut HddsDataWriter {
192    if publisher.is_null() || topic_name.is_null() {
193        return ptr::null_mut();
194    }
195
196    let Ok(topic_str) = CStr::from_ptr(topic_name).to_str() else {
197        return ptr::null_mut();
198    };
199
200    let publisher_ref = &*publisher.cast::<Publisher>();
201
202    let qos_value = if qos.is_null() {
203        QoS::default()
204    } else {
205        (*qos.cast::<QoS>()).clone()
206    };
207
208    match publisher_ref.create_writer::<BytePayload>(topic_str, qos_value) {
209        Ok(writer) => Box::into_raw(Box::new(writer)).cast::<HddsDataWriter>(),
210        Err(e) => {
211            log::error!("Failed to create writer from publisher: {:?}", e);
212            ptr::null_mut()
213        }
214    }
215}
216
217// =============================================================================
218// Subscriber -> DataReader
219// =============================================================================
220
221/// Create a DataReader from a Subscriber with default QoS
222///
223/// # Safety
224/// - `subscriber` must be a valid pointer returned from `hdds_subscriber_create`
225/// - `topic_name` must be a valid null-terminated C string
226///
227/// # Returns
228/// DataReader handle, or NULL on error
229#[no_mangle]
230pub unsafe extern "C" fn hdds_subscriber_create_reader(
231    subscriber: *mut HddsSubscriber,
232    topic_name: *const c_char,
233) -> *mut HddsDataReader {
234    hdds_subscriber_create_reader_with_qos(subscriber, topic_name, ptr::null())
235}
236
237/// Create a DataReader from a Subscriber with custom QoS
238///
239/// # Safety
240/// - `subscriber` must be a valid pointer returned from `hdds_subscriber_create`
241/// - `topic_name` must be a valid null-terminated C string
242/// - `qos` can be NULL for default QoS
243///
244/// # Returns
245/// DataReader handle, or NULL on error
246#[no_mangle]
247pub unsafe extern "C" fn hdds_subscriber_create_reader_with_qos(
248    subscriber: *mut HddsSubscriber,
249    topic_name: *const c_char,
250    qos: *const HddsQoS,
251) -> *mut HddsDataReader {
252    if subscriber.is_null() || topic_name.is_null() {
253        return ptr::null_mut();
254    }
255
256    let Ok(topic_str) = CStr::from_ptr(topic_name).to_str() else {
257        return ptr::null_mut();
258    };
259
260    let subscriber_ref = &*subscriber.cast::<Subscriber>();
261
262    let qos_value = if qos.is_null() {
263        QoS::default()
264    } else {
265        (*qos.cast::<QoS>()).clone()
266    };
267
268    match subscriber_ref.create_reader::<BytePayload>(topic_str, qos_value) {
269        Ok(reader) => Box::into_raw(Box::new(reader)).cast::<HddsDataReader>(),
270        Err(e) => {
271            log::error!("Failed to create reader from subscriber: {:?}", e);
272            ptr::null_mut()
273        }
274    }
275}