1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/bluetooth/bluetooth_blocklist.h"
6 
7 #include "base/check.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/optional.h"
10 #include "base/strings/string_split.h"
11 #include "content/public/browser/content_browser_client.h"
12 #include "content/public/common/content_client.h"
13 
14 using device::BluetoothUUID;
15 
16 namespace {
17 
18 static base::LazyInstance<content::BluetoothBlocklist>::Leaky g_singleton =
19     LAZY_INSTANCE_INITIALIZER;
20 
21 }  // namespace
22 
23 namespace content {
24 
~BluetoothBlocklist()25 BluetoothBlocklist::~BluetoothBlocklist() {}
26 
27 // static
Get()28 BluetoothBlocklist& BluetoothBlocklist::Get() {
29   return g_singleton.Get();
30 }
31 
Add(const BluetoothUUID & uuid,Value value)32 void BluetoothBlocklist::Add(const BluetoothUUID& uuid, Value value) {
33   CHECK(uuid.IsValid());
34   auto insert_result = blocklisted_uuids_.insert(std::make_pair(uuid, value));
35   bool inserted = insert_result.second;
36   if (!inserted) {
37     Value& stored = insert_result.first->second;
38     if (stored != value)
39       stored = Value::EXCLUDE;
40   }
41 }
42 
Add(base::StringPiece blocklist_string)43 void BluetoothBlocklist::Add(base::StringPiece blocklist_string) {
44   if (blocklist_string.empty())
45     return;
46   base::StringPairs kv_pairs;
47   bool parsed_values = false;
48   bool invalid_values = false;
49   SplitStringIntoKeyValuePairs(blocklist_string,
50                                ':',  // Key-value delimiter
51                                ',',  // Key-value pair delimiter
52                                &kv_pairs);
53   for (const auto& pair : kv_pairs) {
54     BluetoothUUID uuid(pair.first);
55     if (uuid.IsValid() && pair.second.size() == 1u) {
56       switch (pair.second[0]) {
57         case 'e':
58           Add(uuid, Value::EXCLUDE);
59           parsed_values = true;
60           continue;
61         case 'r':
62           Add(uuid, Value::EXCLUDE_READS);
63           parsed_values = true;
64           continue;
65         case 'w':
66           Add(uuid, Value::EXCLUDE_WRITES);
67           parsed_values = true;
68           continue;
69       }
70     }
71     invalid_values = true;
72   }
73 }
74 
IsExcluded(const BluetoothUUID & uuid) const75 bool BluetoothBlocklist::IsExcluded(const BluetoothUUID& uuid) const {
76   CHECK(uuid.IsValid());
77   const auto& it = blocklisted_uuids_.find(uuid);
78   if (it == blocklisted_uuids_.end())
79     return false;
80   return it->second == Value::EXCLUDE;
81 }
82 
IsExcluded(const std::vector<blink::mojom::WebBluetoothLeScanFilterPtr> & filters)83 bool BluetoothBlocklist::IsExcluded(
84     const std::vector<blink::mojom::WebBluetoothLeScanFilterPtr>& filters) {
85   for (const blink::mojom::WebBluetoothLeScanFilterPtr& filter : filters) {
86     if (!filter->services) {
87       continue;
88     }
89     for (const BluetoothUUID& service : filter->services.value()) {
90       if (IsExcluded(service)) {
91         return true;
92       }
93     }
94   }
95   return false;
96 }
97 
IsExcludedFromReads(const BluetoothUUID & uuid) const98 bool BluetoothBlocklist::IsExcludedFromReads(const BluetoothUUID& uuid) const {
99   CHECK(uuid.IsValid());
100   const auto& it = blocklisted_uuids_.find(uuid);
101   if (it == blocklisted_uuids_.end())
102     return false;
103   return it->second == Value::EXCLUDE || it->second == Value::EXCLUDE_READS;
104 }
105 
IsExcludedFromWrites(const BluetoothUUID & uuid) const106 bool BluetoothBlocklist::IsExcludedFromWrites(const BluetoothUUID& uuid) const {
107   CHECK(uuid.IsValid());
108   const auto& it = blocklisted_uuids_.find(uuid);
109   if (it == blocklisted_uuids_.end())
110     return false;
111   return it->second == Value::EXCLUDE || it->second == Value::EXCLUDE_WRITES;
112 }
113 
RemoveExcludedUUIDs(blink::mojom::WebBluetoothRequestDeviceOptions * options)114 void BluetoothBlocklist::RemoveExcludedUUIDs(
115     blink::mojom::WebBluetoothRequestDeviceOptions* options) {
116   std::vector<device::BluetoothUUID> optional_services_blocklist_filtered;
117   for (const BluetoothUUID& uuid : options->optional_services) {
118     if (!IsExcluded(uuid)) {
119       optional_services_blocklist_filtered.push_back(uuid);
120     }
121   }
122   options->optional_services = std::move(optional_services_blocklist_filtered);
123 }
124 
ResetToDefaultValuesForTest()125 void BluetoothBlocklist::ResetToDefaultValuesForTest() {
126   blocklisted_uuids_.clear();
127   PopulateWithDefaultValues();
128   PopulateWithServerProvidedValues();
129 }
130 
BluetoothBlocklist()131 BluetoothBlocklist::BluetoothBlocklist() {
132   PopulateWithDefaultValues();
133   PopulateWithServerProvidedValues();
134 }
135 
PopulateWithDefaultValues()136 void BluetoothBlocklist::PopulateWithDefaultValues() {
137   blocklisted_uuids_.clear();
138 
139   // Testing from Web Tests Note:
140   //
141   // Random UUIDs for object & exclude permutations that do not exist in the
142   // standard blocklist are included to facilitate integration testing from
143   // Web Tests.  Unit tests can dynamically modify the blocklist, but don't
144   // offer the full integration test to the Web Bluetooth Javascript bindings.
145   //
146   // This is done for simplicity as opposed to exposing a testing API that can
147   // add to the blocklist over time, which would be over engineered.
148   //
149   // Remove testing UUIDs if the specified blocklist is updated to include UUIDs
150   // that match the specific permutations.
151   DCHECK(BluetoothUUID("00001800-0000-1000-8000-00805f9b34fb") ==
152          BluetoothUUID("1800"));
153 
154   // Blocklist UUIDs updated 2016-09-01 from:
155   // https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt
156   // Short UUIDs are used for readability of this list.
157   //
158   // Services:
159   Add(BluetoothUUID("1812"), Value::EXCLUDE);
160   Add(BluetoothUUID("00001530-1212-efde-1523-785feabcd123"), Value::EXCLUDE);
161   Add(BluetoothUUID("f000ffc0-0451-4000-b000-000000000000"), Value::EXCLUDE);
162   Add(BluetoothUUID("00060000"), Value::EXCLUDE);
163   Add(BluetoothUUID("fffd"), Value::EXCLUDE);
164   // Characteristics:
165   Add(BluetoothUUID("2a02"), Value::EXCLUDE_WRITES);
166   Add(BluetoothUUID("2a03"), Value::EXCLUDE);
167   Add(BluetoothUUID("2a25"), Value::EXCLUDE);
168   // Characteristics for Web Tests:
169   Add(BluetoothUUID("bad1c9a2-9a5b-4015-8b60-1579bbbf2135"),
170       Value::EXCLUDE_READS);
171   // Descriptors:
172   Add(BluetoothUUID("2902"), Value::EXCLUDE_WRITES);
173   Add(BluetoothUUID("2903"), Value::EXCLUDE_WRITES);
174   // Descriptors for Web Tests:
175   Add(BluetoothUUID("bad2ddcf-60db-45cd-bef9-fd72b153cf7c"), Value::EXCLUDE);
176   Add(BluetoothUUID("bad3ec61-3cc3-4954-9702-7977df514114"),
177       Value::EXCLUDE_READS);
178 }
179 
PopulateWithServerProvidedValues()180 void BluetoothBlocklist::PopulateWithServerProvidedValues() {
181   Add(GetContentClient()->browser()->GetWebBluetoothBlocklist());
182 }
183 
184 }  // namespace content
185