Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Security manager take 2 #286

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading