Exercise 2

Implement bonding and a Filter Accept List

In this exercise, we will add storing keys to the application. Then we will use the stored keys to limit the access to only devices previously paired with the peripheral (your board). This is called an Filter Accept List. The peripheral processes only the connection requests from devices in the Filter Accept List. Requests from other devices will be ignored. This makes our device exclusively available to bonded devices.

Exercise steps

In the GitHub repository for this course, go to the base code for this exercise, found in lesson5/blefund_less5_exer2.

1. Add bonding support to the application

While walking through exercise 1, you may have noticed that if you disconnected your phone from the peripheral and then reconnected, you could re-encrypt the link without pairing again. But if we reset the device and tried to reconnect, we needed to pair again. This is because bonding is supported in SMP by default (through CONFIG_BT_BONDABLE).

However, key storing and restoring requires writing data to flash and restoring it from flash. So you will need to include the Bluetooth setting which handles flash, in your application, to be able to store and restore the bond information to flash, through CONFIG_BT_SETTINGS.

1.1 Add setting support in your application to store data in flash.

Enable CONFIG_BT_SETTINGS, and its dependency CONFIG_SETTINGS, to make sure the Bluetooth stack takes care of storing (and restoring) the pairing keys.

Add the following lines to the prj.conf file

CONFIG_SETTINGS=y
CONFIG_BT_SETTINGS=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y

1.2 Include the header file in main.c

Add the following line in main.c

#include <zephyr/settings/settings.h>

1.3 Call settings_load() after initializing Bluetooth so that previous bonds can be restored.

As per the documentation for CONFIG_BT_SETTINGS, we must call the API function settings_load() manually to be able to store the pairing keys and configuration persistently.

settings_load() signature

Add the following line in main.c

settings_load();

1.4 Build and flash the application to your board.

1.5. Verify that after you pair your phone with the device, you can reset the hardware and can connect to the same phone again without having to pair again. This means the information was stored during the first pairing, i.e the devices were bonded.

2. Delete the stored bond

Our application can now store bond information. But it should be able to remove the stored bond information when needed as well. We can do this by calling bt_unpair(), which has the following signature.

bt_unpair() signature

2.1 Add an extra button handling to remove bond information. In this case, we will use button 2.

Add the following line in main.c

#define BOND_DELETE_BUTTON             DK_BTN2_MSK

2.2 Call bt_unpair() to erase all bonded devices

We want to erase all bonded devices whenever button 2 is pressed, by calling bt_unpair() with BT_ADDR_LE_ANY as the address. This will erase all bonded devices. If you want to erase one single device in the bond list, you would need to input the address of the device you want to delete.

Add the following code in the button_changed() function in main.c

if (has_changed & BOND_DELETE_BUTTON) {
	uint32_t bond_delete_button_state = button_state & BOND_DELETE_BUTTON;
	if (bond_delete_button_state==0) {
		int err= bt_unpair(BT_ID_DEFAULT,BT_ADDR_LE_ANY);
		if (err) {
			LOG_INF("Cannot delete bond (err: %d)n", err);
		} else	{
			LOG_INF("Bond deleted succesfully n");
		}				
	}
}

2.3 Build and flash the application to your board.

2.4. Connect your smart phone to the device then disconnect and press button 2 to erase all bonded devices. If you try connecting again, you will notice that the phone can’t re-encryp the link. This usually results in the connection being dropped. You would need to remove the bond on the phone (in Bluetooth settings -> Forget this device) before you connect again to be able to make a new pairing.

3. Add a Filter Accept List to the application.

Now that we have the bond information stored in flash, we will use to to create a Filter Accept List that will only allow the devices on this list to connect to the peripheral.

3.1 Enable support for Filter Accept List and Privacy Features.

Let’s enable the Filter Accept List API (CONFIG_BT_FILTER_ACCEPT_LIST) and the Private Feature support, which makes it possible to generate and use Resolvable Private Addresses (CONFIG_BT_PRIVACY).

Add the following lines in the prj.conf file

CONFIG_BT_FILTER_ACCEPT_LIST=y
CONFIG_BT_PRIVACY=y

3.2 Add new advertising parameters based on the Filter Accept List.

We want our advertising packet to depend on whether or not we are using the Filter Accept List. Let’s create two different ones using the BT_LE_ADV_PARAM helper macro that we used in Lesson 2.

BT_LE_ADV_PARAM() helper macro

3.2.1 Define the advertising parameter BT_LE_ADV_CONN_NO_ACCEPT_LIST for when the Filter Accept List is not used

