use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use matter_ble::central::{BleCentral, BtpChannel, CentralError};
use matter_commissioning::driver::{commission_ble, AsyncDatagram, BleDriverConfig, STREAM_PEER};
use matter_commissioning::NetworkCredentials;
use crate::error::Error;
const SCAN_TIMEOUT: Duration = Duration::from_secs(60);
fn ble_err(e: &CentralError) -> Error {
Error::Operational(format!("ble: {e}"))
}
pub(crate) struct BtpDatagram {
channel: Mutex<BtpChannel>,
}
impl BtpDatagram {
pub(crate) fn new(channel: BtpChannel) -> Self {
Self {
channel: Mutex::new(channel),
}
}
pub(crate) fn into_channel(self) -> BtpChannel {
self.channel.into_inner()
}
}
impl AsyncDatagram for BtpDatagram {
async fn send_to(&self, buf: &[u8], _peer: SocketAddr) -> io::Result<()> {
let channel = self.channel.lock().await;
channel
.send(buf)
.await
.map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e.to_string()))
}
async fn recv_from(&self) -> io::Result<(Vec<u8>, SocketAddr)> {
let mut channel = self.channel.lock().await;
let msg = channel
.recv()
.await
.map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e.to_string()))?;
Ok((msg, STREAM_PEER))
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn run_commission_ble_task(
setup_payload: matter_commissioning::SetupPayload,
trust: Arc<crate::trust::AttestationTrust>,
fabric_record: matter_commissioning::FabricRecord,
commissioner_node_id: u64,
ipk_epoch_key: [u8; 16],
commissioner_noc: matter_cert::MatterCertificate,
commissioner_pkcs8: Vec<u8>,
assigned_node_id: u64,
admin_vendor_id: u16,
now: matter_cert::MatterTime,
rng: Arc<dyn matter_commissioning::NocRng>,
network: NetworkCredentials,
) -> Result<matter_commissioning::CommissionedFabric, Error> {
use matter_commissioning::CommissionerConfig;
let central = BleCentral::new().await.map_err(|e| ble_err(&e))?;
let disc = setup_payload.discriminator.as_u16();
let short_disc = u16::from(setup_payload.discriminator.short());
let device = match central.find_device(disc, false, SCAN_TIMEOUT).await {
Ok(dev) => dev,
Err(CentralError::ScanTimeout) => central
.find_device(short_disc, true, SCAN_TIMEOUT)
.await
.map_err(|e| ble_err(&e))?,
Err(e) => return Err(ble_err(&e)),
};
let channel = central.open_btp(&device).await.map_err(|e| ble_err(&e))?;
let btp = BtpDatagram::new(channel);
let udp = matter_transport::TokioUdpTransport::bind(0)
.await
.map_err(|e| Error::Operational(format!("commission bind: {e}")))?;
let mut discovery = matter_transport::MdnsSdDiscovery::new()
.map_err(|e| Error::Operational(format!("commission mdns: {e}")))?;
let commissioner = CommissionerConfig {
pase_attestation_challenge: [0u8; 16], fabric: &fabric_record,
setup_payload: &setup_payload,
paa_trust_store: &trust.paa,
cd_signing_roots: &trust.cd,
commissioner_node_id,
assigned_node_id,
ipk_epoch_key,
case_admin_subject: commissioner_node_id,
admin_vendor_id,
now,
rng,
network,
};
let config = BleDriverConfig {
commissioner,
passcode: setup_payload.passcode.as_u32(),
commissioner_noc: &commissioner_noc,
commissioner_signer_pkcs8: &commissioner_pkcs8,
};
let result = commission_ble(&btp, &udp, &mut discovery, config)
.await
.map_err(Error::from);
btp.into_channel().close().await;
result
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
use matter_ble::central::PumpCommand;
use tokio::sync::mpsc;
#[tokio::test]
async fn send_and_recv_round_trip() {
let (cmd_tx, mut cmd_rx) = mpsc::channel::<PumpCommand>(8);
let (inbound_tx, inbound_rx) = mpsc::channel::<Result<Vec<u8>, CentralError>>(8);
let dg = BtpDatagram::new(BtpChannel::from_channels(cmd_tx, inbound_rx));
let sent = tokio::join!(
async { dg.send_to(b"hello-btp", STREAM_PEER).await },
async {
match cmd_rx.recv().await.unwrap() {
PumpCommand::Send(bytes, ack) => {
assert_eq!(bytes, b"hello-btp");
ack.send(Ok(())).unwrap();
}
PumpCommand::Close => panic!("unexpected Close"),
}
}
);
sent.0.unwrap();
inbound_tx.send(Ok(b"from-device".to_vec())).await.unwrap();
let (msg, peer) = dg.recv_from().await.unwrap();
assert_eq!(msg, b"from-device");
assert_eq!(peer, STREAM_PEER);
}
#[tokio::test]
async fn recv_on_closed_channel_is_broken_pipe() {
let (cmd_tx, _cmd_rx) = mpsc::channel::<PumpCommand>(8);
let (inbound_tx, inbound_rx) = mpsc::channel::<Result<Vec<u8>, CentralError>>(8);
let dg = BtpDatagram::new(BtpChannel::from_channels(cmd_tx, inbound_rx));
drop(inbound_tx); let err = dg.recv_from().await.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::BrokenPipe);
}
#[tokio::test]
async fn send_on_closed_channel_is_broken_pipe() {
let (cmd_tx, cmd_rx) = mpsc::channel::<PumpCommand>(8);
let (_inbound_tx, inbound_rx) = mpsc::channel::<Result<Vec<u8>, CentralError>>(8);
let dg = BtpDatagram::new(BtpChannel::from_channels(cmd_tx, inbound_rx));
drop(cmd_rx); let err = dg.send_to(b"x", STREAM_PEER).await.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::BrokenPipe);
}
}