Bluetooth: Samples: Add Encrypted Advertising sample

Add a new sample that demonstrate the following points:

- Exchange of Key Material;
- Advertising encrypted data;
- Read advertising data;
- Decrypt advertising data that are encrypted.

Signed-off-by: Théo Battrel <theo.battrel@nordicsemi.no>
This commit is contained in:
Théo Battrel 2023-03-10 09:35:46 +01:00 committed by Carles Cufí
commit 12e9be7cc0
13 changed files with 1386 additions and 0 deletions

View file

@ -0,0 +1,90 @@
.. _bluetooth_encrypted_advertising_sample:
Bluetooth: Encrypted Advertising
################################
Overview
********
This sample demonstrates the use of the encrypted advertising feature, such as:
- the exchange of the session key and the initialization vector using the Key
Material characteristic,
- the encryption of advertising payloads,
- the decryption of those advertising payloads,
- and the update of the Randomizer field whenever the RPA is changed.
To use the `bt_ead_encrypt` and `bt_ead_decrypt` functions, you must enable
the Kconfig symbol :kconfig:option:`CONFIG_BT_EAD`.
While this sample uses extended advertising, it is **not** mandatory when using
the encrypted advertising. The feature can be used with legacy advertising as
well.
Requirements
************
* Two boards with Bluetooth Low Energy support
* Two boards with a push button connected via a GPIO pin, see :ref:`Button
sample <button-sample>` for more details
Building and Running
********************
This sample can be found under
:zephyr_file:`samples/bluetooth/encrypted_advertising` in the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.
This sample uses two applications, so two devices need to be setup.
Flash one device with the central application, and another device with the
peripheral application.
The two devices should automatically connect if they are close enough.
After the boards are connected, you will be asked to press the configured push
button on both boards to confirm the pairing request.
Here are the outputs you should get by default:
Peripheral:
.. code-block:: console
*** Booting Zephyr OS build zephyr-v3.3.0-1872-g6fac3c7581dc ***
<inf> ead_peripheral_sample: Advertising data size: 64
Passkey for 46:04:2E:6F:80:12 (random): 059306
Confirm passkey by pressing button at gpio@50000000 pin 11...
Passkey confirmed.
<inf> ead_peripheral_sample: Advertising data size: 64
Central:
.. code-block:: console
*** Booting Zephyr OS build zephyr-v3.3.0-1872-g6fac3c7581dc ***
Passkey for 6C:7F:67:C2:8B:29 (random): 059306
Confirm passkey by pressing button at gpio@50000000 pin 11...
Passkey confirmed.
<inf> ead_central_sample: Received data size: 64
<inf> ead_central_sample: len : 10
<inf> ead_central_sample: type: 0x09
<inf> ead_central_sample: data:
45 41 44 20 53 61 6d 70 6c 65 |EAD Samp le
<inf> ead_central_sample: len : 8
<inf> ead_central_sample: type: 0xff
<inf> ead_central_sample: data:
05 f1 5a 65 70 68 79 72 |..Zephyr
<inf> ead_central_sample: len : 7
<inf> ead_central_sample: type: 0xff
<inf> ead_central_sample: data:
05 f1 49 d2 f4 55 76 |..I..Uv
<inf> ead_central_sample: len : 4
<inf> ead_central_sample: type: 0xff
<inf> ead_central_sample: data:
05 f1 c1 25 |...%
<inf> ead_central_sample: len : 3
<inf> ead_central_sample: type: 0xff
<inf> ead_central_sample: data:
05 f1 17 |...

View file

@ -0,0 +1,16 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(central_ead)
target_sources(app PRIVATE
src/main.c
src/central_ead.c
)
zephyr_library_include_directories(
${ZEPHYR_BASE}/samples/bluetooth
${CMAKE_CURRENT_LIST_DIR}/../include
)

View file

@ -0,0 +1,17 @@
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_DEVICE_NAME="Central test EAD"
CONFIG_GPIO=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_SMP=y
CONFIG_BT_PRIVACY=y
CONFIG_BT_EAD=y
CONFIG_LOG=y
CONFIG_BT_EAD_LOG_LEVEL_INF=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX=64

View file

@ -0,0 +1,7 @@
sample:
name: Bluetooth Central Encrypted Advertising Data
tests:
sample.bluetooth.central_ead:
harness: bluetooth
platform_allow: nrf52840dk_nrf52840
tags: bluetooth

