Skip to content

Commit

Permalink
Security manager
Browse files Browse the repository at this point in the history
Use ChaCha12 in security manager.
Tested on ESP32-C6, nrf52833 and Serial HCI.
  • Loading branch information
blueluna committed Mar 6, 2025
1 parent 8861159 commit bf8888d
Show file tree
Hide file tree
Showing 36 changed files with 4,799 additions and 34 deletions.
4 changes: 4 additions & 0 deletions examples/apps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ embedded-hal = "1.0"
static_cell = "2"
embedded-io = "0.6"
heapless = "0.8"
rand_core = { version = "0.6", default-features = false }

defmt = { version = "0.3", optional = true }
log = { version = "0.4", optional = true }
Expand All @@ -33,3 +34,6 @@ log = [
"trouble-host/log",
"bt-hci/log"
]
security = [
"trouble-host/security",
]
96 changes: 96 additions & 0 deletions examples/apps/src/ble_bas_central_sec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use rand_core::{CryptoRng, RngCore};
use trouble_host::prelude::*;

/// Max number of connections
const CONNECTIONS_MAX: usize = 1;

/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC

pub async fn run<C, RNG, const L2CAP_MTU: usize>(controller: C, random_generator: &mut RNG)
where
C: Controller,
RNG: RngCore + CryptoRng,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);

let mut resources: HostResources<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources)
.set_random_address(address)
.set_random_generator_seed(random_generator);

let Host {
mut central,
mut runner,
..
} = stack.build();

// NOTE: Modify this to match the address of the peripheral you want to connect to.
// Currently it matches the address used by the peripheral examples
let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);

let config = ConnectConfig {
connect_params: Default::default(),
scan_config: ScanConfig {
filter_accept_list: &[(target.kind, &target.addr)],
..Default::default()
},
};

info!("Scanning for peripheral...");
let _ = join(runner.run(), async {
info!("Connecting");

let conn = central.connect(&config).await.unwrap();
info!("Connected, creating gatt client");

#[cfg(feature = "security")]
{
if let Err(_error) = central.pairing(&conn).await {
error!("Pairing failed");
}
}

let client = GattClient::<C, 10, 24>::new(&stack, &conn).await.unwrap();

let _ = join(client.task(), async {
info!("Looking for battery service");
let services = client.services_by_uuid(&Uuid::new_short(0x180f)).await.unwrap();
let service = services.first().unwrap().clone();

info!("Looking for value handle");
let c: Characteristic<u8> = client
.characteristic_by_uuid(&service, &Uuid::new_short(0x2a19))
.await
.unwrap();

info!("Subscribing notifications");
let mut listener = client.subscribe(&c, false).await.unwrap();

let _ = join(
async {
loop {
let mut data = [0; 1];
client.read_characteristic(&c, &mut data[..]).await.unwrap();
info!("Read value: {}", data[0]);
Timer::after(Duration::from_secs(10)).await;
}
},
async {
loop {
let data = listener.next().await;
info!("Got notification: {:?} (val: {})", data.as_ref(), data.as_ref()[0]);
}
},
)
.await;
})
.await;
})
.await;
}
225 changes: 225 additions & 0 deletions examples/apps/src/ble_bas_peripheral_sec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use embassy_futures::join::join;
use embassy_futures::select::select;
use embassy_time::Timer;
use rand_core::{CryptoRng, RngCore};
use trouble_host::prelude::*;

/// Max number of connections
const CONNECTIONS_MAX: usize = 1;

/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att

// GATT Server definition
#[gatt_server]
struct Server {
battery_service: BatteryService,
}

/// Battery service
#[gatt_service(uuid = service::BATTERY)]
struct BatteryService {
/// Battery Level
#[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])]
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "hello", read, value = "Battery Level")]
#[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify, value = 10)]
level: u8,
#[characteristic(uuid = "408813df-5dd4-1f87-ec11-cdb001100000", write, read, notify)]
status: bool,
}

/// Run the BLE stack.
pub async fn run<C, RNG, const L2CAP_MTU: usize>(controller: C, random_generator: &mut RNG)
where
C: Controller,
RNG: RngCore + CryptoRng,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {}", address);

let mut resources: HostResources<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources)
.set_random_address(address)
.set_random_generator_seed(random_generator);
let Host {
mut peripheral, runner, ..
} = stack.build();

info!("Starting advertising and GATT service");
let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig {
name: "TrouBLE",
appearance: &appearance::power_device::GENERIC_POWER_DEVICE,
}))
.unwrap();

let _ = join(ble_task(runner), async {
loop {
match advertise("Trouble Example", &mut peripheral, &server).await {
Ok(conn) => {
// set up tasks when the connection is established to a central, so they don't run when no one is connected.
let a = gatt_events_task(&server, &conn);
let b = custom_task(&server, &conn, &stack);
// run until any task ends (usually because the connection has been closed),
// then return to advertising state.
select(a, b).await;
}
Err(e) => {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[adv] error: {:?}", e);
}
}
}
})
.await;
}