For options, we want the advertising to be connectable by using BT_LE_ADV_OPT_CONNECTABLE. We also want to use BT_LE_ADV_OPT_ONE_TIME, so the device will not automatically advertise after disconnecting. So that we can do advertise with the new Filter Accept List after it’s bonded to the first device.

#define BT_LE_ADV_CONN_NO_ACCEPT_LIST  BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE|BT_LE_ADV_OPT_ONE_TIME, 
				       BT_GAP_ADV_FAST_INT_MIN_2, 
				       BT_GAP_ADV_FAST_INT_MAX_2, NULL)

3.2.2 Define the advertising parameter BT_LE_ADV_CONN_ACCEPT_LIST for when the Filter Accept List is used

This will be similar to the previous one. However, we will use BT_LE_ADV_OPT_FILTER_CONN to filter out the connection requests from devices not in the list.

#define BT_LE_ADV_CONN_ACCEPT_LIST BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE|BT_LE_ADV_OPT_FILTER_CONN|BT_LE_ADV_OPT_ONE_TIME, 
				       BT_GAP_ADV_FAST_INT_MIN_2, 
				       BT_GAP_ADV_FAST_INT_MAX_2, NULL)

3.3 Define the function setup_accept_list() to loop through the bond list and add the addresses to the Filter Accept List

3.3.1 Define the callback setup_accept_list_cb to add an address to the Filter Accept List.

Define the callback function that will be called for every iteration of the bond list to add the peer address to the Filter Accept List, using bt_le_filter_accept_list_add(), which has the following signature

bt_le_filter_accept_list_add() signature

Add the following code snippet in main.c

static void setup_accept_list_cb(const struct bt_bond_info *info, void *user_data)
{
	int *bond_cnt = user_data;
	if ((*bond_cnt) < 0) {
		return;
	}
	int err = bt_le_filter_accept_list_add(&info->addr);
	printk("Added following peer to whitelist: %x %x n",info->addr.a.val[0],info->addr.a.val[1]);
	if (err) {
		printk("Cannot add peer to Filter Accept List (err: %d)n", err);
		(*bond_cnt) = -EIO;
	} else {
		(*bond_cnt)++;
	}
}

3.3.2 Define a function to iterate through all existing bonds, using bt_foreach_bond() and then call setup_accept_list_cb()

We can use the function bt_foreach_bond() to iterate through all existing bonds.

bt_foreach_bond() signature

For each bond, we will call setup_accept_list_cb() to add the peer address into the Filter Accept List.

Add the following function in main.c

static int setup_accept_list(uint8_t local_id)
{
	int err = bt_le_filter_accept_list_clear();
	if (err) {
		printk("Cannot clear Filter Accept List (err: %d)n", err);
		return err;
	}
	int bond_cnt = 0;
	bt_foreach_bond(local_id, setup_accept_list_cb, &bond_cnt);
	return bond_cnt;
}

3.4.1 Define the function advertise_with_acceptlist() to begin advertising with the Filter Accept List.

The next step is to make a work queue function that will call setup_accept_list() to build the Filter Accept List, and then to start advertising with the Filter Accept List (if it is non-empty), otherwise, advertise with no Filter Accept List.

In this function, we will advertise with BT_LE_ADV_CONN_NO_ACCEPT_LIST if the list is empty. This will do open advertising, but the advertising will not automatically restart when disconnected as explained earlier at step 3.2.

This way we will be able to change advertising setting to use Filter Accept List when disconnected.
Notice how it’s defined as a work queue thread instead of a normal function. The reason for it is explained at Step 3.5.

Add the following code in main.c

void advertise_with_acceptlist(struct k_work *work)
{
	int err=0;
	int allowed_cnt= setup_accept_list(BT_ID_DEFAULT);
	if (allowed_cnt<0){
		printk("Acceptlist setup failed (err:%d)n", allowed_cnt);
	} else {
		if (allowed_cnt==0){
			printk("Advertising with no Filter Accept listn"); 
			err = bt_le_adv_start(BT_LE_ADV_CONN_NO_ACCEPT_LIST, ad, ARRAY_SIZE(ad),
					sd, ARRAY_SIZE(sd));
		}
		else {
			printk("Acceptlist setup number  = %d n",allowed_cnt);
			err = bt_le_adv_start(BT_LE_ADV_CONN_ACCEPT_LIST, ad, ARRAY_SIZE(ad),
				sd, ARRAY_SIZE(sd));	
		}
		if (err) {
		 	printk("Advertising failed to start (err %d)n", err);
			return;
		}
		printk("Advertising successfully startedn");
	}
}
K_WORK_DEFINE(advertise_acceptlist_work, advertise_with_acceptlist);

