• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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