// 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 "core/internal/mediums/ble.h" #include "platform/synchronized.h" namespace location { namespace nearby { namespace connections { template const std::int32_t BLE::kMaxAdvertisementLength = 512; template BLE::BLE(Ptr> bluetooth_radio) : lock_(Platform::createLock()), bluetooth_radio_(bluetooth_radio), bluetooth_adapter_(Platform::createBluetoothAdapter()), ble_medium_(Platform::createBLEMedium()), scanning_info_(), advertising_info_(), accepting_connections_info_() {} template BLE::~BLE() { stopAdvertising(); stopAcceptingConnections(); stopScanning(); } template bool BLE::isAvailable() { Synchronized s(lock_.get()); return !ble_medium_.isNull() && !bluetooth_adapter_.isNull(); } // TODO(ahlee): Add fastPairData for phase 2 of C++ implementation. template bool BLE::startAdvertising(const string& service_id, ConstPtr advertisement) { Synchronized s(lock_.get()); // Avoid leaks. ScopedPtr> scoped_advertisement(advertisement); if (scoped_advertisement.isNull() || service_id.empty()) { // TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising // because a null parameter was passed in."); return false; } if (scoped_advertisement->size() > kMaxAdvertisementLength) { // TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising // because the advertisement was too long. Expected at most %d bytes but // received %d.", kMaxAdvertisementLength, advertisement->size()); return false; } if (isAdvertising()) { // TODO(ahlee): logger.atSevere().log("Failed to BLE advertise because we're // already advertising."); return false; } if (!bluetooth_radio_->isEnabled()) { // TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because // Bluetooth isn't enabled."); return false; } if (!isAvailable()) { // TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because // BLE isn't enabled."); return false; } if (!ble_medium_->startAdvertising(service_id, scoped_advertisement.release())) { // TODO(ahlee) logger.atSevere().log("Failed to start BLE advertising"); return false; } advertising_info_ = MakePtr(new AdvertisingInfo(service_id)); return true; } template void BLE::stopAdvertising() { Synchronized s(lock_.get()); if (!isAdvertising()) { // TODO(ahlee): logger.atDebug().log("Can't turn off BLE advertising because // it never started."); return; } ble_medium_->stopAdvertising(advertising_info_->service_id); // Reset our bundle of advertising state to mark that we're no longer // advertising. advertising_info_.destroy(); // TODO(ahlee): logger.atVerbose().log("Turned BLE advertising off"); } template bool BLE::isAdvertising() { Synchronized s(lock_.get()); return !advertising_info_.isNull(); } template bool BLE::startScanning( const string& service_id, Ptr discovered_peripheral_callback) { Synchronized s(lock_.get()); // Avoid leaks. ScopedPtr> scoped_discovered_peripheral_callback(discovered_peripheral_callback); if (scoped_discovered_peripheral_callback.isNull() || service_id.empty()) { // TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning // because a null parameter was passed in."); return false; } if (isScanning()) { // TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning // because we are already scanning."); return false; } if (!bluetooth_radio_->isEnabled()) { // TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because // Bluetooth was never turned on"); return false; } if (!isAvailable()) { // TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because // BLE isn't available."); return false; } // Avoid leaks. ScopedPtr> scoped_ble_discovered_peripheral_callback( new BLEDiscoveredPeripheralCallback( scoped_discovered_peripheral_callback.release())); if (!ble_medium_->startScanning( service_id, scoped_ble_discovered_peripheral_callback.get())) { // TODO(ahlee): logger.atSevere().log("Failed to start BLE scanning."); return false; } scanning_info_ = MakePtr(new ScanningInfo( service_id, scoped_ble_discovered_peripheral_callback.release())); return true; } template void BLE::stopScanning() { Synchronized s(lock_.get()); if (!isScanning()) { // TODO(ahlee): logger.atDebug().log("Can't turn off BLE scanning because we // never started scanning."); return; } ble_medium_->stopScanning(scanning_info_->service_id); // Reset our bundle of scanning state to mark that we're no longer scanning. scanning_info_.destroy(); } template bool BLE::isScanning() { Synchronized s(lock_.get()); return !scanning_info_.isNull(); } template bool BLE::startAcceptingConnections( const string& service_id, Ptr accepted_connection_callback) { Synchronized s(lock_.get()); // Avoid leaks. ScopedPtr> scoped_accepted_connection_callback(accepted_connection_callback); if (scoped_accepted_connection_callback.isNull() || service_id.empty()) { // TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE // connections because a null parameter was passed in."); return false; } if (isAcceptingConnections()) { // TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE // connections for %s because another BLE server socket is already // in-progress.", service_id); return false; } if (!bluetooth_radio_->isEnabled()) { // TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections // for %s because Bluetooth isn't enabled.", serviceId); return false; } if (!isAvailable()) { // TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections // for %s because BLE isn't available.", serviceId); return false; } // Avoid leaks. ScopedPtr> scoped_ble_accepted_connection_callback(new BLEAcceptedConnectionCallback( scoped_accepted_connection_callback.release())); if (!ble_medium_->startAcceptingConnections( service_id, scoped_ble_accepted_connection_callback.get())) { return false; } accepting_connections_info_ = MakePtr(new AcceptingConnectionsInfo( service_id, scoped_ble_accepted_connection_callback.release())); return true; } template void BLE::stopAcceptingConnections() { Synchronized s(lock_.get()); if (!isAcceptingConnections()) { // TODO(ahlee): logger.atDebug().log("Can't stop accepting BLE connections // because it was never started."); return; } ble_medium_->stopAcceptingConnections( accepting_connections_info_->service_id); // Reset our bundle of accepting connections state to mark that we're no // longer accepting connections. accepting_connections_info_.destroy(); } template bool BLE::isAcceptingConnections() { Synchronized s(lock_.get()); return !accepting_connections_info_.isNull(); } template Ptr BLE::connect(Ptr ble_peripheral, const string& service_id) { Synchronized s(lock_.get()); if (ble_peripheral.isNull() || service_id.empty()) { // TODO(ahlee): logger.atSevere().log("Refusing to create client BLE socket // because at least one of blePeripheral or serviceId is null."); return Ptr(); } if (!bluetooth_radio_->isEnabled()) { // TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s // because Bluetooth isn't enabled.", blePeripheral); return Ptr(); } if (!isAvailable()) { // TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s // because BLE isn't available.", blePeripheral); return Ptr(); } return ble_medium_->connect(ble_peripheral, service_id); } } // namespace connections } // namespace nearby } // namespace location