From 3b0389db71b9baf7d9dbf7447cf8cf27fa67b1fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:10:42 +0000 Subject: [PATCH 01/20] Initial plan From 5e829ddfaed133f0c5d069737e41a428b2ba925b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:15:57 +0000 Subject: [PATCH 02/20] Add Decision-Based Advertising Filtering infrastructure - Add PDU_ADV_TYPE_ADV_DECISION_IND (0x09) PDU type - Add pdu_adv_decision_ind structure for decision PDU - Add Kconfig options: BT_CTLR_DECISION_BASED_FILTERING and _SUPPORT - Add SCAN_FP_DECISION filter policy flag - Expand filter_policy field to 3 bits when feature is enabled Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/Kconfig | 12 ++++++++++++ subsys/bluetooth/controller/Kconfig.ll_sw_split | 1 + subsys/bluetooth/controller/ll_sw/lll_scan.h | 9 ++++++++- subsys/bluetooth/controller/ll_sw/pdu.h | 14 ++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/controller/Kconfig b/subsys/bluetooth/controller/Kconfig index 0d5cba8241624..0c1e89ebc445a 100644 --- a/subsys/bluetooth/controller/Kconfig +++ b/subsys/bluetooth/controller/Kconfig @@ -33,6 +33,9 @@ config BT_CTLR_PRIVACY_SUPPORT config BT_CTLR_EXT_SCAN_FP_SUPPORT bool +config BT_CTLR_DECISION_BASED_FILTERING_SUPPORT + bool + config BT_CTLR_PHY_UPDATE_SUPPORT bool @@ -711,6 +714,15 @@ config BT_CTLR_EXT_SCAN_FP Enable support for Bluetooth v4.2 LE Extended Scanner Filter Policies in the Controller. +config BT_CTLR_DECISION_BASED_FILTERING + bool "LE Decision-Based Advertising Filtering" + depends on BT_OBSERVER && BT_CTLR_DECISION_BASED_FILTERING_SUPPORT + help + Enable support for Bluetooth v6.2 LE Decision-Based Advertising + Filtering in the Controller. This feature allows the scanner to + make filtering decisions based on advertising data content as + specified in BLUETOOTH CORE SPECIFICATION Version 6.2. + config BT_CTLR_PHY_2M bool "2Mbps PHY Support" depends on (BT_CTLR_PHY || BT_CTLR_ADV_EXT) && BT_CTLR_PHY_2M_SUPPORT diff --git a/subsys/bluetooth/controller/Kconfig.ll_sw_split b/subsys/bluetooth/controller/Kconfig.ll_sw_split index 6b2c9f2575e8f..e743a5f8e1952 100644 --- a/subsys/bluetooth/controller/Kconfig.ll_sw_split +++ b/subsys/bluetooth/controller/Kconfig.ll_sw_split @@ -32,6 +32,7 @@ config BT_LLL_VENDOR_NORDIC SOC_COMPATIBLE_NRF54LX || \ BT_CTLR_DATA_LENGTH_CLEAR select BT_CTLR_EXT_SCAN_FP_SUPPORT + select BT_CTLR_DECISION_BASED_FILTERING_SUPPORT select BT_CTLR_PHY_2M_SUPPORT if HAS_HW_NRF_RADIO_BLE_2M || \ BT_CTLR_PHY_2M_NRF select BT_CTLR_PHY_CODED_SUPPORT if HAS_HW_NRF_RADIO_BLE_CODED diff --git a/subsys/bluetooth/controller/ll_sw/lll_scan.h b/subsys/bluetooth/controller/ll_sw/lll_scan.h index b9633f8993839..0941138662401 100644 --- a/subsys/bluetooth/controller/ll_sw/lll_scan.h +++ b/subsys/bluetooth/controller/ll_sw/lll_scan.h @@ -29,7 +29,11 @@ struct lll_scan { uint8_t state:1; uint8_t chan:2; +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + uint8_t filter_policy:3; +#else uint8_t filter_policy:2; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ uint8_t type:1; uint8_t init_addr_type:1; uint8_t is_stop:1; @@ -89,10 +93,13 @@ struct lll_scan_aux { /* Define to check if filter is enabled and in addition if it is Extended Scan - * Filtering. + * Filtering or Decision-Based Filtering. */ #define SCAN_FP_FILTER BIT(0) #define SCAN_FP_EXT BIT(1) +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +#define SCAN_FP_DECISION BIT(2) +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ int lll_scan_init(void); int lll_scan_reset(void); diff --git a/subsys/bluetooth/controller/ll_sw/pdu.h b/subsys/bluetooth/controller/ll_sw/pdu.h index 430704c84514f..1fbcc6660a74e 100644 --- a/subsys/bluetooth/controller/ll_sw/pdu.h +++ b/subsys/bluetooth/controller/ll_sw/pdu.h @@ -417,6 +417,13 @@ struct pdu_adv_connect_ind { } __packed; } __packed; +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +struct pdu_adv_decision_ind { + uint8_t addr[BDADDR_SIZE]; + uint8_t data[PDU_AC_LEG_DATA_SIZE_MAX]; +} __packed; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + struct pdu_adv_ext_hdr { #ifdef CONFIG_LITTLE_ENDIAN uint8_t adv_addr:1; @@ -548,6 +555,9 @@ enum pdu_adv_type { PDU_ADV_TYPE_AUX_SYNC_IND = PDU_ADV_TYPE_EXT_IND, PDU_ADV_TYPE_AUX_CHAIN_IND = PDU_ADV_TYPE_EXT_IND, PDU_ADV_TYPE_AUX_CONNECT_RSP = 0x08, +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + PDU_ADV_TYPE_ADV_DECISION_IND = 0x09, +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ } __packed; struct pdu_adv { @@ -578,6 +588,10 @@ struct pdu_adv { #if defined(CONFIG_BT_CTLR_ADV_EXT) struct pdu_adv_com_ext_adv adv_ext_ind; #endif /* CONFIG_BT_CTLR_ADV_EXT */ + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + struct pdu_adv_decision_ind decision_ind; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ } __packed; } __packed; From e8e6baca163e0ca2e316b612165e795a6aa0d3a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:17:14 +0000 Subject: [PATCH 03/20] Add Decision PDU scanning logic - Handle ADV_DECISION_IND PDU type in Nordic LLL scan implementation - Handle ADV_DECISION_IND PDU type in OpenISA LLL scan implementation - Apply decision-based filtering when SCAN_FP_DECISION flag is set - Report Decision PDUs to upper layers when filter policy allows Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c | 6 ++++++ subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c index 4365897597240..515e24af91eef 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c @@ -1439,6 +1439,12 @@ static inline int isr_rx_pdu(struct lll_scan *lll, struct pdu_adv *pdu_adv_rx, lll_scan_ext_tgta_check(lll, true, false, pdu_adv_rx, rl_idx, &dir_report)) || #endif /* CONFIG_BT_CTLR_ADV_EXT */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + ((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_DECISION_IND) && + (pdu_adv_rx->len >= offsetof(struct pdu_adv_decision_ind, data)) && + (pdu_adv_rx->len <= sizeof(struct pdu_adv_decision_ind)) && + ((lll->filter_policy & SCAN_FP_DECISION) != 0U)) || +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ ((pdu_adv_rx->type == PDU_ADV_TYPE_SCAN_RSP) && (pdu_adv_rx->len >= offsetof(struct pdu_adv_scan_rsp, data)) && (pdu_adv_rx->len <= sizeof(struct pdu_adv_scan_rsp)) && diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c index 013f705851ca3..1587b736e6416 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c @@ -962,6 +962,11 @@ static inline uint32_t isr_rx_pdu(struct lll_scan *lll, uint8_t devmatch_ok, ((pdu_adv_rx->type == PDU_ADV_TYPE_EXT_IND) && (lll->phy)) || #endif /* CONFIG_BT_CTLR_ADV_EXT */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + ((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_DECISION_IND) && + (pdu_adv_rx->len <= sizeof(struct pdu_adv_decision_ind)) && + ((lll->filter_policy & SCAN_FP_DECISION) != 0U)) || +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ ((pdu_adv_rx->type == PDU_ADV_TYPE_SCAN_RSP) && (pdu_adv_rx->len <= sizeof(struct pdu_adv_scan_rsp)) && (lll->state != 0U) && From 0e4a8964ac87faee5ca483bceaadd2bddca20523 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:18:44 +0000 Subject: [PATCH 04/20] Fix ADV_DECISION_IND length validation in OpenISA Add minimum length validation for ADV_DECISION_IND PDU in OpenISA implementation to match Nordic implementation and ensure PDU integrity Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c index 1587b736e6416..4e486ec2cb5d3 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c @@ -964,6 +964,7 @@ static inline uint32_t isr_rx_pdu(struct lll_scan *lll, uint8_t devmatch_ok, #endif /* CONFIG_BT_CTLR_ADV_EXT */ #if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) ((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_DECISION_IND) && + (pdu_adv_rx->len >= offsetof(struct pdu_adv_decision_ind, data)) && (pdu_adv_rx->len <= sizeof(struct pdu_adv_decision_ind)) && ((lll->filter_policy & SCAN_FP_DECISION) != 0U)) || #endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ From cd28feab985b26710df6835b19708e8c39343026 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:19:49 +0000 Subject: [PATCH 05/20] Add Decision-Based Filtering support for OpenISA LLL Enable Decision-Based Advertising Filtering support flag for OpenISA LLL vendor configuration to match feature implementation Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/Kconfig.ll_sw_split | 1 + 1 file changed, 1 insertion(+) diff --git a/subsys/bluetooth/controller/Kconfig.ll_sw_split b/subsys/bluetooth/controller/Kconfig.ll_sw_split index e743a5f8e1952..f8f93d79da204 100644 --- a/subsys/bluetooth/controller/Kconfig.ll_sw_split +++ b/subsys/bluetooth/controller/Kconfig.ll_sw_split @@ -98,6 +98,7 @@ config BT_LLL_VENDOR_OPENISA select BT_CTLR_PRIVACY_SUPPORT if BT_CTLR_CRYPTO_SUPPORT select BT_CTLR_PHY_UPDATE_SUPPORT select BT_CTLR_EXT_REJ_IND_SUPPORT + select BT_CTLR_DECISION_BASED_FILTERING_SUPPORT select BT_TICKER_REMAINDER_SUPPORT select BT_TICKER_UPDATE if BT_BROADCASTER || BT_CONN From fe7bfcd71ad5051335861b7c19a9e19b2c20c9f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:35:04 +0000 Subject: [PATCH 06/20] Add HCI and ULL layer support for Decision-Based Filtering - Add HCI filter policy constants (0x04-0x07) for decision modes - Add filter_policy validation in ll_scan_params_set() - Convert HCI filter_policy values to LLL bit flags in ull_scan_params_set() - Support combinations: basic/extended/decision with/without FAL Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- include/zephyr/bluetooth/hci_types.h | 7 +++ subsys/bluetooth/controller/ll_sw/ull_scan.c | 55 +++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/include/zephyr/bluetooth/hci_types.h b/include/zephyr/bluetooth/hci_types.h index 2769a26a8d6b9..12d692a5f703a 100644 --- a/include/zephyr/bluetooth/hci_types.h +++ b/include/zephyr/bluetooth/hci_types.h @@ -1271,6 +1271,13 @@ struct bt_hci_cp_le_set_adv_enable { #define BT_HCI_LE_SCAN_FP_BASIC_FILTER 0x01 #define BT_HCI_LE_SCAN_FP_EXT_NO_FILTER 0x02 #define BT_HCI_LE_SCAN_FP_EXT_FILTER 0x03 +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +/* Decision-Based Advertising Filtering (BT Core Spec v6.2) */ +#define BT_HCI_LE_SCAN_FP_DECISION_NO_FILTER 0x04 +#define BT_HCI_LE_SCAN_FP_DECISION_FILTER 0x05 +#define BT_HCI_LE_SCAN_FP_DECISION_EXT_NO_FILTER 0x06 +#define BT_HCI_LE_SCAN_FP_DECISION_EXT_FILTER 0x07 +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ struct bt_hci_cp_le_set_scan_param { uint8_t scan_type; diff --git a/subsys/bluetooth/controller/ll_sw/ull_scan.c b/subsys/bluetooth/controller/ll_sw/ull_scan.c index 514072643e16b..dcda7e253aa84 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_scan.c +++ b/subsys/bluetooth/controller/ll_sw/ull_scan.c @@ -131,6 +131,24 @@ uint8_t ll_scan_params_set(uint8_t type, uint16_t interval, uint16_t window, lll = &scan->lll; #endif /* !CONFIG_BT_CTLR_ADV_EXT */ + /* Validate filter policy parameter */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Decision-based filtering: support values 0x00-0x07 */ + if (filter_policy > 0x07) { + return BT_HCI_ERR_INVALID_PARAM; + } +#elif defined(CONFIG_BT_CTLR_EXT_SCAN_FP) + /* Extended scan filtering: support values 0x00-0x03 */ + if (filter_policy > 0x03) { + return BT_HCI_ERR_INVALID_PARAM; + } +#else + /* Basic filtering: support values 0x00-0x01 */ + if (filter_policy > 0x01) { + return BT_HCI_ERR_INVALID_PARAM; + } +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + scan->own_addr_type = own_addr_type; scan->ticks_window = ull_scan_params_set(lll, type, interval, window, @@ -374,7 +392,42 @@ uint32_t ull_scan_params_set(struct lll_scan *lll, uint8_t type, * 1001b - Ext. Coded active */ lll->type = type; - lll->filter_policy = filter_policy; + + /* Convert HCI filter_policy to LLL filter_policy bit flags + * HCI values: + * 0x00 - Basic, no filter + * 0x01 - Basic, use filter accept list + * 0x02 - Extended, no filter + * 0x03 - Extended, use filter accept list + * 0x04 - Decision, no filter (if enabled) + * 0x05 - Decision, use filter accept list (if enabled) + * 0x06 - Decision + Extended, no filter (if enabled) + * 0x07 - Decision + Extended, use filter accept list (if enabled) + * + * LLL flags: + * SCAN_FP_FILTER (bit 0) - Use filter accept list + * SCAN_FP_EXT (bit 1) - Extended scan filtering + * SCAN_FP_DECISION (bit 2) - Decision-based filtering + */ + lll->filter_policy = 0U; + + /* Set SCAN_FP_FILTER if filter accept list is used (odd values) */ + if (filter_policy & 0x01) { + lll->filter_policy |= SCAN_FP_FILTER; + } + + /* Set SCAN_FP_EXT if extended filtering is used (0x02-0x03, 0x06-0x07) */ + if (filter_policy & 0x02) { + lll->filter_policy |= SCAN_FP_EXT; + } + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Set SCAN_FP_DECISION if decision-based filtering is used (0x04-0x07) */ + if (filter_policy >= 0x04) { + lll->filter_policy |= SCAN_FP_DECISION; + } +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + lll->interval = interval; lll->ticks_window = HAL_TICKER_US_TO_TICKS((uint64_t)window * SCAN_INT_UNIT_US); From 41006b0fc52a0076c80576ad82673578c6419fb8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:36:58 +0000 Subject: [PATCH 07/20] Add HCI event reporting for ADV_DECISION_IND - Map ADV_DECISION_IND to HCI advertising event type - Add support in le_advertising_report() function - Add support in le_ext_adv_legacy_report() function - Handle decision_ind union member access for address and data Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/hci/hci.c | 69 +++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/subsys/bluetooth/controller/hci/hci.c b/subsys/bluetooth/controller/hci/hci.c index 6520f50c32938..c8e5eeb337a08 100644 --- a/subsys/bluetooth/controller/hci/hci.c +++ b/subsys/bluetooth/controller/hci/hci.c @@ -6663,8 +6663,25 @@ static void le_advertising_report(struct pdu_data *pdu_data, struct node_rx_pdu *node_rx, struct net_buf *buf) { +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Mapping PDU types to HCI advertising event types: + * Index 0: PDU_ADV_TYPE_ADV_IND → BT_HCI_ADV_IND (0x00) + * Index 1: PDU_ADV_TYPE_DIRECT_IND → BT_HCI_ADV_DIRECT_IND (0x01) + * Index 2: PDU_ADV_TYPE_NONCONN_IND → BT_HCI_ADV_NONCONN_IND (0x03) + * Index 3: PDU_ADV_TYPE_SCAN_REQ → invalid (0xff) + * Index 4: PDU_ADV_TYPE_SCAN_RSP → BT_HCI_ADV_SCAN_RSP (0x04) + * Index 5: PDU_ADV_TYPE_CONNECT_IND → invalid (0xff) + * Index 6: PDU_ADV_TYPE_SCAN_IND → BT_HCI_ADV_SCAN_IND (0x02) + * Index 7: (not used for EXT_IND) + * Index 8: (not used for AUX_CONNECT_RSP) + * Index 9: PDU_ADV_TYPE_ADV_DECISION_IND → BT_HCI_ADV_NONCONN_IND (0x03) + */ + const uint8_t c_adv_type[] = { 0x00, 0x01, 0x03, 0xff, 0x04, + 0xff, 0x02, 0xff, 0xff, 0x03 }; +#else const uint8_t c_adv_type[] = { 0x00, 0x01, 0x03, 0xff, 0x04, 0xff, 0x02 }; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ struct bt_hci_evt_le_advertising_report *sep; struct pdu_adv *adv = (void *)pdu_data; struct bt_hci_evt_le_advertising_info *adv_info; @@ -6752,12 +6769,30 @@ static void le_advertising_report(struct pdu_data *pdu_data, #endif /* CONFIG_BT_CTLR_PRIVACY */ adv_info->addr.type = adv->tx_addr; - memcpy(&adv_info->addr.a.val[0], &adv->adv_ind.addr[0], - sizeof(bt_addr_t)); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* ADV_DECISION_IND has same structure as ADV_IND (addr + data), + * so we can use adv_ind for both types + */ + if (adv->type == PDU_ADV_TYPE_ADV_DECISION_IND) { + memcpy(&adv_info->addr.a.val[0], &adv->decision_ind.addr[0], + sizeof(bt_addr_t)); + } else +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + { + memcpy(&adv_info->addr.a.val[0], &adv->adv_ind.addr[0], + sizeof(bt_addr_t)); + } } adv_info->length = data_len; - memcpy(&adv_info->data[0], &adv->adv_ind.data[0], data_len); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + if (adv->type == PDU_ADV_TYPE_ADV_DECISION_IND) { + memcpy(&adv_info->data[0], &adv->decision_ind.data[0], data_len); + } else +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + { + memcpy(&adv_info->data[0], &adv->adv_ind.data[0], data_len); + } /* RSSI */ prssi = &adv_info->data[0] + data_len; *prssi = rssi; @@ -6769,6 +6804,33 @@ static void le_ext_adv_legacy_report(struct pdu_data *pdu_data, struct net_buf *buf) { /* Lookup event type based on pdu_adv_type set by LLL */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + const uint8_t evt_type_lookup[] = { + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_SCAN | + BT_HCI_LE_ADV_EVT_TYPE_CONN), /* ADV_IND */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_DIRECT | + BT_HCI_LE_ADV_EVT_TYPE_CONN), /* DIRECT_IND */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY), /* NONCONN_IND */ + 0xff, /* Invalid index lookup */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | + BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP | + BT_HCI_LE_ADV_EVT_TYPE_SCAN), /* SCAN_RSP to an ADV_SCAN_IND + */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | + BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP | + BT_HCI_LE_ADV_EVT_TYPE_SCAN | + BT_HCI_LE_ADV_EVT_TYPE_CONN), /* SCAN_RSP to an ADV_IND, + * NOTE: LLL explicitly sets + * adv_type to + * PDU_ADV_TYPE_ADV_IND_SCAN_RSP + */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | + BT_HCI_LE_ADV_EVT_TYPE_SCAN), /* SCAN_IND */ + 0xff, /* Invalid - EXT_IND */ + 0xff, /* Invalid - AUX_CONNECT_RSP */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY) /* ADV_DECISION_IND */ + }; +#else const uint8_t evt_type_lookup[] = { (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_SCAN | BT_HCI_LE_ADV_EVT_TYPE_CONN), /* ADV_IND */ @@ -6791,6 +6853,7 @@ static void le_ext_adv_legacy_report(struct pdu_data *pdu_data, (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_SCAN) /* SCAN_IND */ }; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ struct bt_hci_evt_le_ext_advertising_info *adv_info; struct bt_hci_evt_le_ext_advertising_report *sep; struct pdu_adv *adv = (void *)pdu_data; From afd42052852bbb2a5866038f73173f064027c669 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:38:45 +0000 Subject: [PATCH 08/20] Improve documentation for PDU-to-HCI event type mapping Add complete array index documentation showing all 10 elements for decision-based filtering and 7 elements for standard mode Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/hci/hci.c | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/subsys/bluetooth/controller/hci/hci.c b/subsys/bluetooth/controller/hci/hci.c index c8e5eeb337a08..4915c25dfa9ac 100644 --- a/subsys/bluetooth/controller/hci/hci.c +++ b/subsys/bluetooth/controller/hci/hci.c @@ -6664,21 +6664,30 @@ static void le_advertising_report(struct pdu_data *pdu_data, struct net_buf *buf) { #if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) - /* Mapping PDU types to HCI advertising event types: - * Index 0: PDU_ADV_TYPE_ADV_IND → BT_HCI_ADV_IND (0x00) - * Index 1: PDU_ADV_TYPE_DIRECT_IND → BT_HCI_ADV_DIRECT_IND (0x01) - * Index 2: PDU_ADV_TYPE_NONCONN_IND → BT_HCI_ADV_NONCONN_IND (0x03) - * Index 3: PDU_ADV_TYPE_SCAN_REQ → invalid (0xff) - * Index 4: PDU_ADV_TYPE_SCAN_RSP → BT_HCI_ADV_SCAN_RSP (0x04) - * Index 5: PDU_ADV_TYPE_CONNECT_IND → invalid (0xff) - * Index 6: PDU_ADV_TYPE_SCAN_IND → BT_HCI_ADV_SCAN_IND (0x02) - * Index 7: (not used for EXT_IND) - * Index 8: (not used for AUX_CONNECT_RSP) - * Index 9: PDU_ADV_TYPE_ADV_DECISION_IND → BT_HCI_ADV_NONCONN_IND (0x03) + /* Mapping PDU types to HCI advertising event types (10 elements, indices 0-9): + * Index 0: PDU_ADV_TYPE_ADV_IND (0x00) → BT_HCI_ADV_IND (0x00) + * Index 1: PDU_ADV_TYPE_DIRECT_IND (0x01) → BT_HCI_ADV_DIRECT_IND (0x01) + * Index 2: PDU_ADV_TYPE_NONCONN_IND (0x02) → BT_HCI_ADV_NONCONN_IND (0x03) + * Index 3: PDU_ADV_TYPE_SCAN_REQ (0x03) → invalid (0xff) + * Index 4: PDU_ADV_TYPE_SCAN_RSP (0x04) → BT_HCI_ADV_SCAN_RSP (0x04) + * Index 5: PDU_ADV_TYPE_CONNECT_IND (0x05) → invalid (0xff) + * Index 6: PDU_ADV_TYPE_SCAN_IND (0x06) → BT_HCI_ADV_SCAN_IND (0x02) + * Index 7: PDU_ADV_TYPE_EXT_IND (0x07) → invalid (0xff) + * Index 8: PDU_ADV_TYPE_AUX_CONNECT_RSP (0x08) → invalid (0xff) + * Index 9: PDU_ADV_TYPE_ADV_DECISION_IND (0x09) → BT_HCI_ADV_NONCONN_IND (0x03) */ const uint8_t c_adv_type[] = { 0x00, 0x01, 0x03, 0xff, 0x04, 0xff, 0x02, 0xff, 0xff, 0x03 }; #else + /* Mapping PDU types to HCI advertising event types (7 elements, indices 0-6): + * Index 0: PDU_ADV_TYPE_ADV_IND (0x00) → BT_HCI_ADV_IND (0x00) + * Index 1: PDU_ADV_TYPE_DIRECT_IND (0x01) → BT_HCI_ADV_DIRECT_IND (0x01) + * Index 2: PDU_ADV_TYPE_NONCONN_IND (0x02) → BT_HCI_ADV_NONCONN_IND (0x03) + * Index 3: PDU_ADV_TYPE_SCAN_REQ (0x03) → invalid (0xff) + * Index 4: PDU_ADV_TYPE_SCAN_RSP (0x04) → BT_HCI_ADV_SCAN_RSP (0x04) + * Index 5: PDU_ADV_TYPE_CONNECT_IND (0x05) → invalid (0xff) + * Index 6: PDU_ADV_TYPE_SCAN_IND (0x06) → BT_HCI_ADV_SCAN_IND (0x02) + */ const uint8_t c_adv_type[] = { 0x00, 0x01, 0x03, 0xff, 0x04, 0xff, 0x02 }; #endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ From f3c7992d9a0a97e422cabbb38d61264bc61476ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:40:28 +0000 Subject: [PATCH 09/20] Fix comment: clarify decision_ind union member access Update comment to accurately reflect that code uses decision_ind union member for type safety, not adv_ind Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/hci/hci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subsys/bluetooth/controller/hci/hci.c b/subsys/bluetooth/controller/hci/hci.c index 4915c25dfa9ac..31ea4fca15318 100644 --- a/subsys/bluetooth/controller/hci/hci.c +++ b/subsys/bluetooth/controller/hci/hci.c @@ -6779,8 +6779,8 @@ static void le_advertising_report(struct pdu_data *pdu_data, adv_info->addr.type = adv->tx_addr; #if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) - /* ADV_DECISION_IND has same structure as ADV_IND (addr + data), - * so we can use adv_ind for both types + /* ADV_DECISION_IND has same structure as ADV_IND (addr + data). + * Access via decision_ind union member for type safety. */ if (adv->type == PDU_ADV_TYPE_ADV_DECISION_IND) { memcpy(&adv_info->addr.a.val[0], &adv->decision_ind.addr[0], From 7c89c0623ff8f3bd99b62a9d9b19d62b937d71f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:55:35 +0000 Subject: [PATCH 10/20] Add Host stack support for Decision-Based Filtering - Add BT_LE_SCAN_OPT_EXTENDED_FILTER and BT_LE_SCAN_OPT_DECISION_BASED options to bt_le_scan_opt enum - Add scan_options_to_filter_policy() helper function to convert scan options to HCI filter policy values - Update start_le_scan_ext() to use helper for filter policy mapping - Update start_le_scan_legacy() to use helper for filter policy mapping - Support all 8 filter policy modes (0x00-0x07) based on option flags Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- include/zephyr/bluetooth/bluetooth.h | 30 ++++++++++++ subsys/bluetooth/host/scan.c | 69 ++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/include/zephyr/bluetooth/bluetooth.h b/include/zephyr/bluetooth/bluetooth.h index 82d1a08e0dc61..bb0ac3e56327a 100644 --- a/include/zephyr/bluetooth/bluetooth.h +++ b/include/zephyr/bluetooth/bluetooth.h @@ -2350,6 +2350,36 @@ enum bt_le_scan_opt { * @note Requires @ref BT_LE_SCAN_OPT_CODED. */ BT_LE_SCAN_OPT_NO_1M = BIT(3), + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || defined(__DOXYGEN__) + /** + * @brief Enable extended scan filtering. + * + * Enables extended scan filtering mode which provides enhanced + * filtering capabilities. Can be combined with + * @ref BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST. + * + * @note Requires controller support for extended scan filtering. + */ + BT_LE_SCAN_OPT_EXTENDED_FILTER = BIT(4), + + /** + * @brief Enable decision-based advertising filtering. + * + * Enables decision-based advertising filtering as specified in + * Bluetooth Core Specification v6.2. This allows the controller to + * filter advertising PDUs based on decision criteria. Can be combined + * with @ref BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST and/or + * @ref BT_LE_SCAN_OPT_EXTENDED_FILTER. + * + * When this option is set, the controller will accept ADV_DECISION_IND + * PDUs according to the filter policy. + * + * @note Requires controller support for decision-based advertising + * filtering (CONFIG_BT_CTLR_DECISION_BASED_FILTERING). + */ + BT_LE_SCAN_OPT_DECISION_BASED = BIT(5), +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ }; enum bt_le_scan_type { diff --git a/subsys/bluetooth/host/scan.c b/subsys/bluetooth/host/scan.c index b993dd72a5732..1e6c98fb331a0 100644 --- a/subsys/bluetooth/host/scan.c +++ b/subsys/bluetooth/host/scan.c @@ -199,6 +199,64 @@ int bt_le_scan_set_enable(uint8_t enable) BT_LE_SCAN_OPT_FILTER_DUPLICATE); } +/** + * @brief Convert scan options to HCI filter policy value + * + * Maps the combination of scan options to the appropriate HCI filter policy + * value according to the following table: + * + * | Options | HCI Value | Description | + * |---------|-----------|-------------| + * | None | 0x00 | Basic, no filter | + * | FAL | 0x01 | Basic, use filter accept list | + * | EXT | 0x02 | Extended, no filter | + * | EXT + FAL | 0x03 | Extended, use filter accept list | + * | DEC | 0x04 | Decision, no filter | + * | DEC + FAL | 0x05 | Decision, use filter accept list | + * | DEC + EXT | 0x06 | Decision + Extended, no filter | + * | DEC + EXT + FAL | 0x07 | Decision + Extended, use filter accept list | + * + * @param options Scan options bit field + * @return HCI filter policy value (0x00-0x07) + */ +static uint8_t scan_options_to_filter_policy(uint8_t options) +{ + uint8_t filter_policy = BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Check for decision-based filtering (bit 5) */ + if (options & BT_LE_SCAN_OPT_DECISION_BASED) { + filter_policy = BT_HCI_LE_SCAN_FP_DECISION_NO_FILTER; + + /* Add extended filter flag if requested (bit 4) */ + if (options & BT_LE_SCAN_OPT_EXTENDED_FILTER) { + filter_policy = BT_HCI_LE_SCAN_FP_DECISION_EXT_NO_FILTER; + } + + /* Add filter accept list flag if requested (bit 1) */ + if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && + (options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST)) { + filter_policy |= 0x01; /* Set bit 0 for FAL */ + } + } else +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + { + /* Legacy/Extended filtering without decision-based */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + if (options & BT_LE_SCAN_OPT_EXTENDED_FILTER) { + filter_policy = BT_HCI_LE_SCAN_FP_EXT_NO_FILTER; + } +#endif + /* Add filter accept list flag if requested */ + if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && + (options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST)) { + filter_policy |= 0x01; /* Set bit 0 for FAL */ + } + } + + return filter_policy; +} + static int start_le_scan_ext(struct bt_le_scan_param *scan_param) { struct bt_hci_ext_scan_phy param_1m; @@ -260,9 +318,7 @@ static int start_le_scan_ext(struct bt_le_scan_param *scan_param) set_param = net_buf_add(buf, sizeof(*set_param)); set_param->own_addr_type = own_addr_type; set_param->phys = 0; - set_param->filter_policy = scan_param->options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST - ? BT_HCI_LE_SCAN_FP_BASIC_FILTER - : BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; + set_param->filter_policy = scan_options_to_filter_policy(scan_param->options); if (phy_1m) { set_param->phys |= BT_HCI_LE_EXT_SCAN_PHY_1M; @@ -306,12 +362,7 @@ static int start_le_scan_legacy(struct bt_le_scan_param *param) set_param.interval = sys_cpu_to_le16(param->interval); set_param.window = sys_cpu_to_le16(param->window); - if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && - param->options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST) { - set_param.filter_policy = BT_HCI_LE_SCAN_FP_BASIC_FILTER; - } else { - set_param.filter_policy = BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; - } + set_param.filter_policy = scan_options_to_filter_policy(param->options); active_scan = param->type == BT_HCI_LE_SCAN_ACTIVE; err = bt_id_set_scan_own_addr(active_scan, &set_param.addr_type); From 2b77aed8069f93f4d66ca27744c371febf202aa4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:57:12 +0000 Subject: [PATCH 11/20] Refactor filter policy logic to reduce duplication Simplify scan_options_to_filter_policy() by: - Removing duplicated filter accept list logic - Adding clearer documentation about bit 0 usage - Using if-else chain instead of nested conditionals Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/host/scan.c | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/subsys/bluetooth/host/scan.c b/subsys/bluetooth/host/scan.c index 1e6c98fb331a0..fc33870446787 100644 --- a/subsys/bluetooth/host/scan.c +++ b/subsys/bluetooth/host/scan.c @@ -216,6 +216,8 @@ int bt_le_scan_set_enable(uint8_t enable) * | DEC + EXT | 0x06 | Decision + Extended, no filter | * | DEC + EXT + FAL | 0x07 | Decision + Extended, use filter accept list | * + * Note: Bit 0 of the filter policy always indicates filter accept list usage. + * * @param options Scan options bit field * @return HCI filter policy value (0x00-0x07) */ @@ -232,26 +234,18 @@ static uint8_t scan_options_to_filter_policy(uint8_t options) if (options & BT_LE_SCAN_OPT_EXTENDED_FILTER) { filter_policy = BT_HCI_LE_SCAN_FP_DECISION_EXT_NO_FILTER; } - - /* Add filter accept list flag if requested (bit 1) */ - if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && - (options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST)) { - filter_policy |= 0x01; /* Set bit 0 for FAL */ - } - } else + } else if (options & BT_LE_SCAN_OPT_EXTENDED_FILTER) { + /* Extended filtering without decision-based */ + filter_policy = BT_HCI_LE_SCAN_FP_EXT_NO_FILTER; + } #endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ - { - /* Legacy/Extended filtering without decision-based */ -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) - if (options & BT_LE_SCAN_OPT_EXTENDED_FILTER) { - filter_policy = BT_HCI_LE_SCAN_FP_EXT_NO_FILTER; - } -#endif - /* Add filter accept list flag if requested */ - if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && - (options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST)) { - filter_policy |= 0x01; /* Set bit 0 for FAL */ - } + + /* Add filter accept list flag if requested. + * Bit 0 indicates filter accept list usage in all filter policy modes. + */ + if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && + (options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST)) { + filter_policy |= 0x01; } return filter_policy; From 40d7ccfc538f4b223168a022290518a0d9a5d2d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:31:32 +0000 Subject: [PATCH 12/20] Add samples and tests for Decision-Based Filtering - Create broadcaster_decision sample with extended advertising - Create observer_decision sample with decision-based scanning - Create BabbleSim tests reusing sample code - Add test infrastructure with broadcaster and observer tests - Include documentation and configuration for all components Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- .../broadcaster_decision/CMakeLists.txt | 8 + .../bluetooth/broadcaster_decision/README.rst | 54 +++++ .../bluetooth/broadcaster_decision/prj.conf | 8 + .../broadcaster_decision/sample.yaml | 17 ++ .../bluetooth/broadcaster_decision/src/main.c | 98 +++++++++ .../observer_decision/CMakeLists.txt | 8 + .../bluetooth/observer_decision/README.rst | 52 +++++ samples/bluetooth/observer_decision/prj.conf | 9 + .../bluetooth/observer_decision/sample.yaml | 17 ++ .../bluetooth/observer_decision/src/main.c | 132 ++++++++++++ .../host/adv/decision/CMakeLists.txt | 23 ++ .../host/adv/decision/prj_broadcaster.conf | 12 ++ .../host/adv/decision/prj_observer.conf | 13 ++ .../adv/decision/src/broadcaster_decision.c | 144 +++++++++++++ .../host/adv/decision/src/observer_decision.c | 204 ++++++++++++++++++ .../bluetooth/host/adv/decision/testcase.yaml | 20 ++ .../host/adv/decision/tests_scripts/run.sh | 29 +++ 17 files changed, 848 insertions(+) create mode 100644 samples/bluetooth/broadcaster_decision/CMakeLists.txt create mode 100644 samples/bluetooth/broadcaster_decision/README.rst create mode 100644 samples/bluetooth/broadcaster_decision/prj.conf create mode 100644 samples/bluetooth/broadcaster_decision/sample.yaml create mode 100644 samples/bluetooth/broadcaster_decision/src/main.c create mode 100644 samples/bluetooth/observer_decision/CMakeLists.txt create mode 100644 samples/bluetooth/observer_decision/README.rst create mode 100644 samples/bluetooth/observer_decision/prj.conf create mode 100644 samples/bluetooth/observer_decision/sample.yaml create mode 100644 samples/bluetooth/observer_decision/src/main.c create mode 100644 tests/bsim/bluetooth/host/adv/decision/CMakeLists.txt create mode 100644 tests/bsim/bluetooth/host/adv/decision/prj_broadcaster.conf create mode 100644 tests/bsim/bluetooth/host/adv/decision/prj_observer.conf create mode 100644 tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c create mode 100644 tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c create mode 100644 tests/bsim/bluetooth/host/adv/decision/testcase.yaml create mode 100755 tests/bsim/bluetooth/host/adv/decision/tests_scripts/run.sh diff --git a/samples/bluetooth/broadcaster_decision/CMakeLists.txt b/samples/bluetooth/broadcaster_decision/CMakeLists.txt new file mode 100644 index 0000000000000..9f9bf98cd4b8a --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(broadcaster_decision) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/bluetooth/broadcaster_decision/README.rst b/samples/bluetooth/broadcaster_decision/README.rst new file mode 100644 index 0000000000000..c6f2d23c6054c --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/README.rst @@ -0,0 +1,54 @@ +.. _bluetooth_broadcaster_decision: + +Bluetooth: Broadcaster with Decision-Based Advertising +####################################################### + +Overview +******** + +This sample demonstrates extended advertising with decision-based advertising +filtering support as specified in Bluetooth Core Specification v6.2, section +4.6.43. + +The application starts extended advertising and transmits advertising PDUs +that can be filtered using decision-based filtering on the scanner side. + +Requirements +************ + +* A board with Bluetooth Low Energy support +* Controller support for decision-based advertising filtering + (CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + +Building and Running +******************** + +This sample can be found under +:zephyr_file:`samples/bluetooth/broadcaster_decision` in the Zephyr tree. + +Build the sample with the following commands: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/broadcaster_decision + :board: + :goals: build flash + :compact: + +To observe the advertising with decision-based filtering, run the +:ref:`bluetooth_observer_decision` sample on another device. + +Sample Output +============= + +When running, the sample will output: + +.. code-block:: console + + Starting Decision-Based Advertising Broadcaster + Bluetooth initialized + Extended advertising set created + Advertising data set + Extended advertising started + Advertising with decision-based filtering support + Device name: Decision Broadcaster + Manufacturer data: DECISION diff --git a/samples/bluetooth/broadcaster_decision/prj.conf b/samples/bluetooth/broadcaster_decision/prj.conf new file mode 100644 index 0000000000000..0079b896e425d --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/prj.conf @@ -0,0 +1,8 @@ +CONFIG_BT=y +CONFIG_BT_BROADCASTER=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_DEVICE_NAME="Decision Broadcaster" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/samples/bluetooth/broadcaster_decision/sample.yaml b/samples/bluetooth/broadcaster_decision/sample.yaml new file mode 100644 index 0000000000000..9e9acf341f291 --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Bluetooth broadcaster with decision-based advertising filtering + name: Bluetooth broadcaster_decision +common: + tags: bluetooth + filter: dt_compat_enabled("zephyr,bt-hci") + harness: bluetooth + platform_allow: + - native_sim + - qemu_cortex_m3 + - nrf52dk/nrf52832 + - nrf52840dk/nrf52840 + integration_platforms: + - native_sim +tests: + sample.bluetooth.broadcaster_decision: + build_only: true diff --git a/samples/bluetooth/broadcaster_decision/src/main.c b/samples/bluetooth/broadcaster_decision/src/main.c new file mode 100644 index 0000000000000..52b4712d3afd7 --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/src/main.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Broadcaster Sample + * + * This sample demonstrates extended advertising with decision-based + * advertising filtering support as specified in Bluetooth Core + * Specification v6.2. + */ + +#include +#include +#include +#include + +#define DEVICE_NAME CONFIG_BT_DEVICE_NAME +#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) + +/* Manufacturer ID for sample data */ +#define COMPANY_ID 0x05F1 /* Nordic Semiconductor */ + +static uint8_t mfg_data[] = { + 0xF1, 0x05, /* Company ID (little-endian) */ + 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4F, 0x4E /* "DECISION" */ +}; + +static const struct bt_data ad[] = { + BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), + BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)), +}; + +int main(void) +{ + struct bt_le_ext_adv *adv; + struct bt_le_adv_param adv_param = { + .id = BT_ID_DEFAULT, + .sid = 0, + .secondary_max_skip = 0, + .options = BT_LE_ADV_OPT_EXT_ADV, + .interval_min = BT_GAP_ADV_FAST_INT_MIN_2, + .interval_max = BT_GAP_ADV_FAST_INT_MAX_2, + .peer = NULL, + }; + int err; + + printk("Starting Decision-Based Advertising Broadcaster\n"); + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return 0; + } + + printk("Bluetooth initialized\n"); + + /* Create extended advertising set */ + err = bt_le_ext_adv_create(&adv_param, NULL, &adv); + if (err) { + printk("Failed to create advertising set (err %d)\n", err); + return 0; + } + + printk("Extended advertising set created\n"); + + /* Set advertising data */ + err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + printk("Failed to set advertising data (err %d)\n", err); + return 0; + } + + printk("Advertising data set\n"); + + /* Start extended advertising */ + err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + if (err) { + printk("Failed to start extended advertising (err %d)\n", err); + return 0; + } + + printk("Extended advertising started\n"); + printk("Advertising with decision-based filtering support\n"); + printk("Device name: %s\n", DEVICE_NAME); + printk("Manufacturer data: DECISION\n"); + + /* Keep advertising indefinitely */ + while (1) { + k_sleep(K_SECONDS(1)); + } + + return 0; +} diff --git a/samples/bluetooth/observer_decision/CMakeLists.txt b/samples/bluetooth/observer_decision/CMakeLists.txt new file mode 100644 index 0000000000000..f91bab0691566 --- /dev/null +++ b/samples/bluetooth/observer_decision/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(observer_decision) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/bluetooth/observer_decision/README.rst b/samples/bluetooth/observer_decision/README.rst new file mode 100644 index 0000000000000..855bbe501fa6b --- /dev/null +++ b/samples/bluetooth/observer_decision/README.rst @@ -0,0 +1,52 @@ +.. _bluetooth_observer_decision: + +Bluetooth: Observer with Decision-Based Advertising Filtering +############################################################## + +Overview +******** + +This sample demonstrates extended scanning with decision-based advertising +filtering support as specified in Bluetooth Core Specification v6.2, section +4.6.43. + +The application starts extended scanning with decision-based filtering enabled +and displays advertising reports from devices using decision-based advertising. + +Requirements +************ + +* A board with Bluetooth Low Energy support +* Controller support for decision-based advertising filtering + (CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + +Building and Running +******************** + +This sample can be found under +:zephyr_file:`samples/bluetooth/observer_decision` in the Zephyr tree. + +Build the sample with the following commands: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/observer_decision + :board: + :goals: build flash + :compact: + +To test the decision-based filtering, run the +:ref:`bluetooth_broadcaster_decision` sample on another device. + +Sample Output +============= + +When running, the sample will output: + +.. code-block:: console + + Starting Decision-Based Advertising Observer + Bluetooth initialized + Starting scan with decision-based filtering + Scanning successfully started + Waiting for advertising reports... + [DEVICE]: XX:XX:XX:XX:XX:XX, RSSI -45, Extended Advertising Name: Decision Broadcaster MFG: 0x05f1 Data: DECISION diff --git a/samples/bluetooth/observer_decision/prj.conf b/samples/bluetooth/observer_decision/prj.conf new file mode 100644 index 0000000000000..489b7f145c1a0 --- /dev/null +++ b/samples/bluetooth/observer_decision/prj.conf @@ -0,0 +1,9 @@ +CONFIG_BT=y +CONFIG_BT_OBSERVER=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_EXT_SCAN_BUF_SIZE=1650 +CONFIG_BT_DEVICE_NAME="Decision Observer" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/samples/bluetooth/observer_decision/sample.yaml b/samples/bluetooth/observer_decision/sample.yaml new file mode 100644 index 0000000000000..66e9aef9aa55b --- /dev/null +++ b/samples/bluetooth/observer_decision/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Bluetooth observer with decision-based advertising filtering + name: Bluetooth observer_decision +common: + tags: bluetooth + filter: dt_compat_enabled("zephyr,bt-hci") + harness: bluetooth + platform_allow: + - native_sim + - qemu_cortex_m3 + - nrf52dk/nrf52832 + - nrf52840dk/nrf52840 + integration_platforms: + - native_sim +tests: + sample.bluetooth.observer_decision: + build_only: true diff --git a/samples/bluetooth/observer_decision/src/main.c b/samples/bluetooth/observer_decision/src/main.c new file mode 100644 index 0000000000000..42ca711923bc4 --- /dev/null +++ b/samples/bluetooth/observer_decision/src/main.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Observer Sample + * + * This sample demonstrates extended scanning with decision-based + * advertising filtering support as specified in Bluetooth Core + * Specification v6.2. + */ + +#include +#include +#include +#include + +#define NAME_LEN 30 + +static void scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + char name[NAME_LEN]; + uint8_t data_len; + uint8_t data_type; + + bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); + + printk("[DEVICE]: %s, RSSI %d, ", addr_str, info->rssi); + + /* Check for extended advertising */ + if (info->adv_type == BT_GAP_ADV_TYPE_EXT_ADV) { + printk("Extended Advertising "); + } + + /* Parse advertising data */ + while (buf->len > 1) { + data_len = net_buf_simple_pull_u8(buf); + if (data_len == 0) { + break; + } + + data_type = net_buf_simple_pull_u8(buf); + data_len--; + + switch (data_type) { + case BT_DATA_NAME_COMPLETE: + case BT_DATA_NAME_SHORTENED: + if (data_len < sizeof(name)) { + memcpy(name, buf->data, data_len); + name[data_len] = '\0'; + printk("Name: %s ", name); + } + net_buf_simple_pull(buf, data_len); + break; + + case BT_DATA_MANUFACTURER_DATA: + if (data_len >= 2) { + uint16_t company_id = sys_get_le16(buf->data); + printk("MFG: 0x%04x ", company_id); + + /* Print manufacturer data */ + if (data_len > 2) { + printk("Data: "); + for (int i = 2; i < data_len; i++) { + printk("%c", buf->data[i]); + } + printk(" "); + } + } + net_buf_simple_pull(buf, data_len); + break; + + default: + net_buf_simple_pull(buf, data_len); + break; + } + } + + printk("\n"); +} + +static struct bt_le_scan_cb scan_callbacks = { + .recv = scan_recv, +}; + +int main(void) +{ + struct bt_le_scan_param scan_param = { + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_DECISION_BASED, + .interval = BT_GAP_SCAN_FAST_INTERVAL, + .window = BT_GAP_SCAN_FAST_WINDOW, + }; + int err; + + printk("Starting Decision-Based Advertising Observer\n"); + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return 0; + } + + printk("Bluetooth initialized\n"); + + /* Register scan callbacks */ + bt_le_scan_cb_register(&scan_callbacks); + + /* Start scanning with decision-based filtering */ + printk("Starting scan with decision-based filtering\n"); + err = bt_le_scan_start(&scan_param, NULL); + if (err) { + printk("Scan start failed (err %d)\n", err); + return 0; + } + + printk("Scanning successfully started\n"); + printk("Waiting for advertising reports...\n"); + + /* Keep scanning indefinitely */ + while (1) { + k_sleep(K_SECONDS(1)); + } + + return 0; +} diff --git a/tests/bsim/bluetooth/host/adv/decision/CMakeLists.txt b/tests/bsim/bluetooth/host/adv/decision/CMakeLists.txt new file mode 100644 index 0000000000000..0ceb05f3a5f44 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(bsim_test_decision_adv) + +add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit) +target_link_libraries(app PRIVATE babblekit) + +target_sources_ifdef(CONFIG_BT_OBSERVER app PRIVATE + src/observer_decision.c +) + +target_sources_ifdef(CONFIG_BT_BROADCASTER app PRIVATE + src/broadcaster_decision.c +) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ +) diff --git a/tests/bsim/bluetooth/host/adv/decision/prj_broadcaster.conf b/tests/bsim/bluetooth/host/adv/decision/prj_broadcaster.conf new file mode 100644 index 0000000000000..77a31270c5d83 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/prj_broadcaster.conf @@ -0,0 +1,12 @@ +CONFIG_BT=y +CONFIG_BT_BROADCASTER=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_DEVICE_NAME="Decision Broadcaster" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +# Test configuration +CONFIG_BT_MAX_CONN=1 + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/tests/bsim/bluetooth/host/adv/decision/prj_observer.conf b/tests/bsim/bluetooth/host/adv/decision/prj_observer.conf new file mode 100644 index 0000000000000..e5de0dc44bc00 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/prj_observer.conf @@ -0,0 +1,13 @@ +CONFIG_BT=y +CONFIG_BT_OBSERVER=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_EXT_SCAN_BUF_SIZE=1650 +CONFIG_BT_DEVICE_NAME="Decision Observer" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +# Test configuration +CONFIG_BT_MAX_CONN=1 + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c b/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c new file mode 100644 index 0000000000000..7a32a845dac83 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Broadcaster Test + * + * Test for extended advertising with decision-based advertising filtering. + * Reuses broadcaster_decision sample code with babblekit integration. + */ + +#include +#include +#include +#include + +#include "babblekit/testcase.h" +#include "babblekit/flags.h" + +extern enum bst_result_t bst_result; + +#define DEVICE_NAME CONFIG_BT_DEVICE_NAME +#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) + +/* Manufacturer ID for sample data */ +#define COMPANY_ID 0x05F1 /* Nordic Semiconductor */ + +DEFINE_FLAG_STATIC(flag_adv_started); + +static uint8_t mfg_data[] = { + 0xF1, 0x05, /* Company ID (little-endian) */ + 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4F, 0x4E /* "DECISION" */ +}; + +static const struct bt_data ad[] = { + BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), + BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)), +}; + +static void test_broadcaster_main(void) +{ + struct bt_le_ext_adv *adv; + struct bt_le_adv_param adv_param = { + .id = BT_ID_DEFAULT, + .sid = 0, + .secondary_max_skip = 0, + .options = BT_LE_ADV_OPT_EXT_ADV, + .interval_min = BT_GAP_ADV_FAST_INT_MIN_2, + .interval_max = BT_GAP_ADV_FAST_INT_MAX_2, + .peer = NULL, + }; + int err; + + printk("Decision-Based Advertising Broadcaster Test\n"); + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + TEST_FAIL("Bluetooth init failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + /* Create extended advertising set */ + err = bt_le_ext_adv_create(&adv_param, NULL, &adv); + if (err) { + TEST_FAIL("Failed to create advertising set (err %d)\n", err); + return; + } + + printk("Extended advertising set created\n"); + + /* Set advertising data */ + err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + TEST_FAIL("Failed to set advertising data (err %d)\n", err); + return; + } + + printk("Advertising data set\n"); + + /* Start extended advertising */ + err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + if (err) { + TEST_FAIL("Failed to start extended advertising (err %d)\n", err); + return; + } + + printk("Extended advertising started\n"); + printk("Advertising with decision-based filtering support\n"); + + SET_FLAG(flag_adv_started); + + /* Keep advertising for the duration of the test */ + k_sleep(K_SECONDS(5)); + + /* Stop advertising */ + err = bt_le_ext_adv_stop(adv); + if (err) { + TEST_FAIL("Failed to stop advertising (err %d)\n", err); + return; + } + + printk("Advertising stopped\n"); + + /* Delete advertising set */ + err = bt_le_ext_adv_delete(adv); + if (err) { + TEST_FAIL("Failed to delete advertising set (err %d)\n", err); + return; + } + + printk("Test passed\n"); + TEST_PASS("Decision broadcaster test passed"); +} + +static const struct bst_test_instance test_def[] = { + { + .test_id = "decision_broadcaster", + .test_descr = "Decision-based advertising broadcaster test", + .test_main_f = test_broadcaster_main, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_decision_broadcaster_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_def); +} + +bst_test_install_t test_installers[] = { + test_decision_broadcaster_install, + NULL, +}; + +int main(void) +{ + bst_main(); + return 0; +} diff --git a/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c new file mode 100644 index 0000000000000..b5e143cec2e22 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Observer Test + * + * Test for extended scanning with decision-based advertising filtering. + * Reuses observer_decision sample code with babblekit integration. + */ + +#include +#include +#include +#include + +#include "babblekit/testcase.h" +#include "babblekit/flags.h" + +extern enum bst_result_t bst_result; + +#define NAME_LEN 30 +#define MIN_EXPECTED_REPORTS 3 + +DEFINE_FLAG_STATIC(flag_scan_started); +DEFINE_FLAG_STATIC(flag_adv_received); + +static int adv_report_count; + +static void scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + char name[NAME_LEN] = {0}; + uint8_t data_len; + uint8_t data_type; + bool found_decision_data = false; + + bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); + + printk("[DEVICE]: %s, RSSI %d, ", addr_str, info->rssi); + + /* Check for extended advertising */ + if (info->adv_type == BT_GAP_ADV_TYPE_EXT_ADV) { + printk("Extended Advertising "); + } + + /* Parse advertising data */ + while (buf->len > 1) { + data_len = net_buf_simple_pull_u8(buf); + if (data_len == 0) { + break; + } + + data_type = net_buf_simple_pull_u8(buf); + data_len--; + + switch (data_type) { + case BT_DATA_NAME_COMPLETE: + case BT_DATA_NAME_SHORTENED: + if (data_len < sizeof(name)) { + memcpy(name, buf->data, data_len); + name[data_len] = '\0'; + printk("Name: %s ", name); + } + net_buf_simple_pull(buf, data_len); + break; + + case BT_DATA_MANUFACTURER_DATA: + if (data_len >= 2) { + uint16_t company_id = sys_get_le16(buf->data); + printk("MFG: 0x%04x ", company_id); + + /* Check for our decision data */ + if (data_len >= 10) { + /* Check for "DECISION" string */ + if (memcmp(&buf->data[2], "DECISION", 8) == 0) { + printk("Data: DECISION "); + found_decision_data = true; + } + } + } + net_buf_simple_pull(buf, data_len); + break; + + default: + net_buf_simple_pull(buf, data_len); + break; + } + } + + printk("\n"); + + /* Count reports from our decision broadcaster */ + if (found_decision_data) { + adv_report_count++; + SET_FLAG(flag_adv_received); + printk("Decision advertising report received (count: %d)\n", adv_report_count); + } +} + +static struct bt_le_scan_cb scan_callbacks = { + .recv = scan_recv, +}; + +static void test_observer_main(void) +{ + struct bt_le_scan_param scan_param = { + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_DECISION_BASED, + .interval = BT_GAP_SCAN_FAST_INTERVAL, + .window = BT_GAP_SCAN_FAST_WINDOW, + }; + int err; + + printk("Decision-Based Advertising Observer Test\n"); + + adv_report_count = 0; + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + TEST_FAIL("Bluetooth init failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + /* Register scan callbacks */ + bt_le_scan_cb_register(&scan_callbacks); + + /* Start scanning with decision-based filtering */ + printk("Starting scan with decision-based filtering\n"); + err = bt_le_scan_start(&scan_param, NULL); + if (err) { + TEST_FAIL("Scan start failed (err %d)\n", err); + return; + } + + printk("Scanning successfully started\n"); + SET_FLAG(flag_scan_started); + + /* Wait for advertising reports */ + printk("Waiting for advertising reports...\n"); + + /* Wait for at least one report or timeout */ + WAIT_FOR_FLAG_UNSET_WITH_TIMEOUT(flag_adv_received, 8 * USEC_PER_SEC); + + if (!TEST_FLAG(flag_adv_received)) { + TEST_FAIL("No advertising reports received\n"); + return; + } + + /* Continue scanning for more reports */ + k_sleep(K_SECONDS(3)); + + /* Stop scanning */ + err = bt_le_scan_stop(); + if (err) { + TEST_FAIL("Scan stop failed (err %d)\n", err); + return; + } + + printk("Scanning stopped\n"); + printk("Total advertising reports received: %d\n", adv_report_count); + + /* Verify we received multiple reports */ + if (adv_report_count < MIN_EXPECTED_REPORTS) { + TEST_FAIL("Too few advertising reports (%d < %d)\n", + adv_report_count, MIN_EXPECTED_REPORTS); + return; + } + + printk("Test passed\n"); + TEST_PASS("Decision observer test passed"); +} + +static const struct bst_test_instance test_def[] = { + { + .test_id = "decision_observer", + .test_descr = "Decision-based advertising observer test", + .test_main_f = test_observer_main, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_decision_observer_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_def); +} + +bst_test_install_t test_installers[] = { + test_decision_observer_install, + NULL, +}; + +int main(void) +{ + bst_main(); + return 0; +} diff --git a/tests/bsim/bluetooth/host/adv/decision/testcase.yaml b/tests/bsim/bluetooth/host/adv/decision/testcase.yaml new file mode 100644 index 0000000000000..a9dfba1056b20 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/testcase.yaml @@ -0,0 +1,20 @@ +common: + build_only: true + tags: + - bluetooth + platform_allow: + - nrf52_bsim/native + - nrf5340bsim/nrf5340/cpunet + harness: bsim + +tests: + bluetooth.host.adv.decision.broadcaster: + harness_config: + bsim_exe_name: tests_bsim_bluetooth_host_adv_decision_prj_broadcaster_conf + extra_args: + CONF_FILE=prj_broadcaster.conf + bluetooth.host.adv.decision.observer: + harness_config: + bsim_exe_name: tests_bsim_bluetooth_host_adv_decision_prj_observer_conf + extra_args: + CONF_FILE=prj_observer.conf diff --git a/tests/bsim/bluetooth/host/adv/decision/tests_scripts/run.sh b/tests/bsim/bluetooth/host/adv/decision/tests_scripts/run.sh new file mode 100755 index 0000000000000..13ae7f8ed05f5 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/tests_scripts/run.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Decision-based advertising filtering test: +# +# - A Bluetooth LE broadcaster advertises with extended advertising and +# decision-based advertising filtering support, and an observer scans +# with decision-based filtering enabled. + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +simulation_id="decision_adv" +verbosity_level=2 + +cd ${BSIM_OUT_PATH}/bin + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_host_adv_decision_prj_broadcaster_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=0 -RealEncryption=0 \ + -testid=decision_broadcaster -rs=23 + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_host_adv_decision_prj_observer_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=1 -RealEncryption=0 \ + -testid=decision_observer -rs=6 + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \ + -D=2 -sim_length=10e6 $@ + +wait_for_background_jobs From 2952b1ed0fedf618dc8980e34e99019773d5fe05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:33:04 +0000 Subject: [PATCH 13/20] Fix code review issues in samples and tests - Initialize name buffer to prevent garbage data - Replace incorrect babblekit macro with simple loop - Improve timeout handling in observer test Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- samples/bluetooth/observer_decision/src/main.c | 2 +- .../bsim/bluetooth/host/adv/decision/src/observer_decision.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/bluetooth/observer_decision/src/main.c b/samples/bluetooth/observer_decision/src/main.c index 42ca711923bc4..3722a8942c455 100644 --- a/samples/bluetooth/observer_decision/src/main.c +++ b/samples/bluetooth/observer_decision/src/main.c @@ -24,7 +24,7 @@ static void scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) { char addr_str[BT_ADDR_LE_STR_LEN]; - char name[NAME_LEN]; + char name[NAME_LEN] = {0}; uint8_t data_len; uint8_t data_type; diff --git a/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c index b5e143cec2e22..e505aa70ad770 100644 --- a/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c +++ b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c @@ -147,7 +147,9 @@ static void test_observer_main(void) printk("Waiting for advertising reports...\n"); /* Wait for at least one report or timeout */ - WAIT_FOR_FLAG_UNSET_WITH_TIMEOUT(flag_adv_received, 8 * USEC_PER_SEC); + for (int i = 0; i < 8 && !TEST_FLAG(flag_adv_received); i++) { + k_sleep(K_SECONDS(1)); + } if (!TEST_FLAG(flag_adv_received)) { TEST_FAIL("No advertising reports received\n"); From 3863cdfbddd2b398375b4f24bcf53ab08b62daa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:33:08 +0000 Subject: [PATCH 14/20] Add advertising LLL support for ADV_DECISION_IND PDUs - Update Nordic LLL to treat ADV_DECISION_IND as TX-only (no RX window) - Update OpenISA LLL to treat ADV_DECISION_IND as TX-only (no RX window) - Decision PDUs behave like NONCONN_IND (no scan response or connection) - Completes the advertising side of decision-based filtering Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c | 3 +++ subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c index e5407b7aa1110..9e64b767873b4 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c @@ -1501,6 +1501,9 @@ static struct pdu_adv *chan_prepare(struct lll_adv *lll) radio_pkt_tx_set(pdu); if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND) && +#endif (!IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) || (pdu->type != PDU_ADV_TYPE_EXT_IND))) { struct pdu_adv *scan_pdu; diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c index 96b687aeca165..8c2b45025db43 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c @@ -801,6 +801,9 @@ static void chan_prepare(struct lll_adv *lll) radio_pkt_tx_set(pdu); if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND) && +#endif (!IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) || (pdu->type != PDU_ADV_TYPE_EXT_IND))) { radio_isr_set(isr_tx, lll); From bd8ed07b1ae0114d3aef7138f8d6d303c3706c76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:34:34 +0000 Subject: [PATCH 15/20] Remove #ifdef from HCI constants in public header HCI filter policy constants should always be defined in hci_types.h regardless of controller configuration. This matches the pattern used for extended filter policy constants (0x02-0x03) which are also always defined. The scan options in bluetooth.h correctly use: #if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || defined(__DOXYGEN__) which ensures documentation is always generated while the feature is conditionally available. Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- include/zephyr/bluetooth/hci_types.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/zephyr/bluetooth/hci_types.h b/include/zephyr/bluetooth/hci_types.h index 12d692a5f703a..292893b6208d8 100644 --- a/include/zephyr/bluetooth/hci_types.h +++ b/include/zephyr/bluetooth/hci_types.h @@ -1271,13 +1271,11 @@ struct bt_hci_cp_le_set_adv_enable { #define BT_HCI_LE_SCAN_FP_BASIC_FILTER 0x01 #define BT_HCI_LE_SCAN_FP_EXT_NO_FILTER 0x02 #define BT_HCI_LE_SCAN_FP_EXT_FILTER 0x03 -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) /* Decision-Based Advertising Filtering (BT Core Spec v6.2) */ #define BT_HCI_LE_SCAN_FP_DECISION_NO_FILTER 0x04 #define BT_HCI_LE_SCAN_FP_DECISION_FILTER 0x05 #define BT_HCI_LE_SCAN_FP_DECISION_EXT_NO_FILTER 0x06 #define BT_HCI_LE_SCAN_FP_DECISION_EXT_FILTER 0x07 -#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ struct bt_hci_cp_le_set_scan_param { uint8_t scan_type; From f2cbeb420678e861b7bc3dbbe5fba774ce7eab4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:35:52 +0000 Subject: [PATCH 16/20] Refactor LLL advertising to use IS_ENABLED() macro Replace #if defined() with IS_ENABLED() for better code readability and flow. This keeps the compound boolean expression cohesive without splitting it with preprocessor directives. Before: if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && #if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND) && #endif ... After: if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && (!IS_ENABLED(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND)) && ... Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c | 5 ++--- subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c index 9e64b767873b4..b1a18c95d2fa8 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c @@ -1501,9 +1501,8 @@ static struct pdu_adv *chan_prepare(struct lll_adv *lll) radio_pkt_tx_set(pdu); if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) - (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND) && -#endif + (!IS_ENABLED(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || + (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND)) && (!IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) || (pdu->type != PDU_ADV_TYPE_EXT_IND))) { struct pdu_adv *scan_pdu; diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c index 8c2b45025db43..0277409fc3d8a 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c @@ -801,9 +801,8 @@ static void chan_prepare(struct lll_adv *lll) radio_pkt_tx_set(pdu); if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) - (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND) && -#endif + (!IS_ENABLED(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || + (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND)) && (!IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) || (pdu->type != PDU_ADV_TYPE_EXT_IND))) { radio_isr_set(isr_tx, lll); From 20a25f79cb3d3c08c4f0ef4a088b9c120f6a8fb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:46:28 +0000 Subject: [PATCH 17/20] Add HCI commands and Host API for Decision Data/Instructions - Add LE Set Decision Data command (0x20A3) with structures - Add LE Set Decision Instructions command (0x20A4) with structures - Add bt_le_ext_adv_set_decision_data() public API - Add bt_le_scan_set_decision_instructions() public API - Implement Host functions in adv.c and scan.c - All APIs conditional on CONFIG_BT_CTLR_DECISION_BASED_FILTERING Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- include/zephyr/bluetooth/bluetooth.h | 48 ++++++++++++++++++++++++++++ include/zephyr/bluetooth/hci_types.h | 25 +++++++++++++++ subsys/bluetooth/host/adv.c | 47 +++++++++++++++++++++++++++ subsys/bluetooth/host/scan.c | 41 ++++++++++++++++++++++++ 4 files changed, 161 insertions(+) diff --git a/include/zephyr/bluetooth/bluetooth.h b/include/zephyr/bluetooth/bluetooth.h index bb0ac3e56327a..ba0401925265d 100644 --- a/include/zephyr/bluetooth/bluetooth.h +++ b/include/zephyr/bluetooth/bluetooth.h @@ -1477,6 +1477,30 @@ int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, size_t sd_len); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || defined(__DOXYGEN__) +/** + * @brief Set decision data for extended advertising. + * + * Set decision data that will be used for decision-based advertising filtering + * as specified in Bluetooth Core Specification v6.2, Vol 4, Part E, Section 7.8.144. + * This data is used by observers with decision-based filtering instructions to + * determine whether to accept advertising PDUs from this advertiser. + * + * The decision data is associated with the advertising set and remains valid + * until the advertising set is deleted or new decision data is set. + * + * @param adv Advertising set object. + * @param data Decision data buffer. + * @param data_len Length of decision data (must be > 0). + * + * @return Zero on success or (negative) error code otherwise. + * @retval -EINVAL Invalid parameters. + * @retval -ENOTSUP Decision-based filtering not supported by controller. + */ +int bt_le_ext_adv_set_decision_data(struct bt_le_ext_adv *adv, + const uint8_t *data, uint8_t data_len); +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + /** * @brief Update advertising parameters. * @@ -2650,6 +2674,30 @@ BUILD_ASSERT(BT_GAP_SCAN_FAST_WINDOW == BT_GAP_SCAN_FAST_INTERVAL_MIN, */ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || defined(__DOXYGEN__) +/** + * @brief Set decision instructions for scanning. + * + * Set decision instructions that will be used to filter advertising PDUs during + * scanning as specified in Bluetooth Core Specification v6.2, Vol 4, Part E, + * Section 7.8.145. These instructions define the criteria for accepting advertising + * PDUs based on the decision data included in the advertisements. + * + * The instructions remain active until new instructions are set or scanning is stopped. + * This function should be called before starting scanning with decision-based filtering + * enabled (BT_LE_SCAN_OPT_DECISION_BASED). + * + * @param instructions Decision instructions buffer. + * @param instructions_len Length of decision instructions (must be > 0). + * + * @return Zero on success or (negative) error code otherwise. + * @retval -EINVAL Invalid parameters. + * @retval -ENOTSUP Decision-based filtering not supported by controller. + */ +int bt_le_scan_set_decision_instructions(const uint8_t *instructions, + uint8_t instructions_len); +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + /** * @brief Stop (LE) scanning. * diff --git a/include/zephyr/bluetooth/hci_types.h b/include/zephyr/bluetooth/hci_types.h index 292893b6208d8..bb2c788e57783 100644 --- a/include/zephyr/bluetooth/hci_types.h +++ b/include/zephyr/bluetooth/hci_types.h @@ -2996,6 +2996,31 @@ struct bt_hci_op_le_connection_rate_request { #define BT_HCI_OP_LE_CONNECTION_RATE_REQUEST BT_OP(BT_OGF_LE, 0x00A1) /* 0x20A1 */ +/* LE Set Decision Data command (BT Core Spec v6.2, Vol 4, Part E, Section 7.8.144) */ +#define BT_HCI_OP_LE_SET_DECISION_DATA BT_OP(BT_OGF_LE, 0x00A3) /* 0x20A3 */ + +struct bt_hci_cp_le_set_decision_data { + uint8_t adv_handle; + uint8_t data_length; + uint8_t data[0]; +} __packed; + +struct bt_hci_rp_le_set_decision_data { + uint8_t status; +} __packed; + +/* LE Set Decision Instructions command (BT Core Spec v6.2, Vol 4, Part E, Section 7.8.145) */ +#define BT_HCI_OP_LE_SET_DECISION_INSTRUCTIONS BT_OP(BT_OGF_LE, 0x00A4) /* 0x20A4 */ + +struct bt_hci_cp_le_set_decision_instructions { + uint8_t instructions_length; + uint8_t instructions[0]; +} __packed; + +struct bt_hci_rp_le_set_decision_instructions { + uint8_t status; +} __packed; + /* Event definitions */ #define BT_HCI_EVT_UNKNOWN 0x00 diff --git a/subsys/bluetooth/host/adv.c b/subsys/bluetooth/host/adv.c index 1a8c94aba30fb..0d83da93030fa 100644 --- a/subsys/bluetooth/host/adv.c +++ b/subsys/bluetooth/host/adv.c @@ -1606,6 +1606,53 @@ int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv, return le_adv_update(adv, ad, ad_len, sd, sd_len, ext_adv, scannable); } +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +int bt_le_ext_adv_set_decision_data(struct bt_le_ext_adv *adv, + const uint8_t *data, uint8_t data_len) +{ + struct bt_hci_cp_le_set_decision_data *cp; + struct bt_hci_rp_le_set_decision_data *rp; + struct net_buf *buf, *rsp = NULL; + int err; + + if (!BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) { + return -ENOTSUP; + } + + CHECKIF(adv == NULL) { + LOG_DBG("adv is NULL"); + return -EINVAL; + } + + CHECKIF(data == NULL || data_len == 0) { + LOG_DBG("Invalid decision data parameters"); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(K_FOREVER); + if (!buf) { + LOG_WRN("No HCI buffers"); + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->adv_handle = adv->handle; + cp->data_length = data_len; + net_buf_add_mem(buf, data, data_len); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DECISION_DATA, buf, &rsp); + if (err) { + return err; + } + + rp = (void *)rsp->data; + err = rp->status; + net_buf_unref(rsp); + + return err; +} +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + int bt_le_ext_adv_delete(struct bt_le_ext_adv *adv) { struct bt_hci_cp_le_remove_adv_set *cp; diff --git a/subsys/bluetooth/host/scan.c b/subsys/bluetooth/host/scan.c index fc33870446787..6ff0630104490 100644 --- a/subsys/bluetooth/host/scan.c +++ b/subsys/bluetooth/host/scan.c @@ -1789,6 +1789,47 @@ static bool valid_le_scan_param(const struct bt_le_scan_param *param) return true; } +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +int bt_le_scan_set_decision_instructions(const uint8_t *instructions, + uint8_t instructions_len) +{ + struct bt_hci_cp_le_set_decision_instructions *cp; + struct bt_hci_rp_le_set_decision_instructions *rp; + struct net_buf *buf, *rsp = NULL; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) { + return -EAGAIN; + } + + CHECKIF(instructions == NULL || instructions_len == 0) { + LOG_DBG("Invalid decision instructions parameters"); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(K_FOREVER); + if (!buf) { + LOG_WRN("No HCI buffers"); + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->instructions_length = instructions_len; + net_buf_add_mem(buf, instructions, instructions_len); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DECISION_INSTRUCTIONS, buf, &rsp); + if (err) { + return err; + } + + rp = (void *)rsp->data; + err = rp->status; + net_buf_unref(rsp); + + return err; +} +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb) { int err; From 831af074f1abd2eaafa90cf83c36d3f43607f783 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:47:25 +0000 Subject: [PATCH 18/20] Update samples and tests to use decision data/instructions APIs - broadcaster_decision: Add bt_le_ext_adv_set_decision_data() call with sample decision data (device type, capability, battery, custom) - observer_decision: Add bt_le_scan_set_decision_instructions() call with sample instructions (filter criteria) - Update BabbleSim broadcaster test with decision data - Update BabbleSim observer test with decision instructions - Both samples and tests demonstrate the new API usage Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- .../bluetooth/broadcaster_decision/src/main.c | 18 +++++++++++++++++ .../bluetooth/observer_decision/src/main.c | 20 +++++++++++++++++++ .../adv/decision/src/broadcaster_decision.c | 16 +++++++++++++++ .../host/adv/decision/src/observer_decision.c | 18 +++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/samples/bluetooth/broadcaster_decision/src/main.c b/samples/bluetooth/broadcaster_decision/src/main.c index 52b4712d3afd7..c3c5a05e6afb0 100644 --- a/samples/bluetooth/broadcaster_decision/src/main.c +++ b/samples/bluetooth/broadcaster_decision/src/main.c @@ -77,6 +77,24 @@ int main(void) printk("Advertising data set\n"); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Set decision data for decision-based filtering */ + static const uint8_t decision_data[] = { + 0x01, /* Device type: sensor */ + 0x02, /* Capability: temperature measurement */ + 0x05, /* Battery level indicator */ + 0xFF /* Custom application data */ + }; + + err = bt_le_ext_adv_set_decision_data(adv, decision_data, sizeof(decision_data)); + if (err) { + printk("Failed to set decision data (err %d)\n", err); + return 0; + } + + printk("Decision data set (length: %d bytes)\n", sizeof(decision_data)); +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + /* Start extended advertising */ err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); if (err) { diff --git a/samples/bluetooth/observer_decision/src/main.c b/samples/bluetooth/observer_decision/src/main.c index 3722a8942c455..d480ed94ff2d6 100644 --- a/samples/bluetooth/observer_decision/src/main.c +++ b/samples/bluetooth/observer_decision/src/main.c @@ -112,6 +112,26 @@ int main(void) /* Register scan callbacks */ bt_le_scan_cb_register(&scan_callbacks); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Set decision instructions for filtering */ + static const uint8_t decision_instructions[] = { + 0x01, /* Filter on device type */ + 0x02, /* Accept if capability matches */ + 0x00, /* No additional criteria */ + }; + + printk("Setting decision instructions\n"); + err = bt_le_scan_set_decision_instructions(decision_instructions, + sizeof(decision_instructions)); + if (err) { + printk("Failed to set decision instructions (err %d)\n", err); + return 0; + } + + printk("Decision instructions set (length: %d bytes)\n", + sizeof(decision_instructions)); +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + /* Start scanning with decision-based filtering */ printk("Starting scan with decision-based filtering\n"); err = bt_le_scan_start(&scan_param, NULL); diff --git a/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c b/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c index 7a32a845dac83..6febd3253a8ec 100644 --- a/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c +++ b/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c @@ -83,6 +83,22 @@ static void test_broadcaster_main(void) printk("Advertising data set\n"); + /* Set decision data for decision-based filtering */ + static const uint8_t decision_data[] = { + 0x01, /* Device type: sensor */ + 0x02, /* Capability: temperature measurement */ + 0x05, /* Battery level indicator */ + 0xFF /* Custom application data */ + }; + + err = bt_le_ext_adv_set_decision_data(adv, decision_data, sizeof(decision_data)); + if (err) { + TEST_FAIL("Failed to set decision data (err %d)\n", err); + return; + } + + printk("Decision data set (length: %d bytes)\n", sizeof(decision_data)); + /* Start extended advertising */ err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); if (err) { diff --git a/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c index e505aa70ad770..3db5c184fc3a0 100644 --- a/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c +++ b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c @@ -132,6 +132,24 @@ static void test_observer_main(void) /* Register scan callbacks */ bt_le_scan_cb_register(&scan_callbacks); + /* Set decision instructions for filtering */ + static const uint8_t decision_instructions[] = { + 0x01, /* Filter on device type */ + 0x02, /* Accept if capability matches */ + 0x00, /* No additional criteria */ + }; + + printk("Setting decision instructions\n"); + err = bt_le_scan_set_decision_instructions(decision_instructions, + sizeof(decision_instructions)); + if (err) { + TEST_FAIL("Failed to set decision instructions (err %d)\n", err); + return; + } + + printk("Decision instructions set (length: %d bytes)\n", + sizeof(decision_instructions)); + /* Start scanning with decision-based filtering */ printk("Starting scan with decision-based filtering\n"); err = bt_le_scan_start(&scan_param, NULL); From ca0e1b6404f69507abb27a0727055dfe536d0479 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:49:05 +0000 Subject: [PATCH 19/20] Add Controller HCI command handlers for decision data/instructions - Add le_set_decision_data() handler in hci.c - Add le_set_decision_instructions() handler in hci.c - Add switch cases for both commands (0x20A3, 0x20A4) - Handlers return success with TODO for full implementation - Log received data for debugging - Conditional on CONFIG_BT_CTLR_DECISION_BASED_FILTERING Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- subsys/bluetooth/controller/hci/hci.c | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/subsys/bluetooth/controller/hci/hci.c b/subsys/bluetooth/controller/hci/hci.c index 31ea4fca15318..e426850975890 100644 --- a/subsys/bluetooth/controller/hci/hci.c +++ b/subsys/bluetooth/controller/hci/hci.c @@ -3365,6 +3365,42 @@ static void le_df_read_ant_inf(struct net_buf *buf, struct net_buf **evt) } #endif /* CONFIG_BT_CTLR_DF */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +static void le_set_decision_data(struct net_buf *buf, struct net_buf **evt) +{ + struct bt_hci_cp_le_set_decision_data *cmd = (void *)buf->data; + uint8_t status; + + /* For now, just return success - full implementation needed in controller */ + /* This will store decision data per advertising set */ + LOG_DBG("LE Set Decision Data: handle %u, len %u", cmd->adv_handle, cmd->data_length); + + /* TODO: Store decision data in advertising set structure */ + /* The data should be associated with the advertising handle */ + /* and used during PDU transmission */ + + status = BT_HCI_ERR_SUCCESS; + *evt = cmd_complete_status(status); +} + +static void le_set_decision_instructions(struct net_buf *buf, struct net_buf **evt) +{ + struct bt_hci_cp_le_set_decision_instructions *cmd = (void *)buf->data; + uint8_t status; + + /* For now, just return success - full implementation needed in controller */ + /* This will store decision instructions for scanner */ + LOG_DBG("LE Set Decision Instructions: len %u", cmd->instructions_length); + + /* TODO: Store decision instructions in scanner context */ + /* The instructions should be used during scan filtering */ + /* to match against decision data in received PDUs */ + + status = BT_HCI_ERR_SUCCESS; + *evt = cmd_complete_status(status); +} +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + #if defined(CONFIG_BT_CTLR_DTM_HCI) static void le_rx_test(struct net_buf *buf, struct net_buf **evt) { @@ -5031,6 +5067,16 @@ static int controller_cmd_handle(uint16_t ocf, struct net_buf *cmd, #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ #endif /* CONFIG_BT_CTLR_DF */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + case BT_OCF(BT_HCI_OP_LE_SET_DECISION_DATA): + le_set_decision_data(cmd, evt); + break; + + case BT_OCF(BT_HCI_OP_LE_SET_DECISION_INSTRUCTIONS): + le_set_decision_instructions(cmd, evt); + break; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + #if defined(CONFIG_BT_CTLR_DTM_HCI) case BT_OCF(BT_HCI_OP_LE_RX_TEST): le_rx_test(cmd, evt); From 78b9aab4d1c487fb8d0dd3af1d17a94652a21d8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 03:45:50 +0000 Subject: [PATCH 20/20] Remove conditional compilation from public API header Remove #if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) from bluetooth.h public API header per review feedback. Public API functions and enums are now always visible. Implementation files return -ENOTSUP when feature is disabled, allowing applications to check feature availability at runtime. Changes: - bt_le_ext_adv_set_decision_data() always declared - bt_le_scan_set_decision_instructions() always declared - BT_LE_SCAN_OPT_EXTENDED_FILTER always defined - BT_LE_SCAN_OPT_DECISION_BASED always defined - Implementations return -ENOTSUP when CONFIG not set Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com> --- include/zephyr/bluetooth/bluetooth.h | 6 ------ subsys/bluetooth/host/adv.c | 9 +++++++-- subsys/bluetooth/host/scan.c | 8 ++++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/include/zephyr/bluetooth/bluetooth.h b/include/zephyr/bluetooth/bluetooth.h index ba0401925265d..13a34f83a8214 100644 --- a/include/zephyr/bluetooth/bluetooth.h +++ b/include/zephyr/bluetooth/bluetooth.h @@ -1477,7 +1477,6 @@ int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, size_t sd_len); -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || defined(__DOXYGEN__) /** * @brief Set decision data for extended advertising. * @@ -1499,7 +1498,6 @@ int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv, */ int bt_le_ext_adv_set_decision_data(struct bt_le_ext_adv *adv, const uint8_t *data, uint8_t data_len); -#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ /** * @brief Update advertising parameters. @@ -2375,7 +2373,6 @@ enum bt_le_scan_opt { */ BT_LE_SCAN_OPT_NO_1M = BIT(3), -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || defined(__DOXYGEN__) /** * @brief Enable extended scan filtering. * @@ -2403,7 +2400,6 @@ enum bt_le_scan_opt { * filtering (CONFIG_BT_CTLR_DECISION_BASED_FILTERING). */ BT_LE_SCAN_OPT_DECISION_BASED = BIT(5), -#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ }; enum bt_le_scan_type { @@ -2674,7 +2670,6 @@ BUILD_ASSERT(BT_GAP_SCAN_FAST_WINDOW == BT_GAP_SCAN_FAST_INTERVAL_MIN, */ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb); -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || defined(__DOXYGEN__) /** * @brief Set decision instructions for scanning. * @@ -2696,7 +2691,6 @@ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb); */ int bt_le_scan_set_decision_instructions(const uint8_t *instructions, uint8_t instructions_len); -#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ /** * @brief Stop (LE) scanning. diff --git a/subsys/bluetooth/host/adv.c b/subsys/bluetooth/host/adv.c index 0d83da93030fa..76646c0084004 100644 --- a/subsys/bluetooth/host/adv.c +++ b/subsys/bluetooth/host/adv.c @@ -1606,10 +1606,10 @@ int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv, return le_adv_update(adv, ad, ad_len, sd, sd_len, ext_adv, scannable); } -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) int bt_le_ext_adv_set_decision_data(struct bt_le_ext_adv *adv, const uint8_t *data, uint8_t data_len) { +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) struct bt_hci_cp_le_set_decision_data *cp; struct bt_hci_rp_le_set_decision_data *rp; struct net_buf *buf, *rsp = NULL; @@ -1650,8 +1650,13 @@ int bt_le_ext_adv_set_decision_data(struct bt_le_ext_adv *adv, net_buf_unref(rsp); return err; -} +#else + ARG_UNUSED(adv); + ARG_UNUSED(data); + ARG_UNUSED(data_len); + return -ENOTSUP; #endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ +} int bt_le_ext_adv_delete(struct bt_le_ext_adv *adv) { diff --git a/subsys/bluetooth/host/scan.c b/subsys/bluetooth/host/scan.c index 6ff0630104490..04c68187963f9 100644 --- a/subsys/bluetooth/host/scan.c +++ b/subsys/bluetooth/host/scan.c @@ -1789,10 +1789,10 @@ static bool valid_le_scan_param(const struct bt_le_scan_param *param) return true; } -#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) int bt_le_scan_set_decision_instructions(const uint8_t *instructions, uint8_t instructions_len) { +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) struct bt_hci_cp_le_set_decision_instructions *cp; struct bt_hci_rp_le_set_decision_instructions *rp; struct net_buf *buf, *rsp = NULL; @@ -1827,8 +1827,12 @@ int bt_le_scan_set_decision_instructions(const uint8_t *instructions, net_buf_unref(rsp); return err; -} +#else + ARG_UNUSED(instructions); + ARG_UNUSED(instructions_len); + return -ENOTSUP; #endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ +} int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb) {