Skip to main content

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;