View file

@ -0,0 +1,533 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/printk.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/ead.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/bluetooth.h>
#include "common/bt_str.h"
#include "common.h"
LOG_MODULE_REGISTER(ead_central_sample, CONFIG_BT_EAD_LOG_LEVEL);
static uint8_t *received_data;
static size_t received_data_size;
static struct key_material keymat;
static bt_addr_le_t peer_addr;
static struct bt_conn *default_conn;
static struct bt_conn_cb central_cb;
static struct bt_conn_auth_cb central_auth_cb;
static struct k_poll_signal conn_signal;
static struct k_poll_signal sec_update_signal;
static struct k_poll_signal passkey_enter_signal;
static struct k_poll_signal device_found_cb_completed;
/* GATT Discover data */
static uint8_t gatt_disc_err;
static uint16_t gatt_disc_end_handle;
static uint16_t gatt_disc_start_handle;
static struct k_poll_signal gatt_disc_signal;
/* GATT Read data */
static uint8_t gatt_read_err;
static uint8_t *gatt_read_res;
static uint16_t gatt_read_len;
static uint16_t gatt_read_handle;
static struct k_poll_signal gatt_read_signal;
static bool data_parse_cb(struct bt_data *data, void *user_data)
{
size_t *parsed_data_size = (size_t *)user_data;
if (data->type == BT_DATA_ENCRYPTED_AD_DATA) {
int err;
struct net_buf_simple decrypted_buf;
size_t decrypted_data_size = BT_EAD_DECRYPTED_PAYLOAD_SIZE(data->data_len);
uint8_t decrypted_data[decrypted_data_size];
err = bt_ead_decrypt(keymat.session_key, keymat.iv, data->data, data->data_len,
decrypted_data);
if (err < 0) {
LOG_ERR("Error during decryption (err %d)", err);
}
net_buf_simple_init_with_data(&decrypted_buf, &decrypted_data[0],
decrypted_data_size);
bt_data_parse(&decrypted_buf, &data_parse_cb, user_data);
} else {
LOG_INF("len : %u", data->data_len);
LOG_INF("type: 0x%02x", data->type);
LOG_HEXDUMP_INF(data->data, data->data_len, "data:");
/* Copy the data out if we are running in a test context */
if (received_data != NULL) {
if (bt_data_get_len(data, 1) <=
(received_data_size - (*parsed_data_size))) {
*parsed_data_size +=
bt_data_serialize(data, &received_data[*parsed_data_size]);
}
} else {
*parsed_data_size += bt_data_get_len(data, 1);
}
}
return true;
}
static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
int err;
size_t parsed_data_size;
char addr_str[BT_ADDR_LE_STR_LEN];
if (default_conn) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
/* We are only interested in the previously connected device. */
if (!bt_addr_le_eq(addr, &peer_addr)) {
return;
}
LOG_DBG("Peer found.");
parsed_data_size = 0;
LOG_INF("Received data size: %zu", ad->len);
bt_data_parse(ad, data_parse_cb, &parsed_data_size);
LOG_DBG("All data parsed. (total size: %zu)", parsed_data_size);
err = bt_le_scan_stop();
if (err) {
LOG_DBG("Failed to stop scanner (err %d)", err);
return;
}
k_poll_signal_raise(&device_found_cb_completed, 0);
}
static void connect_device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
int err;
char addr_str[BT_ADDR_LE_STR_LEN];
if (default_conn) {
return;
}
/* Connect only to devices in close range */
if (rssi < -70) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
LOG_DBG("Device found: %s (RSSI %d)", addr_str, rssi);
err = bt_le_scan_stop();
if (err) {
LOG_DBG("Failed to stop scanner (err %d)", err);
return;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT,
&default_conn);
if (err) {
LOG_DBG("Failed to connect to %s (err %d)", addr_str, err);
return;
}
k_poll_signal_raise(&device_found_cb_completed, 0);
}
static int start_scan(bool connect)
{
int err;
k_poll_signal_reset(&conn_signal);
k_poll_signal_reset(&device_found_cb_completed);
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, connect ? connect_device_found : device_found);
if (err) {
LOG_DBG("Scanning failed to start (err %d)", err);
return -1;
}
LOG_DBG("Scanning started.");
if (connect) {
LOG_DBG("Waiting for connection");
await_signal(&conn_signal);
}
await_signal(&device_found_cb_completed);
return 0;
}
static uint8_t gatt_read_cb(struct bt_conn *conn, uint8_t att_err,
struct bt_gatt_read_params *params, const void *data, uint16_t read_len)
{
gatt_read_err = att_err;
gatt_read_len = read_len;
gatt_read_handle = params->by_uuid.start_handle;
if (!att_err) {
memcpy(gatt_read_res, data, read_len);
k_poll_signal_raise(&gatt_read_signal, 0);
} else {
LOG_ERR("ATT error (err %d)", att_err);
}
return BT_GATT_ITER_STOP;
}
static int gatt_read(struct bt_conn *conn, const struct bt_uuid *uuid, size_t read_size,
uint16_t start_handle, uint16_t end_handle, uint8_t *buf)
{
int err;
size_t offset;
uint16_t handle;
struct bt_gatt_read_params params;
gatt_read_res = &buf[0];
params.handle_count = 0;
params.by_uuid.start_handle = start_handle;
params.by_uuid.end_handle = end_handle;
params.by_uuid.uuid = uuid;
params.func = gatt_read_cb;
k_poll_signal_reset(&gatt_read_signal);
err = bt_gatt_read(conn, &params);
if (err) {
LOG_DBG("GATT read failed (err %d)", err);
return -1;
}
await_signal(&gatt_read_signal);
offset = gatt_read_len;
handle = gatt_read_handle;
while (offset < read_size) {
gatt_read_res = &buf[offset];
params.handle_count = 1;
params.single.handle = handle;
params.single.offset = offset;
k_poll_signal_reset(&gatt_read_signal);
err = bt_gatt_read(conn, &params);
if (err) {
LOG_DBG("GATT read failed (err %d)", err);
return -1;
}
await_signal(&gatt_read_signal);
offset += gatt_read_len;
}
return 0;
}
static uint8_t gatt_discover_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
gatt_disc_err = attr ? 0 : BT_ATT_ERR_ATTRIBUTE_NOT_FOUND;
if (attr) {
gatt_disc_start_handle = attr->handle;
gatt_disc_end_handle = ((struct bt_gatt_service_val *)attr->user_data)->end_handle;
}
k_poll_signal_raise(&gatt_disc_signal, 0);
return BT_GATT_ITER_STOP;
}
static int gatt_discover_primary_service(struct bt_conn *conn, const struct bt_uuid *service_type,
uint16_t *start_handle, uint16_t *end_handle)
{
int err;
struct bt_gatt_discover_params params;
params.type = BT_GATT_DISCOVER_PRIMARY;
params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
params.uuid = service_type;
params.func = gatt_discover_cb;
k_poll_signal_reset(&gatt_disc_signal);
err = bt_gatt_discover(conn, &params);
if (err) {
LOG_DBG("Primary service discover failed (err %d)", err);
return -1;
}
await_signal(&gatt_disc_signal);
*start_handle = gatt_disc_start_handle;
*end_handle = gatt_disc_end_handle;
return gatt_disc_err;
}
static void connected(struct bt_conn *conn, uint8_t conn_err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (conn_err) {
LOG_DBG("Failed to connect to %s (err %u)", addr, conn_err);
bt_conn_unref(default_conn);
default_conn = NULL;
(void)start_scan(true);
return;
}
LOG_DBG("Connected to: %s", addr);
k_poll_signal_raise(&conn_signal, 0);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("Disconnected: %s (reason 0x%02x)", addr, reason);
if (default_conn != conn) {
return;
}
bt_conn_unref(default_conn);
default_conn = NULL;
}
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (!err) {
LOG_DBG("Security changed: %s level %u", addr, level);
k_poll_signal_raise(&sec_update_signal, 0);
} else {
LOG_DBG("Security failed: %s level %u err %d", addr, level, err);
}
}
static void identity_resolved(struct bt_conn *conn, const bt_addr_le_t *rpa,
const bt_addr_le_t *identity)
{
char addr_identity[BT_ADDR_LE_STR_LEN];
char addr_rpa[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(identity, addr_identity, sizeof(addr_identity));
bt_addr_le_to_str(rpa, addr_rpa, sizeof(addr_rpa));
LOG_DBG("Identity resolved %s -> %s", addr_rpa, addr_identity);
bt_addr_le_copy(&peer_addr, identity);
}
static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey)
{
char passkey_str[7];
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
snprintk(passkey_str, ARRAY_SIZE(passkey_str), "%06u", passkey);
printk("Passkey for %s: %s\n", addr, passkey_str);
k_poll_signal_raise(&passkey_enter_signal, 0);
}
static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
{
char passkey_str[7];
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
snprintk(passkey_str, ARRAY_SIZE(passkey_str), "%06u", passkey);
LOG_DBG("Passkey for %s: %s", addr, passkey_str);
}
static void auth_cancel(struct bt_conn *conn)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("Pairing cancelled: %s", addr);
}
static int init_bt(void)
{
int err;
default_conn = NULL;
k_poll_signal_init(&conn_signal);
k_poll_signal_init(&passkey_enter_signal);
k_poll_signal_init(&sec_update_signal);
k_poll_signal_init(&gatt_disc_signal);
k_poll_signal_init(&gatt_read_signal);
k_poll_signal_init(&device_found_cb_completed);
err = bt_enable(NULL);
if (err) {
LOG_ERR("Bluetooth init failed (err %d)", err);
return -1;
}
LOG_DBG("Bluetooth initialized");
err = bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
if (err) {
LOG_ERR("Unpairing failed (err %d)", err);
}
central_cb.connected = connected;
central_cb.disconnected = disconnected;
central_cb.security_changed = security_changed;
central_cb.identity_resolved = identity_resolved;
bt_conn_cb_register(&central_cb);
central_auth_cb.pairing_confirm = NULL;
central_auth_cb.passkey_confirm = auth_passkey_confirm;
central_auth_cb.passkey_display = auth_passkey_display;
central_auth_cb.passkey_entry = NULL;
central_auth_cb.oob_data_request = NULL;
central_auth_cb.cancel = auth_cancel;
err = bt_conn_auth_cb_register(&central_auth_cb);
if (err) {
return -1;
}
return 0;
}
int run_central_sample(int get_passkey_confirmation(struct bt_conn *conn),
uint8_t *test_received_data, size_t test_received_data_size,
struct key_material *test_received_keymat)
{
int err;
bool connect;
uint16_t end_handle;
uint16_t start_handle;
if (test_received_data != NULL) {
received_data = test_received_data;
received_data_size = test_received_data_size;
}
/* Initialize Bluetooth and callbacks */
err = init_bt();
if (err) {
return -1;
}
/* Start scan and connect to our peripheral */
connect = true;
err = start_scan(connect);
if (err) {
return -2;
}
/* Update connection security level */
err = bt_conn_set_security(default_conn, BT_SECURITY_L4);
if (err) {
LOG_ERR("Failed to set security (err %d)", err);
return -3;
}
await_signal(&passkey_enter_signal);
err = get_passkey_confirmation(default_conn);
if (err) {
LOG_ERR("Security update failed");
return -4;
}
/* Locate the primary service */
err = gatt_discover_primary_service(default_conn, BT_UUID_CUSTOM_SERVICE, &start_handle,
&end_handle);
if (err) {
LOG_ERR("Service not found (err %d)", err);
return -5;
}
/* Read the Key Material characteristic */
err = gatt_read(default_conn, BT_UUID_GATT_EDKM, sizeof(keymat), start_handle, end_handle,
(uint8_t *)&keymat);
if (err) {
LOG_ERR("GATT read failed (err %d)", err);
return -6;
}
LOG_HEXDUMP_DBG(keymat.session_key, BT_EAD_KEY_SIZE, "Session Key");
LOG_HEXDUMP_DBG(keymat.iv, BT_EAD_IV_SIZE, "IV");
if (test_received_keymat != NULL) {
memcpy(test_received_keymat, &keymat, sizeof(keymat));
}
/* Start a new scan to get and decrypt the Advertising Data */
err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err) {
LOG_ERR("Failed to disconnect.");
return -7;
}
connect = false;
err = start_scan(connect);
if (err) {
return -2;
}
return 0;
}

