extern crate alloc;
use core::ffi::c_char;
use core::ptr;
use std::ffi::CStr;
use std::path::PathBuf;
use std::sync::Arc;
use zerodds_dcps::runtime::{DcpsRuntime, RuntimeConfig};
use zerodds_security_runtime::{SecurityProfile, SecurityProfileConfig};
use crate::{ZeroDdsRuntime, random_guid_prefix};
#[derive(Debug, Default)]
pub struct ZeroDdsSecurityConfig {
pub identity_ca_path: Option<PathBuf>,
pub identity_cert_path: Option<PathBuf>,
pub identity_key_path: Option<PathBuf>,
pub permissions_ca_path: Option<PathBuf>,
pub governance_path: Option<PathBuf>,
pub permissions_path: Option<PathBuf>,
}
impl ZeroDdsSecurityConfig {
fn try_to_profile_cfg(&self, domain_id: u32) -> Result<SecurityProfileConfig, &'static str> {
Ok(SecurityProfileConfig {
domain_id,
identity_ca_pem: self
.identity_ca_path
.clone()
.ok_or("identity_ca_path not set")?,
identity_cert_pem: self
.identity_cert_path
.clone()
.ok_or("identity_cert_path not set")?,
identity_key_pem: self
.identity_key_path
.clone()
.ok_or("identity_key_path not set")?,
permissions_ca_pem: self
.permissions_ca_path
.clone()
.ok_or("permissions_ca_path not set")?,
governance_p7s: self
.governance_path
.clone()
.ok_or("governance_path not set")?,
permissions_p7s: self
.permissions_path
.clone()
.ok_or("permissions_path not set")?,
})
}
}
#[unsafe(no_mangle)]
pub extern "C" fn zerodds_security_config_create() -> *mut ZeroDdsSecurityConfig {
Box::into_raw(Box::new(ZeroDdsSecurityConfig::default()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_security_config_destroy(cfg: *mut ZeroDdsSecurityConfig) {
if cfg.is_null() {
return;
}
drop(unsafe { Box::from_raw(cfg) });
}
fn set_path_field(
cfg: *mut ZeroDdsSecurityConfig,
path: *const c_char,
field: fn(&mut ZeroDdsSecurityConfig) -> &mut Option<PathBuf>,
) -> i32 {
if cfg.is_null() {
eprintln!("zerodds_security_config_set_*: cfg is NULL");
return -1;
}
let c = unsafe { &mut *cfg };
if path.is_null() {
*field(c) = None;
return 0;
}
let bytes = unsafe { CStr::from_ptr(path) };
let s = match bytes.to_str() {
Ok(s) => s,
Err(e) => {
eprintln!("zerodds_security_config_set_*: path non-UTF-8: {e}");
return -1;
}
};
if s.is_empty() {
*field(c) = None;
} else {
*field(c) = Some(PathBuf::from(s));
}
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_security_set_identity_ca_path(
cfg: *mut ZeroDdsSecurityConfig,
path: *const c_char,
) -> i32 {
set_path_field(cfg, path, |c| &mut c.identity_ca_path)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_security_set_identity_cert_path(
cfg: *mut ZeroDdsSecurityConfig,
path: *const c_char,
) -> i32 {
set_path_field(cfg, path, |c| &mut c.identity_cert_path)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_security_set_private_key_path(
cfg: *mut ZeroDdsSecurityConfig,
path: *const c_char,
) -> i32 {
set_path_field(cfg, path, |c| &mut c.identity_key_path)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_security_set_permissions_ca_path(
cfg: *mut ZeroDdsSecurityConfig,
path: *const c_char,
) -> i32 {
set_path_field(cfg, path, |c| &mut c.permissions_ca_path)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_security_set_governance_path(
cfg: *mut ZeroDdsSecurityConfig,
path: *const c_char,
) -> i32 {
set_path_field(cfg, path, |c| &mut c.governance_path)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_security_set_permissions_path(
cfg: *mut ZeroDdsSecurityConfig,
path: *const c_char,
) -> i32 {
set_path_field(cfg, path, |c| &mut c.permissions_path)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_runtime_create_secure(
domain_id: u32,
cfg: *const ZeroDdsSecurityConfig,
) -> *mut ZeroDdsRuntime {
if cfg.is_null() {
eprintln!("zerodds_runtime_create_secure: cfg is NULL");
return ptr::null_mut();
}
let cfg_ref: &ZeroDdsSecurityConfig = unsafe { &*cfg };
let profile_cfg = match cfg_ref.try_to_profile_cfg(domain_id) {
Ok(c) => c,
Err(msg) => {
eprintln!("zerodds_runtime_create_secure: {msg}");
return ptr::null_mut();
}
};
let prefix = random_guid_prefix();
let mut participant_guid = [0u8; 16];
participant_guid[..12].copy_from_slice(&prefix.0);
participant_guid[12..].copy_from_slice(&[0x00, 0x00, 0x01, 0xC1]);
let profile = match SecurityProfile::from_files(&profile_cfg, participant_guid) {
Ok(p) => p,
Err(e) => {
eprintln!("zerodds_runtime_create_secure: {e}");
return ptr::null_mut();
}
};
finish_secure_runtime(domain_id, profile)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zerodds_runtime_create_secure_from_env(
domain_id: u32,
) -> *mut ZeroDdsRuntime {
let prefix = random_guid_prefix();
let mut participant_guid = [0u8; 16];
participant_guid[..12].copy_from_slice(&prefix.0);
participant_guid[12..].copy_from_slice(&[0x00, 0x00, 0x01, 0xC1]);
match SecurityProfile::from_env(participant_guid) {
Ok(Some(profile)) => finish_secure_runtime(domain_id, profile),
Ok(None) => {
eprintln!(
"zerodds_runtime_create_secure_from_env: ZERODDS_SECURITY_DIR not set — \
no enclave to load"
);
ptr::null_mut()
}
Err(e) => {
eprintln!("zerodds_runtime_create_secure_from_env: {e}");
ptr::null_mut()
}
}
}
fn finish_secure_runtime(domain_id: u32, profile: SecurityProfile) -> *mut ZeroDdsRuntime {
let now_secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
if !profile
.governance
.check_create_participant(&profile.permissions, domain_id, now_secs)
{
eprintln!(
"zerodds_runtime_create_secure(domain_id={domain_id}): check_create_participant \
denied (DDS-Security §8.4.2.9.3: join access control enabled and no permissions \
grant covers this domain, or no governance domain rule) — participant create denied"
);
return ptr::null_mut();
}
let rt_cfg = RuntimeConfig {
security: Some(Arc::clone(&profile.gate)),
..RuntimeConfig::default()
};
let mut adjusted_prefix_bytes = [0u8; 12];
adjusted_prefix_bytes.copy_from_slice(&profile.adjusted_participant_guid[..12]);
let prefix = zerodds_rtps::wire_types::GuidPrefix::from_bytes(adjusted_prefix_bytes);
let rt = match DcpsRuntime::start(domain_id as i32, prefix, rt_cfg) {
Ok(r) => r,
Err(e) => {
eprintln!("zerodds_runtime_create_secure(domain_id={domain_id}): {e:?}");
return ptr::null_mut();
}
};
rt.enable_security_builtins_with_auth(
zerodds_rtps::wire_types::VendorId::ZERODDS,
profile.pki.clone(),
profile.identity_handle,
);
Box::into_raw(Box::new(ZeroDdsRuntime { rt, _shutdown: () }))
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn config_create_destroy_roundtrip() {
let cfg = zerodds_security_config_create();
assert!(!cfg.is_null());
unsafe { zerodds_security_config_destroy(cfg) };
}
#[test]
fn null_setter_clears_path() {
let cfg = zerodds_security_config_create();
let path = std::ffi::CString::new("/tmp/foo.pem").unwrap();
let rc = unsafe { zerodds_security_set_identity_ca_path(cfg, path.as_ptr()) };
assert_eq!(rc, 0);
unsafe {
assert_eq!((*cfg).identity_ca_path, Some(PathBuf::from("/tmp/foo.pem")));
}
let rc = unsafe { zerodds_security_set_identity_ca_path(cfg, ptr::null()) };
assert_eq!(rc, 0);
unsafe { assert_eq!((*cfg).identity_ca_path, None) };
unsafe { zerodds_security_config_destroy(cfg) };
}
#[test]
fn missing_path_yields_null_runtime() {
let cfg = zerodds_security_config_create();
let path = std::ffi::CString::new("/tmp/identity_ca.pem").unwrap();
unsafe {
zerodds_security_set_identity_ca_path(cfg, path.as_ptr());
}
let rt = unsafe { zerodds_runtime_create_secure(0, cfg) };
assert!(rt.is_null());
unsafe { zerodds_security_config_destroy(cfg) };
}
#[test]
fn secure_from_env_null_when_dir_unset() {
if std::env::var("ZERODDS_SECURITY_DIR").is_ok() {
return;
}
let rt = unsafe { zerodds_runtime_create_secure_from_env(0) };
assert!(rt.is_null());
}
#[test]
fn null_cfg_yields_null_runtime() {
let rt = unsafe { zerodds_runtime_create_secure(0, ptr::null()) };
assert!(rt.is_null());
}
#[test]
fn six_setters_all_writeable() {
let cfg = zerodds_security_config_create();
let p = std::ffi::CString::new("/x").unwrap();
for setter in [
zerodds_security_set_identity_ca_path
as unsafe extern "C" fn(*mut ZeroDdsSecurityConfig, *const c_char) -> i32,
zerodds_security_set_identity_cert_path,
zerodds_security_set_private_key_path,
zerodds_security_set_permissions_ca_path,
zerodds_security_set_governance_path,
zerodds_security_set_permissions_path,
] {
let rc = unsafe { setter(cfg, p.as_ptr()) };
assert_eq!(rc, 0);
}
unsafe {
assert!((*cfg).identity_ca_path.is_some());
assert!((*cfg).identity_cert_path.is_some());
assert!((*cfg).identity_key_path.is_some());
assert!((*cfg).permissions_ca_path.is_some());
assert!((*cfg).governance_path.is_some());
assert!((*cfg).permissions_path.is_some());
}
unsafe { zerodds_security_config_destroy(cfg) };
}
}