use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_time::{Duration, with_timeout};
use trouble_host::prelude::appearance::human_interface_device::KEYBOARD;
use trouble_host::prelude::service::{BATTERY, HUMAN_INTERFACE_DEVICE};
use trouble_host::prelude::*;
const RMK_ADV_COMPANY_ID: u16 = 0x5253;
const SPLIT_PERIPHERAL: u8 = 0;
const DONGLE_SEEKING: u8 = 1;
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(Debug))]
pub(crate) enum Adv<'a> {
Directed(Address),
Host { name: &'a str },
SplitPeripheral { id: u8 },
DongleSeeking,
}
impl Adv<'_> {
fn build<'b>(&self, buf: &'b mut [u8; 31]) -> Result<Advertisement<'b>, Error> {
let adv_data: &[AdStructure] = match *self {
Self::Directed(peer) => return Ok(Advertisement::ConnectableNonscannableDirected { peer }),
Self::Host { name } => &[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::CompleteServiceUuids16(&[BATTERY.to_le_bytes(), HUMAN_INTERFACE_DEVICE.to_le_bytes()]),
AdStructure::CompleteLocalName(name.as_bytes()),
AdStructure::Unknown {
ty: 0x19, data: &KEYBOARD.to_le_bytes(),
},
],
Self::SplitPeripheral { id } => &[
AdStructure::Flags(BR_EDR_NOT_SUPPORTED),
AdStructure::ManufacturerSpecificData {
company_identifier: RMK_ADV_COMPANY_ID,
payload: &[SPLIT_PERIPHERAL, id],
},
],
Self::DongleSeeking => &[
AdStructure::Flags(BR_EDR_NOT_SUPPORTED),
AdStructure::ManufacturerSpecificData {
company_identifier: RMK_ADV_COMPANY_ID,
payload: &[DONGLE_SEEKING],
},
],
};
AdStructure::encode_slice(adv_data, &mut buf[..])?;
Ok(Advertisement::ConnectableScannableUndirected {
adv_data: &buf[..],
scan_data: &[],
})
}
pub(crate) fn decode(adv_data: &[u8]) -> Option<Adv<'static>> {
let mut rest = adv_data;
loop {
let (&len, tail) = rest.split_first()?;
let (structure, tail) = tail.split_at_checked(len as usize)?;
rest = tail;
let [0xFF, lo, hi, payload @ ..] = structure else {
continue;
};
if u16::from_le_bytes([*lo, *hi]) != RMK_ADV_COMPANY_ID {
continue;
}
return match payload {
[SPLIT_PERIPHERAL, id] => Some(Adv::SplitPeripheral { id: *id }),
[DONGLE_SEEKING] => Some(Adv::DongleSeeking),
_ => None,
};
}
}
fn params(&self) -> AdvertisementParameters {
let (phy, interval) = match self {
Self::Host { .. } => (PhyKind::Le2M, Duration::from_millis(200)),
_ => (PhyKind::Le1M, Duration::from_millis(50)),
};
AdvertisementParameters {
primary_phy: phy,
secondary_phy: phy,
tx_power: TxPower::Plus8dBm,
interval_min: interval,
interval_max: interval,
..Default::default()
}
}
}
pub(crate) async fn advertise<'a, 'b, C: Controller, const ATT: usize, const CONN: usize>(
peripheral: &mut Peripheral<'a, C, DefaultPacketPool>,
server: &'b AttributeServer<'_, NoopRawMutex, DefaultPacketPool, ATT, CONN>,
adv: Adv<'_>,
timeout: Duration,
) -> Result<GattConnection<'a, 'b, DefaultPacketPool>, BleHostError<C::Error>> {
let mut buf = [0; 31];
let advertiser = peripheral.advertise(&adv.params(), adv.build(&mut buf)?).await?;
let conn = with_timeout(timeout, advertiser.accept())
.await
.map_err(|_| Error::Timeout)??;
Ok(conn.with_attribute_server(server)?)
}
#[cfg(test)]
mod tests {
use super::Adv;
fn fits(adv: Adv<'_>) -> bool {
adv.build(&mut [0; 31]).is_ok()
}
#[test]
fn every_advertisement_fits_the_legacy_budget() {
assert!(fits(Adv::SplitPeripheral { id: 0xFF }));
assert!(fits(Adv::DongleSeeking));
assert!(fits(Adv::Host {
name: "0123456789abcdef"
}));
assert!(!fits(Adv::Host {
name: "0123456789abcdefg"
}));
}
#[test]
fn every_rmk_kind_round_trips_through_an_advertisement() {
for adv in [Adv::SplitPeripheral { id: 2 }, Adv::DongleSeeking] {
let mut buf = [0; 31];
adv.build(&mut buf).unwrap();
assert_eq!(Adv::decode(&buf), Some(adv));
}
}
#[test]
fn decode_skips_preceding_structures() {
let mut data = [0u8; 27];
data[..5].copy_from_slice(&[0x02, 0x01, 0x06, 0x11, 0x07]);
data[21..].copy_from_slice(&[0x05, 0xFF, 0x53, 0x52, 0x00, 0x02]);
assert_eq!(Adv::decode(&data), Some(Adv::SplitPeripheral { id: 2 }));
}
#[test]
fn decode_rejects_foreign_unknown_and_malformed_reports() {
assert_eq!(
Adv::decode(&[0x02, 0x01, 0x04, 0x05, 0xFF, 0x4C, 0x00, 0x00, 0x02]),
None
);
assert_eq!(Adv::decode(&[0x04, 0xFF, 0x53, 0x52, 0x7F]), None);
assert_eq!(Adv::decode(&[0x02, 0x01, 0x04, 0x09, 0xFF, 0x53]), None);
}
}