View file

@ -0,0 +1,118 @@
/* main.c - Application main entry point */
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stddef.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/ead.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/logging/log.h>
#include "common.h"
LOG_MODULE_DECLARE(ead_central_sample, CONFIG_BT_EAD_LOG_LEVEL);
/*
* Get button configuration from the devicetree sw0 alias. This is mandatory.
*/
#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS(SW0_NODE, okay)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
static struct gpio_callback button_cb_data;
extern int run_central_sample(int get_passkey_confirmation(struct bt_conn *conn),
uint8_t *received_data, size_t received_data_size,
struct key_material *keymat);
static struct k_poll_signal button_pressed_signal;
static void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
LOG_DBG("Button pressed...");
k_poll_signal_raise(&button_pressed_signal, 0);
k_sleep(K_SECONDS(1));
k_poll_signal_reset(&button_pressed_signal);
}
static int get_passkey_confirmation(struct bt_conn *conn)
{
int err;
printk("Confirm passkey by pressing button at %s pin %d...\n", button.port->name,
button.pin);
await_signal(&button_pressed_signal);
err = bt_conn_auth_passkey_confirm(conn);
if (err) {
LOG_DBG("Failed to confirm passkey.");
return -1;
}
printk("Passkey confirmed.\n");
return 0;
}
static int setup_btn(void)
{
int ret;
k_poll_signal_init(&button_pressed_signal);
if (!gpio_is_ready_dt(&button)) {
LOG_ERR("Error: button device %s is not ready", button.port->name);
return -1;
}
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Error %d: failed to configure %s pin %d", ret, button.port->name,
button.pin);
return -1;
}
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
LOG_ERR("Error %d: failed to configure interrupt on %s pin %d", ret,
button.port->name, button.pin);
return -1;
}
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
gpio_add_callback(button.port, &button_cb_data);
LOG_DBG("Set up button at %s pin %d", button.port->name, button.pin);
return 0;
}
void main(void)
{
int err;
err = setup_btn();
if (err) {
return;
}
LOG_DBG("Starting central sample...");
(void)run_central_sample(get_passkey_confirmation, NULL, 0, NULL);
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/ead.h>
#ifndef __EAD_SAMPLE_COMMON_H
#define __EAD_SAMPLE_COMMON_H
struct key_material {
uint8_t session_key[BT_EAD_KEY_SIZE];
uint8_t iv[BT_EAD_IV_SIZE];
} __packed;
#define CUSTOM_SERVICE_TYPE BT_UUID_128_ENCODE(0x2e2b8dc3, 0x06e0, 0x4f93, 0x9bb2, 0x734091c356f0)
#define BT_UUID_CUSTOM_SERVICE BT_UUID_DECLARE_128(CUSTOM_SERVICE_TYPE)
static inline void await_signal(struct k_poll_signal *sig)
{
struct k_poll_event events[] = {
K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, sig),
};
k_poll(events, ARRAY_SIZE(events), K_FOREVER);
}
#endif /* __EAD_SAMPLE_COMMON_H */

