// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "connections/implementation/mediums/ble.h" #include #include #include #include #include "absl/strings/escaping.h" #include "connections/implementation/mediums/ble_v2/ble_advertisement.h" #include "connections/implementation/mediums/bluetooth_radio.h" #include "connections/implementation/mediums/utils.h" #include "internal/platform/ble.h" #include "internal/platform/bluetooth_adapter.h" #include "internal/platform/byte_array.h" #include "internal/platform/cancellation_flag.h" #include "internal/platform/expected.h" #include "internal/platform/logging.h" #include "internal/platform/mutex_lock.h" #include "internal/platform/prng.h" namespace nearby { namespace connections { namespace { using location::nearby::proto::connections::OperationResultCode; } // namespace ByteArray Ble::GenerateHash(const std::string& source, size_t size) { return Utils::Sha256Hash(source, size); } ByteArray Ble::GenerateDeviceToken() { return Utils::Sha256Hash(std::to_string(Prng().NextUint32()), mediums::BleAdvertisement::kDeviceTokenLength); } Ble::Ble(BluetoothRadio& radio) : radio_(radio) {} bool Ble::IsAvailable() const { MutexLock lock(&mutex_); return IsAvailableLocked(); } bool Ble::IsAvailableLocked() const { return medium_.IsValid() && adapter_.IsValid() && adapter_.IsEnabled(); } bool Ble::StartAdvertising(const std::string& service_id, const ByteArray& advertisement_bytes, const std::string& fast_advertisement_service_uuid) { MutexLock lock(&mutex_); if (advertisement_bytes.Empty()) { LOG(INFO) << "Refusing to turn on BLE advertising. Empty advertisement data."; return false; } if (advertisement_bytes.size() > kMaxAdvertisementLength) { LOG(INFO) << "Refusing to start BLE advertising because the advertisement " "was too long. Expected at most " << kMaxAdvertisementLength << " bytes but received " << advertisement_bytes.size(); return false; } if (IsAdvertisingLocked(service_id)) { LOG(INFO) << "Failed to BLE advertise because we're already advertising."; return false; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't start BLE adveertising because Bluetooth was never turned on"; return false; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't turn on BLE advertising. BLE is not available."; return false; } LOG(INFO) << "Turning on BLE advertising (advertisement size=" << advertisement_bytes.size() << ")" << ", service id=" << service_id << ", fast advertisement service uuid=" << absl::BytesToHexString(fast_advertisement_service_uuid); // Wrap the connections advertisement to the medium advertisement. const bool fast_advertisement = !fast_advertisement_service_uuid.empty(); ByteArray service_id_hash{GenerateHash( service_id, mediums::BleAdvertisement::kServiceIdHashLength)}; ByteArray medium_advertisement_bytes{mediums::BleAdvertisement{ mediums::BleAdvertisement::Version::kV2, mediums::BleAdvertisement::SocketVersion::kV2, fast_advertisement ? ByteArray{} : service_id_hash, advertisement_bytes, GenerateDeviceToken()}}; if (medium_advertisement_bytes.Empty()) { LOG(INFO) << "Failed to BLE advertise because we could not " "create a medium advertisement."; return false; } if (!medium_.StartAdvertising(service_id, medium_advertisement_bytes, fast_advertisement_service_uuid)) { LOG(ERROR) << "Failed to turn on BLE advertising with advertisement bytes=" << absl::BytesToHexString(advertisement_bytes.data()) << ", size=" << advertisement_bytes.size() << ", fast advertisement service uuid=" << fast_advertisement_service_uuid; return false; } advertising_info_.Add(service_id); return true; } bool Ble::StopAdvertising(const std::string& service_id) { MutexLock lock(&mutex_); if (!IsAdvertisingLocked(service_id)) { LOG(INFO) << "Can't turn off BLE advertising; it is already off"; return false; } LOG(INFO) << "Turned off BLE advertising with service id=" << service_id; bool ret = medium_.StopAdvertising(service_id); // Reset our bundle of advertising state to mark that we're no longer // advertising. advertising_info_.Remove(service_id); return ret; } bool Ble::StartLegacyAdvertising( const std::string& input_service_id, const std::string& local_endpoint_id, const std::string& fast_advertisement_service_uuid) { LOG(INFO) << "StartLegacyAdvertising: " << input_service_id << ", local_endpoint_id: " << local_endpoint_id; MutexLock lock(&mutex_); std::string service_id = input_service_id + "-Legacy"; if (IsAdvertisingLocked(service_id)) { LOG(INFO) << "Failed to BLE legacy advertise because we're already advertising."; return false; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't start BLE legacy advertising because Bluetooth " "was never turned on"; return false; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't turn on BLE legacy advertising. BLE is not available."; return false; } // TODO(hais) improve working dummy set to feed proper hash value. std::array encoded_legacy_char_array = { 0x51, 0x43, 0x41, 0x41, 0x41, 0x42, 0x41, 0x43, 0x41, 0x41, 0x41, 0x44, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41}; ByteArray encoded_bytes{encoded_legacy_char_array}; LOG(INFO) << "Turning on BLE advertising (advertisement size=" << encoded_bytes.size() << "): " << absl::BytesToHexString(encoded_bytes.data()) << ", service id=" << service_id << ", fast advertisement service uuid=" << fast_advertisement_service_uuid; if (!medium_.StartAdvertising(service_id, encoded_bytes, fast_advertisement_service_uuid)) { LOG(ERROR) << "Failed to turn on BLE advertising with advertisement bytes=" << absl::BytesToHexString(encoded_bytes.data()) << ", size=" << encoded_bytes.size() << ", fast advertisement service uuid=" << fast_advertisement_service_uuid; return false; } advertising_info_.Add(service_id); return true; } bool Ble::StopLegacyAdvertising(const std::string& input_service_id) { LOG(INFO) << "StopLegacyAdvertising:" << input_service_id; MutexLock lock(&mutex_); std::string service_id = input_service_id + "-Legacy"; if (!IsAdvertisingLocked(service_id)) { LOG(INFO) << "Can't turn off BLE legacy advertising; it is already off"; return false; } LOG(INFO) << "Turned off BLE legacy advertising with service id=" << service_id; bool ret = medium_.StopAdvertising(service_id); // Reset our bundle of advertising state to mark that we're no longer // advertising. advertising_info_.Remove(service_id); return ret; } bool Ble::IsAdvertising(const std::string& service_id) { MutexLock lock(&mutex_); return IsAdvertisingLocked(service_id); } bool Ble::IsAdvertisingLocked(const std::string& service_id) { return advertising_info_.Existed(service_id); } bool Ble::StartScanning(const std::string& service_id, const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) { MutexLock lock(&mutex_); discovered_peripheral_callback_ = std::move(callback); if (service_id.empty()) { LOG(INFO) << "Refusing to start BLE scanning with empty service id."; return false; } if (IsScanningLocked(service_id)) { LOG(INFO) << "Refusing to start scan of BLE peripherals because " "another scanning is already in-progress."; return false; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't start BLE scanning because Bluetooth was never turned on"; return false; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't scan BLE peripherals because BLE isn't available."; return false; } if (!medium_.StartScanning( service_id, fast_advertisement_service_uuid, { .peripheral_discovered_cb = [this](BlePeripheral& peripheral, const std::string& service_id, const ByteArray& medium_advertisement_bytes, bool fast_advertisement) { // Don't bother trying to parse zero byte advertisements. if (medium_advertisement_bytes.size() == 0) { LOG(INFO) << "Skipping zero byte advertisement " << "with service_id: " << service_id; return; } // Unwrap connection BleAdvertisement from medium // BleAdvertisement. auto connection_advertisement_bytes = UnwrapAdvertisementBytes(medium_advertisement_bytes); discovered_peripheral_callback_.peripheral_discovered_cb( peripheral, service_id, connection_advertisement_bytes, fast_advertisement); }, .peripheral_lost_cb = [this](BlePeripheral& peripheral, const std::string& service_id) { discovered_peripheral_callback_.peripheral_lost_cb( peripheral, service_id); }, })) { LOG(INFO) << "Failed to start scan of BLE services."; return false; } LOG(INFO) << "Turned on BLE scanning with service id=" << service_id; // Mark the fact that we're currently performing a BLE discovering. scanning_info_.Add(service_id); return true; } bool Ble::StopScanning(const std::string& service_id) { MutexLock lock(&mutex_); if (!IsScanningLocked(service_id)) { LOG(INFO) << "Can't turn off BLE scanning because we never " "started scanning."; return false; } LOG(INFO) << "Turned off BLE scanning with service id=" << service_id; bool ret = medium_.StopScanning(service_id); scanning_info_.Clear(); return ret; } bool Ble::IsScanning(const std::string& service_id) { MutexLock lock(&mutex_); return IsScanningLocked(service_id); } bool Ble::IsScanningLocked(const std::string& service_id) { return scanning_info_.Existed(service_id); } bool Ble::StartAcceptingConnections(const std::string& service_id, AcceptedConnectionCallback callback) { MutexLock lock(&mutex_); if (service_id.empty()) { LOG(INFO) << "Refusing to start accepting BLE connections with empty service id."; return false; } if (IsAcceptingConnectionsLocked(service_id)) { LOG(INFO) << "Refusing to start accepting BLE connections for " << service_id << " because another BLE peripheral socket is already in-progress."; return false; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't start accepting BLE connections for " << service_id << " because Bluetooth isn't enabled."; return false; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't start accepting BLE connections for " << service_id << " because BLE isn't available."; return false; } if (!medium_.StartAcceptingConnections(service_id, std::move(callback))) { LOG(INFO) << "Failed to accept connections callback for " << service_id << " ."; return false; } accepting_connections_info_.Add(service_id); return true; } bool Ble::StopAcceptingConnections(const std::string& service_id) { MutexLock lock(&mutex_); if (!IsAcceptingConnectionsLocked(service_id)) { LOG(INFO) << "Can't stop accepting BLE connections because it was never started."; return false; } bool ret = medium_.StopAcceptingConnections(service_id); // Reset our bundle of accepting connections state to mark that we're no // longer accepting connections. accepting_connections_info_.Remove(service_id); return ret; } bool Ble::IsAcceptingConnections(const std::string& service_id) { MutexLock lock(&mutex_); return IsAcceptingConnectionsLocked(service_id); } bool Ble::IsAcceptingConnectionsLocked(const std::string& service_id) { return accepting_connections_info_.Existed(service_id); } ErrorOr Ble::Connect(BlePeripheral& peripheral, const std::string& service_id, CancellationFlag* cancellation_flag) { MutexLock lock(&mutex_); LOG(INFO) << "BLE::Connect: service=" << &peripheral; // Socket to return. To allow for NRVO to work, it has to be a single object. BleSocket socket; if (service_id.empty()) { LOG(INFO) << "Refusing to create BLE socket with empty service_id."; return {Error(OperationResultCode::NEARBY_LOCAL_CLIENT_STATE_WRONG)}; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't create client BLE socket to " << &peripheral << " because Bluetooth isn't enabled."; return {Error(OperationResultCode::MISCELLEANEOUS_BLE_SYSTEM_SERVICE_NULL)}; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't create client BLE socket [service_id=" << service_id << "]; BLE isn't available."; return {Error(OperationResultCode::MEDIUM_UNAVAILABLE_BLE_NOT_AVAILABLE)}; } if (cancellation_flag->Cancelled()) { LOG(INFO) << "Can't create client BLE socket due to cancel."; return {Error(OperationResultCode:: CLIENT_CANCELLATION_CANCEL_BLE_OUTGOING_CONNECTION)}; } socket = medium_.Connect(peripheral, service_id, cancellation_flag); if (!socket.IsValid()) { LOG(INFO) << "Failed to Connect via BLE [service=" << service_id << "]"; } return socket; } ByteArray Ble::UnwrapAdvertisementBytes( const ByteArray& medium_advertisement_data) { auto medium_ble_advertisement_status_or = mediums::BleAdvertisement::CreateBleAdvertisement( medium_advertisement_data); if (!medium_ble_advertisement_status_or.ok()) { LOG(INFO) << medium_ble_advertisement_status_or.status(); return ByteArray(); } return medium_ble_advertisement_status_or.value().GetData(); } } // namespace connections } // namespace nearby