use core::ffi::c_char;
use core::ptr;
use std::ffi::CStr;
use std::sync::{Arc, Mutex as StdMutex};
use mbus_core::transport::UnitIdOrSlaveAddr;
use mbus_gateway::AsyncTcpGatewayServer;
use mbus_network::TokioTcpTransport;
use tokio::sync::{Mutex as TokioMutex, Notify};
use crate::dotnet::runtime;
use crate::dotnet::status::MbusDnStatus;
use super::router::DnRouter;
pub struct MbusDnTcpGateway {
inner: Arc<StdMutex<GatewayConfig>>,
stop_signal: Arc<Notify>,
}
struct GatewayConfig {
bind_addr: String,
downstreams: Vec<(String, u16)>,
router: DnRouter,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mbus_dn_tcp_gateway_new(
host: *const c_char,
port: u16,
) -> *mut MbusDnTcpGateway {
if host.is_null() {
return ptr::null_mut();
}
let host_str = match unsafe { CStr::from_ptr(host) }.to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
};
let config = GatewayConfig {
bind_addr: format!("{host_str}:{port}"),
downstreams: Vec::new(),
router: DnRouter::new(),
};
Box::into_raw(Box::new(MbusDnTcpGateway {
inner: Arc::new(StdMutex::new(config)),
stop_signal: Arc::new(Notify::new()),
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mbus_dn_tcp_gateway_free(handle: *mut MbusDnTcpGateway) {
if !handle.is_null() {
drop(unsafe { Box::from_raw(handle) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mbus_dn_tcp_gateway_add_downstream(
handle: *mut MbusDnTcpGateway,
host: *const c_char,
port: u16,
) -> u32 {
let gw = match unsafe { handle.as_ref() } {
Some(g) => g,
None => return u32::MAX,
};
if host.is_null() {
return u32::MAX;
}
let host_str = match unsafe { CStr::from_ptr(host) }.to_str() {
Ok(s) => s,
Err(_) => return u32::MAX,
};
let mut cfg = gw.inner.lock().unwrap();
cfg.downstreams.push((host_str.to_owned(), port));
(cfg.downstreams.len() - 1) as u32
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mbus_dn_tcp_gateway_add_unit_route(
handle: *mut MbusDnTcpGateway,
unit_id: u8,
channel: u32,
) -> MbusDnStatus {
let gw = match unsafe { handle.as_ref() } {
Some(g) => g,
None => return MbusDnStatus::MbusErrNullPointer,
};
if UnitIdOrSlaveAddr::new(unit_id).is_err() {
return MbusDnStatus::MbusErrInvalidAddress;
}
let mut cfg = gw.inner.lock().unwrap();
let ch = channel as usize;
if ch >= cfg.downstreams.len() {
return MbusDnStatus::MbusErrInvalidAddress;
}
cfg.router.add_unit(unit_id, ch);
MbusDnStatus::MbusOk
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mbus_dn_tcp_gateway_add_range_route(
handle: *mut MbusDnTcpGateway,
unit_min: u8,
unit_max: u8,
channel: u32,
) -> MbusDnStatus {
let gw = match unsafe { handle.as_ref() } {
Some(g) => g,
None => return MbusDnStatus::MbusErrNullPointer,
};
if unit_min == 0 || unit_max < unit_min {
return MbusDnStatus::MbusErrInvalidAddress;
}
let mut cfg = gw.inner.lock().unwrap();
let ch = channel as usize;
if ch >= cfg.downstreams.len() {
return MbusDnStatus::MbusErrInvalidAddress;
}
cfg.router.add_range(unit_min, unit_max, ch);
MbusDnStatus::MbusOk
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mbus_dn_tcp_gateway_start(handle: *mut MbusDnTcpGateway) -> MbusDnStatus {
let gw = match unsafe { handle.as_ref() } {
Some(g) => g,
None => return MbusDnStatus::MbusErrNullPointer,
};
let (bind_addr, downstreams, router) = {
let cfg = gw.inner.lock().unwrap();
if cfg.downstreams.is_empty() || cfg.router.is_empty() {
return MbusDnStatus::MbusErrInvalidAddress;
}
(
cfg.bind_addr.clone(),
cfg.downstreams.clone(),
cfg.router.clone(),
)
};
let stop_signal = gw.stop_signal.clone();
std::thread::spawn(move || {
let rt = runtime::get();
rt.block_on(async move {
let mut ds = Vec::with_capacity(downstreams.len());
for (host, port) in &downstreams {
match TokioTcpTransport::connect((host.as_str(), *port)).await {
Ok(t) => ds.push(Arc::new(TokioMutex::new(t))),
Err(_) => return,
}
}
let handler = Arc::new(TokioMutex::new(mbus_gateway::NoopEventHandler));
let response_timeout = std::time::Duration::from_secs(1);
let _ = AsyncTcpGatewayServer::serve_with_shutdown(
bind_addr.as_str(),
router,
ds,
handler,
response_timeout,
stop_signal.notified(),
)
.await;
});
});
MbusDnStatus::MbusOk
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mbus_dn_tcp_gateway_stop(handle: *mut MbusDnTcpGateway) {
if let Some(gw) = unsafe { handle.as_ref() } {
gw.stop_signal.notify_one();
}
}