View file

@ -0,0 +1,16 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(peripheral_ead)
target_sources(app PRIVATE
src/main.c
src/peripheral_ead.c
)
zephyr_library_include_directories(
${ZEPHYR_BASE}/samples/bluetooth
${CMAKE_CURRENT_LIST_DIR}/../include
)

View file

@ -0,0 +1,18 @@
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="Peripheral test EAD"
CONFIG_GPIO=y
CONFIG_BT_SMP=y
CONFIG_BT_PRIVACY=y
CONFIG_BT_RPA_TIMEOUT=300
CONFIG_BT_EXT_ADV=y
CONFIG_BT_EAD=y
CONFIG_LOG=y
CONFIG_BT_EAD_LOG_LEVEL_INF=y
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=64

View file

@ -0,0 +1,7 @@
sample:
name: Bluetooth Peripheral Encrypted Advertising Data
tests:
sample.bluetooth.peripheral_ead:
harness: bluetooth
platform_allow: nrf52840dk_nrf52840
tags: bluetooth

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/bluetooth.h>
#include "common.h"
#define LF_ID_MSB ((BT_COMP_ID_LF >> 8) & 0xff)
#define LF_ID_LSB ((BT_COMP_ID_LF) & 0xff)
#define AD_DATA_0_SIZE 10
static uint8_t ad_data_0[] = {'E', 'A', 'D', ' ', 'S', 'a', 'm', 'p', 'l', 'e'};
BUILD_ASSERT(sizeof(ad_data_0) == AD_DATA_0_SIZE);
#define AD_DATA_1_SIZE 8
static uint8_t ad_data_1[] = {LF_ID_MSB, LF_ID_LSB, 'Z', 'e', 'p', 'h', 'y', 'r'};
BUILD_ASSERT(sizeof(ad_data_1) == AD_DATA_1_SIZE);
#define AD_DATA_2_SIZE 7
static uint8_t ad_data_2[] = {LF_ID_MSB, LF_ID_LSB, 0x49, 0xd2, 0xf4, 0x55, 0x76};
BUILD_ASSERT(sizeof(ad_data_2) == AD_DATA_2_SIZE);
#define AD_DATA_3_SIZE 4
static uint8_t ad_data_3[] = {LF_ID_MSB, LF_ID_LSB, 0xc1, 0x25};
BUILD_ASSERT(sizeof(ad_data_3) == AD_DATA_3_SIZE);
#define AD_DATA_4_SIZE 3
static uint8_t ad_data_4[] = {LF_ID_MSB, LF_ID_LSB, 0x17};
BUILD_ASSERT(sizeof(ad_data_4) == AD_DATA_4_SIZE);
static const struct bt_data ad[] = {
BT_DATA(BT_DATA_NAME_COMPLETE, ad_data_0, sizeof(ad_data_0)),
BT_DATA(BT_DATA_MANUFACTURER_DATA, ad_data_1, sizeof(ad_data_1)),
BT_DATA(BT_DATA_MANUFACTURER_DATA, ad_data_2, sizeof(ad_data_2)),
BT_DATA(BT_DATA_MANUFACTURER_DATA, ad_data_3, sizeof(ad_data_3)),
BT_DATA(BT_DATA_MANUFACTURER_DATA, ad_data_4, sizeof(ad_data_4)),
};
static struct key_material mk = {
.session_key = {0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB,
0xCC, 0xCD, 0xCE, 0xCF},
.iv = {0xFB, 0x56, 0xE1, 0xDA, 0xDC, 0x7E, 0xAD, 0xF5},
};

