use bacnet_client::client::BACnetClient;
use bacnet_transport::mstp::{Echo, MstpDiagnostics, MstpThreadConfig, MstpTransport, SerialPort};
use bacnet_types::error::Error;
use tokio::io::AsyncRead;
use crate::transport::TransportArgs;
pub(crate) type MonitorReader = Box<dyn AsyncRead + Unpin + Send>;
pub(crate) enum CliSerial {
#[cfg(feature = "serial")]
System(crate::mstp::Serial),
#[cfg(test)]
Test(bacnet_transport::mstp::LoopbackSerial),
}
#[cfg_attr(not(any(feature = "serial", test)), allow(unused_variables))]
impl SerialPort for CliSerial {
async fn write(&self, data: &[u8]) -> Result<(), Error> {
match *self {
#[cfg(feature = "serial")]
Self::System(ref port) => port.write(data).await,
#[cfg(test)]
Self::Test(ref port) => port.write(data).await,
}
}
async fn drain(&self) -> Result<(), Error> {
match *self {
#[cfg(feature = "serial")]
Self::System(ref port) => port.drain().await,
#[cfg(test)]
Self::Test(ref port) => port.drain().await,
}
}
async fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
match *self {
#[cfg(feature = "serial")]
Self::System(ref port) => port.read(buf).await,
#[cfg(test)]
Self::Test(ref port) => port.read(buf).await,
}
}
fn echo(&self) -> Echo {
match *self {
#[cfg(feature = "serial")]
Self::System(ref port) => port.echo(),
#[cfg(test)]
Self::Test(ref port) => port.echo(),
}
}
}
#[derive(Clone, Default)]
pub(crate) enum Lines {
#[default]
System,
#[cfg(test)]
Test(std::sync::Arc<test_line::TestLine>),
}
impl std::fmt::Debug for Lines {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::System => f.write_str("System"),
#[cfg(test)]
Self::Test(_) => f.write_str("Test"),
}
}
}
impl Lines {
pub(crate) fn open_monitor(&self, port: &str, baud: u32) -> Result<MonitorReader, Error> {
match self {
Self::System => system_monitor(port, baud),
#[cfg(test)]
Self::Test(line) => line.monitor(),
}
}
pub(crate) async fn participant(
&self,
args: &TransportArgs,
thread: &MstpThreadConfig,
) -> Result<(BACnetClient<MstpTransport<CliSerial>>, MstpDiagnostics), Error> {
let mstp = args
.mstp
.as_ref()
.ok_or_else(|| Error::Encoding("no MS/TP settings to join with".into()))?;
let (serial, mode) = match self {
Self::System => (system_serial(mstp)?, crate::mstp_thread::mode(thread)),
#[cfg(test)]
Self::Test(line) => {
use bacnet_transport::mstp::MstpExecutionMode;
MstpExecutionMode::DedicatedThread(thread.clone()).validate()?;
(line.participant()?, MstpExecutionMode::Tokio)
}
};
crate::mstp::build_client_on(serial, mstp, args.timeout_ms, args.min_interval_ms, mode)
.await
}
}
#[cfg(feature = "serial")]
fn system_monitor(port: &str, baud: u32) -> Result<MonitorReader, Error> {
use bacnet_transport::mstp_serial::{ReceiveOnlyPort, SerialConfig};
let config = SerialConfig {
port_name: port.to_string(),
baud_rate: baud,
};
Ok(Box::new(ReceiveOnlyPort::open(&config, None)?))
}
#[cfg(not(feature = "serial"))]
fn system_monitor(_port: &str, _baud: u32) -> Result<MonitorReader, Error> {
Err(Error::Encoding(crate::mstp::NO_SERIAL.into()))
}
#[cfg(feature = "serial")]
fn system_serial(mstp: &crate::mstp::MstpArgs) -> Result<CliSerial, Error> {
crate::mstp::open_serial(mstp).map(CliSerial::System)
}
#[cfg(not(feature = "serial"))]
fn system_serial(_mstp: &crate::mstp::MstpArgs) -> Result<CliSerial, Error> {
Err(Error::Encoding(crate::mstp::NO_SERIAL.into()))
}
#[cfg(test)]
pub(crate) mod test_line {
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use bacnet_transport::mstp::LoopbackSerial;
use bacnet_types::error::Error;
use tokio::io::{AsyncRead, DuplexStream, ReadBuf};
use super::{CliSerial, MonitorReader};
pub(crate) struct TestLine {
monitor: Mutex<Option<DuplexStream>>,
participant: Mutex<Option<LoopbackSerial>>,
held: Arc<AtomicBool>,
}
impl TestLine {
pub(crate) fn new(monitor: DuplexStream, participant: Option<LoopbackSerial>) -> Arc<Self> {
Arc::new(Self {
monitor: Mutex::new(Some(monitor)),
participant: Mutex::new(participant),
held: Arc::new(AtomicBool::new(false)),
})
}
pub(super) fn monitor(&self) -> Result<MonitorReader, Error> {
let stream = self.monitor.lock().unwrap().take();
let stream =
stream.ok_or_else(|| Error::Encoding("test line: no monitor end".into()))?;
self.held.store(true, Ordering::SeqCst);
Ok(Box::new(Held {
stream,
held: Arc::clone(&self.held),
}))
}
pub(crate) fn held(&self) -> bool {
self.held.load(Ordering::SeqCst)
}
pub(super) fn participant(&self) -> Result<CliSerial, Error> {
if self.held.load(Ordering::SeqCst) {
return Err(Error::Transport(std::io::Error::new(
std::io::ErrorKind::ResourceBusy,
"test line: the monitor still holds the port",
)));
}
let serial = self.participant.lock().unwrap().take();
serial
.map(CliSerial::Test)
.ok_or_else(|| Error::Encoding("test line: no participant end".into()))
}
}
struct Held {
stream: DuplexStream,
held: Arc<AtomicBool>,
}
impl AsyncRead for Held {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.stream).poll_read(cx, buf)
}
}
impl Drop for Held {
fn drop(&mut self) {
self.held.store(false, Ordering::SeqCst);
}
}
}