osdp 0.3.0

Pure-Rust, no_std-friendly implementation of the SIA Open Supervised Device Protocol (OSDP) v2.2
Documentation
//! Pure-Rust, `no_std`-friendly implementation of the SIA OSDP v2.2 protocol.
//!
//! # Crate features
//!
//! - `std` *(default)*: enables [`alloc`], plus desktop convenience.
//! - `alloc` *(default via `std`)*: enables [`alloc::vec::Vec`]-based codec paths.
//! - `secure-channel` *(default)*: enables Annex D (AES-128, MAC, encryption).
//! - `embedded-io` / `embedded-io-async`: transport adapters.
//! - `defmt`: structured logging on embedded targets.
//!
//! # Layering
//!
//! See [`architecture`] for a rendered diagram of how these modules relate.
//!
//! - [`packet`] — wire framing (SOM, header, SCB, MAC, trailer)
//! - [`command`] / [`reply`] — typed messages
//! - [`multipart`] — RFC §5.10 multi-part assembly / disassembly
//! - [`secure`] — Annex D secure channel
//! - [`transport`] — byte-stream abstraction
//! - [`driver`] — ACU and PD state machines
//! - [`caps`] — Annex B function codes
//!
//! # Quick start
//!
//! Drive a PD at address `0x05` through one POLL exchange. The example uses
//! [`transport::VecTransport`], so without a peer feeding bytes back the
//! exchange will end in [`driver::acu::ExchangeOutcome::Timeout`] — wire it
//! up to a real `Transport` (or another `VecTransport`, see
//! `examples/loopback_poll.rs`) for a successful round-trip.
//!
//! ```
//! use osdp::clock::SystemClock;
//! use osdp::command::{Command, Poll};
//! use osdp::driver::acu::{Acu, ExchangeOutcome, PdState};
//! use osdp::reply::Reply;
//! use osdp::transport::VecTransport;
//!
//! let mut acu = Acu::new(VecTransport::new(), SystemClock::new());
//! let mut pd = PdState::default();
//! match acu.exchange(0x05, &mut pd, &Command::Poll(Poll))? {
//!     ExchangeOutcome::Reply(Reply::Ack(_)) => { /* PD alive */ }
//!     ExchangeOutcome::Busy => { /* PD asked us to back off */ }
//!     ExchangeOutcome::Timeout => { /* no reply within budget */ }
//!     ExchangeOutcome::Offline => { /* PD declared offline */ }
//!     _ => {}
//! }
//! # Ok::<(), osdp::Error>(())
//! ```
//!
//! For the secure-channel walk see `examples/handshake.rs`; for an end-to-end
//! loopback that exercises SQN cycling see `examples/loopback_poll.rs`.
//!
//! # Specification cross-references
//!
//! All spec citations refer to *SIA OSDP v2.2* (©2020 Security Industry
//! Association). Where an item maps directly onto a spec section it is noted
//! in the docs as `# Spec: §X.Y`.

#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
#![deny(unsafe_code)]

#[cfg(feature = "alloc")]
extern crate alloc;

pub mod caps;
pub mod clock;
pub mod error;
pub mod packet;
pub mod transport;

/// Crate architecture diagram.
///
/// `osdp-rs` layers responsibilities so that the high-level state machines
/// in [`driver`] can stay independent of the wire format and the I/O
/// substrate. Every arrow points "uses".
///
#[cfg_attr(feature = "_docs", aquamarine::aquamarine)]
/// ```mermaid
/// flowchart TB
///     APP([Application])
///     subgraph drivers["driver — high-level state machines"]
///         ACU[acu::Acu]
///         PD[pd::Pd]
///     end
///     subgraph messages["typed messages"]
///         CMD[command]
///         REP[reply]
///         MP[multipart]
///     end
///     subgraph wire["wire layer"]
///         PKT[packet]
///         SEC["secure (Annex D)"]
///     end
///     TR[transport]
///     APP --> drivers
///     drivers --> messages
///     drivers --> wire
///     drivers --> TR
///     messages --> wire
///     wire --> TR
/// ```
pub mod architecture {}

#[cfg(feature = "alloc")]
mod payload_util;

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub mod command;
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub mod multipart;
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub mod reply;

#[cfg(all(feature = "alloc", feature = "secure-channel"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "secure-channel"))))]
pub mod secure;

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub mod driver;

pub use error::{Error, Result};
pub use packet::{Address, ControlByte, ParsedPacket, Sqn, Trailer};

#[cfg(feature = "alloc")]
pub use packet::Packet;

/// Start of message marker — begins every OSDP packet header.
///
/// # Spec: §5.9
pub const SOM: u8 = 0x53;

/// Broadcast address.
///
/// # Spec: §5.4
///
/// Address `0x7F` is reserved as a special "BROADCAST" address that each PD
/// will accept and respond to. The reply uses `0x7F | 0x80 = 0xFF` in its
/// address field.
pub const BROADCAST_ADDR: u8 = 0x7F;

/// Largest legal PD unicast address.
///
/// # Spec: §5.4
pub const MAX_PD_ADDR: u8 = 0x7E;

/// Reply flag set in [`Address`] when sent from PD to ACU.
///
/// # Spec: §5.9, Table 1
pub const REPLY_FLAG: u8 = 0x80;

/// Minimum receive-buffer size every PD must support.
///
/// # Spec: §5.6
pub const MIN_RX_SIZE: usize = 128;

/// Maximum packet length that any device must tolerate on the wire (even if
/// addressed elsewhere).
///
/// # Spec: §5.6
pub const MAX_BUS_PACKET: usize = 1440;

/// Default ACU reply-delay budget.
///
/// # Spec: §5.7
pub const REPLY_DELAY_MS: u32 = 200;

/// Off-line declaration threshold.
///
/// # Spec: §5.7
pub const OFFLINE_THRESHOLD_MS: u32 = 8_000;