View file

@ -0,0 +1,112 @@
/* main.c - Application main entry point */
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/logging/log.h>
#include "common.h"
LOG_MODULE_DECLARE(ead_peripheral_sample, CONFIG_BT_EAD_LOG_LEVEL);
/*
* Get button configuration from the devicetree sw0 alias. This is mandatory.
*/
#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS(SW0_NODE, okay)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
static struct gpio_callback button_cb_data;
extern int run_peripheral_sample(int get_passkey_confirmation(struct bt_conn *conn));
static struct k_poll_signal button_pressed_signal;
static void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
LOG_DBG("Button pressed...");
k_poll_signal_raise(&button_pressed_signal, 0);
k_sleep(K_SECONDS(1));
k_poll_signal_reset(&button_pressed_signal);
}
static int get_passkey_confirmation(struct bt_conn *conn)
{
int err;
printk("Confirm passkey by pressing button at %s pin %d...\n", button.port->name,
button.pin);
await_signal(&button_pressed_signal);
err = bt_conn_auth_passkey_confirm(conn);
if (err) {
LOG_DBG("Failed to confirm passkey.");
return -1;
}
printk("Passkey confirmed.\n");
return 0;
}
static int setup_btn(void)
{
int ret;
k_poll_signal_init(&button_pressed_signal);
if (!gpio_is_ready_dt(&button)) {
LOG_ERR("Error: button device %s is not ready", button.port->name);
return -1;
}
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Error %d: failed to configure %s pin %d", ret, button.port->name,
button.pin);
return -1;
}
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
LOG_ERR("Error %d: failed to configure interrupt on %s pin %d", ret,
button.port->name, button.pin);
return -1;
}
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
gpio_add_callback(button.port, &button_cb_data);
LOG_DBG("Set up button at %s pin %d", button.port->name, button.pin);
return 0;
}
void main(void)
{
int err;
err = setup_btn();
if (err) {
return;
}
LOG_DBG("Starting peripheral sample...");
(void)run_peripheral_sample(get_passkey_confirmation);
}

