use core::ffi::c_void;
use std::future::Future;
use std::sync::Arc;
use mbus_core::errors::ExceptionCode;
use mbus_core::function_codes::public::FunctionCode;
use mbus_server_async::{AsyncAppHandler, ModbusRequest, ModbusResponse};
pub const VTABLE_BUF_BYTES: usize = 256;
pub const VTABLE_BUF_WORDS: usize = 128;
#[repr(C)]
pub struct MbusDnServerVtable {
pub ctx: *mut c_void,
#[cfg(feature = "coils")]
pub read_coils: Option<unsafe extern "C" fn(*mut c_void, u16, u16, *mut u8, *mut u16) -> i32>,
#[cfg(feature = "coils")]
pub write_single_coil: Option<unsafe extern "C" fn(*mut c_void, u16, u8) -> i32>,
#[cfg(feature = "coils")]
pub write_multiple_coils:
Option<unsafe extern "C" fn(*mut c_void, u16, *const u8, u16, u16) -> i32>,
#[cfg(feature = "discrete-inputs")]
pub read_discrete_inputs:
Option<unsafe extern "C" fn(*mut c_void, u16, u16, *mut u8, *mut u16) -> i32>,
#[cfg(feature = "registers")]
pub read_holding_registers:
Option<unsafe extern "C" fn(*mut c_void, u16, u16, *mut u16, *mut u16) -> i32>,
#[cfg(feature = "registers")]
pub read_input_registers:
Option<unsafe extern "C" fn(*mut c_void, u16, u16, *mut u16, *mut u16) -> i32>,
#[cfg(feature = "registers")]
pub write_single_register: Option<unsafe extern "C" fn(*mut c_void, u16, u16) -> i32>,
#[cfg(feature = "registers")]
pub write_multiple_registers:
Option<unsafe extern "C" fn(*mut c_void, u16, *const u8, u16) -> i32>,
#[cfg(feature = "registers")]
pub mask_write_register: Option<unsafe extern "C" fn(*mut c_void, u16, u16, u16) -> i32>,
#[cfg(feature = "registers")]
pub read_write_multiple_registers: Option<
unsafe extern "C" fn(*mut c_void, u16, u16, u16, *const u8, u16, *mut u16, *mut u16) -> i32,
>,
#[cfg(feature = "fifo")]
pub read_fifo_queue: Option<unsafe extern "C" fn(*mut c_void, u16, *mut u16, *mut u16) -> i32>,
#[cfg(feature = "diagnostics")]
pub read_exception_status: Option<unsafe extern "C" fn(*mut c_void, *mut u8) -> i32>,
#[cfg(feature = "diagnostics")]
pub diagnostics: Option<unsafe extern "C" fn(*mut c_void, u16, u16, *mut u16, *mut u16) -> i32>,
#[cfg(feature = "diagnostics")]
pub get_comm_event_counter:
Option<unsafe extern "C" fn(*mut c_void, *mut u16, *mut u16) -> i32>,
#[cfg(feature = "diagnostics")]
pub get_comm_event_log: Option<unsafe extern "C" fn(*mut c_void, *mut u8, *mut u16) -> i32>,
#[cfg(feature = "diagnostics")]
pub report_server_id: Option<unsafe extern "C" fn(*mut c_void, *mut u8, *mut u16) -> i32>,
}
unsafe impl Send for MbusDnServerVtable {}
unsafe impl Sync for MbusDnServerVtable {}
#[derive(Clone)]
pub struct DotNetServerAdapter {
pub(super) vtable: Arc<MbusDnServerVtable>,
}
impl DotNetServerAdapter {
pub fn new(vtable: MbusDnServerVtable) -> Self {
Self {
vtable: Arc::new(vtable),
}
}
pub fn new_with_arc(vtable: Arc<MbusDnServerVtable>) -> Self {
Self { vtable }
}
}
#[cfg(feature = "traffic")]
impl mbus_server_async::AsyncServerTrafficNotifier for DotNetServerAdapter {}
impl AsyncAppHandler for DotNetServerAdapter {
fn handle(&mut self, req: ModbusRequest) -> impl Future<Output = ModbusResponse> + Send {
let vt = self.vtable.clone();
async move { dispatch(&vt, req) }
}
}
fn exception_from_i32(fc: FunctionCode, code: i32) -> ModbusResponse {
let ex = match code {
1 => ExceptionCode::IllegalFunction,
2 => ExceptionCode::IllegalDataAddress,
3 => ExceptionCode::IllegalDataValue,
_ => ExceptionCode::ServerDeviceFailure,
};
ModbusResponse::exception(fc, ex)
}
fn exception_raw_from_i32(fc_byte: u8, code: i32) -> ModbusResponse {
let ex = match code {
1 => ExceptionCode::IllegalFunction,
2 => ExceptionCode::IllegalDataAddress,
3 => ExceptionCode::IllegalDataValue,
_ => ExceptionCode::ServerDeviceFailure,
};
ModbusResponse::exception_raw(fc_byte, ex)
}
fn dispatch(vt: &MbusDnServerVtable, req: ModbusRequest) -> ModbusResponse {
match req {
#[cfg(feature = "coils")]
ModbusRequest::ReadCoils { address, count, .. } => {
let Some(f) = vt.read_coils else {
return ModbusResponse::exception(
FunctionCode::ReadCoils,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u8; VTABLE_BUF_BYTES];
let mut written: u16 = 0;
let rc = unsafe { f(vt.ctx, address, count, buf.as_mut_ptr(), &mut written) };
if rc != 0 {
return exception_from_i32(FunctionCode::ReadCoils, rc);
}
ModbusResponse::packed_bits(FunctionCode::ReadCoils, &buf[..written as usize])
}
#[cfg(feature = "coils")]
ModbusRequest::WriteSingleCoil { address, value, .. } => {
let Some(f) = vt.write_single_coil else {
return ModbusResponse::exception(
FunctionCode::WriteSingleCoil,
ExceptionCode::IllegalFunction,
);
};
let rc = unsafe { f(vt.ctx, address, value as u8) };
if rc != 0 {
return exception_from_i32(FunctionCode::WriteSingleCoil, rc);
}
ModbusResponse::echo_coil(address, value)
}
#[cfg(feature = "coils")]
ModbusRequest::WriteMultipleCoils {
address,
count,
data,
..
} => {
let Some(f) = vt.write_multiple_coils else {
return ModbusResponse::exception(
FunctionCode::WriteMultipleCoils,
ExceptionCode::IllegalFunction,
);
};
let rc = unsafe { f(vt.ctx, address, data.as_ptr(), data.len() as u16, count) };
if rc != 0 {
return exception_from_i32(FunctionCode::WriteMultipleCoils, rc);
}
ModbusResponse::echo_multi_write(FunctionCode::WriteMultipleCoils, address, count)
}
#[cfg(feature = "discrete-inputs")]
ModbusRequest::ReadDiscreteInputs { address, count, .. } => {
let Some(f) = vt.read_discrete_inputs else {
return ModbusResponse::exception(
FunctionCode::ReadDiscreteInputs,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u8; VTABLE_BUF_BYTES];
let mut written: u16 = 0;
let rc = unsafe { f(vt.ctx, address, count, buf.as_mut_ptr(), &mut written) };
if rc != 0 {
return exception_from_i32(FunctionCode::ReadDiscreteInputs, rc);
}
ModbusResponse::packed_bits(FunctionCode::ReadDiscreteInputs, &buf[..written as usize])
}
#[cfg(feature = "registers")]
ModbusRequest::ReadHoldingRegisters { address, count, .. } => {
let Some(f) = vt.read_holding_registers else {
return ModbusResponse::exception(
FunctionCode::ReadHoldingRegisters,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u16; VTABLE_BUF_WORDS];
let mut written: u16 = 0;
let rc = unsafe { f(vt.ctx, address, count, buf.as_mut_ptr(), &mut written) };
if rc != 0 {
return exception_from_i32(FunctionCode::ReadHoldingRegisters, rc);
}
ModbusResponse::registers(FunctionCode::ReadHoldingRegisters, &buf[..written as usize])
}
#[cfg(feature = "registers")]
ModbusRequest::ReadInputRegisters { address, count, .. } => {
let Some(f) = vt.read_input_registers else {
return ModbusResponse::exception(
FunctionCode::ReadInputRegisters,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u16; VTABLE_BUF_WORDS];
let mut written: u16 = 0;
let rc = unsafe { f(vt.ctx, address, count, buf.as_mut_ptr(), &mut written) };
if rc != 0 {
return exception_from_i32(FunctionCode::ReadInputRegisters, rc);
}
ModbusResponse::registers(FunctionCode::ReadInputRegisters, &buf[..written as usize])
}
#[cfg(feature = "registers")]
ModbusRequest::WriteSingleRegister { address, value, .. } => {
let Some(f) = vt.write_single_register else {
return ModbusResponse::exception(
FunctionCode::WriteSingleRegister,
ExceptionCode::IllegalFunction,
);
};
let rc = unsafe { f(vt.ctx, address, value) };
if rc != 0 {
return exception_from_i32(FunctionCode::WriteSingleRegister, rc);
}
ModbusResponse::echo_register(address, value)
}
#[cfg(feature = "registers")]
ModbusRequest::WriteMultipleRegisters {
address,
count,
data,
..
} => {
let Some(f) = vt.write_multiple_registers else {
return ModbusResponse::exception(
FunctionCode::WriteMultipleRegisters,
ExceptionCode::IllegalFunction,
);
};
let rc = unsafe { f(vt.ctx, address, data.as_ptr(), count) };
if rc != 0 {
return exception_from_i32(FunctionCode::WriteMultipleRegisters, rc);
}
ModbusResponse::echo_multi_write(FunctionCode::WriteMultipleRegisters, address, count)
}
#[cfg(feature = "registers")]
ModbusRequest::MaskWriteRegister {
address,
and_mask,
or_mask,
..
} => {
let Some(f) = vt.mask_write_register else {
return ModbusResponse::exception(
FunctionCode::MaskWriteRegister,
ExceptionCode::IllegalFunction,
);
};
let rc = unsafe { f(vt.ctx, address, and_mask, or_mask) };
if rc != 0 {
return exception_from_i32(FunctionCode::MaskWriteRegister, rc);
}
ModbusResponse::echo_mask_write(address, and_mask, or_mask)
}
#[cfg(feature = "registers")]
ModbusRequest::ReadWriteMultipleRegisters {
read_address,
read_count,
write_address,
write_count,
data,
..
} => {
let Some(f) = vt.read_write_multiple_registers else {
return ModbusResponse::exception(
FunctionCode::ReadWriteMultipleRegisters,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u16; VTABLE_BUF_WORDS];
let mut written: u16 = 0;
let rc = unsafe {
f(
vt.ctx,
read_address,
read_count,
write_address,
data.as_ptr(),
write_count,
buf.as_mut_ptr(),
&mut written,
)
};
if rc != 0 {
return exception_from_i32(FunctionCode::ReadWriteMultipleRegisters, rc);
}
ModbusResponse::registers(
FunctionCode::ReadWriteMultipleRegisters,
&buf[..written as usize],
)
}
#[cfg(feature = "fifo")]
ModbusRequest::ReadFifoQueue {
pointer_address, ..
} => {
let Some(f) = vt.read_fifo_queue else {
return ModbusResponse::exception(
FunctionCode::ReadFifoQueue,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u16; VTABLE_BUF_WORDS];
let mut written: u16 = 0;
let rc = unsafe { f(vt.ctx, pointer_address, buf.as_mut_ptr(), &mut written) };
if rc != 0 {
return exception_from_i32(FunctionCode::ReadFifoQueue, rc);
}
let count = written as usize;
let mut payload =
heapless::Vec::<u8, { mbus_core::data_unit::common::MAX_ADU_FRAME_LEN }>::new();
let _ = payload.extend_from_slice(&(count as u16).to_be_bytes());
for v in &buf[..count] {
let _ = payload.extend_from_slice(&v.to_be_bytes());
}
ModbusResponse::fifo_response(&payload)
}
#[cfg(feature = "diagnostics")]
ModbusRequest::ReadExceptionStatus { .. } => {
let Some(f) = vt.read_exception_status else {
return ModbusResponse::exception(
FunctionCode::ReadExceptionStatus,
ExceptionCode::IllegalFunction,
);
};
let mut status: u8 = 0;
let rc = unsafe { f(vt.ctx, &mut status) };
if rc != 0 {
return exception_from_i32(FunctionCode::ReadExceptionStatus, rc);
}
ModbusResponse::read_exception_status(status)
}
#[cfg(feature = "diagnostics")]
ModbusRequest::Diagnostics {
sub_function, data, ..
} => {
let Some(f) = vt.diagnostics else {
return ModbusResponse::diagnostics_echo(sub_function, data);
};
let mut out_sub: u16 = sub_function;
let mut out_data: u16 = data;
let rc = unsafe { f(vt.ctx, sub_function, data, &mut out_sub, &mut out_data) };
if rc != 0 {
return exception_from_i32(FunctionCode::Diagnostics, rc);
}
ModbusResponse::diagnostics_echo(out_sub, out_data)
}
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventCounter { .. } => {
let Some(f) = vt.get_comm_event_counter else {
return ModbusResponse::exception(
FunctionCode::GetCommEventCounter,
ExceptionCode::IllegalFunction,
);
};
let mut status_word: u16 = 0;
let mut event_count: u16 = 0;
let rc = unsafe { f(vt.ctx, &mut status_word, &mut event_count) };
if rc != 0 {
return exception_from_i32(FunctionCode::GetCommEventCounter, rc);
}
ModbusResponse::comm_event_counter(status_word, event_count)
}
#[cfg(feature = "diagnostics")]
ModbusRequest::GetCommEventLog { .. } => {
let Some(f) = vt.get_comm_event_log else {
return ModbusResponse::exception(
FunctionCode::GetCommEventLog,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u8; VTABLE_BUF_BYTES];
let mut written: u16 = 0;
let rc = unsafe { f(vt.ctx, buf.as_mut_ptr(), &mut written) };
if rc != 0 {
return exception_from_i32(FunctionCode::GetCommEventLog, rc);
}
ModbusResponse::comm_event_log(&buf[..written as usize])
}
#[cfg(feature = "diagnostics")]
ModbusRequest::ReportServerId { .. } => {
let Some(f) = vt.report_server_id else {
return ModbusResponse::exception(
FunctionCode::ReportServerId,
ExceptionCode::IllegalFunction,
);
};
let mut buf = [0u8; VTABLE_BUF_BYTES];
let mut written: u16 = 0;
let rc = unsafe { f(vt.ctx, buf.as_mut_ptr(), &mut written) };
if rc != 0 {
return exception_from_i32(FunctionCode::ReportServerId, rc);
}
ModbusResponse::report_server_id(&buf[..written as usize])
}
other => exception_raw_from_i32(other.function_code_byte(), 1),
}
}