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 (idempotent across restarts — load the
73//! // snapshot instead of re-creating in real apps).
74//! let fabric_id = controller.create_fabric(FabricConfig::new(
75//! 1,
76//! 1,
77//! 1,
78//! (MatterTime::from_unix_secs(0), MatterTime::NO_EXPIRY),
79//! )).await?;
80//! let _ = fabric_id;
81//!
82//! // Commission a device, then control it. `label` is an opaque
83//! // caller-supplied string persisted on the device entry; pass `None` if
84//! // you have nothing to attach.
85//! let info = controller
86//! .commission("MT:Y.K90AFN00KA0648G00", Some("kitchen plug".into()))
87//! .await?;
88//! let node = controller.node(info.node_id);
89//!
90//! // Read all attributes of the OnOff cluster (0x0006) on endpoint 1.
91//! let report = node.read(&[ReadPath::cluster(1, 0x0006)]).await?;
92//! for (path, value) in report {
93//! println!("{path:?} = {value:?}");
94//! }
95//!
96//! // Subscribe to live changes.
97//! let mut sub = node.subscribe(&[ReadPath::cluster(1, 0x0006)], &[], 1, 30).await?;
98//! while let Some(event) = sub.next().await {
99//! if let SubscriptionEvent::Report(change) = event {
100//! println!("changed: {:?} = {:?}", change.path, change.value);
101//! }
102//! }
103//! # Ok(())
104//! # }
105//! ```
106//!
107//! Migrating from matter.js? See `docs/matter-js-migration-guide.md`.
108
109#![forbid(unsafe_code)]
110
111pub(crate) mod acl;
112pub(crate) mod actor;
113pub(crate) mod admin;
114pub(crate) mod binding;
115#[cfg(feature = "ble")]
116pub(crate) mod ble_commission;
117pub mod builder;
118pub(crate) mod commission;
119pub mod controller;
120pub(crate) mod credentials;
121pub mod error;
122pub mod fabric;
123pub(crate) mod group;
124pub(crate) mod handshake_socket;
125pub(crate) mod icd;
126pub(crate) mod icd_listener;
127pub mod node;
128pub(crate) mod node_info;
129pub(crate) mod opcreds;
130pub(crate) mod provider_server;
131pub(crate) mod resumption;
132pub(crate) mod snapshot;
133pub mod state;
134pub mod store;
135pub mod subscription;
136pub mod trust;
137
138pub use acl::{AclAuthMode, AclEntry, AclPrivilege, AclTarget};
139pub use admin::{
140 CommissioningWindow, CommissioningWindowStatus, OpenWindowOpts, WindowStatus,
141 DEFAULT_WINDOW_ITERATIONS, DEFAULT_WINDOW_TIMEOUT_S,
142};
143pub use binding::BindingTarget;
144pub use builder::MatterControllerBuilder;
145pub use controller::MatterController;
146pub use error::Error;
147pub use fabric::FabricConfig;
148pub use group::{GroupKeyMapEntry, GroupKeySet};
149pub use icd::{IcdClientType, IcdRegistration};
150pub use icd_listener::CheckIn;
151pub use matter_cert::MatterTime;
152pub use matter_codec::Value;
153/// Network (Wi-Fi/Thread) credentials for `MatterController::commission_ble`
154/// (feature `ble`), re-exported from `matter-commissioning`.
155pub use matter_commissioning::{NetworkCredentials, ThreadDataset, WiFiCredentials};
156pub use matter_interaction::{
157 AttributePath, CommandPath, EventFilter, EventPath, EventPriority, EventReport,
158 EventReportItem, EventTimestamp, ImStatus, ReadPath,
159};
160pub use node::{DstOffsetEntry, InvokeResult, Node, TimeGranularity, TimeZoneEntry};
161pub use node_info::NodeInfo;
162pub use opcreds::FabricDescriptor;
163/// Low-level provider-server building blocks. **Unstable** — gated behind the
164/// `unstable-provider` feature and not covered by semver. The stable OTA path is
165/// [`MatterController::serve_ota`](controller::MatterController::serve_ota).
166#[cfg(feature = "unstable-provider")]
167pub use provider_server::{build_operational_service, ProviderServer};
168pub use state::{
169 CommissionerIdentity, ControllerState, DeviceEntry, FabricEntry, GroupKeySetConfig,
170};
171pub use store::{ControllerStore, FileStore, StoreError};
172pub use subscription::{AttributeReport, Subscription, SubscriptionEvent};
173pub use trust::AttestationTrust;