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/gap/discovery_filter.h"
16
17 #include <pw_bytes/endian.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
21 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
22
23 namespace bt::gap {
24
SetGeneralDiscoveryFlags()25 void DiscoveryFilter::SetGeneralDiscoveryFlags() {
26 set_flags(static_cast<uint8_t>(AdvFlag::kLEGeneralDiscoverableMode) |
27 static_cast<uint8_t>(AdvFlag::kLELimitedDiscoverableMode));
28 }
29
MatchLowEnergyResult(const std::optional<std::reference_wrapper<const AdvertisingData>> advertising_data,bool connectable,int8_t rssi) const30 bool DiscoveryFilter::MatchLowEnergyResult(
31 const std::optional<std::reference_wrapper<const AdvertisingData>>
32 advertising_data,
33 bool connectable,
34 int8_t rssi) const {
35 // No need to check |advertising_data| for the |connectable_| filter.
36 if (connectable_ && *connectable_ != connectable) {
37 return false;
38 }
39
40 // If a pathloss filter is not set then apply the RSSI filter before
41 // checking |advertising_data|. (An RSSI value of hci_spec::kRSSIInvalid means
42 // that RSSI is not available, which we check for here).
43 bool rssi_ok = !rssi_ || (rssi != hci_spec::kRSSIInvalid && rssi >= *rssi_);
44 if (!pathloss_ && !rssi_ok) {
45 return false;
46 }
47
48 // Any of these filters being set requires us to have a valid
49 // |advertising_data| to pass.
50 bool needs_ad_check = flags_ || !service_uuids_.empty() ||
51 !service_data_uuids_.empty() ||
52 !name_substring_.empty() || manufacturer_code_;
53
54 if (!advertising_data.has_value() && needs_ad_check) {
55 return false;
56 }
57
58 // Pathloss is complicated because we can pass if it's set and we have no
59 // |advertising_data| by passing RSSI instead.
60 if (pathloss_) {
61 if (!advertising_data.has_value() ||
62 !advertising_data->get().tx_power().has_value()) {
63 // If no RSSI filter was set OR if one was set but it didn't match the
64 // scan result, we fail.
65 if (!rssi_ || !rssi_ok) {
66 return false;
67 }
68 // Otherwise we fall back to RSSI passing if tx_power was not set.
69 } else {
70 int8_t tx_power_lvl = *advertising_data->get().tx_power();
71 if (tx_power_lvl < rssi) {
72 bt_log(WARN,
73 "gap",
74 "reported tx-power level is less than RSSI, failed pathloss");
75 return false;
76 }
77 int8_t pathloss = tx_power_lvl - rssi;
78 if (pathloss > *pathloss_) {
79 return false;
80 }
81 // mark the rssi_ok since we pass based on pathloss.
82 rssi_ok = true;
83 }
84 }
85
86 // If we made it here without advetising_data, and there's no need to check,
87 // we pass if rssi passed (which also passes if RSSI filtering was not set)
88 if (!advertising_data.has_value() && !needs_ad_check) {
89 return rssi_ok;
90 }
91
92 PW_DCHECK(advertising_data.has_value());
93 const AdvertisingData& ad = advertising_data->get();
94
95 if (flags_) {
96 if (all_flags_required_ && ad.flags() != flags_) {
97 return false;
98 }
99 if (!ad.flags().has_value()) {
100 return false;
101 }
102 uint8_t matched_flags = ad.flags().value() & *flags_;
103 if (matched_flags == 0) {
104 return false;
105 }
106 }
107
108 if (!name_substring_.empty()) {
109 if (!ad.local_name()) {
110 return false;
111 }
112 // TODO(jamuraa): If this is an incomplete name should we match the first
113 // part?
114 if (ad.local_name()->name.find(name_substring_) == std::string_view::npos) {
115 return false;
116 }
117 }
118
119 if (manufacturer_code_) {
120 if (ad.manufacturer_data_ids().find(*manufacturer_code_) ==
121 ad.manufacturer_data_ids().end()) {
122 return false;
123 }
124 }
125
126 if (!service_uuids_.empty()) {
127 bool service_found = false;
128 const auto& ad_service_uuids = ad.service_uuids();
129 for (auto uuid : service_uuids_) {
130 if (ad_service_uuids.count(uuid) != 0) {
131 service_found = true;
132 break;
133 }
134 }
135 if (!service_found) {
136 return false;
137 }
138 }
139
140 if (!service_data_uuids_.empty()) {
141 bool service_data_found = false;
142 const auto& ad_data_uuids = ad.service_data_uuids();
143 for (auto uuid : service_data_uuids_) {
144 if (ad_data_uuids.count(uuid) != 0) {
145 service_data_found = true;
146 break;
147 }
148 }
149 if (!service_data_found) {
150 return false;
151 }
152 }
153
154 // We haven't filtered it out, so it matches.
155 return true;
156 }
157
Reset()158 void DiscoveryFilter::Reset() {
159 service_uuids_.clear();
160 service_data_uuids_.clear();
161 name_substring_.clear();
162 connectable_.reset();
163 manufacturer_code_.reset();
164 pathloss_.reset();
165 rssi_.reset();
166 }
167
168 } // namespace bt::gap
169