use ::core::ffi::c_void;
use std::{
panic::{AssertUnwindSafe, catch_unwind},
ptr::NonNull,
};
use open62541_sys::{
UA_EMPTY_ARRAY_SENTINEL, UA_MethodCallback, UA_NodeId, UA_Server, UA_StatusCode, UA_Variant,
};
use thiserror::Error;
use crate::{DataType as _, Error, server::NodeContext, ua};
pub type MethodCallbackResult = Result<(), MethodCallbackError>;
#[derive(Debug, Error)]
pub enum MethodCallbackError {
#[error("{0}")]
StatusCode(ua::StatusCode),
#[error(transparent)]
Error(#[from] Error),
}
impl MethodCallbackError {
#[must_use]
pub fn from_status_code(status_code: ua::StatusCode) -> Self {
Self::StatusCode(if status_code.is_good() {
ua::StatusCode::BADINTERNALERROR
} else {
status_code
})
}
pub(crate) fn into_status_code(self) -> ua::StatusCode {
match self {
MethodCallbackError::StatusCode(status_code) => status_code,
MethodCallbackError::Error(err) => err.status_code(),
}
}
}
pub trait MethodCallback {
fn call(&mut self, context: &mut MethodCallbackContext) -> MethodCallbackResult;
}
#[derive(Debug)]
pub struct MethodCallbackContext {
object_id: NonNull<UA_NodeId>,
input_size: usize,
input_source: NonNull<UA_Variant>,
output_size: usize,
output_target: NonNull<UA_Variant>,
}
impl MethodCallbackContext {
fn new(
object_id: *const UA_NodeId,
input_size: usize,
input: *const UA_Variant,
output_size: usize,
output: *mut UA_Variant,
) -> Option<Self> {
let ptr = unsafe { UA_EMPTY_ARRAY_SENTINEL };
if input == ptr.cast::<UA_Variant>() {
return None;
}
if output == ptr.cast::<UA_Variant>().cast_mut() {
return None;
}
Some(Self {
object_id: NonNull::new(object_id.cast_mut())?,
input_size,
input_source: NonNull::new(input.cast_mut())?,
output_size,
output_target: NonNull::new(output)?,
})
}
#[must_use]
pub fn object_id(&self) -> &ua::NodeId {
let object_id = unsafe { self.object_id.as_ref() };
ua::NodeId::raw_ref(object_id)
}
#[must_use]
pub fn input_arguments(&self) -> &[ua::Variant] {
let Some(input_arguments) = (unsafe {
ua::Array::slice_from_raw_parts(self.input_size, self.input_source.as_ptr())
}) else {
unreachable!("received invalid input arguments array");
};
input_arguments
}
pub fn output_arguments_mut(&mut self) -> &mut [ua::Variant] {
let Some(output_arguments) = (unsafe {
ua::Array::slice_from_raw_parts_mut(self.output_size, self.output_target.as_ptr())
}) else {
unreachable!("received invalid input arguments array");
};
output_arguments
}
}
pub(crate) unsafe fn wrap_method_callback(
method_callback: impl MethodCallback + 'static,
) -> (UA_MethodCallback, NodeContext) {
unsafe extern "C" fn callback_c(
_server: *mut UA_Server,
_session_id: *const UA_NodeId,
_session_context: *mut c_void,
_method_id: *const UA_NodeId,
method_context: *mut c_void,
object_id: *const UA_NodeId,
_object_context: *mut c_void,
input_size: usize,
input: *const UA_Variant,
output_size: usize,
output: *mut UA_Variant,
) -> UA_StatusCode {
let node_context = unsafe { NodeContext::peek_at(method_context) };
let NodeContext::MethodCallback(method_callback) = node_context else {
return ua::StatusCode::BADINTERNALERROR.into_raw();
};
let Some(mut context) =
MethodCallbackContext::new(object_id, input_size, input, output_size, output)
else {
return ua::StatusCode::BADINTERNALERROR.into_raw();
};
let mut method_callback = AssertUnwindSafe(method_callback);
let status_code = match catch_unwind(move || method_callback.call(&mut context)) {
Ok(Ok(())) => ua::StatusCode::GOOD,
Ok(Err(err)) => err.into_status_code(),
Err(err) => {
log::error!("Call callback in method callback panicked: {err:?}");
ua::StatusCode::BADINTERNALERROR
}
};
status_code.into_raw()
}
let node_context = NodeContext::MethodCallback(Box::new(method_callback));
(Some(callback_c), node_context)
}