matter_controller/lib.rs
1//! The high-level Matter controller API — the single crate a consumer depends
2//! on to commission and control Matter devices from pure Rust.
3//!
4//! [`MatterController`] is the entry point. It persists a fabric and a stable
5//! commissioner identity through a pluggable [`ControllerStore`] (a default
6//! [`FileStore`] ships), commissions devices over IP, and exposes each device
7//! through a cheap [`Node`] handle that transparently establishes, caches, and
8//! reuses the operational CASE session.
9//!
10//! # Capabilities
11//!
12//! - **Fabric & identity** — [`MatterController::create_fabric`] mints and
13//! persists the controller's stable operational identity once per fabric.
14//! - **Commissioning** — [`MatterController::commission`] brings a device onto
15//! the fabric from a QR (`MT:…`) or manual pairing code, verifying device
16//! attestation against the configured [`AttestationTrust`].
17//! - **Interaction** — [`Node::read`] / [`Node::write`] / [`Node::invoke`] work
18//! over raw [`Value`]s and support **wildcard reads** ([`ReadPath::cluster`],
19//! [`ReadPath::all`]) for reading every attribute off a device.
20//! - **Subscriptions** — [`Node::subscribe`] returns a [`Subscription`] stream
21//! of [`SubscriptionEvent`]s (`Report` / `Established` / `Resubscribing` /
22//! `Lagged`; `next().await` + `cancel()`).
23//! - **Multi-admin / commissioning windows** — [`Node::open_commissioning_window`]
24//! opens an enhanced commissioning window (generates secrets, computes the PAKE
25//! verifier, returns a [`CommissioningWindow`] with `manual_code`/`qr_code`);
26//! [`Node::open_basic_commissioning_window`] opens a basic window; and
27//! [`Node::revoke_commissioning`] closes any open window.
28//! [`Node::commissioning_window_status`] reads the current window state.
29//! - **Fabric management** — [`Node::list_fabrics`] reads the device's full
30//! fabric table as [`Vec<FabricDescriptor>`]; [`Node::remove_fabric`] removes a
31//! fabric by index (self-protected: returns [`Error::WouldRemoveSelf`] for our
32//! own fabric); [`Node::update_fabric_label`] relabels the accessing fabric.
33//! - **ACL management** — [`Node::read_acl`] returns the device's
34//! `AccessControl.Acl` list as [`Vec<AclEntry>`]; [`Node::write_acl`] replaces it
35//! atomically (single-chunk) or via a multi-chunk `MoreChunkedMessages` sequence
36//! (large lists), with a lockout guard that returns [`Error::AclWouldLockOut`]
37//! before sending any bytes if the new list would drop our own Administer/CASE
38//! access.
39//! - **Group provisioning** — [`Node::write_group_key_set`] provisions a key set
40//! on the device (`KeySetWrite`, `GroupKeyManagement` cluster 0x003F);
41//! [`Node::write_group_key_map`] writes the `GroupKeyMap` attribute via the
42//! chunked list-write mechanism; [`Node::add_group`] / [`Node::remove_group`]
43//! add and remove an endpoint from a group (`Groups` cluster 0x0004). Public
44//! types: [`GroupKeySet`] and [`GroupKeyMapEntry`].
45//! - **Group multicast send** — [`MatterController::create_group`] generates and
46//! persists a group epoch key (returns a [`GroupKeySet`] ready to program onto
47//! member devices); [`MatterController::invoke_group`] sends a fire-and-forget
48//! group command over IPv6 multicast (`ff35:…`), encrypted with the operational
49//! group key derived from the persisted epoch key. Returns `Ok` on datagram
50//! send; there is no acknowledgement. [`Error::GroupNotProvisioned`] when the
51//! key set has not been created via `create_group`.
52//!
53//! # Quickstart
54//!
55//! ```no_run
56//! use std::sync::Arc;
57//! use matter_controller::{
58//! AttestationTrust, FabricConfig, FileStore, MatterController, MatterTime, ReadPath,
59//! SubscriptionEvent,
60//! };
61//!
62//! # async fn run() -> Result<(), matter_controller::Error> {
63//! // Persisted store + attestation trust (test roots shown; use
64//! // `AttestationTrust::from_dirs(..)` with production PAA/CD roots for
65//! // certified devices).
66//! let store = Arc::new(FileStore::new("controller-state.bin"));
67//! let controller = MatterController::builder(store)
68//! .attestation_trust(AttestationTrust::example_device_roots())
69//! .build()
70//! .await?;
71//!
72//! // One-time: create the fabric. `create_fabric` is NOT idempotent — a
73//! // second call with an existing `fabric_id` returns
74//! // `Error::FabricAlreadyExists` — so gate it on `fabrics()` (a run that
75//! // loaded an existing store already has its fabric and identity).
76//! if controller.fabrics().await?.is_empty() {
77//! // `not_before` must be a real wall-clock time, backdated a little (an
78//! // hour is plenty) to tolerate device clock skew. `MatterTime(0)` /
79//! // `from_unix_secs(0)` — the Matter epoch — is rejected; so is a time
80//! // far in the future. See `FabricConfig::validity`.
81//! let now_unix = std::time::SystemTime::now()
82//! .duration_since(std::time::UNIX_EPOCH)
83//! .map_err(|e| matter_controller::Error::Operational(e.to_string()))?
84//! .as_secs();
85//! controller.create_fabric(FabricConfig::new(
86//! 1,
87//! 1,
88//! 1,
89//! (
90//! MatterTime::from_unix_secs(now_unix.saturating_sub(3600)),
91//! MatterTime::NO_EXPIRY,
92//! ),
93//! )).await?;
94//! }
95//!
96//! // Commission a device, then control it. `label` is an opaque
97//! // caller-supplied string persisted on the device entry; pass `None` if
98//! // you have nothing to attach.
99//! let info = controller
100//! .commission("MT:Y.K90AFN00KA0648G00", Some("kitchen plug".into()))
101//! .await?;
102//! let node = controller.node(info.node_id);
103//!
104//! // Read all attributes of the OnOff cluster (0x0006) on endpoint 1.
105//! let report = node.read(&[ReadPath::cluster(1, 0x0006)]).await?;
106//! for (path, value) in report {
107//! println!("{path:?} = {value:?}");
108//! }
109//!
110//! // Subscribe to live changes.
111//! let mut sub = node.subscribe(&[ReadPath::cluster(1, 0x0006)], &[], 1, 30).await?;
112//! while let Some(event) = sub.next().await {
113//! if let SubscriptionEvent::Report(change) = event {
114//! println!("changed: {:?} = {:?}", change.path, change.value);
115//! }
116//! }
117//! # Ok(())
118//! # }
119//! ```
120//!
121//! Migrating from matter.js? See `docs/matter-js-migration-guide.md`.
122
123#![forbid(unsafe_code)]
124
125pub(crate) mod acl;
126pub(crate) mod actor;
127pub(crate) mod admin;
128pub(crate) mod binding;
129#[cfg(feature = "ble")]
130pub(crate) mod ble_commission;
131pub mod builder;
132pub(crate) mod commission;
133pub mod controller;
134pub(crate) mod credentials;
135pub mod error;
136pub mod fabric;
137pub(crate) mod fabric_info;
138pub(crate) mod group;
139pub(crate) mod handshake_socket;
140pub(crate) mod icd;
141pub(crate) mod icd_listener;
142pub mod node;
143pub(crate) mod node_info;
144pub(crate) mod opcreds;
145pub(crate) mod provider_server;
146pub(crate) mod resumption;
147pub(crate) mod snapshot;
148pub mod state;
149pub mod store;
150pub mod subscription;
151pub mod trust;
152
153pub use acl::{AclAuthMode, AclEntry, AclPrivilege, AclTarget};
154pub use admin::{
155 CommissioningWindow, CommissioningWindowStatus, OpenWindowOpts, WindowStatus,
156 DEFAULT_WINDOW_ITERATIONS, DEFAULT_WINDOW_TIMEOUT_S,
157};
158pub use binding::BindingTarget;
159pub use builder::MatterControllerBuilder;
160pub use controller::MatterController;
161pub use error::Error;
162pub use fabric::FabricConfig;
163pub use fabric_info::FabricInfo;
164pub use group::{GroupKeyMapEntry, GroupKeySet};
165pub use icd::{IcdClientType, IcdRegistration};
166pub use icd_listener::CheckIn;
167pub use matter_cert::MatterTime;
168pub use matter_codec::Value;
169/// Network (Wi-Fi/Thread) credentials for `MatterController::commission_ble`
170/// (feature `ble`) and the network-type witness for
171/// [`Error::network_feature_unsupported`], re-exported from
172/// `matter-commissioning`.
173pub use matter_commissioning::{NetworkCredentials, NetworkKind, ThreadDataset, WiFiCredentials};
174pub use matter_interaction::{
175 AttributePath, CommandPath, EventFilter, EventPath, EventPriority, EventReport,
176 EventReportItem, EventTimestamp, ImStatus, ReadPath,
177};
178pub use node::{DstOffsetEntry, InvokeResult, Node, TimeGranularity, TimeZoneEntry};
179pub use node_info::NodeInfo;
180pub use opcreds::FabricDescriptor;
181/// Low-level provider-server building blocks. **Unstable** — gated behind the
182/// `unstable-provider` feature and not covered by semver. The stable OTA path is
183/// `MatterController::serve_ota`.
184#[cfg(feature = "unstable-provider")]
185pub use provider_server::{build_operational_service, ProviderServer};
186pub use state::{
187 CommissionerIdentity, ControllerState, DeviceEntry, FabricEntry, GroupKeySetConfig,
188};
189pub use store::{ControllerStore, FileStore, StoreError};
190pub use subscription::{AttributeReport, Subscription, SubscriptionEvent};
191pub use trust::AttestationTrust;