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

Add support for secured central connections #230

Merged
merged 1 commit into from
Mar 18, 2024
Merged
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
8 changes: 8 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,18 @@ required-features = ["ble-gatt-server"]
name = "ble_bas_peripheral_notify"
required-features = ["ble-gatt-server"]

[[bin]]
name = "ble_bond_peripheral"
required-features = ["ble-gatt-server"]

[[bin]]
name = "ble_bas_central"
required-features = ["ble-gatt-client"]

[[bin]]
name = "ble_bond_central"
required-features = ["ble-gatt-client"]

[[bin]]
name = "ble_peripheral_onoff"
required-features = ["ble-gatt-server"]
Expand Down
243 changes: 243 additions & 0 deletions examples/src/bin/ble_bond_central.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
#![no_std]
#![no_main]

#[path = "../example_common.rs"]
mod example_common;

use core::cell::{Cell, RefCell};
use core::mem;

use defmt::{info, *};
use embassy_executor::Spawner;
use embassy_nrf::interrupt;
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::{with_timeout, Duration, Timer};
use nrf_softdevice::ble::security::{IoCapabilities, SecurityHandler};
use nrf_softdevice::ble::{
central, gatt_client, Address, AddressType, Connection, EncryptError, EncryptionInfo, IdentityKey, MasterId,
SecurityMode,
};
use nrf_softdevice::{raw, Softdevice};
use static_cell::StaticCell;

const PERIPHERAL_REQUESTS_SECURITY: bool = false;

#[embassy_executor::task]
async fn softdevice_task(sd: &'static Softdevice) -> ! {
sd.run().await
}

#[derive(Debug, Clone, Copy)]
struct Peer {
master_id: MasterId,
key: EncryptionInfo,
peer_id: IdentityKey,
}

pub struct Bonder {
peer: Cell<Option<Peer>>,
sys_attrs: RefCell<heapless::Vec<u8, 62>>,
secured: Signal<ThreadModeRawMutex, bool>,
}

impl Default for Bonder {
fn default() -> Self {
Bonder {
peer: Cell::new(None),
sys_attrs: Default::default(),
secured: Signal::new(),
}
}
}

impl SecurityHandler for Bonder {
fn io_capabilities(&self) -> IoCapabilities {
IoCapabilities::DisplayOnly
}

fn can_bond(&self, _conn: &Connection) -> bool {
true
}

fn display_passkey(&self, passkey: &[u8; 6]) {
info!("The passkey is \"{:a}\"", passkey)
}

fn on_bonded(&self, _conn: &Connection, master_id: MasterId, key: EncryptionInfo, peer_id: IdentityKey) {
debug!("storing bond for: id: {}, key: {}", master_id, key);

// In a real application you would want to signal another task to permanently store the keys in non-volatile memory here.
self.sys_attrs.borrow_mut().clear();
self.peer.set(Some(Peer {
master_id,
key,
peer_id,
}));
}

fn on_security_update(&self, _conn: &Connection, security_mode: SecurityMode) {
match security_mode {
SecurityMode::NoAccess | SecurityMode::Open => self.secured.signal(false),
_ => self.secured.signal(true),
}
}

fn save_sys_attrs(&self, _conn: &Connection) {
self.secured.signal(false);
}

fn get_key(&self, _conn: &Connection, master_id: MasterId) -> Option<EncryptionInfo> {
debug!("getting bond for: id: {}", master_id);

self.peer
.get()
.and_then(|peer| (master_id == peer.master_id).then_some(peer.key))
}

fn get_peripheral_key(&self, conn: &Connection) -> Option<(MasterId, EncryptionInfo)> {
self.peer.get().and_then(|peer| {
peer.peer_id
.is_match(conn.peer_address())
.then_some((peer.master_id, peer.key))
})
}
}

