1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of 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,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "core/internal/mediums/ble.h"
16
17 #include <memory>
18 #include <string>
19 #include <utility>
20
21 #include "core/internal/mediums/ble_v2/ble_advertisement.h"
22 #include "core/internal/mediums/utils.h"
23 #include "platform/base/prng.h"
24 #include "platform/public/logging.h"
25 #include "platform/public/mutex_lock.h"
26
27 namespace location {
28 namespace nearby {
29 namespace connections {
30
GenerateHash(const std::string & source,size_t size)31 ByteArray Ble::GenerateHash(const std::string& source, size_t size) {
32 return Utils::Sha256Hash(source, size);
33 }
34
GenerateDeviceToken()35 ByteArray Ble::GenerateDeviceToken() {
36 return Utils::Sha256Hash(std::to_string(Prng().NextUint32()),
37 mediums::BleAdvertisement::kDeviceTokenLength);
38 }
39
Ble(BluetoothRadio & radio)40 Ble::Ble(BluetoothRadio& radio) : radio_(radio) {}
41
IsAvailable() const42 bool Ble::IsAvailable() const {
43 MutexLock lock(&mutex_);
44
45 return IsAvailableLocked();
46 }
47
IsAvailableLocked() const48 bool Ble::IsAvailableLocked() const { return medium_.IsValid(); }
49
StartAdvertising(const std::string & service_id,const ByteArray & advertisement_bytes,const std::string & fast_advertisement_service_uuid)50 bool Ble::StartAdvertising(const std::string& service_id,
51 const ByteArray& advertisement_bytes,
52 const std::string& fast_advertisement_service_uuid) {
53 MutexLock lock(&mutex_);
54
55 if (advertisement_bytes.Empty()) {
56 NEARBY_LOGS(INFO)
57 << "Refusing to turn on BLE advertising. Empty advertisement data.";
58 return false;
59 }
60
61 if (advertisement_bytes.size() > kMaxAdvertisementLength) {
62 NEARBY_LOG(INFO,
63 "Refusing to start BLE advertising because the advertisement "
64 "was too long. Expected at most %d bytes but received %d.",
65 kMaxAdvertisementLength, advertisement_bytes.size());
66 return false;
67 }
68
69 if (IsAdvertisingLocked(service_id)) {
70 NEARBY_LOGS(INFO)
71 << "Failed to BLE advertise because we're already advertising.";
72 return false;
73 }
74
75 if (!radio_.IsEnabled()) {
76 NEARBY_LOGS(INFO)
77 << "Can't start BLE scanning because Bluetooth was never turned on";
78 return false;
79 }
80
81 if (!IsAvailableLocked()) {
82 NEARBY_LOGS(INFO) << "Can't turn on BLE advertising. BLE is not available.";
83 return false;
84 }
85
86 NEARBY_LOGS(INFO) << "Turning on BLE advertising with advertisement bytes="
87 << advertisement_bytes.data() << "("
88 << advertisement_bytes.size() << ")"
89 << ", service id=" << service_id
90 << ", fast advertisement service uuid="
91 << fast_advertisement_service_uuid;
92
93 // Wrap the connections advertisement to the medium advertisement.
94 const bool fast_advertisement = !fast_advertisement_service_uuid.empty();
95 ByteArray service_id_hash{GenerateHash(
96 service_id, mediums::BleAdvertisement::kServiceIdHashLength)};
97 ByteArray medium_advertisement_bytes{mediums::BleAdvertisement{
98 mediums::BleAdvertisement::Version::kV2,
99 mediums::BleAdvertisement::SocketVersion::kV2,
100 fast_advertisement ? ByteArray{} : service_id_hash, advertisement_bytes,
101 GenerateDeviceToken()}};
102 if (medium_advertisement_bytes.Empty()) {
103 NEARBY_LOGS(INFO) << "Failed to BLE advertise because we could not "
104 "create a medium advertisement.";
105 return false;
106 }
107
108 if (!medium_.StartAdvertising(service_id, medium_advertisement_bytes,
109 fast_advertisement_service_uuid)) {
110 NEARBY_LOGS(INFO)
111 << "Failed to turn on BLE advertising with advertisement bytes="
112 << advertisement_bytes.data() << "(" << advertisement_bytes.size()
113 << ")"
114 << ", fast advertisement service uuid="
115 << fast_advertisement_service_uuid;
116 return false;
117 }
118
119 advertising_info_.Add(service_id);
120 return true;
121 }
122
StopAdvertising(const std::string & service_id)123 bool Ble::StopAdvertising(const std::string& service_id) {
124 MutexLock lock(&mutex_);
125
126 if (!IsAdvertisingLocked(service_id)) {
127 NEARBY_LOGS(INFO) << "Can't turn off BLE advertising; it is already off";
128 return false;
129 }
130
131 NEARBY_LOGS(INFO) << "Turned off BLE advertising with service id="
132 << service_id;
133 bool ret = medium_.StopAdvertising(service_id);
134 // Reset our bundle of advertising state to mark that we're no longer
135 // advertising.
136 advertising_info_.Remove(service_id);
137 return ret;
138 }
139
IsAdvertising(const std::string & service_id)140 bool Ble::IsAdvertising(const std::string& service_id) {
141 MutexLock lock(&mutex_);
142
143 return IsAdvertisingLocked(service_id);
144 }
145
IsAdvertisingLocked(const std::string & service_id)146 bool Ble::IsAdvertisingLocked(const std::string& service_id) {
147 return advertising_info_.Existed(service_id);
148 }
149
StartScanning(const std::string & service_id,const std::string & fast_advertisement_service_uuid,DiscoveredPeripheralCallback callback)150 bool Ble::StartScanning(const std::string& service_id,
151 const std::string& fast_advertisement_service_uuid,
152 DiscoveredPeripheralCallback callback) {
153 MutexLock lock(&mutex_);
154
155 discovered_peripheral_callback_ = std::move(callback);
156
157 if (service_id.empty()) {
158 NEARBY_LOGS(INFO)
159 << "Refusing to start BLE scanning with empty service id.";
160 return false;
161 }
162
163 if (IsScanningLocked(service_id)) {
164 NEARBY_LOGS(INFO) << "Refusing to start scan of BLE peripherals because "
165 "another scanning is already in-progress.";
166 return false;
167 }
168
169 if (!radio_.IsEnabled()) {
170 NEARBY_LOGS(INFO)
171 << "Can't start BLE scanning because Bluetooth was never turned on";
172 return false;
173 }
174
175 if (!IsAvailableLocked()) {
176 NEARBY_LOGS(INFO)
177 << "Can't scan BLE peripherals because BLE isn't available.";
178 return false;
179 }
180
181 if (!medium_.StartScanning(
182 service_id, fast_advertisement_service_uuid,
183 {
184 .peripheral_discovered_cb =
185 [this](BlePeripheral& peripheral,
186 const std::string& service_id,
187 const ByteArray& medium_advertisement_bytes,
188 bool fast_advertisement) {
189 // Unwrap connection BleAdvertisement from medium
190 // BleAdvertisement.
191 auto connection_advertisement_bytes =
192 UnwrapAdvertisementBytes(medium_advertisement_bytes);
193 discovered_peripheral_callback_.peripheral_discovered_cb(
194 peripheral, service_id, connection_advertisement_bytes,
195 fast_advertisement);
196 },
197 .peripheral_lost_cb =
198 [this](BlePeripheral& peripheral,
199 const std::string& service_id) {
200 discovered_peripheral_callback_.peripheral_lost_cb(
201 peripheral, service_id);
202 },
203 })) {
204 NEARBY_LOGS(INFO) << "Failed to start scan of BLE services.";
205 return false;
206 }
207
208 NEARBY_LOGS(INFO) << "Turned on BLE scanning with service id=" << service_id;
209 // Mark the fact that we're currently performing a BLE discovering.
210 scanning_info_.Add(service_id);
211 return true;
212 }
213
StopScanning(const std::string & service_id)214 bool Ble::StopScanning(const std::string& service_id) {
215 MutexLock lock(&mutex_);
216
217 if (!IsScanningLocked(service_id)) {
218 NEARBY_LOGS(INFO) << "Can't turn off BLE sacanning because we never "
219 "started scanning.";
220 return false;
221 }
222
223 NEARBY_LOG(INFO, "Turned off BLE scanning with service id=%s",
224 service_id.c_str());
225 bool ret = medium_.StopScanning(service_id);
226 scanning_info_.Clear();
227 return ret;
228 }
229
IsScanning(const std::string & service_id)230 bool Ble::IsScanning(const std::string& service_id) {
231 MutexLock lock(&mutex_);
232
233 return IsScanningLocked(service_id);
234 }
235
IsScanningLocked(const std::string & service_id)236 bool Ble::IsScanningLocked(const std::string& service_id) {
237 return scanning_info_.Existed(service_id);
238 }
239
StartAcceptingConnections(const std::string & service_id,AcceptedConnectionCallback callback)240 bool Ble::StartAcceptingConnections(const std::string& service_id,
241 AcceptedConnectionCallback callback) {
242 MutexLock lock(&mutex_);
243
244 if (service_id.empty()) {
245 NEARBY_LOGS(INFO)
246 << "Refusing to start accepting BLE connections with empty service id.";
247 return false;
248 }
249
250 if (IsAcceptingConnectionsLocked(service_id)) {
251 NEARBY_LOGS(INFO)
252 << "Refusing to start accepting BLE connections for "
253 << service_id
254 << " because another BLE peripheral socket is already in-progress.";
255 return false;
256 }
257
258 if (!radio_.IsEnabled()) {
259 NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
260 << service_id
261 << " because Bluetooth isn't enabled.";
262 return false;
263 }
264
265 if (!IsAvailableLocked()) {
266 NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
267 << service_id << " because BLE isn't available.";
268 return false;
269 }
270
271 if (!medium_.StartAcceptingConnections(service_id, callback)) {
272 NEARBY_LOGS(INFO) << "Failed to accept connections callback for "
273 << service_id << " .";
274 return false;
275 }
276
277 accepting_connections_info_.Add(service_id);
278 return true;
279 }
280
StopAcceptingConnections(const std::string & service_id)281 bool Ble::StopAcceptingConnections(const std::string& service_id) {
282 MutexLock lock(&mutex_);
283
284 if (!IsAcceptingConnectionsLocked(service_id)) {
285 NEARBY_LOGS(INFO)
286 << "Can't stop accepting BLE connections because it was never started.";
287 return false;
288 }
289
290 bool ret = medium_.StopAcceptingConnections(service_id);
291 // Reset our bundle of accepting connections state to mark that we're no
292 // longer accepting connections.
293 accepting_connections_info_.Remove(service_id);
294 return ret;
295 }
296
IsAcceptingConnections(const std::string & service_id)297 bool Ble::IsAcceptingConnections(const std::string& service_id) {
298 MutexLock lock(&mutex_);
299
300 return IsAcceptingConnectionsLocked(service_id);
301 }
302
IsAcceptingConnectionsLocked(const std::string & service_id)303 bool Ble::IsAcceptingConnectionsLocked(const std::string& service_id) {
304 return accepting_connections_info_.Existed(service_id);
305 }
306
Connect(BlePeripheral & peripheral,const std::string & service_id)307 BleSocket Ble::Connect(BlePeripheral& peripheral,
308 const std::string& service_id) {
309 MutexLock lock(&mutex_);
310 NEARBY_LOGS(INFO) << "BLE::Connect: service=" << &peripheral;
311 // Socket to return. To allow for NRVO to work, it has to be a single object.
312 BleSocket socket;
313
314 if (service_id.empty()) {
315 NEARBY_LOGS(INFO) << "Refusing to create BLE socket with empty service_id.";
316 return socket;
317 }
318
319 if (!radio_.IsEnabled()) {
320 NEARBY_LOGS(INFO) << "Can't create client BLE socket to "
321 << &peripheral << " because Bluetooth isn't enabled.";
322 return socket;
323 }
324
325 if (!IsAvailableLocked()) {
326 NEARBY_LOGS(INFO) << "Can't create client BLE socket [service_id="
327 << service_id << "]; BLE isn't available.";
328 return socket;
329 }
330
331 socket = medium_.Connect(peripheral, service_id);
332 if (!socket.IsValid()) {
333 NEARBY_LOGS(INFO) << "Failed to Connect via BLE [service=" << service_id
334 << "]";
335 }
336
337 return socket;
338 }
339
UnwrapAdvertisementBytes(const ByteArray & medium_advertisement_data)340 ByteArray Ble::UnwrapAdvertisementBytes(
341 const ByteArray& medium_advertisement_data) {
342 mediums::BleAdvertisement medium_ble_advertisement{medium_advertisement_data};
343 if (!medium_ble_advertisement.IsValid()) {
344 return ByteArray{};
345 }
346
347 return medium_ble_advertisement.GetData();
348 }
349
350 } // namespace connections
351 } // namespace nearby
352 } // namespace location
353