#![allow(clippy::too_many_lines, clippy::doc_markdown)]
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use clap::Parser;
use matter_controller::{
AclAuthMode, AclEntry, AclPrivilege, CommandPath, EventPath, FileStore, MatterController,
OpenWindowOpts, ReadPath, SubscriptionEvent, Value,
};
const ACCESS_CONTROL_CLUSTER: u32 = 0x001F;
const ATTR_ENTRIES_PER_FABRIC: u32 = 0x0004;
const ATTR_SUBJECTS_PER_ENTRY: u32 = 0x0002;
const ONOFF_ENDPOINT: u16 = 1;
const ONOFF_CLUSTER: u32 = 0x0006;
const ONOFF_CMD_TOGGLE: u32 = 0x02;
const BASIC_INFORMATION: u32 = 0x0028;
const GENERAL_DIAGNOSTICS: u32 = 0x0033;
#[derive(Parser)]
#[command(about = "M9 extra hardware validation (events, timed, chunked ACL)")]
struct Args {
#[arg(long)]
store: PathBuf,
#[arg(long)]
node: u64,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let store = Arc::new(FileStore::new(&args.store));
let controller = MatterController::builder(store)
.build()
.await
.context("opening controller")?;
let node = controller.node(args.node);
println!(
"== M9 extra validation against node 0x{:016X} ==\n",
args.node
);
println!("[B1] read_events (BasicInformation + GeneralDiagnostics on ep0)");
let event_paths = [
EventPath::cluster(0, BASIC_INFORMATION),
EventPath::cluster(0, GENERAL_DIAGNOSTICS),
];
let events = node
.read_events(&event_paths, &[])
.await
.context("read_events")?;
println!(" got {} event report(s)", events.len());
for ev in &events {
println!(" {ev:?}");
}
println!();
println!("[B2] subscribe (OnOff attrs ep1 + BasicInformation events ep0)");
let mut sub = node
.subscribe(
&[ReadPath::cluster(ONOFF_ENDPOINT, ONOFF_CLUSTER)],
&[EventPath::cluster(0, BASIC_INFORMATION)],
1,
30,
)
.await
.context("subscribe")?;
node.invoke(
CommandPath {
endpoint: ONOFF_ENDPOINT,
cluster: ONOFF_CLUSTER,
command: ONOFF_CMD_TOGGLE,
},
Value::Structure(vec![]),
)
.await
.context("toggle to drive a report")?;
println!(" toggled OnOff; printing up to 4 subscription events…");
for _ in 0..4 {
match sub.next().await {
Some(SubscriptionEvent::Established { subscription_id }) => {
println!(" established (id 0x{subscription_id:08X})");
}
Some(SubscriptionEvent::Report(r)) => {
println!(" attr report: {:?} = {:?}", r.path, r.value);
}
Some(SubscriptionEvent::Event(e)) => {
println!(" EVENT report: {e:?}");
}
Some(other) => println!(" {other:?}"),
None => break,
}
}
sub.cancel().await.ok();
node.invoke(
CommandPath {
endpoint: ONOFF_ENDPOINT,
cluster: ONOFF_CLUSTER,
command: ONOFF_CMD_TOGGLE,
},
Value::Structure(vec![]),
)
.await
.ok();
println!(" (toggled back)\n");
println!("[B3] timed interaction: open_commissioning_window (timed) then revoke (timed)");
let win = node
.open_commissioning_window(OpenWindowOpts::default())
.await
.context("open_commissioning_window (timed invoke)")?;
println!(" window opened via timed invoke ✓");
println!(
" onboarding manual_code={} discriminator={}",
win.manual_code, win.discriminator
);
let st = node
.commissioning_window_status()
.await
.context("status after open")?;
println!(" WindowStatus now: {:?}", st.status);
node.revoke_commissioning()
.await
.context("revoke_commissioning (timed invoke)")?;
println!(" window revoked via timed invoke ✓");
let st2 = node
.commissioning_window_status()
.await
.context("status after revoke")?;
println!(" WindowStatus now: {:?}\n", st2.status);
println!("[D3] ACL capacity + (maybe) multi-chunk write");
let cap = node
.read(&[
ReadPath::concrete(0, ACCESS_CONTROL_CLUSTER, ATTR_ENTRIES_PER_FABRIC),
ReadPath::concrete(0, ACCESS_CONTROL_CLUSTER, ATTR_SUBJECTS_PER_ENTRY),
])
.await
.context("read ACL capacity")?;
let entries_per_fabric = cap
.iter()
.find(|(p, _)| p.attribute == ATTR_ENTRIES_PER_FABRIC)
.and_then(|(_, v)| {
if let Value::Uint(n) = v {
Some(*n)
} else {
None
}
})
.unwrap_or(0);
let subjects_per_entry = cap
.iter()
.find(|(p, _)| p.attribute == ATTR_SUBJECTS_PER_ENTRY)
.and_then(|(_, v)| {
if let Value::Uint(n) = v {
Some(*n)
} else {
None
}
})
.unwrap_or(0);
println!(" AccessControlEntriesPerFabric={entries_per_fabric} SubjectsPerEntry={subjects_per_entry}");
let original = node
.read_acl()
.await
.context("read_acl (capture original)")?;
let base: Vec<AclEntry> = original
.iter()
.cloned()
.map(|mut e| {
e.fabric_index = None;
e
})
.collect();
let subj = subjects_per_entry.clamp(1, 4) as usize;
let mut big = base.clone();
let target = entries_per_fabric.min(64) as usize;
let mut next_subject: u64 = 0xCAFE_0000;
while big.len() < target {
let subjects: Vec<u64> = (0..subj)
.map(|_| {
next_subject += 1;
next_subject
})
.collect();
big.push(AclEntry::new(
AclPrivilege::Operate,
AclAuthMode::Case,
Some(subjects),
None,
));
}
let est = big
.iter()
.map(|e| 8 + 9 * e.subjects.as_ref().map_or(0, Vec::len))
.sum::<usize>();
println!(
" writing {} entries (~{} bytes est; >800 ⇒ multi-chunk)…",
big.len(),
est
);
match node.write_acl(&big).await {
Ok(statuses) => {
let after = node.read_acl().await.context("read_acl after big write")?;
println!(" write OK -> statuses {statuses:?}");
println!(" read_acl now: {} entry(ies)", after.len());
if est > 800 {
println!(" => encoded list exceeded the 800B budget: MULTI-CHUNK write exercised on hardware ✓");
} else {
println!(" => list fit one message (device capacity too small to force multi-chunk; single-chunk validated)");
}
}
Err(e) => {
println!(" write rejected ({e}) — likely over device ACL capacity; restoring");
}
}
let restored = node.write_acl(&base).await.context("write_acl restore")?;
let final_acl = node.read_acl().await.context("read_acl after restore")?;
println!(
" restored original ACL -> statuses {restored:?}; now {} entry(ies)\n",
final_acl.len()
);
println!("== M9 extra validation complete — device left as found ==");
Ok(())
}