#[nrf_softdevice::gatt_client(uuid = "180f")]
struct BatteryServiceClient {
#[characteristic(uuid = "2a19", read, write, notify)]
battery_level: u8,
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
info!("Hello World!");

// First we get the peripherals access crate.
let mut config = embassy_nrf::config::Config::default();
config.gpiote_interrupt_priority = interrupt::Priority::P2;
config.time_interrupt_priority = interrupt::Priority::P2;
let _p = embassy_nrf::init(config);

let config = nrf_softdevice::Config {
clock: Some(raw::nrf_clock_lf_cfg_t {
source: raw::NRF_CLOCK_LF_SRC_RC as u8,
rc_ctiv: 16,
rc_temp_ctiv: 2,
accuracy: raw::NRF_CLOCK_LF_ACCURACY_500_PPM as u8,
}),
conn_gap: Some(raw::ble_gap_conn_cfg_t {
conn_count: 6,
event_length: 6,
}),
conn_gatt: Some(raw::ble_gatt_conn_cfg_t { att_mtu: 128 }),
gatts_attr_tab_size: Some(raw::ble_gatts_cfg_attr_tab_size_t {
attr_tab_size: raw::BLE_GATTS_ATTR_TAB_SIZE_DEFAULT,
}),
gap_role_count: Some(raw::ble_gap_cfg_role_count_t {
adv_set_count: 1,
periph_role_count: 3,
central_role_count: 3,
central_sec_count: 1,
_bitfield_1: raw::ble_gap_cfg_role_count_t::new_bitfield_1(0),
}),
gap_device_name: Some(raw::ble_gap_cfg_device_name_t {
p_value: b"HelloRust" as *const u8 as _,
current_len: 9,
max_len: 9,
write_perm: unsafe { mem::zeroed() },
_bitfield_1: raw::ble_gap_cfg_device_name_t::new_bitfield_1(raw::BLE_GATTS_VLOC_STACK as u8),
}),
..Default::default()
};

let sd = Softdevice::enable(&config);
unwrap!(spawner.spawn(softdevice_task(sd)));

static BONDER: StaticCell<Bonder> = StaticCell::new();
let bonder = BONDER.init(Bonder::default());

loop {
let addrs = &[&Address::new(
AddressType::RandomStatic,
[0x99, 0xbc, 0x25, 0x5d, 0x1e, 0xf7],
)];
let mut config = central::ConnectConfig::default();
config.scan_config.whitelist = Some(addrs);
info!("scanning");
bonder.secured.reset();
let conn = unwrap!(central::connect_with_security(sd, &config, bonder).await);
info!("connected");

info!("encrypting connection");
let secured = if PERIPHERAL_REQUESTS_SECURITY {
bonder.secured.wait().await
} else {
match conn.encrypt() {
Ok(()) => {
if bonder.secured.wait().await {
true
} else {
warn!("failed to encrypt connection with stored keys, requesting pairing");
if let Err(err) = conn.request_pairing() {
error!("failed to initiate pairing: {:?}", err);
continue;
}
bonder.secured.wait().await
}
}
Err(EncryptError::PeerKeysNotFound) => {
info!("peer keys not found, requesting pairing");
if let Err(err) = conn.request_pairing() {
error!("failed to initiate pairing: {:?}", err);
continue;
}
bonder.secured.wait().await
}
Err(err) => {
error!("failed to initiate encryption: {:?}", err);
continue;
}
}
};

if !secured {
error!("failed to create secure connection");
continue;
}

let client: BatteryServiceClient = unwrap!(gatt_client::discover(&conn).await);

// Read
let val = unwrap!(client.battery_level_read().await);
info!("read battery level: {}", val);

// Write, set it to 42
unwrap!(client.battery_level_write(&42).await);
info!("Wrote battery level!");

// Read to check it's changed
let val = unwrap!(client.battery_level_read().await);
info!("read battery level: {}", val);

// Enable battery level notifications from the peripheral
client.battery_level_cccd_write(true).await.unwrap();

// Receive notifications

let _ = with_timeout(
Duration::from_secs(30),
gatt_client::run(&conn, &client, |event| match event {
BatteryServiceClientEvent::BatteryLevelNotification(val) => {
info!("battery level notification: {}", val);
}
}),
)
.await;
let _ = conn.disconnect();

info!("Disconnected, waiting before reconnecting");
Timer::after_secs(10).await;
}
}
9 changes: 9 additions & 0 deletions examples/src/bin/ble_bond_peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use static_cell::StaticCell;
const BATTERY_SERVICE: Uuid = Uuid::new_16(0x180f);
const BATTERY_LEVEL: Uuid = Uuid::new_16(0x2a19);

const PERIPHERAL_REQUESTS_SECURITY: bool = false;

#[embassy_executor::task]
async fn softdevice_task(sd: &'static Softdevice) {
sd.run().await;
Expand Down Expand Up @@ -246,6 +248,13 @@ async fn main(spawner: Spawner) -> ! {

info!("advertising done!");

if PERIPHERAL_REQUESTS_SECURITY {
if let Err(err) = conn.request_security() {
error!("Security request failed: {:?}", err);
continue;
}
}

// Run the GATT server on the connection. This returns when the connection gets disconnected.
let e = gatt_server::run(&conn, &server, |_| {}).await;

Expand Down
25 changes: 22 additions & 3 deletions nrf-softdevice/src/ble/central.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use core::{mem, ptr};

use crate::ble::types::*;
use crate::ble::{Address, Connection};
use crate::ble::{Address, Connection, OutOfConnsError};
use crate::util::{get_union_field, OnDrop, Portal};
use crate::{raw, RawError, Softdevice};

Expand Down Expand Up @@ -39,8 +39,27 @@ impl From<RawError> for ConnectError {

pub(crate) static CONNECT_PORTAL: Portal<*const raw::ble_evt_t> = Portal::new();

pub async fn connect(sd: &Softdevice, config: &ConnectConfig<'_>) -> Result<Connection, ConnectError> {
connect_inner(sd, config, Connection::new).await
}

#[cfg(feature = "ble-sec")]
pub async fn connect_with_security(
sd: &Softdevice,
config: &ConnectConfig<'_>,
security_handler: &'static dyn crate::ble::security::SecurityHandler,
) -> Result<Connection, ConnectError> {
connect_inner(sd, config, |conn_handle, role, peer_address, conn_params| {
Connection::with_security_handler(conn_handle, role, peer_address, conn_params, security_handler)
})
.await
}

// Begins an ATT MTU exchange procedure, followed by a data length update request as necessary.
pub async fn connect(_sd: &Softdevice, config: &ConnectConfig<'_>) -> Result<Connection, ConnectError> {
async fn connect_inner<F>(_sd: &Softdevice, config: &ConnectConfig<'_>, new_conn: F) -> Result<Connection, ConnectError>
where
F: Fn(u16, Role, Address, raw::ble_gap_conn_params_t) -> Result<Connection, OutOfConnsError>,
{
if let Some(w) = config.scan_config.whitelist {
if w.len() == 0 {
return Err(ConnectError::NoAddresses);
Expand Down Expand Up @@ -78,7 +97,7 @@ pub async fn connect(_sd: &Softdevice, config: &ConnectConfig<'_>) -> Result<Con
let conn_params = params.conn_params;
debug!("connected role={:?} peer_addr={:?}", role, peer_address);

match Connection::new(conn_handle, role, peer_address, conn_params) {
match new_conn(conn_handle, role, peer_address, conn_params) {
Ok(conn) => {
#[cfg(any(feature = "s113", feature = "s132", feature = "s140"))]
crate::ble::gap::do_data_length_update(conn_handle, ptr::null());
Expand Down
Loading
Loading