/* * * Copyright (c) 2021-2022 Project CHIP Authors * * 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 * * http://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 "ChipDeviceScanner.h" #include #include #include #include #include #include #include #include #include "Types.h" namespace chip { namespace DeviceLayer { namespace Internal { namespace { /// Retrieve CHIP device identification info from the device advertising data bool BluezGetChipDeviceInfo(BluezDevice1 & aDevice, chip::Ble::ChipBLEDeviceIdentificationInfo & aDeviceInfo) { GVariant * serviceData = bluez_device1_get_service_data(&aDevice); VerifyOrReturnError(serviceData != nullptr, false); GAutoPtr dataValue(g_variant_lookup_value(serviceData, Ble::CHIP_BLE_SERVICE_LONG_UUID_STR, nullptr)); VerifyOrReturnError(dataValue != nullptr, false); size_t dataLen = 0; const void * dataBytes = g_variant_get_fixed_array(dataValue.get(), &dataLen, sizeof(uint8_t)); VerifyOrReturnError(dataBytes != nullptr && dataLen >= sizeof(aDeviceInfo), false); memcpy(&aDeviceInfo, dataBytes, sizeof(aDeviceInfo)); return true; } } // namespace CHIP_ERROR ChipDeviceScanner::Init(BluezAdapter1 * adapter, ChipDeviceScannerDelegate * delegate) { VerifyOrReturnError(adapter != nullptr, CHIP_ERROR_INVALID_ARGUMENT); // Make this function idempotent by shutting down previously initialized state if any. Shutdown(); mAdapter.reset(reinterpret_cast(g_object_ref(adapter))); mDelegate = delegate; mScannerState = ChipDeviceScannerState::INITIALIZED; return CHIP_NO_ERROR; } void ChipDeviceScanner::Shutdown() { VerifyOrReturn(mScannerState != ChipDeviceScannerState::UNINITIALIZED); StopScan(); // Release resources on the glib thread. This is necessary because the D-Bus manager client // object handles D-Bus signals. Otherwise, we might face a race when the manager object is // released during a D-Bus signal being processed. PlatformMgrImpl().GLibMatterContextInvokeSync( +[](ChipDeviceScanner * self) { self->mAdapter.reset(); return CHIP_NO_ERROR; }, this); mScannerState = ChipDeviceScannerState::UNINITIALIZED; } CHIP_ERROR ChipDeviceScanner::StartScan() { assertChipStackLockedByCurrentThread(); VerifyOrReturnError(mScannerState != ChipDeviceScannerState::SCANNING, CHIP_ERROR_INCORRECT_STATE); mCancellable.reset(g_cancellable_new()); CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync( +[](ChipDeviceScanner * self) { return self->StartScanImpl(); }, this); if (err != CHIP_NO_ERROR) { ChipLogError(Ble, "Failed to initiate BLE scan start: %" CHIP_ERROR_FORMAT, err.Format()); mDelegate->OnScanComplete(); return err; } mScannerState = ChipDeviceScannerState::SCANNING; ChipLogDetail(Ble, "ChipDeviceScanner has started scanning!"); return CHIP_NO_ERROR; } CHIP_ERROR ChipDeviceScanner::StopScan() { assertChipStackLockedByCurrentThread(); VerifyOrReturnError(mScannerState == ChipDeviceScannerState::SCANNING, CHIP_NO_ERROR); CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync( +[](ChipDeviceScanner * self) { return self->StopScanImpl(); }, this); if (err != CHIP_NO_ERROR) { ChipLogError(Ble, "Failed to initiate BLE scan stop: %" CHIP_ERROR_FORMAT, err.Format()); return CHIP_ERROR_INTERNAL; } // Stop scanning and return to initialization state mScannerState = ChipDeviceScannerState::INITIALIZED; ChipLogDetail(Ble, "ChipDeviceScanner has stopped scanning!"); mDelegate->OnScanComplete(); return CHIP_NO_ERROR; } CHIP_ERROR ChipDeviceScanner::StopScanImpl() { // In case we are currently running a scan g_cancellable_cancel(mCancellable.get()); mCancellable.reset(); mObjectManager.UnsubscribeDeviceNotifications(mAdapter.get(), this); GAutoPtr error; if (!bluez_adapter1_call_stop_discovery_sync(mAdapter.get(), nullptr /* not cancellable */, &error.GetReceiver())) { // Do not report error if returned error indicates that the BLE adapter is not available. // In such case the scan is already stopped. if (BluezCallToChipError(error.get()) != BLE_ERROR_ADAPTER_UNAVAILABLE) { ChipLogError(Ble, "Failed to stop discovery: %s", error->message); return CHIP_ERROR_INTERNAL; } } return CHIP_NO_ERROR; } void ChipDeviceScanner::OnDeviceAdded(BluezDevice1 & device) { ReportDevice(device); } void ChipDeviceScanner::OnDevicePropertyChanged(BluezDevice1 & device, GVariant * changedProps, const char * const * invalidatedProps) { ReportDevice(device); } void ChipDeviceScanner::ReportDevice(BluezDevice1 & device) { VerifyOrReturn(strcmp(bluez_device1_get_adapter(&device), g_dbus_proxy_get_object_path(reinterpret_cast(mAdapter.get()))) == 0); chip::Ble::ChipBLEDeviceIdentificationInfo deviceInfo; if (!BluezGetChipDeviceInfo(device, deviceInfo)) { ChipLogDetail(Ble, "Device %s does not look like a CHIP device.", bluez_device1_get_address(&device)); return; } mDelegate->OnDeviceScanned(device, deviceInfo); } void ChipDeviceScanner::RemoveDevice(BluezDevice1 & device) { VerifyOrReturn(strcmp(bluez_device1_get_adapter(&device), g_dbus_proxy_get_object_path(reinterpret_cast(mAdapter.get()))) == 0); chip::Ble::ChipBLEDeviceIdentificationInfo deviceInfo; if (!BluezGetChipDeviceInfo(device, deviceInfo)) { return; } GAutoPtr error; const auto devicePath = g_dbus_proxy_get_object_path(reinterpret_cast(&device)); if (!bluez_adapter1_call_remove_device_sync(mAdapter.get(), devicePath, nullptr, &error.GetReceiver())) { ChipLogDetail(Ble, "Failed to remove device %s: %s", StringOrNullMarker(devicePath), error->message); } } CHIP_ERROR ChipDeviceScanner::StartScanImpl() { CHIP_ERROR err = mObjectManager.SubscribeDeviceNotifications(mAdapter.get(), this); ReturnErrorOnFailure(err); ChipLogProgress(Ble, "BLE removing known devices"); for (BluezObject & object : mObjectManager.GetObjects()) { GAutoPtr device(bluez_object_get_device1(&object)); if (device) { RemoveDevice(*device.get()); } } // Search for LE only. // Do NOT add filtering by UUID as it is done by the following kernel function: // https://github.com/torvalds/linux/blob/bdb575f872175ed0ecf2638369da1cb7a6e86a14/net/bluetooth/mgmt.c#L9258. // The function requires that devices advertise its services' UUIDs in UUID16/32/128 fields // while the Matter specification requires only FLAGS (0x01) and SERVICE_DATA_16 (0x16) fields // in the advertisement packets. GVariantBuilder filterBuilder; g_variant_builder_init(&filterBuilder, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&filterBuilder, "{sv}", "Transport", g_variant_new_string("le")); GVariant * filter = g_variant_builder_end(&filterBuilder); GAutoPtr error; if (!bluez_adapter1_call_set_discovery_filter_sync(mAdapter.get(), filter, mCancellable.get(), &error.GetReceiver())) { // Not critical: ignore if fails ChipLogError(Ble, "Failed to set discovery filters: %s", error->message); error.reset(); } ChipLogProgress(Ble, "BLE initiating scan"); if (!bluez_adapter1_call_start_discovery_sync(mAdapter.get(), mCancellable.get(), &error.GetReceiver())) { ChipLogError(Ble, "Failed to start discovery: %s", error->message); return BluezCallToChipError(error.get()); } return CHIP_NO_ERROR; } } // namespace Internal } // namespace DeviceLayer } // namespace chip