1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! Hand an APDU the engine delivers locally to the router's application,
//! with the original ingress metadata.
use bacnet_encoding::npdu::NpduAddress;
use bacnet_router_core::router::Local;
use bacnet_transport::port::ReceivedNpdu;
use bacnet_types::MacAddr;
use crate::layer::ReceivedApdu;
/// What a [`Local`] result says, owned, so the frame it borrowed can move.
#[derive(Debug, Clone)]
pub(super) struct Delivery {
reply: bacnet_router_core::router::ReplyPlan,
max_npdu: u16,
/// Where the APDU starts in the frame's NPDU.
offset: usize,
/// SNET and SADR, when a router relayed it.
source: Option<NpduAddress>,
/// Whether it was sent to a group.
group: bool,
/// Whether it was a global broadcast.
global: bool,
}
impl Delivery {
/// The delivery `local` describes.
pub(super) fn of(local: &Local<'_>, max_npdu: u16) -> Self {
Self {
reply: local.reply,
max_npdu,
offset: local.apdu_offset,
source: local.source.map(|source| NpduAddress {
network: source.network,
mac_address: MacAddr::from_slice(source.mac),
}),
group: local.group,
global: local.global,
}
}
/// The APDU `received` carries, as the application gets it, from the
/// port on `port_network`. The APDU is a slice of the frame, not a copy.
///
/// A broadcast on the arrival network, a DLEN 0 one or a group DADR
/// sent as one, is also passed back out that port, and its delivery
/// keeps no MS/TP reply channel, as before the engine: it is a copy of
/// the frame the router relays. Every other delivery keeps it.
pub(super) fn application(
self,
received: ReceivedNpdu,
port_network: u16,
outputs: &std::sync::Arc<super::dispatch::Outputs>,
) -> ReceivedApdu {
let relayed_too = self.group && !self.global && has_destination(&received.npdu);
let apdu = received.npdu.slice(self.offset..);
// Only a Confirmed-Request is answered on its arrival route, so only
// one gets the reply capability, and an I-Am flood allocates nothing
// for it (#1713).
let confirmed = apdu.first().is_some_and(|octet| octet >> 4 == 0);
ReceivedApdu {
router_response: confirmed
.then(|| super::RouterResponse::new(self.reply, self.max_npdu, outputs)),
apdu,
source_mac: received.source_mac,
ingress_network: Some(port_network),
source_network: self.source,
link_layer_group: received.link_layer_group,
is_group: self.group,
global_broadcast: self.global,
data_attributes: received.data_attributes,
provenance: received.provenance,
direct_response: received.direct_response,
reply_tx: if relayed_too { None } else { received.reply_tx },
}
}
}
/// Whether an NPDU the engine parsed names a DNET: its control octet's
/// destination bit.
fn has_destination(npdu: &[u8]) -> bool {
npdu.get(1).is_some_and(|control| control & 0x20 != 0)
}