xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/gap/discovery_filter.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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