#![cfg(all(feature = "std", feature = "async-io"))]
use core::pin::pin;
use embassy_futures::select::{select, Either};
use embassy_time::{Duration, Timer};
use log::info;
use std::sync::Mutex;
use rs_matter::cert::gen::VALID_FOREVER;
use rs_matter::cert::MAX_CERT_TLV_AND_ASN1_LEN;
use rs_matter::crypto::{
test_only_crypto, CanonAeadKey, CanonAeadKeyRef, CanonPkcSecretKey, Crypto, RngCore, SecretKey,
SigningSecretKey, AEAD_CANON_KEY_LEN,
};
use rs_matter::dm::devices::test::{TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET};
use rs_matter::error::Error;
use rs_matter::onboard::cac::RcacGenerator;
use rs_matter::onboard::noc::NocGenerator;
use rs_matter::respond::Responder;
use rs_matter::sc::case::CaseInitiator;
use rs_matter::sc::{OpCode, SecureChannel, PROTO_ID_SECURE_CHANNEL};
use rs_matter::transport::exchange::Exchange;
use rs_matter::transport::network::{Address, NetworkSend, NoNetwork};
use rs_matter::transport::packet::PacketHdr;
use rs_matter::utils::select::Coalesce;
use rs_matter::utils::storage::ParseBuf;
use rs_matter::Matter;
use crate::common::{create_localhost_socket_pair, init_env_logger, run_device_controller};
#[allow(dead_code)]
mod common;
const TEST_FABRIC_ID: u64 = 1;
const CONTROLLER_NODE_ID: u64 = 100;
const DEVICE_NODE_ID: u64 = 200;
struct DropFirstSigma2<'a> {
socket: &'a async_io::Async<std::net::UdpSocket>,
enabled: bool,
first_two_packets: &'a Mutex<Vec<Vec<u8>>>,
}
impl NetworkSend for DropFirstSigma2<'_> {
async fn send_to(&mut self, data: &[u8], addr: Address) -> Result<(), Error> {
if self.enabled {
let mut packets = self.first_two_packets.lock().unwrap();
if packets.is_empty() && is_sigma2(data) {
packets.push(data.to_vec());
return Ok(());
} else if packets.len() == 1 {
if is_sigma2(data) {
packets.push(data.to_vec());
} else {
return Ok(());
}
}
}
let mut socket = self.socket;
NetworkSend::send_to(&mut socket, data, addr).await
}
}
fn is_sigma2(data: &[u8]) -> bool {
let mut data = data.to_vec();
let mut pb = ParseBuf::new(data.as_mut_slice());
let mut header = PacketHdr::new();
if header.decode_plain_hdr(&mut pb).is_err()
|| header
.decode_remaining(test_only_crypto(), None, 0, &mut pb)
.is_err()
{
return false;
}
header.proto.proto_id == PROTO_ID_SECURE_CHANNEL
&& header.proto.proto_opcode == OpCode::CASESigma2 as u8
}
#[test]
fn test_case_handshake() {
run_case_handshake_test(false);
}
#[test]
fn test_case_handshake_with_sigma2_retransmission() {
run_case_handshake_test(true);
}
fn run_case_handshake_test(drop_first_sigma2: bool) {
init_env_logger();
futures_lite::future::block_on(async {
let crypto = test_only_crypto();
let mut rcac_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut rcac_gen = RcacGenerator::new(&mut rcac_buf);
let (rcac_privkey, rcac) = rcac_gen
.generate(&crypto, TEST_FABRIC_ID, VALID_FOREVER)
.unwrap();
let mut noc_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut noc_generator =
NocGenerator::create(rcac_privkey.reference(), rcac, &[], &mut noc_buf).unwrap();
let mut ipk = CanonAeadKey::new();
let mut ipk_bytes = [0u8; AEAD_CANON_KEY_LEN];
crypto.rand().unwrap().fill_bytes(&mut ipk_bytes);
ipk.load_from_array(&ipk_bytes);
let ipk_ref: CanonAeadKeyRef<'_> = ipk.reference();
let controller_secret_key = crypto.generate_secret_key().unwrap();
let mut controller_csr_buf = [0u8; 256];
let controller_csr = controller_secret_key.csr(&mut controller_csr_buf).unwrap();
let mut controller_secret_key_canon = CanonPkcSecretKey::new();
controller_secret_key
.write_canon(&mut controller_secret_key_canon)
.unwrap();
let device_secret_key = crypto.generate_secret_key().unwrap();
let mut device_csr_buf = [0u8; 256];
let device_csr = device_secret_key.csr(&mut device_csr_buf).unwrap();
let mut device_secret_key_canon = CanonPkcSecretKey::new();
device_secret_key
.write_canon(&mut device_secret_key_canon)
.unwrap();
let device_matter = Matter::new(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, 0);
let controller_matter = Matter::new(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, 0);
let controller_noc = noc_generator
.generate(
&crypto,
controller_csr,
CONTROLLER_NODE_ID,
&[],
VALID_FOREVER,
)
.unwrap();
let controller_fab_idx = controller_matter.with_state(|state| {
state
.fabrics
.add(
&crypto,
controller_secret_key_canon.reference(),
rcac,
controller_noc,
&[], Some(ipk_ref),
0xFFF1,
CONTROLLER_NODE_ID,
)
.unwrap()
.fab_idx()
});
let device_noc = noc_generator
.generate(&crypto, device_csr, DEVICE_NODE_ID, &[], VALID_FOREVER)
.unwrap();
device_matter.with_state(|state| {
state
.fabrics
.add(
&crypto,
device_secret_key_canon.reference(),
rcac,
device_noc,
&[], Some(ipk_ref),
0xFFF1,
CONTROLLER_NODE_ID,
)
.unwrap();
});
let (device_socket, controller_socket) = create_localhost_socket_pair();
let peer_addr = Address::Udp(device_socket.get_ref().local_addr().unwrap());
let sc = SecureChannel::new(&crypto, &());
let responder = Responder::new("device", sc, &device_matter, 0);
let first_two_packets = Mutex::new(Vec::new());
let device_fut = async {
select(
device_matter.run(
&crypto,
DropFirstSigma2 {
socket: &device_socket,
enabled: drop_first_sigma2,
first_two_packets: &first_two_packets,
},
&device_socket,
NoNetwork,
),
responder.run::<4>(),
)
.coalesce()
.await
};
let controller_fut = async {
let mut transport = pin!(controller_matter.run(
&crypto,
&controller_socket,
&controller_socket,
NoNetwork,
));
let mut test = pin!(run_case_handshake(
&controller_matter,
&crypto,
peer_addr,
controller_fab_idx,
DEVICE_NODE_ID,
));
match select(&mut transport, &mut test).await {
Either::First(transport_result) => {
panic!("Controller transport exited prematurely: {transport_result:?}");
}
Either::Second(test_result) => {
let mut flush = pin!(Timer::after(Duration::from_millis(300)));
if let Either::First(transport_result) =
select(&mut transport, &mut flush).await
{
panic!("Controller transport error during flush: {transport_result:?}");
}
test_result
}
}
};
let result = run_device_controller(device_fut, controller_fut).await;
if drop_first_sigma2 {
let packets = first_two_packets.lock().unwrap();
assert_eq!(
packets.len(),
2,
"expected the initial Sigma2 and one retransmission"
);
assert_eq!(
packets[0], packets[1],
"CASE Sigma2 must be byte-identical when retransmitted"
);
}
result.unwrap();
});
}
async fn run_case_handshake<C: Crypto>(
matter: &Matter<'_>,
crypto: &C,
peer_addr: Address,
fab_idx: core::num::NonZeroU8,
peer_node_id: u64,
) -> Result<(), Error> {
info!("Creating plaintext session and initiating CASE handshake...");
let exchange = Exchange::initiate_plaintext(matter, crypto, peer_addr).await?;
info!("Exchange initiated: {}", exchange.id());
info!("Starting CASE handshake...");
let mut case_fut = pin!(CaseInitiator::perform(
exchange,
crypto,
fab_idx,
peer_node_id,
));
let mut timeout = pin!(Timer::after(Duration::from_secs(30)));
let result = match select(&mut case_fut, &mut timeout).await {
Either::First(result) => result,
Either::Second(_) => panic!("CASE handshake timed out after 30 seconds"),
};
result?;
info!("CASE handshake completed successfully - secure session established");
Ok(())
}