3.4.2 Submit this work queue in main, right before the original advertising code

k_work_submit(&advertise_acceptlist_work);

3.4.3 Remove the original advertising code that advertises without using Filter Accept List

	 err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
	 		      sd, ARRAY_SIZE(sd));
	 if (err) {
	 	printk("Advertising failed to start (err %d)n", err);
	 	return;
	}

3.5 Submit the same work queue in the on_disconnected() callback

Submit the work queue for advertise_acceptlist() in the callback for a disconnected event. So if there is any new bond we will have it updated to the Filter Accept List upon disconnection.

k_work_submit(&advertise_acceptlist_work);

We submit a work item here because if we call bt_le_adv_start() directly in callback we will receive an -ENOMEM error (-12). This is because in the disconnected() callback, the connection object is still reserved for the connection that was just terminated. If we start advertising here, we will need to increase the maximum number of connections (CONFIG_BT_MAX_CONN) by one., in our case, it would need to be two. In order to save on RAM, we submit a work item using k_work_submit that will delay starting advertising until after the callback has returned.

3.6 Verify if the Filter Accept List works as intended.

To perform this step, you will need an extra phone or a central device to verify if the Filter Accept List works or not. We will start by erasing the bond information on both the Nordic hardware and your phone that has previously been connected to the device. Next, connect and bond the device with your phone, as we have done previously, and make sure you can control the LED from your phone. The disconnect.

Now use another central device to connect to the Nordic device. If the Filter Accept List works as it should, you will not be able to connect with this new central device. Now use the original phone that you have bonded to the Nordic deviceand you should be able to connect.

This shows that the Filter Accept List now works as expected. The device only accepts connections from the previously bonded phone.

4. Add “Pairing mode” to allow new devices to be added to the Filter Accept List

The code we have built so far, only allows a single device to be added to the Filter Accept List.

To allow new devices to connect and bond, we need to add an option to do “open advertising”, often called “Pairing mode”. We will use a button press to enable “Pairing mode”.

4.1 Increase the number of maximum paired devices

Increase the number of paired devices allowed at one time, using CONFIG_BT_MAX_PAIRED, which has a default value of 1.

Let’s increase this to 5, by adding the following line in the prj.conf file

CONFIG_BT_MAX_PAIRED=5

4.2.1 Add the button handling code to enable pairing mode.

Define a new button for “pairing mode”. We will use button 3.

#define PAIRING_BUTTON             DK_BTN3_MSK

4.2.2 Add the following code to button_changed() callback.

When button 3 is pressed, we want to stop advertising, clear the Filter Accept List and start to advertise with the advertising parameters we made for the case with no Filter Accept List.

Add the following code in main.c

if (has_changed & PAIRING_BUTTON) {
	uint32_t pairing_button_state = button_state & PAIRING_BUTTON;
	if (pairing_button_state==0) {
		int err_code = bt_le_adv_stop();
		if (err_code) {
			printk("Cannot stop advertising err= %d n", err_code);
			return;
		}
		err_code = bt_le_filter_accept_list_clear();
		if (err_code) {
			printk("Cannot clear accept list (err: %d)n", err_code);
		} else	{
			printk("Filter Accept List cleared succesfully");
		}				
		err_code = bt_le_adv_start(BT_LE_ADV_CONN_NO_ACCEPT_LIST, ad, ARRAY_SIZE(ad),	sd, ARRAY_SIZE(sd));
		if (err_code) {
			printk("Cannot start open advertising (err: %d)n", err_code);
		} else	{
			printk("Advertising in pairing mode started");
		}	
	}	
}

Note that if you want to advertise with an Filter Accept List again you have two options: either reset the device or make a new bond then disconnect.

Note

The button should only be pressed when the device is advertising and not connected to a peer. It is possible to set a flag, so that if the device is in a connection it will return to open advertising once it is disconnected. But this is not covered in the scope of this exercise.

4.3 Verify the pairing mode

You will need an extra phone or a central device to perform this step. Start by erasing the bond information on the Nordic hardware and your phone that was previously connected to the device. Next, connect and bond the device with your phone, as we have done previously, and ensure you can control the LED from your phone. Then disconnect.

Now use another central device to try and connect to the Nordic device. Just like we saw previously, you should not be able to connect.

Now press button 3 to enable “Pairing mode”. When in “Pairing mode”, try to use the second phone to connect again. This time it should be able to connect, and you can perform pairing with this phone. After this, your Filter Accept List will contain 2 devices.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.