use std::ffi::CStr;
use std::net::SocketAddr;
use std::os::raw::c_char;
use std::ptr;
use hdds::Participant;
use hdds::ShmPolicy;
use hdds::TcpConfig;
use hdds::TcpRole;
use hdds::TransportMode;
use hdds::TransportPreference;
use crate::{HddsError, HddsParticipant};
struct ParticipantConfigInner {
name: String,
domain_id: u32,
participant_id: Option<u8>,
transport_mode: TransportMode,
shm_policy: ShmPolicy,
tcp_config: Option<TcpConfig>,
transport_preference: TransportPreference,
discovery_ports: Option<(u16, u16, u16)>,
static_peers: Vec<SocketAddr>,
#[cfg(feature = "security")]
security_config: Option<crate::security_config::SecurityConfigInner>,
}
impl ParticipantConfigInner {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
domain_id: 0,
participant_id: None,
transport_mode: TransportMode::UdpMulticast,
shm_policy: ShmPolicy::Prefer,
tcp_config: None,
transport_preference: TransportPreference::UdpOnly,
discovery_ports: None,
static_peers: Vec::new(),
#[cfg(feature = "security")]
security_config: None,
}
}
}
#[repr(C)]
pub struct HddsParticipantConfig {
_private: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code, clippy::enum_variant_names)]
pub enum HddsShmPolicy {
HddsShmPrefer = 0,
HddsShmRequire = 1,
HddsShmDisable = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code, clippy::enum_variant_names)]
pub enum HddsTransportPreference {
HddsTransportPrefUdpOnly = 0,
HddsTransportPrefTcpOnly = 1,
HddsTransportPrefUdpDiscoveryTcpData = 2,
HddsTransportPrefShmPreferred = 3,
HddsTransportPrefShmLocalTcpRemote = 4,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code, clippy::enum_variant_names)]
pub enum HddsTcpRole {
HddsTcpRoleAuto = 0,
HddsTcpRoleServerOnly = 1,
HddsTcpRoleClientOnly = 2,
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_create(name: *const c_char) -> *mut HddsParticipantConfig {
if name.is_null() {
return ptr::null_mut();
}
let Ok(name_str) = CStr::from_ptr(name).to_str() else {
return ptr::null_mut();
};
let inner = ParticipantConfigInner::new(name_str);
Box::into_raw(Box::new(inner)).cast::<HddsParticipantConfig>()
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_destroy(config: *mut HddsParticipantConfig) {
if !config.is_null() {
let _ = Box::from_raw(config.cast::<ParticipantConfigInner>());
}
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_domain_id(
config: *mut HddsParticipantConfig,
domain_id: u32,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.domain_id = domain_id;
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_participant_id(
config: *mut HddsParticipantConfig,
participant_id: u8,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.participant_id = Some(participant_id);
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_transport_mode(
config: *mut HddsParticipantConfig,
mode: crate::HddsTransportMode,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.transport_mode = match mode {
crate::HddsTransportMode::HddsTransportIntraProcess => TransportMode::IntraProcess,
crate::HddsTransportMode::HddsTransportUdpMulticast => TransportMode::UdpMulticast,
};
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_discovery_ports(
config: *mut HddsParticipantConfig,
spdp_multicast: u16,
sedp_unicast: u16,
user_unicast: u16,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.discovery_ports = Some((spdp_multicast, sedp_unicast, user_unicast));
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_add_static_peer(
config: *mut HddsParticipantConfig,
addr: *const c_char,
) -> HddsError {
if config.is_null() || addr.is_null() {
return HddsError::HddsInvalidArgument;
}
let Ok(addr_str) = CStr::from_ptr(addr).to_str() else {
return HddsError::HddsInvalidArgument;
};
let Ok(socket_addr) = addr_str.parse::<SocketAddr>() else {
return HddsError::HddsInvalidArgument;
};
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.static_peers.push(socket_addr);
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_shm_policy(
config: *mut HddsParticipantConfig,
policy: HddsShmPolicy,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.shm_policy = match policy {
HddsShmPolicy::HddsShmPrefer => ShmPolicy::Prefer,
HddsShmPolicy::HddsShmRequire => ShmPolicy::Require,
HddsShmPolicy::HddsShmDisable => ShmPolicy::Disable,
};
HddsError::HddsOk
}
fn ensure_tcp(inner: &mut ParticipantConfigInner) -> &mut TcpConfig {
if inner.tcp_config.is_none() {
inner.tcp_config = Some(TcpConfig::enabled());
}
inner.tcp_config.as_mut().unwrap()
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_enable_tcp(
config: *mut HddsParticipantConfig,
listen_port: u16,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
let tcp = ensure_tcp(inner);
tcp.listen_port = listen_port;
tcp.enabled = true;
inner.transport_preference = TransportPreference::UdpDiscoveryTcpData;
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_tcp_role(
config: *mut HddsParticipantConfig,
role: HddsTcpRole,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
let tcp = ensure_tcp(inner);
tcp.role = match role {
HddsTcpRole::HddsTcpRoleAuto => TcpRole::Auto,
HddsTcpRole::HddsTcpRoleServerOnly => TcpRole::ServerOnly,
HddsTcpRole::HddsTcpRoleClientOnly => TcpRole::ClientOnly,
};
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_add_tcp_peer(
config: *mut HddsParticipantConfig,
addr: *const c_char,
) -> HddsError {
if config.is_null() || addr.is_null() {
return HddsError::HddsInvalidArgument;
}
let Ok(addr_str) = CStr::from_ptr(addr).to_str() else {
return HddsError::HddsInvalidArgument;
};
let Ok(socket_addr) = addr_str.parse::<SocketAddr>() else {
return HddsError::HddsInvalidArgument;
};
let inner = &mut *config.cast::<ParticipantConfigInner>();
let tcp = ensure_tcp(inner);
tcp.initial_peers.push(socket_addr);
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_tcp_nodelay(
config: *mut HddsParticipantConfig,
nodelay: bool,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
let tcp = ensure_tcp(inner);
tcp.nodelay = nodelay;
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_enable_tls(config: *mut HddsParticipantConfig) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
let tcp = ensure_tcp(inner);
tcp.tls_enabled = true;
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_transport_preference(
config: *mut HddsParticipantConfig,
pref: HddsTransportPreference,
) -> HddsError {
if config.is_null() {
return HddsError::HddsInvalidArgument;
}
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.transport_preference = match pref {
HddsTransportPreference::HddsTransportPrefUdpOnly => TransportPreference::UdpOnly,
HddsTransportPreference::HddsTransportPrefTcpOnly => TransportPreference::TcpOnly,
HddsTransportPreference::HddsTransportPrefUdpDiscoveryTcpData => {
TransportPreference::UdpDiscoveryTcpData
}
HddsTransportPreference::HddsTransportPrefShmPreferred => TransportPreference::ShmPreferred,
HddsTransportPreference::HddsTransportPrefShmLocalTcpRemote => {
TransportPreference::ShmLocalTcpRemote
}
};
HddsError::HddsOk
}
#[cfg(feature = "security")]
#[no_mangle]
pub unsafe extern "C" fn hdds_config_set_security(
config: *mut HddsParticipantConfig,
security: *mut crate::security_config::HddsSecurityConfig,
) -> HddsError {
if config.is_null() || security.is_null() {
return HddsError::HddsInvalidArgument;
}
let sec_inner = *Box::from_raw(security.cast::<crate::security_config::SecurityConfigInner>());
let inner = &mut *config.cast::<ParticipantConfigInner>();
inner.security_config = Some(sec_inner);
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_config_build(
config: *mut HddsParticipantConfig,
) -> *mut HddsParticipant {
if config.is_null() {
return ptr::null_mut();
}
use std::sync::Once;
static INIT: Once = Once::new();
INIT.call_once(|| {
let _ = env_logger::try_init();
});
let inner = *Box::from_raw(config.cast::<ParticipantConfigInner>());
let mut builder = Participant::builder(&inner.name)
.domain_id(inner.domain_id)
.participant_id(inner.participant_id)
.with_transport(inner.transport_mode)
.shm_policy(inner.shm_policy)
.with_transport_preference(inner.transport_preference);
if let Some(tcp) = inner.tcp_config {
builder = builder.tcp_config(tcp);
builder = builder.with_transport_preference(inner.transport_preference);
}
if let Some((spdp, sedp, user)) = inner.discovery_ports {
builder = builder.with_discovery_ports(spdp, sedp, user);
}
for peer in &inner.static_peers {
builder = builder.add_static_peer(&peer.to_string());
}
#[cfg(feature = "security")]
if let Some(sec) = inner.security_config {
let sec_builder = hdds::SecurityConfig::builder();
let mut sec_builder = sec_builder;
if let Some(p) = sec.identity_certificate {
sec_builder = sec_builder.identity_certificate(p);
}
if let Some(p) = sec.private_key {
sec_builder = sec_builder.private_key(p);
}
if let Some(p) = sec.ca_certificates {
sec_builder = sec_builder.ca_certificates(p);
}
if let Some(p) = sec.governance_xml {
sec_builder = sec_builder.governance_xml(p);
}
if let Some(p) = sec.permissions_xml {
sec_builder = sec_builder.permissions_xml(p);
}
sec_builder = sec_builder.enable_encryption(sec.enable_encryption);
sec_builder = sec_builder.enable_audit_log(sec.enable_audit_log);
if let Some(p) = sec.audit_log_path {
sec_builder = sec_builder.audit_log_path(p);
}
sec_builder = sec_builder.require_authentication(sec.require_authentication);
sec_builder = sec_builder.check_certificate_revocation(sec.check_certificate_revocation);
match sec_builder.build() {
Ok(security_config) => {
builder = builder.with_security(security_config);
}
Err(e) => {
log::error!("hdds_config_build: invalid security config: {:?}", e);
return ptr::null_mut();
}
}
}
match builder.build() {
Ok(participant) => Box::into_raw(Box::new(participant)).cast::<HddsParticipant>(),
Err(e) => {
log::error!("hdds_config_build: failed to create participant: {:?}", e);
ptr::null_mut()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_config_create_and_destroy() {
unsafe {
let name = CString::new("test_app").unwrap();
let config = hdds_config_create(name.as_ptr());
assert!(!config.is_null());
hdds_config_destroy(config);
}
}
#[test]
fn test_config_null_safety() {
unsafe {
assert!(hdds_config_create(ptr::null()).is_null());
hdds_config_destroy(ptr::null_mut());
assert_eq!(
hdds_config_set_domain_id(ptr::null_mut(), 0),
HddsError::HddsInvalidArgument,
);
assert_eq!(
hdds_config_set_participant_id(ptr::null_mut(), 0),
HddsError::HddsInvalidArgument,
);
assert_eq!(
hdds_config_enable_tcp(ptr::null_mut(), 7410),
HddsError::HddsInvalidArgument,
);
assert_eq!(
hdds_config_set_shm_policy(ptr::null_mut(), HddsShmPolicy::HddsShmPrefer),
HddsError::HddsInvalidArgument,
);
assert!(hdds_config_build(ptr::null_mut()).is_null());
}
}
#[test]
fn test_config_setters() {
unsafe {
let name = CString::new("test_setters").unwrap();
let config = hdds_config_create(name.as_ptr());
assert!(!config.is_null());
assert_eq!(hdds_config_set_domain_id(config, 42), HddsError::HddsOk);
assert_eq!(hdds_config_set_participant_id(config, 5), HddsError::HddsOk);
assert_eq!(
hdds_config_set_shm_policy(config, HddsShmPolicy::HddsShmDisable),
HddsError::HddsOk,
);
assert_eq!(hdds_config_enable_tcp(config, 7410), HddsError::HddsOk);
assert_eq!(
hdds_config_set_tcp_role(config, HddsTcpRole::HddsTcpRoleServerOnly),
HddsError::HddsOk,
);
assert_eq!(hdds_config_set_tcp_nodelay(config, true), HddsError::HddsOk);
assert_eq!(hdds_config_enable_tls(config), HddsError::HddsOk);
assert_eq!(
hdds_config_set_transport_preference(
config,
HddsTransportPreference::HddsTransportPrefTcpOnly,
),
HddsError::HddsOk,
);
assert_eq!(
hdds_config_set_discovery_ports(config, 9400, 9410, 9411),
HddsError::HddsOk,
);
let peer = CString::new("127.0.0.1:7412").unwrap();
assert_eq!(
hdds_config_add_tcp_peer(config, peer.as_ptr()),
HddsError::HddsOk
);
let static_peer = CString::new("192.168.1.100:7411").unwrap();
assert_eq!(
hdds_config_add_static_peer(config, static_peer.as_ptr()),
HddsError::HddsOk,
);
let inner = &*config.cast::<ParticipantConfigInner>();
assert_eq!(inner.domain_id, 42);
assert_eq!(inner.participant_id, Some(5));
assert_eq!(inner.shm_policy, ShmPolicy::Disable);
assert_eq!(inner.transport_preference, TransportPreference::TcpOnly);
assert!(inner.tcp_config.is_some());
let tcp = inner.tcp_config.as_ref().unwrap();
assert_eq!(tcp.listen_port, 7410);
assert!(tcp.nodelay);
assert!(tcp.tls_enabled);
assert_eq!(tcp.initial_peers.len(), 1);
assert_eq!(inner.discovery_ports, Some((9400, 9410, 9411)));
assert_eq!(inner.static_peers.len(), 1);
hdds_config_destroy(config);
}
}
#[test]
fn test_config_bad_peer_address() {
unsafe {
let name = CString::new("test_bad_peer").unwrap();
let config = hdds_config_create(name.as_ptr());
let bad_addr = CString::new("not_a_valid_address").unwrap();
assert_eq!(
hdds_config_add_tcp_peer(config, bad_addr.as_ptr()),
HddsError::HddsInvalidArgument,
);
assert_eq!(
hdds_config_add_static_peer(config, bad_addr.as_ptr()),
HddsError::HddsInvalidArgument,
);
hdds_config_destroy(config);
}
}
}