use std::{ffi::c_void, ptr};
use open62541_sys::{
UA_AccessControl_default, UA_AccessControl_defaultWithLoginCallback, UA_ByteString,
UA_STATUSCODE_BADINTERNALERROR, UA_ServerConfig, UA_StatusCode, UA_String,
UA_UsernamePasswordLogin,
};
use crate::{DataType, Error, Result, Userdata, ua, userdata::UserdataSentinel};
pub unsafe trait AccessControl {
type Sentinel: Send + 'static;
unsafe fn apply(self, config: &mut UA_ServerConfig) -> Result<Self::Sentinel>;
}
#[expect(missing_debug_implementations, reason = "Do not leak credentials.")]
pub struct DefaultAccessControl<'a> {
allow_anonymous: bool,
username_password_login: &'a [(&'a ua::String, &'a ua::String)],
}
impl<'a> DefaultAccessControl<'a> {
#[must_use]
pub const fn new(
allow_anonymous: bool,
username_password_login: &'a [(&'a ua::String, &'a ua::String)],
) -> Self {
Self {
allow_anonymous,
username_password_login,
}
}
}
unsafe impl AccessControl for DefaultAccessControl<'_> {
type Sentinel = ();
unsafe fn apply(self, config: &mut UA_ServerConfig) -> Result<Self::Sentinel> {
let Self {
allow_anonymous,
username_password_login,
} = self;
let username_password_login = username_password_login
.iter()
.map(|(username, password)| unsafe {
UA_UsernamePasswordLogin {
username: DataType::to_raw_copy(*username),
password: DataType::to_raw_copy(*password),
}
})
.collect::<Vec<_>>();
let status_code = ua::StatusCode::new(unsafe {
UA_AccessControl_default(
config,
allow_anonymous,
ptr::null(),
username_password_login.len(),
username_password_login.as_ptr(),
)
});
Error::verify_good(&status_code)
}
}
#[derive(Debug)]
pub struct DefaultAccessControlWithLoginCallback<F> {
allow_anonymous: bool,
login_callback: F,
}
impl<F> DefaultAccessControlWithLoginCallback<F> {
pub const fn new(allow_anonymous: bool, login_callback: F) -> Self {
Self {
allow_anonymous,
login_callback,
}
}
}
unsafe impl<F> AccessControl for DefaultAccessControlWithLoginCallback<F>
where
F: Fn(&ua::String, &ua::ByteString) -> ua::StatusCode + Send + 'static,
{
type Sentinel = UserdataSentinel<F>;
unsafe fn apply(self, config: &mut UA_ServerConfig) -> Result<Self::Sentinel> {
unsafe extern "C" fn login_callback_c<F>(
user_name: *const UA_String,
password: *const UA_ByteString,
_username_password_login_size: usize,
_username_password_login: *const UA_UsernamePasswordLogin,
_session_context: *mut *mut c_void,
login_context: *mut c_void,
) -> UA_StatusCode
where
F: Fn(&ua::String, &ua::ByteString) -> ua::StatusCode + 'static,
{
let Some(user_name) = (unsafe { user_name.as_ref() }) else {
return UA_STATUSCODE_BADINTERNALERROR;
};
let user_name = ua::String::raw_ref(user_name);
let Some(password) = (unsafe { password.as_ref() }) else {
return UA_STATUSCODE_BADINTERNALERROR;
};
let password = ua::ByteString::raw_ref(password);
log::debug!("Handling login request for {user_name:?}");
let login_callback = unsafe { Userdata::<F>::peek_at(login_context) };
let status_code = login_callback(user_name, password);
log::debug!("Login callback for {user_name:?} returned {status_code}");
status_code.into_raw()
}
let Self {
allow_anonymous,
login_callback,
} = self;
let username = ua::String::invalid();
let password = ua::String::invalid();
let username_password_login = [unsafe {
UA_UsernamePasswordLogin {
username: DataType::to_raw_copy(&username),
password: DataType::to_raw_copy(&password),
}
}];
let login_callback_sentinel = Userdata::<F>::prepare_sentinel(login_callback);
let status_code = ua::StatusCode::new(unsafe {
UA_AccessControl_defaultWithLoginCallback(
config,
allow_anonymous,
ptr::null(),
username_password_login.len(),
username_password_login.as_ptr(),
Some(login_callback_c::<F>),
login_callback_sentinel.as_ptr(),
)
});
Error::verify_good(&status_code)?;
drop((username, password));
Ok(login_callback_sentinel)
}
}