1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/hci/discovery_filter.h"
16
17 #include <pw_assert/check.h>
18 #include <pw_bytes/endian.h>
19
20 #include "pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
21 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
22 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
23
24 namespace bt::hci {
25
SetGeneralDiscoveryFlags()26 void DiscoveryFilter::SetGeneralDiscoveryFlags() {
27 set_flags(static_cast<uint8_t>(AdvFlag::kLEGeneralDiscoverableMode) |
28 static_cast<uint8_t>(AdvFlag::kLELimitedDiscoverableMode));
29 }
30
MatchLowEnergyResult(const std::optional<std::reference_wrapper<const AdvertisingData>> advertising_data,bool connectable,int8_t rssi) const31 bool DiscoveryFilter::MatchLowEnergyResult(
32 const std::optional<std::reference_wrapper<const AdvertisingData>>
33 advertising_data,
34 bool connectable,
35 int8_t rssi) const {
36 // No need to check |advertising_data| for the |connectable_| filter.
37 if (connectable_ && *connectable_ != connectable) {
38 return false;
39 }
40
41 // If a pathloss filter is not set then apply the RSSI filter before
42 // checking |advertising_data|. (An RSSI value of hci_spec::kRSSIInvalid means
43 // that RSSI is not available, which we check for here).
44 bool rssi_ok = !rssi_ || (rssi != hci_spec::kRSSIInvalid && rssi >= *rssi_);
45 if (!pathloss_ && !rssi_ok) {
46 return false;
47 }
48
49 // Any of these filters being set requires us to have a valid
50 // |advertising_data| to pass.
51 bool needs_ad_check = flags_ || !service_uuids_.empty() ||
52 !service_data_uuids_.empty() ||
53 !solicitation_uuids_.empty() ||
54 !name_substring_.empty() || manufacturer_code_;
55
56 if (!advertising_data.has_value() && needs_ad_check) {
57 return false;
58 }
59
60 // Pathloss is complicated because we can pass if it's set and we have no
61 // |advertising_data| by passing RSSI instead.
62 if (pathloss_) {
63 if (!advertising_data.has_value() ||
64 !advertising_data->get().tx_power().has_value()) {
65 // If no RSSI filter was set OR if one was set but it didn't match the
66 // scan result, we fail.
67 if (!rssi_ || !rssi_ok) {
68 return false;
69 }
70 // Otherwise we fall back to RSSI passing if tx_power was not set.
71 } else {
72 int8_t tx_power_lvl = *advertising_data->get().tx_power();
73 if (tx_power_lvl < rssi) {
74 bt_log(WARN,
75 "gap",
76 "reported tx-power level is less than RSSI, failed pathloss");
77 return false;
78 }
79 int8_t pathloss = tx_power_lvl - rssi;
80 if (pathloss > *pathloss_) {
81 return false;
82 }
83 // mark the rssi_ok since we pass based on pathloss.
84 rssi_ok = true;
85 }
86 }
87
88 // If we made it here without advetising_data, and there's no need to check,
89 // we pass if rssi passed (which also passes if RSSI filtering was not set)
90 if (!advertising_data.has_value() && !needs_ad_check) {
91 return rssi_ok;
92 }
93
94 PW_DCHECK(advertising_data.has_value());
95 const AdvertisingData& ad = advertising_data->get();
96
97 if (flags_) {
98 if (all_flags_required_ && ad.flags() != flags_) {
99 return false;
100 }
101 if (!ad.flags().has_value()) {
102 return false;
103 }
104 uint8_t matched_flags = ad.flags().value() & *flags_;
105 if (matched_flags == 0) {
106 return false;
107 }
108 }
109
110 if (!name_substring_.empty()) {
111 if (!ad.local_name()) {
112 return false;
113 }
114 // TODO(jamuraa): If this is an incomplete name should we match the first
115 // part?
116 if (ad.local_name()->name.find(name_substring_) == std::string_view::npos) {
117 return false;
118 }
119 }
120
121 if (manufacturer_code_) {
122 if (ad.manufacturer_data_ids().find(*manufacturer_code_) ==
123 ad.manufacturer_data_ids().end()) {
124 return false;
125 }
126 }
127
128 if (!service_uuids_.empty()) {
129 bool service_found = false;
130 const auto& ad_service_uuids = ad.service_uuids();
131 for (auto uuid : service_uuids_) {
132 if (ad_service_uuids.count(uuid) != 0) {
133 service_found = true;
134 break;
135 }
136 }
137 if (!service_found) {
138 return false;
139 }
140 }
141
142 if (!service_data_uuids_.empty()) {
143 bool service_data_found = false;
144 const auto& ad_data_uuids = ad.service_data_uuids();
145 for (auto uuid : service_data_uuids_) {
146 if (ad_data_uuids.count(uuid) != 0) {
147 service_data_found = true;
148 break;
149 }
150 }
151 if (!service_data_found) {
152 return false;
153 }
154 }
155
156 if (!solicitation_uuids_.empty()) {
157 bool solicitation_uuid_found = false;
158 const auto& ad_solicitation_uuids = ad.solicitation_uuids();
159 for (auto uuid : solicitation_uuids_) {
160 if (ad_solicitation_uuids.count(uuid) != 0) {
161 solicitation_uuid_found = true;
162 break;
163 }
164 }
165 if (!solicitation_uuid_found) {
166 return false;
167 }
168 }
169
170 // We haven't filtered it out, so it matches.
171 return true;
172 }
173
Reset()174 void DiscoveryFilter::Reset() {
175 service_uuids_.clear();
176 service_data_uuids_.clear();
177 solicitation_uuids_.clear();
178 name_substring_.clear();
179 connectable_.reset();
180 manufacturer_code_.reset();
181 pathloss_.reset();
182 rssi_.reset();
183 }
184
185 } // namespace bt::hci
186