View file

@ -0,0 +1,369 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/ead.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/logging/log.h>
#include "data.h"
LOG_MODULE_REGISTER(ead_peripheral_sample, CONFIG_BT_EAD_LOG_LEVEL);
static struct bt_le_ext_adv_cb adv_cb;
static struct bt_conn_cb peripheral_cb;
static struct bt_conn_auth_cb peripheral_auth_cb;
static struct bt_conn *default_conn;
static struct k_poll_signal disconn_signal;
static struct k_poll_signal passkey_enter_signal;
static ssize_t read_key_material(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data,
sizeof(struct key_material));
}
BT_GATT_SERVICE_DEFINE(key_mat, BT_GATT_PRIMARY_SERVICE(BT_UUID_CUSTOM_SERVICE),
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_EDKM, BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_AUTHEN, read_key_material, NULL,
&mk));
static int update_ad_data(struct bt_le_ext_adv *adv)
{
int err;
size_t offset;
/* Encrypt ad structure 1 */
size_t size_ad_1 = BT_DATA_SERIALIZED_SIZE(ad[1].data_len);
uint8_t ad_1[size_ad_1];
size_t size_ead_1 = BT_EAD_ENCRYPTED_PAYLOAD_SIZE(size_ad_1);
uint8_t ead_1[size_ead_1];
bt_data_serialize(&ad[1], ad_1);
err = bt_ead_encrypt(mk.session_key, mk.iv, ad_1, size_ad_1, ead_1);
if (err != 0) {
LOG_ERR("Error during first encryption");
return -1;
}
/* Encrypt ad structures 3 and 4 */
size_t size_ad_3_4 =
BT_DATA_SERIALIZED_SIZE(ad[3].data_len) + BT_DATA_SERIALIZED_SIZE(ad[4].data_len);
uint8_t ad_3_4[size_ad_3_4];
size_t size_ead_2 = BT_EAD_ENCRYPTED_PAYLOAD_SIZE(size_ad_3_4);
uint8_t ead_2[size_ead_2];
offset = bt_data_serialize(&ad[3], &ad_3_4[0]);
bt_data_serialize(&ad[4], &ad_3_4[offset]);
err = bt_ead_encrypt(mk.session_key, mk.iv, ad_3_4, size_ad_3_4, ead_2);
if (err != 0) {
LOG_ERR("Error during second encryption");
return -1;
}
/* Concatenate and update the advertising data */
struct bt_data ad_structs[] = {
ad[0],
BT_DATA(BT_DATA_ENCRYPTED_AD_DATA, ead_1, size_ead_1),
ad[2],
BT_DATA(BT_DATA_ENCRYPTED_AD_DATA, ead_2, size_ead_2),
};
LOG_INF("Advertising data size: %zu", bt_data_get_len(ad_structs, ARRAY_SIZE(ad_structs)));
err = bt_le_ext_adv_set_data(adv, ad_structs, ARRAY_SIZE(ad_structs), NULL, 0);
if (err) {
LOG_ERR("Failed to set advertising data (%d)", err);
return -1;
}
LOG_DBG("Advertising Data Updated");
return 0;
}
static int set_ad_data(struct bt_le_ext_adv *adv)
{
return update_ad_data(adv);
}
static bool rpa_expired_cb(struct bt_le_ext_adv *adv)
{
LOG_DBG("RPA expired");
/* The Bluetooth Core Specification say that the Randomizer and thus the
* Advertising Data shall be updated each time the address is changed.
*
* ref:
* Supplement to the Bluetooth Core Specification | v11, Part A 1.23.4
*/
update_ad_data(adv);
return true;
}
static int create_adv(struct bt_le_ext_adv **adv)
{
int err;
struct bt_le_adv_param params;
memset(&params, 0, sizeof(struct bt_le_adv_param));
params.options |= BT_LE_ADV_OPT_CONNECTABLE;
params.options |= BT_LE_ADV_OPT_EXT_ADV;
params.id = BT_ID_DEFAULT;
params.sid = 0;
params.interval_min = BT_GAP_ADV_FAST_INT_MIN_2;
params.interval_max = BT_GAP_ADV_FAST_INT_MAX_2;
adv_cb.rpa_expired = rpa_expired_cb;
err = bt_le_ext_adv_create(&params, &adv_cb, adv);
if (err) {
LOG_ERR("Failed to create advertiser (%d)", err);
return -1;
}
return 0;
}
static int start_adv(struct bt_le_ext_adv *adv)
{
int err;
int32_t timeout = 0;
uint8_t num_events = 0;
struct bt_le_ext_adv_start_param start_params;
start_params.timeout = timeout;
start_params.num_events = num_events;
err = bt_le_ext_adv_start(adv, &start_params);
if (err) {
LOG_ERR("Failed to start advertiser (%d)", err);
return -1;
}
LOG_DBG("Advertiser started");
return 0;
}
static int stop_and_delete_adv(struct bt_le_ext_adv *adv)
{
int err;
err = bt_le_ext_adv_stop(adv);
if (err) {
LOG_ERR("Failed to stop advertiser (err %d)", err);
return -1;
}
err = bt_le_ext_adv_delete(adv);
if (err) {
LOG_ERR("Failed to delete advertiser (err %d)", err);
return -1;
}
return 0;
}
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err) {
LOG_ERR("Failed to connect to %s (%u)", addr, err);
return;
}
LOG_DBG("Connected to %s", addr);
default_conn = conn;
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("Disconnected from %s (reason 0x%02x)", addr, reason);
k_poll_signal_raise(&disconn_signal, 0);
}
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (!err) {
LOG_DBG("Security changed: %s level %u", addr, level);
} else {
LOG_DBG("Security failed: %s level %u err %d", addr, level, err);
}
}
static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey)
{
char passkey_str[7];
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
snprintk(passkey_str, ARRAY_SIZE(passkey_str), "%06u", passkey);
printk("Passkey for %s: %s\n", addr, passkey_str);
k_poll_signal_raise(&passkey_enter_signal, 0);
}
static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
{
char passkey_str[7];
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
snprintk(passkey_str, ARRAY_SIZE(passkey_str), "%06u", passkey);
LOG_DBG("Passkey for %s: %s", addr, passkey_str);
}
static void auth_cancel(struct bt_conn *conn)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("Pairing cancelled: %s", addr);
}
static int init_bt(void)
{
int err;
k_poll_signal_init(&disconn_signal);
k_poll_signal_init(&passkey_enter_signal);
err = bt_enable(NULL);
if (err) {
LOG_ERR("Bluetooth init failed (err %d)", err);
return -1;
}
LOG_DBG("Bluetooth initialized");
err = bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
if (err) {
LOG_ERR("Unpairing failed (err %d)", err);
}
peripheral_cb.connected = connected;
peripheral_cb.disconnected = disconnected;
peripheral_cb.security_changed = security_changed;
bt_conn_cb_register(&peripheral_cb);
peripheral_auth_cb.pairing_confirm = NULL;
peripheral_auth_cb.passkey_confirm = auth_passkey_confirm;
peripheral_auth_cb.passkey_display = auth_passkey_display;
peripheral_auth_cb.passkey_entry = NULL;
peripheral_auth_cb.oob_data_request = NULL;
peripheral_auth_cb.cancel = auth_cancel;
err = bt_conn_auth_cb_register(&peripheral_auth_cb);
if (err) {
LOG_ERR("Failed to register bt_conn_auth_cb (err %d)", err);
return -1;
}
return 0;
}
int run_peripheral_sample(int get_passkey_confirmation(struct bt_conn *conn))
{
int err;
struct bt_le_ext_adv *adv = NULL;
err = init_bt();
if (err) {
return -1;
}
/* Setup advertiser */
err = create_adv(&adv);
if (err) {
return -2;
}
err = start_adv(adv);
if (err) {
return -3;
}
err = set_ad_data(adv);
if (err) {
return -4;
}
/* Wait for the peer to update security */
await_signal(&passkey_enter_signal);
err = get_passkey_confirmation(default_conn);
if (err) {
LOG_ERR("Failure during security update");
return -5;
}
/* Wait for the peer to disconnect */
await_signal(&disconn_signal);
/* Restart advertising */
err = start_adv(adv);
if (err) {
return -3;
}
err = set_ad_data(adv);
if (err) {
return -4;
}
/* Wait 10s before stopping and deleting the advertiser */
k_sleep(K_SECONDS(10));
err = stop_and_delete_adv(adv);
if (err) {
return -6;
}
return 0;
}