use std::io;
use std::sync::{Arc, Weak};
use bacnet_npdu::{MAX_NPDU, Npdu, NpduLimits};
use bacnet_router_core::router::{OriginateError, Request, SourceSelection};
use bacnet_transport::port::{ReceivedNpdu, TransportPort};
use bacnet_types::MacAddr;
use bacnet_types::error::Error;
use tokio::sync::{mpsc, watch};
use tokio::task::JoinHandle;
use super::application_feed::{Feed, outbound, slot};
use super::application_replies::Plan;
use super::application_transactions::Side;
use super::dispatch::{Outputs, Shared};
use super::originate::{OriginateFailure, originate};
use super::{BACnetRouter, LocalApduReceiver, StartedRouter};
const NPCI_ROOM: u16 = (MAX_NPDU - 1476) as u16;
pub struct RouterApplicationPort {
outputs: Weak<Outputs>,
shared: Arc<Shared>,
feed: Arc<Feed>,
side: Option<Side>,
started: bool,
local_mac: MacAddr,
limits: watch::Receiver<NpduLimits>,
initial: NpduLimits,
cap: Option<NpduLimits>,
published: watch::Sender<NpduLimits>,
follow: Option<JoinHandle<()>>,
source: SourceSelection,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct RouterApplicationPorts {
pub client: RouterApplicationPort,
pub server: RouterApplicationPort,
}
impl<A: LocalApduReceiver, C> StartedRouter<A, C> {
pub fn into_application_port(self) -> (BACnetRouter, RouterApplicationPort, Option<C>) {
let port = self.router.application_port(self.apdus);
(self.router, port, self.network_control)
}
pub fn into_application_ports(self) -> (BACnetRouter, RouterApplicationPorts, Option<C>) {
let ports = self.router.application_ports(self.apdus);
(self.router, ports, self.network_control)
}
}
impl BACnetRouter {
pub(super) fn application_port<A: LocalApduReceiver>(&self, apdus: A) -> RouterApplicationPort {
let feed = Arc::new(Feed::new(apdus.into_source(), false));
self.port_on(feed, None)
}
pub(super) fn application_ports<A: LocalApduReceiver>(
&self,
apdus: A,
) -> RouterApplicationPorts {
let feed = Arc::new(Feed::new(apdus.into_source(), true));
RouterApplicationPorts {
client: self.port_on(Arc::clone(&feed), Some(Side::Client)),
server: self.port_on(feed, Some(Side::Server)),
}
}
fn port_on(&self, feed: Arc<Feed>, side: Option<Side>) -> RouterApplicationPort {
let local_mac = {
let router = self.shared.engine.lock();
let longest = router.ports().iter().map(|port| port.mac.as_slice()).fold(
&[][..],
|longest, mac| {
if mac.len() > longest.len() {
mac
} else {
longest
}
},
);
MacAddr::from_slice(longest)
};
let limits = self.shared.application_limits.subscribe();
let initial = *limits.borrow();
RouterApplicationPort {
outputs: self.outputs.clone(),
shared: Arc::clone(&self.shared),
feed,
side,
started: false,
local_mac,
limits,
initial,
cap: None,
published: watch::Sender::new(initial),
follow: None,
source: SourceSelection::default(),
}
}
}
impl RouterApplicationPort {
#[must_use]
pub fn with_source(mut self, source: SourceSelection) -> Self {
self.source = source;
self
}
#[must_use]
pub fn with_max_npdu(mut self, max_npdu: NpduLimits) -> Self {
self.cap = Some(max_npdu);
self.published
.send_replace(capped(Some(max_npdu), *self.limits.borrow()));
self
}
async fn send(&self, npdu: &[u8]) -> Result<(), Error> {
let npdu = Npdu::decode(npdu)
.map_err(|error| Error::Encoding(format!("router application send: {error}")))?;
if npdu.message_type.is_some() {
return Err(Error::Encoding(
"the router's application port carries APDUs only; network messages are \
the router's own"
.into(),
));
}
let Some(destination) = npdu.destination else {
return Err(Error::Encoding(
"the router's application has no network of its own: address a device by \
its network, or broadcast to one network or to all"
.into(),
));
};
let replies = &self.feed.replies[slot(self.side)];
match replies.plan_for(destination, npdu.payload) {
Some(Plan::Send(response)) => {
return replies
.send(&response, npdu.payload, npdu.expecting_reply, npdu.priority)
.await;
}
Some(Plan::Silenced) => return Ok(()),
None => {}
}
let apdu = match (self.side, &self.feed.transactions) {
(Some(side), Some(transactions)) => {
outbound(transactions, side, destination, npdu.payload)?
}
_ => npdu.payload.into(),
};
let mut request = Request::new(destination, &apdu);
request.expecting_reply = npdu.expecting_reply;
request.priority = npdu.priority;
request.source = self.source;
originate(&self.outputs, &self.shared, &request).map_err(failure_error)
}
fn end_tasks(&mut self) {
self.feed.detach(slot(self.side));
if let Some(task) = self.follow.take() {
task.abort();
}
}
#[cfg(test)]
pub(super) fn saved_replies(&self) -> usize {
self.feed.replies[slot(self.side)].len()
}
}
impl Drop for RouterApplicationPort {
fn drop(&mut self) {
self.end_tasks();
}
}
impl std::fmt::Debug for RouterApplicationPort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RouterApplicationPort")
.field("side", &self.side)
.field("local_mac", &self.local_mac)
.field("max_npdu", &self.max_npdu())
.field("source", &self.source)
.field("started", &self.started)
.finish_non_exhaustive()
}
}
impl TransportPort for RouterApplicationPort {
async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedNpdu>, Error> {
if std::mem::replace(&mut self.started, true) {
return Err(Error::Encoding(
"router application port already started".to_string(),
));
}
let mut limits = self.limits.clone();
let published = self.published.clone();
let cap = self.cap;
self.follow = Some(tokio::spawn(async move {
loop {
let now = capped(cap, *limits.borrow_and_update());
published.send_if_modified(|current| std::mem::replace(current, now) != now);
if limits.changed().await.is_err() {
return;
}
}
}));
Ok(self.feed.attach(slot(self.side)))
}
async fn stop(&mut self) -> Result<(), Error> {
self.end_tasks();
Ok(())
}
fn abort(&mut self) {
self.end_tasks();
}
async fn send_unicast(&self, npdu: &[u8], _mac: &[u8]) -> Result<(), Error> {
self.send(npdu).await
}
async fn send_broadcast(&self, npdu: &[u8]) -> Result<(), Error> {
self.send(npdu).await
}
fn local_mac(&self) -> &[u8] {
&self.local_mac
}
fn local_receive_apdu_capacity(&self) -> u16 {
apdu_room(capped(self.cap, self.initial).unicast)
}
fn egress_apdu_limit(&self) -> u16 {
apdu_room(self.max_npdu().unicast)
}
fn max_npdu(&self) -> NpduLimits {
capped(self.cap, *self.limits.borrow())
}
fn max_npdu_changes(&self) -> Option<watch::Receiver<NpduLimits>> {
Some(self.published.subscribe())
}
}
fn capped(cap: Option<NpduLimits>, limits: NpduLimits) -> NpduLimits {
match cap {
Some(cap) => NpduLimits {
unicast: limits.unicast.min(cap.unicast),
broadcast: limits.broadcast.min(cap.broadcast),
},
None => limits,
}
}
fn apdu_room(max_npdu: u16) -> u16 {
max_npdu.saturating_sub(NPCI_ROOM).min(1476)
}
fn failure_error(failure: OriginateFailure) -> Error {
let kind = match failure {
OriginateFailure::Refused(OriginateError::TooLong { octets, limit }) => {
return Error::SendTooLong {
length: octets,
limit,
};
}
OriginateFailure::Refused(OriginateError::NoRoute | OriginateError::Unreachable) => {
io::ErrorKind::NetworkUnreachable
}
OriginateFailure::Refused(OriginateError::Busy) | OriginateFailure::QueueFull => {
io::ErrorKind::WouldBlock
}
OriginateFailure::Stopped => io::ErrorKind::NotConnected,
_ => io::ErrorKind::InvalidInput,
};
Error::Transport(io::Error::new(kind, failure))
}
impl OriginateFailure {
#[must_use]
pub fn from_error(error: &Error) -> Option<Self> {
match error {
Error::Transport(error) => error.get_ref()?.downcast_ref::<Self>().copied(),
_ => None,
}
}
}
#[cfg(test)]
#[path = "application_tests.rs"]
mod tests;