use std::{
ffi::c_void,
panic::{AssertUnwindSafe, catch_unwind},
ptr::NonNull,
};
use open62541_sys::{
UA_Boolean, UA_DataSource, UA_DataValue, UA_NodeId, UA_NumericRange, UA_Server, UA_StatusCode,
};
use thiserror::Error;
use crate::{DataType as _, Error, server::NodeContext, ua};
pub type DataSourceResult = Result<(), DataSourceError>;
#[derive(Debug, Error)]
pub enum DataSourceError {
#[error("{0}")]
StatusCode(ua::StatusCode),
#[error(transparent)]
Error(#[from] Error),
}
impl DataSourceError {
#[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 {
DataSourceError::StatusCode(status_code) => status_code,
DataSourceError::Error(err) => err.status_code(),
}
}
}
pub trait DataSource {
fn read(&mut self, context: &mut DataSourceReadContext) -> DataSourceResult;
#[expect(unused_variables, reason = "unused in default implementation")]
fn write(&mut self, context: &mut DataSourceWriteContext) -> DataSourceResult {
Err(DataSourceError::from_status_code(
ua::StatusCode::BADNOTSUPPORTED,
))
}
}
#[derive(Debug)]
pub struct DataSourceReadContext {
value_target: NonNull<UA_DataValue>,
}
impl DataSourceReadContext {
fn new(value: *mut UA_DataValue) -> Option<Self> {
Some(Self {
value_target: NonNull::new(value)?,
})
}
#[must_use]
pub fn value_mut(&mut self) -> &mut ua::DataValue {
let value_source = unsafe { self.value_target.as_mut() };
ua::DataValue::raw_mut(value_source)
}
pub fn set_value(&mut self, value: ua::DataValue) {
*self.value_mut() = value;
}
pub fn set_variant(&mut self, variant: ua::Variant) {
*self.value_mut() = ua::DataValue::new(variant);
}
}
#[derive(Debug)]
pub struct DataSourceWriteContext {
value_source: NonNull<UA_DataValue>,
}
impl DataSourceWriteContext {
fn new(value: *const UA_DataValue) -> Option<Self> {
Some(Self {
value_source: NonNull::new(value.cast_mut())?,
})
}
#[must_use]
pub fn value(&self) -> &ua::DataValue {
let value_source = unsafe { self.value_source.as_ref() };
ua::DataValue::raw_ref(value_source)
}
}
pub(crate) unsafe fn wrap_data_source(
data_source: impl DataSource + 'static,
) -> (UA_DataSource, NodeContext) {
unsafe extern "C" fn read_c(
_server: *mut UA_Server,
_session_id: *const UA_NodeId,
_session_context: *mut c_void,
_node_id: *const UA_NodeId,
node_context: *mut c_void,
_include_source_time_stamp: UA_Boolean,
_range: *const UA_NumericRange,
value: *mut UA_DataValue,
) -> UA_StatusCode {
let node_context = unsafe { NodeContext::peek_at(node_context) };
let NodeContext::DataSource(data_source) = node_context else {
return ua::StatusCode::BADINTERNALERROR.into_raw();
};
let Some(mut context) = DataSourceReadContext::new(value) else {
return ua::StatusCode::BADINTERNALERROR.into_raw();
};
let mut data_source = AssertUnwindSafe(data_source);
let status_code = match catch_unwind(move || data_source.read(&mut context)) {
Ok(Ok(())) => ua::StatusCode::GOOD,
Ok(Err(err)) => err.into_status_code(),
Err(err) => {
log::error!("Read callback in data source panicked: {err:?}");
ua::StatusCode::BADINTERNALERROR
}
};
status_code.into_raw()
}
unsafe extern "C" fn write_c(
_server: *mut UA_Server,
_session_id: *const UA_NodeId,
_session_context: *mut c_void,
_node_id: *const UA_NodeId,
node_context: *mut c_void,
_range: *const UA_NumericRange,
value: *const UA_DataValue,
) -> UA_StatusCode {
let node_context = unsafe { NodeContext::peek_at(node_context) };
let NodeContext::DataSource(data_source) = node_context else {
return ua::StatusCode::BADINTERNALERROR.into_raw();
};
let Some(mut context) = DataSourceWriteContext::new(value) else {
return ua::StatusCode::BADINTERNALERROR.into_raw();
};
let mut data_source = AssertUnwindSafe(data_source);
let status_code = match catch_unwind(move || data_source.write(&mut context)) {
Ok(Ok(())) => ua::StatusCode::GOOD,
Ok(Err(err)) => err.into_status_code(),
Err(err) => {
log::error!("Write callback in data source panicked: {err:?}");
ua::StatusCode::BADINTERNALERROR
}
};
status_code.into_raw()
}
let raw_data_source = UA_DataSource {
read: Some(read_c),
write: Some(write_c),
};
let node_context = NodeContext::DataSource(Box::new(data_source));
(raw_data_source, node_context)
}