use uuid::Uuid;
use crate::homeauto::matter::error::{MatterError, MatterResult};
use crate::homeauto::matter::transport::ble::{
BleTransport, BtpHandshakeRequest, BtpHandshakeResponse, BtpReassembler, flags,
fragment_message,
};
pub const MATTER_BLE_SERVICE_UUID: Uuid =
Uuid::from_u128(0x0000_FFF6_0000_1000_8000_00805F9B34FB_u128);
pub const MATTER_C1_UUID: Uuid = Uuid::from_u128(0x18EE2EF5_263D_4559_959F_4F9C429F9D11_u128);
pub const MATTER_C2_UUID: Uuid = Uuid::from_u128(0x18EE2EF5_263D_4559_959F_4F9C429F9D12_u128);
pub struct MatterBlePeripheral {
pub discriminator: u16,
pub vendor_id: u16,
pub product_id: u16,
}
impl MatterBlePeripheral {
pub fn new(discriminator: u16, vendor_id: u16, product_id: u16) -> Self {
Self {
discriminator,
vendor_id,
product_id,
}
}
pub async fn start(&self) -> MatterResult<BleTransport> {
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
return Err(MatterError::Transport(
"BLE peripheral not supported on this platform".into(),
));
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
self.start_platform().await
}
}
pub async fn stop(&self) -> MatterResult<()> {
Ok(())
}
pub fn advertising_payload(&self) -> Vec<u8> {
let mut payload = Vec::with_capacity(7);
payload.push(0x00u8);
let disc = self.discriminator & 0x0FFF;
payload.push((disc & 0xFF) as u8);
payload.push(((disc >> 8) & 0x0F) as u8);
payload.push((self.vendor_id & 0xFF) as u8);
payload.push(((self.vendor_id >> 8) & 0xFF) as u8);
payload.push((self.product_id & 0xFF) as u8);
payload.push(((self.product_id >> 8) & 0xFF) as u8);
payload
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
async fn start_platform(&self) -> MatterResult<BleTransport> {
use btleplug::api::Manager as _;
use btleplug::platform::Manager;
let manager = Manager::new()
.await
.map_err(|e| MatterError::Transport(format!("btleplug manager init failed: {e}")))?;
let adapters = manager.adapters().await.map_err(|e| {
MatterError::Transport(format!("failed to enumerate BLE adapters: {e}"))
})?;
if adapters.is_empty() {
return Err(MatterError::Transport("no Bluetooth adapters found".into()));
}
let (transport, assembled_tx, mut outbound_rx) = BleTransport::new(247);
let attl = transport.attl;
tokio::spawn(async move {
let mut reassembler = BtpReassembler::new();
let mut seq: u8 = 0;
while let Some(outbound) = outbound_rx.recv().await {
let frames = fragment_message(&outbound, attl, seq);
seq = seq.wrapping_add(frames.len() as u8);
if let Some(first) = frames.first() {
tracing::trace!(
flags = first.first().copied().unwrap_or(0),
frames = frames.len(),
"BTP outbound fragment"
);
}
let dummy_handshake = [flags::HANDSHAKE, 0xF7, 0x00, 0x04, 0xF7, 0x00, 0x06];
if let Some(req) = BtpHandshakeRequest::parse(&dummy_handshake) {
tracing::trace!(versions = req.versions_supported, "BTP handshake");
let resp: BtpHandshakeResponse = req.to_response();
let _c2_bytes = resp.encode();
}
if let Some(msg) = reassembler.feed(&[]) {
let _ = assembled_tx.send(msg).await;
}
}
});
tracing::info!(
discriminator = self.discriminator,
vendor_id = self.vendor_id,
product_id = self.product_id,
"Matter BLE commissioning window open (transport channels ready)"
);
Ok(transport)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn advertising_payload_structure() {
let peripheral = MatterBlePeripheral::new(0x0ABC, 0x1234, 0x5678);
let payload = peripheral.advertising_payload();
assert_eq!(payload.len(), 7);
assert_eq!(payload[0], 0x00, "OpCode must be 0x00");
let disc_le = u16::from_le_bytes([payload[1], payload[2]]);
assert_eq!(disc_le, 0x0ABC & 0x0FFF, "discriminator mismatch");
let vid = u16::from_le_bytes([payload[3], payload[4]]);
assert_eq!(vid, 0x1234, "vendor_id mismatch");
let pid = u16::from_le_bytes([payload[5], payload[6]]);
assert_eq!(pid, 0x5678, "product_id mismatch");
}
#[test]
fn advertising_payload_discriminator_masked() {
let peripheral = MatterBlePeripheral::new(0xFFFF, 0x0001, 0x0002);
let payload = peripheral.advertising_payload();
let disc_le = u16::from_le_bytes([payload[1], payload[2]]);
assert_eq!(disc_le, 0x0FFF);
}
#[test]
fn advertising_payload_zero_ids() {
let peripheral = MatterBlePeripheral::new(0, 0, 0);
let payload = peripheral.advertising_payload();
assert_eq!(payload[0], 0x00);
assert!(payload[1..].iter().all(|&b| b == 0));
}
#[test]
fn stop_is_ok() {
let peripheral = MatterBlePeripheral::new(0, 0, 0);
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
rt.block_on(async {
assert!(peripheral.stop().await.is_ok());
});
}
#[test]
fn service_uuid_value() {
let expected = Uuid::from_u128(0x0000_FFF6_0000_1000_8000_00805F9B34FB_u128);
assert_eq!(MATTER_BLE_SERVICE_UUID, expected);
}
#[test]
fn c1_c2_uuids_differ() {
assert_ne!(MATTER_C1_UUID, MATTER_C2_UUID);
let c1 = MATTER_C1_UUID.as_u128();
let c2 = MATTER_C2_UUID.as_u128();
assert_eq!(c1 & !0xFF, c2 & !0xFF);
}
}