# bacnet-network
NPDU assembly, APDU dispatch and routing components for the
[Rusty BACnet](https://github.com/jscott3201/rusty-bacnet) workspace.
`layer::NetworkLayer` wraps one transport and handles NPDU headers around APDUs.
It can address remote devices through a local router, but does not forward
between networks. `router::BACnetRouter` is the separate multi-port router.
This hosted crate requires `std` and Tokio.
## Quickstart from development source
This dependency block uses a checkout named `rusty-bacnet` beside your application
directory. It describes development source, not the contents of a published
crate version. Adjust the relative paths for another layout. Each crate used
by the example is a direct dependency, including the Tokio runtime and macros.
```toml
[dependencies]
bacnet-network = { path = "../rusty-bacnet/crates/bacnet-network" }
bacnet-transport = { path = "../rusty-bacnet/crates/bacnet-transport" }
bacnet-npdu = { path = "../rusty-bacnet/crates/bacnet-npdu" }
tokio = { version = "1", features = ["macros", "rt", "time"] }
```
Send an APDU through two network layers connected by in-process loopback
channels. The receiving layer removes the NPDU header and exposes the immediate
source MAC. The timeout bounds the wait; no socket or device is opened.
```rust
use bacnet_network::layer::NetworkLayer;
use bacnet_transport::loopback::LoopbackTransport;
use bacnet_npdu::NetworkPriority;
use tokio::time::{timeout, Duration};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (left_port, right_port) = LoopbackTransport::pair(vec![1], vec![2]);
let mut left = NetworkLayer::new(left_port);
let mut right = NetworkLayer::new(right_port);
let left_rx = left.start().await?;
let mut right_rx = right.start().await?;
let apdu = [0x10, 0x08];
left.send_apdu(&apdu, &[2], false, NetworkPriority::Normal)
.await?;
let received = timeout(Duration::from_secs(2), right_rx.recv())
.await?
.ok_or("network receive channel closed")?;
assert_eq!(received.apdu.as_ref(), &apdu);
assert_eq!(received.source_mac.as_ref(), &[1]);
assert!(received.source_network.is_none());
assert!(!received.link_layer_group);
left.stop().await?;
right.stop().await?;
drop((left_rx, right_rx));
Ok(())
}
```
Both receivers returned by `start` stay owned until cleanup. Each network
layer's `stop` ends and joins its dispatch task before stopping its transport.
Loopback always hands a send to its paired peer, regardless of destination MAC;
the example does not establish destination filtering. These assertions prove
local APDU delivery without a routed source. They do not demonstrate a remote
response, a completed BACnet transaction or forwarding between networks.
## API boundaries and features
- `layer` owns single-transport NPDU processing, APDU delivery and optional
network-control reception. `start_with_admission` exposes receive-queue
counters and the documented per-source admission policy.
- `router` owns forwarding between configured ports and delivery to local
endpoints. Its `BACnetRouter`, `RouterPort` and `RouterOptions` types describe
that lifecycle separately from `NetworkLayer`. `router.stop().await` returns
`Result<(), RouterStopError>`: success means router workers and each transport's
async cleanup have completed. A cancelled stop wait leaves cleanup running;
subsequent calls replay the same result, including original transport errors
in `RouterStopError::failures()`. Dropping a router or cancelling startup
initiates cleanup on a live runtime, without an immediate resource-reuse
guarantee. Awaited startup failure settles rollback for every attempted port;
its original startup error takes precedence over logged cleanup failures.
- `network_number`, `router_table`, `response_route` and `priority_channel`
provide the associated state, captured reply routes and queue primitives.
This crate declares no optional features. Select an optional transport through
a direct `bacnet-transport` dependency; Cargo unifies that crate's features.
Neither crate has a `no_std` or allocation-free hosted configuration. Physical
transport access, TLS configuration and hardware qualification are separate
from this channel-based quickstart.
## API documentation
This README is also the crate's Rust documentation landing page. The docs.rs
profile disables implicit defaults, selects no additional features, and renders
only `x86_64-unknown-linux-gnu`. Its own network layer and router APIs are present
without enabling every optional transport. The underlying transport crate has
its own documentation profile. Local rendering checks documentation; it does
not establish a docs.rs deployment or physical-network interoperability.