/// This is a background task that is required to run forever alongside any other BLE tasks.
///
/// ## Alternative
///
/// If you didn't require this to be generic for your application, you could statically spawn this with i.e.
///
/// ```rust,ignore
///
/// #[embassy_executor::task]
/// async fn ble_task(mut runner: Runner<'static, SoftdeviceController<'static>>) {
/// runner.run().await;
/// }
///
/// spawner.must_spawn(ble_task(runner));
/// ```
async fn ble_task<C: Controller>(mut runner: Runner<'_, C>) {
loop {
if let Err(e) = runner.run().await {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[ble_task] error: {:?}", e);
}
}
}

/// Stream Events until the connection closes.
///
/// This function will handle the GATT events and process them.
/// This is how we interact with read and write requests.
async fn gatt_events_task(server: &Server<'_>, conn: &GattConnection<'_, '_>) -> Result<(), Error> {
let level = server.battery_service.level;
loop {
match conn.next().await {
GattConnectionEvent::Disconnected { reason } => {
info!("[gatt] disconnected: {:?}", reason);
break;
}
GattConnectionEvent::Gatt { event } => match event {
Ok(event) => {
let result = match &event {
GattEvent::Read(event) => {
if event.handle() == level.handle {
let value = server.get(&level);
info!("[gatt] Read Event to Level Characteristic: {:?}", value);
}
#[cfg(feature = "security")]
if conn.raw().encrypted() {
None
} else {
Some(AttErrorCode::INSUFFICIENT_ENCRYPTION)
}
#[cfg(not(feature = "security"))]
None
}
GattEvent::Write(event) => {
if event.handle() == level.handle {
info!("[gatt] Write Event to Level Characteristic: {:?}", event.data());
}
#[cfg(feature = "security")]
if conn.raw().encrypted() {
None
} else {
Some(AttErrorCode::INSUFFICIENT_ENCRYPTION)
}
#[cfg(not(feature = "security"))]
None
}
};

// This step is also performed at drop(), but writing it explicitly is necessary
// in order to ensure reply is sent.
let result = if let Some(code) = result {
event.reject(code)
} else {
event.accept()
};
match result {
Ok(reply) => {
reply.send().await;
}
Err(e) => {
warn!("[gatt] error sending response: {:?}", e);
}
}
}
Err(e) => warn!("[gatt] error processing event: {:?}", e),
},
}
}
info!("[gatt] task finished");
Ok(())
}

/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect.
async fn advertise<'a, 'b, C: Controller>(
name: &'a str,
peripheral: &mut Peripheral<'a, C>,
server: &'b Server<'_>,
) -> Result<GattConnection<'a, 'b>, BleHostError<C::Error>> {
let mut advertiser_data = [0; 31];
AdStructure::encode_slice(
&[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[[0x0f, 0x18]]),
AdStructure::CompleteLocalName(name.as_bytes()),
],
&mut advertiser_data[..],
)?;
let advertiser = peripheral
.advertise(
&Default::default(),
Advertisement::ConnectableScannableUndirected {
adv_data: &advertiser_data[..],
scan_data: &[],
},
)
.await?;
info!("[adv] advertising");
let conn = advertiser.accept().await?.with_attribute_server(server)?;
info!("[adv] connection established");
Ok(conn)
}

/// Example task to use the BLE notifier interface.
/// This task will notify the connected central of a counter value every 2 seconds.
/// It will also read the RSSI value every 2 seconds.
/// and will stop when the connection is closed by the central or an error occurs.
async fn custom_task<C: Controller>(server: &Server<'_>, conn: &GattConnection<'_, '_>, stack: &Stack<'_, C>) {
let mut tick: u8 = 0;
let level = server.battery_service.level;
loop {
tick = tick.wrapping_add(1);
info!("[custom_task] notifying connection of tick {}", tick);
if level.notify(conn, &tick).await.is_err() {
info!("[custom_task] error notifying connection");
break;
};
// read RSSI (Received Signal Strength Indicator) of the connection.
if let Ok(rssi) = conn.raw().rssi(stack).await {
info!("[custom_task] RSSI: {:?}", rssi);
} else {
info!("[custom_task] error getting RSSI");
break;
};
Timer::after_secs(2).await;
}
}
5 changes: 3 additions & 2 deletions examples/apps/src/ble_scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ use trouble_host::prelude::*;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
const L2CAP_CHANNELS_MAX: usize = 1;
const L2CAP_MTU: usize = 27;

pub async fn run<C>(controller: C)
pub async fn run<C, const L2CAP_MTU: usize>(controller: C)
where
C: Controller + ControllerCmdSync<LeSetScanParams>,
{
Expand All @@ -20,8 +19,10 @@ where
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);

info!("Our address = {:?}", address);

let mut resources: HostResources<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);

let Host {
central, mut runner, ..
} = stack.build();
Expand Down
2 changes: 2 additions & 0 deletions examples/apps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ pub(crate) mod fmt;

pub mod ble_advertise_multiple;
pub mod ble_bas_central;
pub mod ble_bas_central_sec;
pub mod ble_bas_peripheral;
pub mod ble_bas_peripheral_sec;
pub mod ble_l2cap_central;
pub mod ble_l2cap_peripheral;
pub mod ble_scanner;
Loading

0 comments on commit bf8888d

Please sign in to comment.