#![cfg(feature = "driver")]
#![allow(clippy::unwrap_used, clippy::expect_used)]
#![allow(clippy::doc_markdown, clippy::similar_names)]
mod support;
use std::sync::Arc;
use matter_cert::{MatterTime, TrustAnchor, TrustedRoots};
use matter_commissioning::attestation::CdSigningRoots;
use matter_commissioning::driver::{
commission, operational_instance_name, DriverConfig, InMemoryDatagram,
};
use matter_commissioning::noc::{
issue_icac, issue_noc, FabricRecord, NocRng, SystemNocRng, VerifiedCsr,
};
use matter_commissioning::setup::{
CommissioningFlow, DiscoveryCapabilities, Discriminator, Passcode, SetupPayload,
};
use matter_commissioning::state_machine::{CommissionerConfig, Stage};
use matter_crypto::{derive_compressed_fabric_id, CaseCredentials, RingSigner, Signer};
use matter_transport::{Discovery, MatterService, QueryHandle, ServiceKind};
use support::{build_mock_device_pki, run_mock_device, MockDeviceCaseSetup, PID, VID};
const PASSCODE: u32 = 20_202_021;
const DISCRIMINATOR: u16 = 0x0F00;
const FABRIC_ID: u64 = 0x0000_0000_0000_0001;
const COMMISSIONER_NODE_ID: u64 = 0x0000_0000_0000_0001;
const ASSIGNED_NODE_ID: u64 = 0x0000_0000_0000_0002;
const PASE_RESPONDER_SESSION_ID: u16 = 0x00BB;
const CASE_RESPONDER_SESSION_ID: u16 = 0x00D2;
fn now() -> MatterTime {
MatterTime::from_unix_secs(1_800_000_000)
}
struct FakeDiscovery {
service: MatterService,
}
impl Discovery for FakeDiscovery {
fn publish(&mut self, _s: &MatterService) -> matter_transport::Result<()> {
Ok(())
}
fn unpublish(&mut self, _n: &str, _k: ServiceKind) -> matter_transport::Result<()> {
Ok(())
}
fn query(&mut self, _k: ServiceKind) -> matter_transport::Result<QueryHandle> {
Ok(QueryHandle(1))
}
fn stop_query(&mut self, _h: QueryHandle) {}
fn poll_results(&mut self, _h: QueryHandle) -> Vec<MatterService> {
vec![self.service.clone()]
}
}
fn build_device_case_setup(fabric: &FabricRecord, ipk_epoch_key: [u8; 16]) -> MockDeviceCaseSetup {
let (device_op_signer, _pkcs8) = RingSigner::generate().expect("device op key");
let device_op_pub = device_op_signer.public_key().clone();
let verified_csr = VerifiedCsr {
public_key: device_op_pub,
};
let device_noc = issue_noc(
fabric,
&verified_csr,
ASSIGNED_NODE_ID,
&[],
(now(), MatterTime::NO_EXPIRY),
&SystemNocRng,
)
.expect("device NOC issuance under fabric RCAC");
let compressed_fabric_id = matter_crypto::derive_compressed_fabric_id(
fabric.root_public_key.as_bytes(),
fabric.fabric_id,
)
.expect("compressed fabric id");
let operational_ipk =
matter_crypto::derive_operational_ipk(&ipk_epoch_key, &compressed_fabric_id)
.expect("operational IPK derivation");
let credentials = CaseCredentials {
noc: device_noc,
icac: fabric.icac_cert.clone(),
signer: Box::new(device_op_signer),
fabric_id: fabric.fabric_id,
node_id: ASSIGNED_NODE_ID,
ipk: operational_ipk,
rcac_public_key: *fabric.root_public_key.as_bytes(),
};
let mut trusted_roots = TrustedRoots::new();
trusted_roots.add(TrustAnchor::from_root_cert(&fabric.root_cert));
MockDeviceCaseSetup {
credentials,
trusted_roots,
responder_session_id: CASE_RESPONDER_SESSION_ID,
now: now(),
}
}
#[allow(clippy::too_many_lines)] async fn run_loopback_commission() {
let mock_pki = build_mock_device_pki(now());
let (root_signer, _pkcs8) = RingSigner::generate().expect("fabric root key");
let root_signer: Arc<dyn Signer> = Arc::new(root_signer);
let fabric = FabricRecord::new_root_only(
FABRIC_ID,
root_signer,
now(),
MatterTime::NO_EXPIRY,
1, &SystemNocRng,
)
.expect("fabric RCAC");
let setup = SetupPayload {
version: 0,
vendor_id: Some(VID),
product_id: Some(PID),
commissioning_flow: CommissioningFlow::Standard,
discovery_capabilities: DiscoveryCapabilities::ON_NETWORK,
discriminator: Discriminator::new(DISCRIMINATOR).unwrap(),
passcode: Passcode::new(PASSCODE).unwrap(),
};
let cd_signing_roots = CdSigningRoots::with_example_device_roots();
let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
let (ctrl_io, dev_io) = InMemoryDatagram::pair();
let dev_addr = dev_io.local_addr();
let ctrl_addr = ctrl_io.local_addr();
let compressed = derive_compressed_fabric_id(fabric.root_public_key.as_bytes(), FABRIC_ID)
.expect("compressed fabric id");
let op_instance_name = operational_instance_name(compressed, ASSIGNED_NODE_ID);
let mut fake_disc = FakeDiscovery {
service: MatterService::new(
op_instance_name,
ServiceKind::Operational,
vec![dev_addr.ip()],
dev_addr.port(),
std::collections::HashMap::new(),
),
};
let commissioner = CommissionerConfig {
pase_attestation_challenge: [0u8; 16],
fabric: &fabric,
setup_payload: &setup,
paa_trust_store: &mock_pki.paa_trust_store,
cd_signing_roots: &cd_signing_roots,
commissioner_node_id: COMMISSIONER_NODE_ID,
assigned_node_id: ASSIGNED_NODE_ID,
ipk_epoch_key: [0x42_u8; 16],
case_admin_subject: COMMISSIONER_NODE_ID,
admin_vendor_id: VID,
now: now(),
rng,
network: matter_commissioning::NetworkCredentials::AlreadyOnNetwork, };
let (commissioner_signer, commissioner_pkcs8) =
matter_crypto::RingSigner::generate().expect("commissioner keypair");
let commissioner_noc = matter_commissioning::issue_noc(
&fabric,
&matter_commissioning::VerifiedCsr {
public_key: matter_crypto::CaseSigner::public_key(&commissioner_signer).clone(),
},
COMMISSIONER_NODE_ID,
&[],
(now(), matter_cert::MatterTime::NO_EXPIRY),
&matter_commissioning::SystemNocRng,
)
.expect("commissioner NOC");
let config = DriverConfig {
commissioner,
commissionable_addr: Some(dev_addr),
passcode: PASSCODE,
commissioner_noc: &commissioner_noc,
commissioner_signer_pkcs8: &commissioner_pkcs8,
};
let case_setup = build_device_case_setup(&fabric, [0x42_u8; 16]);
let device = run_mock_device(
&dev_io,
ctrl_addr, &mock_pki,
PASSCODE,
matter_crypto::pase::PasePbkdfParams {
iterations: 1000,
salt: vec![0x55; 16],
},
PASE_RESPONDER_SESSION_ID,
case_setup,
);
let (commission_result, device_result) =
tokio::join!(commission(&ctrl_io, &mut fake_disc, config), device);
device_result.expect("mock device side completed without error");
let commissioned = commission_result.expect("commission() reached Done");
assert_eq!(
commissioned.terminated_at,
Stage::Cleanup,
"commissioning must terminate at the Cleanup stage"
);
assert_eq!(
commissioned.peer_node_id, ASSIGNED_NODE_ID,
"commissioned peer node id must equal the assigned node id"
);
assert_eq!(
commissioned.fabric.fabric_id, FABRIC_ID,
"commissioned fabric id must match the fabric we commissioned under"
);
}
#[tokio::test]
async fn commission_reaches_done_against_mock_device() {
run_loopback_commission().await;
}
#[allow(clippy::too_many_lines)] #[tokio::test]
async fn run_loopback_commission_with_icac() {
let mock_pki = build_mock_device_pki(now());
let (root_signer, _pkcs8) = RingSigner::generate().expect("fabric root key");
let root_signer: Arc<dyn Signer> = Arc::new(root_signer);
let mut fabric = FabricRecord::new_root_only(
FABRIC_ID,
root_signer,
now(),
MatterTime::NO_EXPIRY,
1, &SystemNocRng,
)
.expect("fabric RCAC");
let (icac_signer, _icac_pkcs8) = RingSigner::generate().expect("icac key");
let icac_public_key = icac_signer.public_key().clone();
let icac_cert = issue_icac(
&fabric,
1, &icac_public_key,
(now(), MatterTime::NO_EXPIRY),
&SystemNocRng,
)
.expect("issue icac under fabric RCAC"); let icac_subject = icac_cert.subject().clone();
fabric.icac_signer = Some(Arc::new(icac_signer));
fabric.icac_cert = Some(icac_cert);
let setup = SetupPayload {
version: 0,
vendor_id: Some(VID),
product_id: Some(PID),
commissioning_flow: CommissioningFlow::Standard,
discovery_capabilities: DiscoveryCapabilities::ON_NETWORK,
discriminator: Discriminator::new(DISCRIMINATOR).unwrap(),
passcode: Passcode::new(PASSCODE).unwrap(),
};
let cd_signing_roots = CdSigningRoots::with_example_device_roots();
let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
let (ctrl_io, dev_io) = InMemoryDatagram::pair();
let dev_addr = dev_io.local_addr();
let ctrl_addr = ctrl_io.local_addr();
let compressed = derive_compressed_fabric_id(fabric.root_public_key.as_bytes(), FABRIC_ID)
.expect("compressed fabric id");
let op_instance_name = operational_instance_name(compressed, ASSIGNED_NODE_ID);
let mut fake_disc = FakeDiscovery {
service: MatterService::new(
op_instance_name,
ServiceKind::Operational,
vec![dev_addr.ip()],
dev_addr.port(),
std::collections::HashMap::new(),
),
};
let commissioner = CommissionerConfig {
pase_attestation_challenge: [0u8; 16],
fabric: &fabric,
setup_payload: &setup,
paa_trust_store: &mock_pki.paa_trust_store,
cd_signing_roots: &cd_signing_roots,
commissioner_node_id: COMMISSIONER_NODE_ID,
assigned_node_id: ASSIGNED_NODE_ID,
ipk_epoch_key: [0x42_u8; 16],
case_admin_subject: COMMISSIONER_NODE_ID,
admin_vendor_id: VID,
now: now(),
rng,
network: matter_commissioning::NetworkCredentials::AlreadyOnNetwork,
};
let (commissioner_signer, commissioner_pkcs8) =
matter_crypto::RingSigner::generate().expect("commissioner keypair");
let commissioner_noc = matter_commissioning::issue_noc(
&fabric,
&matter_commissioning::VerifiedCsr {
public_key: matter_crypto::CaseSigner::public_key(&commissioner_signer).clone(),
},
COMMISSIONER_NODE_ID,
&[],
(now(), matter_cert::MatterTime::NO_EXPIRY),
&matter_commissioning::SystemNocRng,
)
.expect("commissioner NOC");
let config = DriverConfig {
commissioner,
commissionable_addr: Some(dev_addr),
passcode: PASSCODE,
commissioner_noc: &commissioner_noc,
commissioner_signer_pkcs8: &commissioner_pkcs8,
};
let case_setup = build_device_case_setup(&fabric, [0x42_u8; 16]);
let device_noc_issuer = case_setup.credentials.noc.issuer().clone();
let device_credentials_has_icac = case_setup.credentials.icac.is_some();
let device = run_mock_device(
&dev_io,
ctrl_addr,
&mock_pki,
PASSCODE,
matter_crypto::pase::PasePbkdfParams {
iterations: 1000,
salt: vec![0x55; 16],
},
PASE_RESPONDER_SESSION_ID,
case_setup,
);
let (commission_result, device_result) =
tokio::join!(commission(&ctrl_io, &mut fake_disc, config), device);
device_result.expect("mock device side completed without error");
let commissioned = commission_result.expect("commission() reached Done");
assert_eq!(
commissioned.terminated_at,
Stage::Cleanup,
"commissioning must terminate at the Cleanup stage"
);
assert_eq!(
commissioned.peer_node_id, ASSIGNED_NODE_ID,
"commissioned peer node id must equal the assigned node id"
);
assert_eq!(
commissioned.fabric.fabric_id, FABRIC_ID,
"commissioned fabric id must match the fabric we commissioned under"
);
assert!(
commissioned.fabric.icac_cert.is_some(),
"the fabric returned by commission() must still carry the ICAC"
);
assert!(
device_credentials_has_icac,
"device-side CaseCredentials.icac must be Some once the fabric carries an ICAC"
);
assert_eq!(
device_noc_issuer, icac_subject,
"device NOC issuer must be the ICAC subject, proving the NOC was signed under \
the ICAC and not the RCAC"
);
}
#[cfg(all(feature = "tracing", feature = "wiretrace"))]
#[tokio::test]
async fn loopback_commission_emits_wire_trace() {
use std::sync::{Arc, Mutex, OnceLock};
use matter_commissioning::wiretrace::JsonlLayer;
use tracing_subscriber::layer::SubscriberExt as _;
#[derive(Clone)]
struct SharedBuf(Arc<Mutex<Vec<u8>>>);
impl std::io::Write for SharedBuf {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
static GLOBAL_INIT: OnceLock<()> = OnceLock::new();
GLOBAL_INIT.get_or_init(|| {
struct AlwaysSometimes;
impl tracing::Subscriber for AlwaysSometimes {
fn enabled(&self, _: &tracing::Metadata<'_>) -> bool {
true
}
fn new_span(&self, _: &tracing::span::Attributes<'_>) -> tracing::span::Id {
tracing::span::Id::from_u64(1)
}
fn record(&self, _: &tracing::span::Id, _: &tracing::span::Record<'_>) {}
fn record_follows_from(&self, _: &tracing::span::Id, _: &tracing::span::Id) {}
fn event(&self, _: &tracing::Event<'_>) {}
fn enter(&self, _: &tracing::span::Id) {}
fn exit(&self, _: &tracing::span::Id) {}
fn register_callsite(
&self,
_: &'static tracing::Metadata<'static>,
) -> tracing::subscriber::Interest {
tracing::subscriber::Interest::sometimes()
}
}
let _ = tracing::subscriber::set_global_default(AlwaysSometimes);
});
let buf = Arc::new(Mutex::new(Vec::new()));
let subscriber = tracing_subscriber::registry().with(JsonlLayer::new(SharedBuf(buf.clone())));
let guard = tracing::subscriber::set_default(subscriber);
run_loopback_commission().await;
drop(guard);
let text = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
let records: Vec<serde_json::Value> = text
.lines()
.map(|l| serde_json::from_str(l).unwrap_or_else(|e| panic!("bad JSONL line {l:?}: {e}")))
.collect();
assert!(
!records.is_empty(),
"no wire events captured — has the AlwaysSometimes interest-priming regressed?"
);
for (i, r) in records.iter().enumerate() {
assert_eq!(r["seq"], i as u64, "record {i} seq out of order");
}
let first = records
.iter()
.find(|r| r["opcode"] != 0x10) .expect("a non-ack first message");
assert_eq!(first["dir"], "tx");
assert_eq!(first["session_id"], 0);
assert_eq!(first["protocol"], 0);
assert_eq!(first["opcode"], 0x20);
let sc_unsecured: Vec<u64> = records
.iter()
.filter(|r| r["session_id"] == 0 && r["protocol"] == 0 && r["opcode"] != 0x10)
.map(|r| r["opcode"].as_u64().unwrap())
.collect();
let pase: Vec<u64> = sc_unsecured
.iter()
.copied()
.filter(|op| (0x20..=0x24).contains(op))
.collect();
assert_eq!(pase, vec![0x20, 0x21, 0x22, 0x23, 0x24], "PASE sequence");
let sigmas: Vec<u64> = sc_unsecured
.iter()
.copied()
.filter(|op| (0x30..=0x32).contains(op))
.collect();
assert_eq!(sigmas, vec![0x30, 0x31, 0x32], "CASE sigma sequence");
let secured_sessions: std::collections::BTreeSet<u64> = records
.iter()
.filter(|r| r["session_id"] != 0 && r["protocol"] == 1)
.map(|r| r["session_id"].as_u64().unwrap())
.collect();
assert!(
secured_sessions.len() >= 2,
"expected PASE + CASE secured sessions, got {secured_sessions:?}"
);
}