-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Bluetooth: Host: Reassemble extended advertising reports #39352
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: Reassemble extended advertising reports #39352
Conversation
The host reassembles fragmented advertising reports from the controller. Fragmented advertising reports from different advertisers may be interleaved. If fragmented advertising reports from multiple advertisers are received, only the first one is kept and reassembled. The rest are discarded until the first one is complete. If a complete advertising report is not reveived from an advertiser within the timeout, the data is discarded and another advertiser is allowed to be reassembled. Signed-off-by: Herman Berget <herman.berget@nordicsemi.no>
@sjanc , FYI |
"A solution in the spec is to add another bit to the Event_Type/Data status field which tells whether the advertising report is part of a chain of fragmented advertising reports or if it is complete by its own." Data status: Why is this not enough? |
If we get an adv report with data_status=complete, there is no way to know if this is a non-fragmented adv report and this is all the data. If this is all the data, we can create an event. If we previously have discarded data from this advertiser we can not create an event because the data would be incomplete. |
I'd assume that if you discard event with "0b01 = Incomplete, more data to come" then you discard all until you get either complete or truncated status |
I think you would still have the same problem. If you discard until you receive complete or truncated reports, you may have discarded the start of advertising data from other advertisers. I am thinking of a scenario like this, where fragmented adv reports from multiple advertisers (A and B) are interleaved:
|
You have advertiser address and SID in report to handle matching, haven't you? |
Or even:
I see no way to disambiguate between C1 and B2 without knowing that B1 has been discarded. |
Yes |
The issue with anonymous advertisers is not resolved. That needs to be discussed. As mentioned above I would also like to keep a simpler solution for use with controllers that do no interleave advertising reports from different advertisers. A Kconfig for that is not implemented yet. But please do comment on any issues in the current implementation you see. |
@@ -27,6 +31,105 @@ static bt_le_scan_cb_t *scan_dev_found_cb; | |||
static sys_slist_t scan_cbs = SYS_SLIST_STATIC_INIT(&scan_cbs); | |||
|
|||
#if defined(CONFIG_BT_EXT_ADV) | |||
/* A buffer pool used to reassemble advertisement data from the controller. */ | |||
NET_BUF_POOL_FIXED_DEFINE(ext_scan_buf_pool, 1, CONFIG_BT_EXT_SCAN_BUF_SIZE, NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only one chain is expected to be reassembled at time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's OK to start with I reckon, but we should support multiple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the relationsship between this and the fragmented_advertiser_heap
?
So you are saying that if a device has 2 advertisement sets, S1 and S2, that sends data D1 and D2, where S1 sends D1 in multiple segments D1_1, D1_2, etc., we can't know to tell them apart? I think @sjanc already mentioned this, but the advertising set ID would tell them apart, so I don't think this would be an issue.
That is true, and it looks like it isn't something that is intended we can do. From the score spec:
Which I read as we should treat all adv reports from anonymous devices as a single device. If two anonymous devices advertise segmented data at once, it will very often be gibberish unfortunately. I guess this is a known limitation in BLE (we could add an erratum asking for a recommendation not to use segmented adv data with the anonymous address). |
@@ -27,6 +31,105 @@ static bt_le_scan_cb_t *scan_dev_found_cb; | |||
static sys_slist_t scan_cbs = SYS_SLIST_STATIC_INIT(&scan_cbs); | |||
|
|||
#if defined(CONFIG_BT_EXT_ADV) | |||
/* A buffer pool used to reassemble advertisement data from the controller. */ | |||
NET_BUF_POOL_FIXED_DEFINE(ext_scan_buf_pool, 1, CONFIG_BT_EXT_SCAN_BUF_SIZE, NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's OK to start with I reckon, but we should support multiple.
config BT_EXT_SCAN_BUF_SIZE | ||
int "Maximum advertisement report size" | ||
range 1 1650 | ||
default 255 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest a default of 229 which is the maximum data length of a single report.
We have the sid, so we can tell them apart. The problem is with interleaving fragmented reports from multiple advertiser sets (either from the same device or different devices) as in the example in my comment above.
Yes, I guess the application should discard the first report from each advertising set/device as it may have never received the first fragment(s) and the data could be incomplete.
This text is from the LL spec, so I am not sure it applies to the host. Although it would make sense. In addition the spec says right above that:
Which implies that there may also be a situation where the controller does not do this deduplication. |
I feel like I'm missing some crucial amount information. In your example, I assume A, B and C are advertising sets For the sake of reassembly, the pair of {address, sid} is unique (except for anonymous advertising), so it should be possibly to perform reassembly, even with multiple sets interleaved (assuming that the reassembly buffers supports reassembly of multiple advertising data). |
Yes, agree. However, we need to know if we previously have discarded any reports from this addr-sid pair. If we have, we must discard all further reports from this pair until a report with data_status=complete is received. If we reassemble reports for an addr-sid pair for which we have previously discarded reports from, the data would be incomplete. Imagine two different series of adv reports received from the controller. Here A, B and C are different addr-sir pairs. First example:
Second example:
In these examples B2 and C1 may have the exact same data. The only difference is the addr-sid pair. In the first example we have discarded data from B, so we cannot use the rest of the data until it sends a report with data_status=complete. In the second example, we have not discarded any data from C, so we should use the data and create an event. |
Thanks for the clarification - I think I understand your concerns now.
Given the lack of a "start" event type, can we ever guarantee that the reassembled data is correctly assembled? Is there any scenario where we would not receive e.g. the first segment from the LL, but only the subsequent ones? e.g. if a device sends data in 3 segments D1, D2 and D3; can we in the host ever just receive D2 and D3? In any case, in order to handle the previously discarded issue, I guess we need to have a discarded list as well, right? Alternatively we simply discard any events, even complete ones, if it is not in the reassembly buffer. This is perhaps not the best solution (especially if we only support a single reassembly buffer), but adv reports are considered discardable in Zephyr, and any host discarding would only be in the case that we are reassembling another PDU. This feature is definitely going to be memory demanding, but that's not surprising. |
I think the controller knows whether a PDU is the first in the chain or not, so it should never send adv reports starting from the middle of the chain. I don't know how likely it is to lose reports over HCI.
I think the current list does that job, unless i misunderstand something. If an advertiser is in the list and we should not keep its reports, we have previously received a report from that advertiser and discarded it. |
That should never happen
Does the current implementation support the scenario you created in #39352 (comment)? |
adv_info->interval = sys_le16_to_cpu(evt->interval); | ||
adv_info->adv_type = get_adv_type(evt->evt_type); | ||
/* Convert "Legacy" property to Extended property. */ | ||
adv_info->adv_props = evt->evt_type ^ BT_HCI_LE_ADV_PROP_LEGACY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recognize that this is moved code. But can someone check this line for me? This converts BT_HCI_LE_ADV_PROP_
to BT_GAP_ADV_PROP_
. But there is a mismatch in meaning between these bitfields on BIT(3), BIT(5) and BIT(6). Is this safe?
We should consider making the conversion explicit:
adv_info->adv_props = evt->evt_type ^ BT_HCI_LE_ADV_PROP_LEGACY; | |
adv_info->adv_props = (((evt->evt_type & BT_HCI_LE_ADV_PROP_CONN) ? | |
BT_GAP_ADV_PROP_CONNECTABLE : 0) | |
& ((evt->evt_type & BT_HCI_LE_ADV_PROP_SCAN) ? | |
BT_GAP_ADV_PROP_SCANNABLE : 0) | |
& (!(evt->evt_type & BT_HCI_LE_ADV_PROP_LEGACY) ? | |
BT_GAP_ADV_PROP_EXT_ADV : 0)); |
Looks to me that it is handled correctly so long as It might be fine to pass on some corrupted advertisements. So long as they are rare. A real world application should anyway be resistant to adversarial advertisers that try to disrupt the application by sending bad advertisements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we
subsys/bluetooth/host/scan.c
Outdated
*/ | ||
static sys_dnode_t *add_frag_advertiser(const bt_addr_le_t *addr, uint8_t sid) | ||
{ | ||
struct fragmented_advertiser *frag_adv = k_malloc(sizeof(struct fragmented_advertiser)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is no way to know how many advertisers we will see
Advertisers seen should be limited using a Kconfig, old advertising reports that have been reassembled before can be replaced with newly seen advertisers. Use of k_malloc
will have non-deterministic application behavior based on use of allocations across the application (outside Bluetooth subsystem).
/* Get a pointer to the fragmented advertiser the dnode pointer points to */ | ||
#define FRAG_ADVERTISER_FROM_DNODE(dnode_ptr) \ | ||
((struct fragmented_advertiser *)((dnode_ptr)-offsetof(struct fragmented_advertiser, node))) | ||
#define FRAGMENTED_ADVERTISER_TIMEOUT_MS 10000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a Kconfig and the range should be as per the maximum auxiliary offset possible. I am not very familiar with the current implementation in the PR, but I would expect, that a fragment that does not received the next report within the timeout will be given to the application as truncated data, right?
subsys/bluetooth/host/scan.c
Outdated
/* We have truncated the data and the controller will provide more data. | ||
* Therefore we must discard the remaining part of the advertisement. | ||
*/ | ||
FRAG_ADVERTISER_FROM_DNODE(current_advertiser)->should_keep_reports = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Life of current_advertiser
in case of timeout and truncated, to be able to correctly handle reports continue to come from the controller for the host timed out and host truncated, is current_advertiser
valid until controller returns truncated or incomplete?
Misc. style changes from PR comments. Signed-off-by: Berget, Herman <herman.berget@nordicsemi.no>
Make the fragmented_advertiser functions take and return `struct fragmented_advertiser *` instead of `sys_dnode_t *`. Signed-off-by: Berget, Herman <herman.berget@nordicsemi.no>
Make `struct fragmented_advertiser` have a pointer to the reassembly buffer. Currently only pointing to the global `ext_scan_buf` if it has a buffer. Makes it easier to add support for multiple reassembly buffers. Signed-off-by: Herman Berget <herman.berget@nordicsemi.no>
Use a local heap instead of the global one. The size is configured by a Kconfig. Signed-off-by: Herman Berget <herman.berget@nordicsemi.no>
Remove debug logging. Signed-off-by: Herman Berget <herman.berget@nordicsemi.no>
@@ -125,6 +125,12 @@ enum { | |||
BT_GAP_ADV_PROP_SCAN_RESPONSE = BIT(3), | |||
/** Extended advertising. */ | |||
BT_GAP_ADV_PROP_EXT_ADV = BIT(4), | |||
/** The reported data is a truncated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** The reported data is a truncated. | |
/** @brief The reported data is a truncated. |
range 1 1650 | ||
default 255 | ||
help | ||
Maximum size of an advertisement report. If the advertisement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maximum size of an advertisement report. If the advertisement | |
Maximum size of an advertisement report in octets. If the advertisement |
provided by the controller is larger than this buffer size, | ||
the remaining data will be discarded. | ||
|
||
config BT_EXT_SCAN_MAX_FRAGMENTED_ADVERTISERS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
config BT_EXT_SCAN_MAX_FRAGMENTED_ADVERTISERS | |
config BT_EXT_SCAN_MAX_REASSEMBLY_BUFFERS |
The term "fragmented advertiser" isn't particularly clear, and may indicate that this is related to the advertiser role.
@@ -27,6 +31,105 @@ static bt_le_scan_cb_t *scan_dev_found_cb; | |||
static sys_slist_t scan_cbs = SYS_SLIST_STATIC_INIT(&scan_cbs); | |||
|
|||
#if defined(CONFIG_BT_EXT_ADV) | |||
/* A buffer pool used to reassemble advertisement data from the controller. */ | |||
NET_BUF_POOL_FIXED_DEFINE(ext_scan_buf_pool, 1, CONFIG_BT_EXT_SCAN_BUF_SIZE, NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the relationsship between this and the fragmented_advertiser_heap
?
It seems that discussion has stagnated a bit. Maybe we could have a meeting to recap the discussion and kickoff a new iteration? I'm thinking, to get it going again, we could at first merge a simplistic implementation supporting only non-interleaved adv reports. This is sufficient for the SoftDeviceController at least. (Does anyone know if the Zephyr Controller interleaves adv reports?) We can add detection of interleaving so we can log it as an error. Then we can extend the implementation to support a config-bounded adv report concurrency. |
Zephyr Controller does not interleave Extended Advertising Reports, but it appears Periodic Advertising Report implementation is heading towards a possibility of being interleaved as HCI LE Periodic Advertising Reports are generated without buffering of complete AUX_SYNC_IND+AUX_CHAIN_IND PDU chains. |
Agree. The simple implementation seems sufficient for Softdevice controller and the Zephyr Link Layer for now. |
Closed in favor of #41337 |
The host reassembles fragmented advertising reports from the controller.
Fragmented advertising reports from different advertisers may be
interleaved. If fragmented advertising reports from multiple advertisers
are received, only the first one is kept and reassembled. The rest are
discarded until the first one is complete.
If a complete advertising report is not received from an advertiser
within the timeout, the data is discarded and another advertiser is
allowed to be reassembled.
This PR is based on the work by @rugeGerritsen in #37377.
Fixes: #37368
There are a couple issues that need to be discussed:
Data status
"complete".Most of the complexity in this PR arises because the controller may interleave fragmented advertising reports from different advertisers. For controllers that do no interleave advertising reports from different advertisers (like the Softdevice Controller), a much simpler implementation like the one in #37377 can be used. One solution to this is to hide the complex implementation that handles interleaving behind a Kconfig and let controllers that do not need it use the simpler implementation.
A solution in the spec is to add another bit to the
Event_Type
/Data status
field which tells whether the advertising report is part of a chain of fragmented advertising reports or if it is complete by its own.Tagging some people from previous discussions: @rugeGerritsen @Thalley @cvinayak