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

Bluetooth: Host: Add API to clean BT settings #59821

Closed
wants to merge 2 commits into from

Conversation

theob-pro
Copy link
Contributor

See commits message.

Fixes #55475.

include/zephyr/bluetooth/settings.h Outdated Show resolved Hide resolved
subsys/bluetooth/host/settings.c Show resolved Hide resolved
@theob-pro theob-pro force-pushed the settings-cleanup branch 2 times, most recently from 14025a8 to 1af09fd Compare July 5, 2023 08:35
@jhedberg
Copy link
Member

jhedberg commented Jul 5, 2023

Shouldn't the Bluetooth subsystem do this automatically upon init? Introducing an explicit public API creates the need for applications to figure out when to call it, not to mention that the inconsistency in storage might not be the application's fault at all. It also seems arbitrary to leave mesh of the scope. What's the solution for inconsistent storage for mesh, or for non-Bluetooth related data for that matter?

@jori-nordic
Copy link
Collaborator

Shouldn't the Bluetooth subsystem do this automatically upon init? Introducing an explicit public API creates the need for applications to figure out when to call it, not to mention that the inconsistency in storage might not be the application's fault at all. It also seems arbitrary to leave mesh of the scope. What's the solution for inconsistent storage for mesh, or for non-Bluetooth related data for that matter?

The problem might be that this will take too much time - writing to flash - at an infortunate time. That's why we thought it'd be better for the app to decide when to do GC.

@theob-pro
Copy link
Contributor Author

It also seems arbitrary to leave mesh of the scope. What's the solution for inconsistent storage for mesh, or for non-Bluetooth related data for that matter?

I discussed it with @PavelVPV, they might do it differently with their own settings layer.

Copy link
Collaborator

@Thalley Thalley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good. One thing that I request changed is the break in the for loop, and then a few minor comments/suggestions

Comment on lines +17 to +22
* Example of possible leftover: An identity has been deleted, the corresponding
* address as been removed from 'bt/id' but before removing the corresponding
* key in 'bt/keys', the device has been powered off. Calling
* `bt_settings_cleanup` will ensure that the key is removed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have an additional function that verifies the settings to indicate whether calling this function will change anything?

Something along the lines of bt_settings_valid so that the use of this function will be

if (!bt_settings_valid()) {
    bt_settings_cleanup();
}

or some other way of doing a dry run? I could imagine that for some it may be interesting to see what is incorrect / will be cleaned up before this is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure of the use case. Maybe we could simply add LOG_WRN for each deleted setting?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure of the use case.

If I am a user and I have my settings stored in flash, I'd perhaps rather be aware of an issue in my settings before deleting them, rather than just silently deleting them, e.g. if I store my own settings and that I am foolish enough to store something under bt/id myself, then I'd rather become aware of that, rather than it just silently getting deleted by the stack

Comment on lines +22 to +26
* @warning User must not call this function if they use the 'bt/' namespace to
* store settings. All settings stored under 'bt/' namespace that are not
* defined by the Bluetooth stack will be deleted.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like something that should be placed other places as well.

We should perhaps generally recommend (if not already) that applications do not use the bt/ namespace for their BT settings?

Copy link
Collaborator

@MarekPieta MarekPieta Jul 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think that sharing a settings namespace between an application and any Zephyr subsystem is a bad idea (applications need to use their own namespaces). Maybe we could assume that user should not do it?

subsys/bluetooth/host/settings.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/settings.c Outdated Show resolved Hide resolved
tests/bsim/bluetooth/host/settings/cleanup/src/test.c Outdated Show resolved Hide resolved
LOG_ERR("Failed to read data (err %d)", len);
}

id_count = read_len / sizeof(id_addr[0]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could become alive :p
I suggest a BT_DBG("Read back %u identity addresses", id_count);.

@alwa-nordic
Copy link
Collaborator

The test fails if I add these:

	settings_save_one("bt/irk", &dummy_value, sizeof(dummy_value));
	settings_save_one("bt/irk/", &dummy_value, sizeof(dummy_value));
	settings_save_one("bt/irk/foo", &dummy_value, sizeof(dummy_value));

Should this be handled?

"mesh",
};

