osdp/lib.rs
1//! Pure-Rust, `no_std`-friendly implementation of the SIA OSDP v2.2 protocol.
2//!
3//! # Crate features
4//!
5//! - `std` *(default)*: enables [`alloc`], plus desktop convenience.
6//! - `alloc` *(default via `std`)*: enables [`alloc::vec::Vec`]-based codec paths.
7//! - `secure-channel` *(default)*: enables Annex D (AES-128, MAC, encryption).
8//! - `embedded-io` / `embedded-io-async`: transport adapters.
9//! - `defmt`: structured logging on embedded targets.
10//!
11//! # Layering
12//!
13//! See [`architecture`] for a rendered diagram of how these modules relate.
14//!
15//! - [`packet`] — wire framing (SOM, header, SCB, MAC, trailer)
16//! - [`command`] / [`reply`] — typed messages
17//! - [`multipart`] — RFC §5.10 multi-part assembly / disassembly
18//! - [`secure`] — Annex D secure channel
19//! - [`transport`] — byte-stream abstraction
20//! - [`driver`] — ACU and PD state machines
21//! - [`caps`] — Annex B function codes
22//!
23//! # Quick start
24//!
25//! Drive a PD at address `0x05` through one POLL exchange. The example uses
26//! [`transport::VecTransport`], so without a peer feeding bytes back the
27//! exchange will end in [`driver::acu::ExchangeOutcome::Timeout`] — wire it
28//! up to a real `Transport` (or another `VecTransport`, see
29//! `examples/loopback_poll.rs`) for a successful round-trip.
30//!
31//! ```
32//! use osdp::clock::SystemClock;
33//! use osdp::command::{Command, Poll};
34//! use osdp::driver::acu::{Acu, ExchangeOutcome, PdState};
35//! use osdp::reply::Reply;
36//! use osdp::transport::VecTransport;
37//!
38//! let mut acu = Acu::new(VecTransport::new(), SystemClock::new());
39//! let mut pd = PdState::default();
40//! match acu.exchange(0x05, &mut pd, &Command::Poll(Poll))? {
41//! ExchangeOutcome::Reply(Reply::Ack(_)) => { /* PD alive */ }
42//! ExchangeOutcome::Busy => { /* PD asked us to back off */ }
43//! ExchangeOutcome::Timeout => { /* no reply within budget */ }
44//! ExchangeOutcome::Offline => { /* PD declared offline */ }
45//! _ => {}
46//! }
47//! # Ok::<(), osdp::Error>(())
48//! ```
49//!
50//! For the secure-channel walk see `examples/handshake.rs`; for an end-to-end
51//! loopback that exercises SQN cycling see `examples/loopback_poll.rs`.
52//!
53//! # Specification cross-references
54//!
55//! All spec citations refer to *SIA OSDP v2.2* (©2020 Security Industry
56//! Association). Where an item maps directly onto a spec section it is noted
57//! in the docs as `# Spec: §X.Y`.
58
59#![cfg_attr(not(feature = "std"), no_std)]
60#![cfg_attr(docsrs, feature(doc_cfg))]
61#![deny(missing_docs)]
62#![deny(unsafe_code)]
63
64#[cfg(feature = "alloc")]
65extern crate alloc;
66
67pub mod caps;
68pub mod clock;
69pub mod error;
70pub mod packet;
71pub mod transport;
72
73/// Crate architecture diagram.
74///
75/// `osdp-rs` layers responsibilities so that the high-level state machines
76/// in [`driver`] can stay independent of the wire format and the I/O
77/// substrate. Every arrow points "uses".
78///
79#[cfg_attr(feature = "_docs", aquamarine::aquamarine)]
80/// ```mermaid
81/// flowchart TB
82/// APP([Application])
83/// subgraph drivers["driver — high-level state machines"]
84/// ACU[acu::Acu]
85/// PD[pd::Pd]
86/// end
87/// subgraph messages["typed messages"]
88/// CMD[command]
89/// REP[reply]
90/// MP[multipart]
91/// end
92/// subgraph wire["wire layer"]
93/// PKT[packet]
94/// SEC["secure (Annex D)"]
95/// end
96/// TR[transport]
97/// APP --> drivers
98/// drivers --> messages
99/// drivers --> wire
100/// drivers --> TR
101/// messages --> wire
102/// wire --> TR
103/// ```
104pub mod architecture {}
105
106#[cfg(feature = "alloc")]
107mod payload_util;
108
109#[cfg(feature = "alloc")]
110#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
111pub mod command;
112#[cfg(feature = "alloc")]
113#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
114pub mod multipart;
115#[cfg(feature = "alloc")]
116#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
117pub mod reply;
118
119#[cfg(all(feature = "alloc", feature = "secure-channel"))]
120#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "secure-channel"))))]
121pub mod secure;
122
123#[cfg(feature = "alloc")]
124#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
125pub mod driver;
126
127pub use error::{Error, Result};
128pub use packet::{Address, ControlByte, ParsedPacket, Sqn, Trailer};
129
130#[cfg(feature = "alloc")]
131pub use packet::Packet;
132
133/// Start of message marker — begins every OSDP packet header.
134///
135/// # Spec: §5.9
136pub const SOM: u8 = 0x53;
137
138/// Broadcast address.
139///
140/// # Spec: §5.4
141///
142/// Address `0x7F` is reserved as a special "BROADCAST" address that each PD
143/// will accept and respond to. The reply uses `0x7F | 0x80 = 0xFF` in its
144/// address field.
145pub const BROADCAST_ADDR: u8 = 0x7F;
146
147/// Largest legal PD unicast address.
148///
149/// # Spec: §5.4
150pub const MAX_PD_ADDR: u8 = 0x7E;
151
152/// Reply flag set in [`Address`] when sent from PD to ACU.
153///
154/// # Spec: §5.9, Table 1
155pub const REPLY_FLAG: u8 = 0x80;
156
157/// Minimum receive-buffer size every PD must support.
158///
159/// # Spec: §5.6
160pub const MIN_RX_SIZE: usize = 128;
161
162/// Maximum packet length that any device must tolerate on the wire (even if
163/// addressed elsewhere).
164///
165/// # Spec: §5.6
166pub const MAX_BUS_PACKET: usize = 1440;
167
168/// Default ACU reply-delay budget.
169///
170/// # Spec: §5.7
171pub const REPLY_DELAY_MS: u32 = 200;
172
173/// Off-line declaration threshold.
174///
175/// # Spec: §5.7
176pub const OFFLINE_THRESHOLD_MS: u32 = 8_000;