Skip to main content

bacnet_bip_core/
lib.rs

1//! Portable BACnet/IP behaviour (Annex J), above the wire format of
2//! `bacnet-bip-codec`.
3//!
4//! The crate is `no_std` and needs no allocator: tables live in arrays whose
5//! size the caller picks with a const generic, results are written into
6//! storage the caller passes, and time is an [`Instant`] the caller supplies
7//! with each call. It reads no clock and opens no socket, so the same code
8//! runs on firmware and under the Tokio B/IP transport of
9//! `bacnet-transport`, which drives it.
10//!
11//! [`Instant`] and [`Duration`] are `bacnet-time`'s, in nanoseconds,
12//! re-exported here: the types `bacnet-mstp-core` and `bacnet-router-core`
13//! take too, so firmware that runs them all feeds them one clock. The codec
14//! is re-exported as [`codec`], since the API speaks its types
15//! ([`Bvll`](codec::Bvll), [`BdtEntries`](codec::BdtEntries) and the rest).
16//!
17//! - [`bbmd`]: a BBMD's two tables (the BDT and the FDT), foreign-device
18//!   registration, and where each broadcast is forwarded (Annex J.4, J.5).
19//! - [`node`]: a sans-IO B/IP node in normal, foreign-device and BBMD
20//!   mode: its receive rules and NAKs, what it sends, its own management
21//!   exchanges, and a foreign device's registration lifecycle (Annex J.2,
22//!   J.4, J.5).
23//! - [`port`]: the port driver that joins a node to a socket and carries
24//!   out what it asks: [`NodePort`](port::NodePort) for a node in normal or
25//!   foreign-device mode, and [`BbmdPort`](port::BbmdPort) for a BBMD,
26//!   which forwards broadcasts and answers management requests from its
27//!   tables.
28//! - [`Arrival`]: whether a datagram was addressed to the node or to its
29//!   subnet's broadcast address, which the rules above and a node's own
30//!   both depend on, and [`LocalSubnet`], the subnet it reaches that way.
31//!
32//! The types that may grow are `#[non_exhaustive]`. Build a
33//! [`Datagram`](node::Datagram), a [`Context`](bbmd::Context), a
34//! [`LocalSubnet`] or a [`Registration`](bbmd::Registration) with its
35//! `new`; start a [`Link`](node::Link) or a [`Foreign`](node::Foreign) from
36//! its `new`, or the [`RegistrationLimits`](bbmd::RegistrationLimits) from
37//! `DEFAULT`, and set the fields to change; and give a `match` on an
38//! error, a refusal, a [`Discard`](node::Discard) or another of these
39//! enums a wildcard arm. The enums a host acts on case by case, such as
40//! [`Action`](node::Action) and [`Received`](node::Received), stay
41//! exhaustive.
42//!
43//! # Example
44//!
45//! A normal node's link takes a unicast NPDU from a peer, and says what to
46//! send for a broadcast:
47//!
48//! ```
49//! use core::net::{Ipv4Addr, SocketAddrV4};
50//! use bacnet_bip_core::codec::Bvll;
51//! use bacnet_bip_core::node::{Addressed, Datagram, Link, Mode, Permissive, Received};
52//! use bacnet_bip_core::{Instant, LocalSubnet};
53//!
54//! let own = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 10), 47808);
55//! let subnet = LocalSubnet::new(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 255), 47808));
56//! let link = Link::new(own, subnet, Mode::Normal)?;
57//!
58//! // A peer's Original-Unicast-NPDU, sent to this node's own address.
59//! let npdu = [0x01, 0x00, 0x10, 0x08];
60//! let mut wire = [0u8; 8];
61//! let length = Bvll::OriginalUnicastNpdu(&npdu).encode_into(&mut wire)?;
62//! let peer = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 20), 47808);
63//! let to = Addressed { node: true, broadcast: false };
64//! let datagram = Datagram::new(&wire[..length], peer, Some(to));
65//! let Received::Deliver(delivery) = link.receive(&datagram, Instant::ZERO, &mut Permissive)
66//! else {
67//!     panic!("delivered");
68//! };
69//! assert_eq!((delivery.npdu, delivery.source, delivery.group), (&npdu[..], peer, false));
70//!
71//! // A broadcast goes to the subnet's broadcast address.
72//! let (outgoing, forward_too) = link.broadcast(&npdu)?;
73//! assert_eq!(outgoing.to, subnet.broadcast);
74//! assert_eq!(outgoing.message, Bvll::OriginalBroadcastNpdu(&npdu));
75//! assert!(!forward_too, "only a BBMD forwards its own broadcasts");
76//! # Ok::<(), Box<dyn core::error::Error>>(())
77//! ```
78//!
79//! # Time
80//!
81//! A `now` earlier than the latest one passed in is taken as that latest
82//! one: time never runs backwards, so a clock that steps back holds the
83//! timers still instead of failing. Here that is the [`node::Node`]; the
84//! BBMD tables keep no clock of their own, and count a `now` before a
85//! registration's start as no time passed.
86//!
87//! # Features
88//!
89//! None. The crate depends only on `bacnet-bip-codec`, with its default
90//! features, and `bacnet-time`, and builds on bare-metal targets without an
91//! allocator.
92
93#![no_std]
94#![forbid(unsafe_code)]
95#![cfg_attr(docsrs, feature(doc_cfg))]
96#![warn(missing_debug_implementations)]
97#![warn(clippy::must_use_candidate)]
98
99mod arrival;
100pub mod bbmd;
101pub mod node;
102pub mod port;
103mod subnet;
104
105pub use arrival::Arrival;
106pub use bacnet_bip_codec as codec;
107pub use bacnet_time::{Duration, Instant};
108pub use subnet::LocalSubnet;
109
110/// The README's example, compiled and run as a doctest.
111#[cfg(doctest)]
112#[doc = include_str!("../README.md")]
113#[derive(Debug)]
114pub struct ReadmeDoctests;