static bool mem_eq(const uint8_t *a1, size_t n1, const uint8_t *a2, size_t n2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could use create the is_supported_bt_key function instead (mem_eq is quite ambiguous name)?
That would also allow us to also limit scope of bt_settings_keys.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was actually thinking the same during my review

subsys/bluetooth/host/settings.c Outdated Show resolved Hide resolved
snprintk(full_key, sizeof(full_key), "bt/%s", key);
LOG_DBG("Key to delete: %s", full_key);

err = settings_delete(full_key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is deleting settings in the middle of load (exactly direct load) operation guaranteed to be safe?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems there are no guarantees, and it is the backend (ie nvs, file, etc) that does the iteration and callback invocation.
So yeah, to be on the safe side, we could store the full names somewhere (maybe a list of malloc'd strings) and do the deleting (and str freeing) after settings_load_subtree_direct("bt", bt_settings_load_direct_cleanup, &id_addr);.

int id_count;
bt_addr_le_t *id_addr = (bt_addr_le_t *)param;

read_len = read_cb(cb_arg, id_addr, CONFIG_BT_ID_MAX * sizeof(id_addr[0]));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check for number of stored identities? The settings content may not be aligned with this expectations (e.g. after a DFU)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm also concerned about that. A test could check the num of IDs increasing and decreasing. idk how settings work but with the current logic i'd think increasing would read back garbage and decreasing would/could not read back the right identities.

Although I think the stack could break down in other places if we tried to decrease the number of bonds. At the very least, test increasing the number.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could make use of len parameter of the direct load callback here? That could let you know the expected data length in advance.
btw. IIRC reading more bytes than available in the storage would simply lower the returned value of read_cb to indicate that not all bytes were actually read.

For details, see: https://docs.zephyrproject.org/apidoc/latest/group__settings.html#ga51cdac276c1fb8cd27fc3eec42749a04

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may also think about how to handle cases where the number of identities from settings readout does not match value defined in Kconfig.

subsys/bluetooth/host/settings.c Outdated Show resolved Hide resolved
return 0;
}

next_len = settings_name_next(next, &id_str);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should somehow check if given record is expected to have associated Bluetooth address and identity string (e.g. hash should not have it)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it should. That's way out of the scope of this PR, but storing the last hash seen by a given bond would simplify SC / caching handling quite a bit.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. The main drawback is the fact that storing hash per bond would require quite significant changes in the implementation

int err;
bt_addr_le_t id_addr[CONFIG_BT_ID_MAX] = {0};

err = settings_load_subtree_direct("bt/id", bt_settings_load_direct_id, (void *)id_addr);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw. Keep in mind that there may be another dependencies similar to this one. E.g. one may delete keys and leave CCC/CF/SC as a leftover data in storage. We may need to think about some more advanced way of handling settings dependencies at some point if we want to ensure fully proper implementation.

include/zephyr/bluetooth/settings.h Show resolved Hide resolved
subsys/bluetooth/host/settings.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/settings.c Outdated Show resolved Hide resolved
include/zephyr/bluetooth/settings.h Outdated Show resolved Hide resolved
include/zephyr/bluetooth/settings.h Outdated Show resolved Hide resolved
include/zephyr/bluetooth/settings.h Outdated Show resolved Hide resolved
int id_count;
bt_addr_le_t *id_addr = (bt_addr_le_t *)param;

read_len = read_cb(cb_arg, id_addr, CONFIG_BT_ID_MAX * sizeof(id_addr[0]));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm also concerned about that. A test could check the num of IDs increasing and decreasing. idk how settings work but with the current logic i'd think increasing would read back garbage and decreasing would/could not read back the right identities.

Although I think the stack could break down in other places if we tried to decrease the number of bonds. At the very least, test increasing the number.

{
ssize_t read_len;
int id_count;
bt_addr_le_t *id_addr = (bt_addr_le_t *)param;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/id_addr/id_addrs. I suggest the same for bt_settings_cleanup, this way it's unambiguous that this is an array of identity addresses and not a ptr to a single identity address.

id = strtoul(id_str, NULL, 10);

if (id >= CONFIG_BT_ID_MAX) {
LOG_ERR("Invalid local identity (%lu)", id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah i'd suggest a LOG_WRN("Invalid local identity: %lu > %lu", id, CONFIG_BT_ID_MAX);
I believe them's the guidelines: ERR for unrecoverable, WRN for recoverable/ignored.

subsys/bluetooth/host/settings.c Outdated Show resolved Hide resolved

/* At this point, we know that the key need to be cleared */
snprintk(full_key, sizeof(full_key), "bt/%s", key);
LOG_DBG("Key to delete: %s", full_key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest a "Deleting key: %s"

snprintk(full_key, sizeof(full_key), "bt/%s", key);
LOG_DBG("Key to delete: %s", full_key);

err = settings_delete(full_key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems there are no guarantees, and it is the backend (ie nvs, file, etc) that does the iteration and callback invocation.
So yeah, to be on the safe side, we could store the full names somewhere (maybe a list of malloc'd strings) and do the deleting (and str freeing) after settings_load_subtree_direct("bt", bt_settings_load_direct_cleanup, &id_addr);.

bt_settings_store_cf(2, &dummy_addr, &dummy_value, sizeof(dummy_value));
bt_settings_store_ccc(2, &dummy_addr, &dummy_value, sizeof(dummy_value));

settings_save_one("bt/mesh/dummy/key", &dummy_value, sizeof(dummy_value));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: @alwa-nordic 's comment: you could add a "bt/mes/somekey" setting for that.

Add a new function `bt_settings_cleanup` that allow users to clean
potential leftover present in the Bluetooth settings.

The function will look for every settings key under the 'bt/' namespace
and remove the one not listed in the `bt_settings_keys` list.

The settings that are linked to an ID that no longer exist will also be
deleted.

Signed-off-by: Théo Battrel <[email protected]>
Add a test for the new `bt_settings_cleanup` function.

The test artificially write settings then call `bt_settings_cleanup` and
check that the settings that should not have been deleted have not been.

Signed-off-by: Théo Battrel <[email protected]>
@theob-pro
Copy link
Contributor Author

This seems to be non-trivial to implement without a way to delete settings keys while loading them and trying to respect MISRA (ie no malloc).
This issue does not seem to be an actual problem for devices in the field (for now).

We are not happy with the current solution (w/ @alwa-nordic and @jori-nordic) so will close the PR.

@theob-pro theob-pro closed this Jul 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bluetooth: settings: Possible Bluetooth settings leftovers
7 participants