-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Rework Darwin BLE layer #37704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
ksperling-apple
merged 11 commits into
project-chip:master
from
ksperling-apple:darwin-ble-no-queue
Mar 12, 2025
Merged
Rework Darwin BLE layer #37704
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
8a859fe
Rename Ble*Delegate headers to match .mm / class names
ksperling-apple eedfb77
Simplify BLEManagerImpl, we don't need to allocate the DelegateImpls …
ksperling-apple eebae5b
Add some tests that exercise BLE code by mocking Core Bluetooth
ksperling-apple 9cc1ea7
Retain the underlying CBPeripheral in MTRCommissionableBrowserResult
ksperling-apple e4d376e
Use the CHIP queue instead of a separate BLE queue
ksperling-apple fe077fe
Tidy up BLE delegate headers
ksperling-apple 915f426
Factor out connObj bridge casts into helper functions
ksperling-apple 50e3e11
Simplify UUID handling, CBUUID does most of it already
ksperling-apple 7412708
Review comments / tidyup
ksperling-apple f7f5f73
Avoid leaks false positive in CoreBluetooth mock
ksperling-apple 6fcbc70
Final round of review comments
ksperling-apple File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| /** | ||
| * Copyright (c) 2024 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. | ||
| */ | ||
|
|
||
| #import "MTRMockCB.h" | ||
| #import "MTRTestCase.h" | ||
| #import "MTRTestKeys.h" | ||
| #import "MTRTestStorage.h" | ||
|
|
||
| #import <Matter/Matter.h> | ||
| #import <XCTest/XCTest.h> | ||
| #import <stdatomic.h> | ||
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @interface MTRBleTests : MTRTestCase | ||
| @end | ||
|
|
||
| @interface TestBrowserDelegate : NSObject <MTRCommissionableBrowserDelegate> | ||
| @property (strong) void (^onDidFindCommissionableDevice)(MTRDeviceController *, MTRCommissionableBrowserResult *); | ||
| @property (strong) void (^onDidRemoveCommissionableDevice)(MTRDeviceController *, MTRCommissionableBrowserResult *); | ||
| @end | ||
|
|
||
| @implementation TestBrowserDelegate | ||
|
|
||
| - (void)controller:(nonnull MTRDeviceController *)controller didFindCommissionableDevice:(MTRCommissionableBrowserResult *)device | ||
| { | ||
| __auto_type block = self.onDidFindCommissionableDevice; | ||
| if (block) { | ||
| block(controller, device); | ||
| } | ||
| } | ||
|
|
||
| - (void)controller:(nonnull MTRDeviceController *)controller didRemoveCommissionableDevice:(MTRCommissionableBrowserResult *)device | ||
| { | ||
| __auto_type block = self.onDidRemoveCommissionableDevice; | ||
| if (block) { | ||
| block(controller, device); | ||
| } | ||
| } | ||
|
|
||
| @end | ||
|
|
||
| MTRDeviceController * sController; | ||
|
|
||
| @implementation MTRBleTests | ||
|
|
||
| - (void)setUp | ||
| { | ||
| [super setUp]; | ||
|
|
||
| [self.class.mockCoreBluetooth reset]; | ||
|
|
||
| sController = [MTRTestCase createControllerOnTestFabric]; | ||
| XCTAssertNotNil(sController); | ||
| } | ||
|
|
||
| - (void)tearDown | ||
| { | ||
| [sController shutdown]; | ||
| sController = nil; | ||
| [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; | ||
|
|
||
| [super tearDown]; | ||
| } | ||
|
|
||
| - (void)testBleCommissionableBrowserResultAdditionAndRemoval | ||
| { | ||
| __block MTRCommissionableBrowserResult * device; | ||
| XCTestExpectation * didFindDevice = [self expectationWithDescription:@"did find device"]; | ||
| TestBrowserDelegate * delegate = [[TestBrowserDelegate alloc] init]; | ||
| delegate.onDidFindCommissionableDevice = ^(MTRDeviceController * controller, MTRCommissionableBrowserResult * result) { | ||
| if ([result.instanceName isEqualToString:@"BLE"]) { // TODO: This is a lame API | ||
| XCTAssertNil(device); | ||
| XCTAssertEqualObjects(result.vendorID, @0xfff1); | ||
| XCTAssertEqualObjects(result.productID, @0x1234); | ||
| XCTAssertEqualObjects(result.discriminator, @0x444); | ||
| device = result; | ||
| [didFindDevice fulfill]; | ||
| } | ||
| }; | ||
|
|
||
| XCTestExpectation * didRemoveDevice = [self expectationWithDescription:@"did remove device"]; | ||
| delegate.onDidRemoveCommissionableDevice = ^(MTRDeviceController * controller, MTRCommissionableBrowserResult * result) { | ||
| if ([result.instanceName isEqualToString:@"BLE"]) { | ||
| XCTAssertNotNil(device); | ||
| XCTAssertEqual(result, device); | ||
| [didRemoveDevice fulfill]; | ||
| } | ||
| }; | ||
|
|
||
| XCTAssertTrue([sController startBrowseForCommissionables:delegate queue:dispatch_get_main_queue()]); | ||
|
|
||
| NSUUID * peripheralID = [NSUUID UUID]; | ||
| [self.class.mockCoreBluetooth addMockCommissionableMatterDeviceWithIdentifier:peripheralID vendorID:@0xfff1 productID:@0x1234 discriminator:@0x444]; | ||
| [self.class.mockCoreBluetooth removeMockPeripheralWithIdentifier:peripheralID]; | ||
|
|
||
| // BleConnectionDelegateImpl kCachePeripheralTimeoutInSeconds is approximately 10 seconds | ||
| [self waitForExpectations:@[ didFindDevice, didRemoveDevice ] timeout:14 enforceOrder:YES]; | ||
| XCTAssertTrue([sController stopBrowseForCommissionables]); | ||
| } | ||
|
|
||
| - (void)testBleCommissionAfterStopBrowseUAF | ||
| { | ||
| __block MTRCommissionableBrowserResult * device; | ||
| XCTestExpectation * didFindDevice = [self expectationWithDescription:@"did find device"]; | ||
| TestBrowserDelegate * delegate = [[TestBrowserDelegate alloc] init]; | ||
| delegate.onDidFindCommissionableDevice = ^(MTRDeviceController * controller, MTRCommissionableBrowserResult * result) { | ||
| if ([result.instanceName isEqualToString:@"BLE"]) { | ||
| XCTAssertNil(device); | ||
| XCTAssertEqualObjects(result.vendorID, @0xfff1); | ||
| XCTAssertEqualObjects(result.productID, @0x1234); | ||
| XCTAssertEqualObjects(result.discriminator, @0x444); | ||
| device = result; | ||
| [didFindDevice fulfill]; | ||
| } | ||
| }; | ||
|
|
||
| XCTAssertTrue([sController startBrowseForCommissionables:delegate queue:dispatch_get_main_queue()]); | ||
|
|
||
| NSUUID * peripheralID = [NSUUID UUID]; | ||
| [self.class.mockCoreBluetooth addMockCommissionableMatterDeviceWithIdentifier:peripheralID vendorID:@0xfff1 productID:@0x1234 discriminator:@0x444]; | ||
| [self waitForExpectations:@[ didFindDevice ] timeout:2 enforceOrder:YES]; | ||
|
|
||
| XCTAssertTrue([sController stopBrowseForCommissionables]); | ||
|
|
||
| // Attempt to use the MTRCommissionableBrowserResult after we stopped browsing | ||
| // This used to result in a UAF because BLE_CONNECTION_OBJECT is a void* | ||
| // carrying a CBPeripheral without retaining it. When browsing is stopped, | ||
| // BleConnectionDelegateImpl releases all cached CBPeripherals. | ||
| MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithSetupPasscode:@54321 discriminator:@0x444]; | ||
| [sController setupCommissioningSessionWithDiscoveredDevice:device | ||
| payload:payload | ||
| newNodeID:@999 | ||
| error:NULL]; | ||
| [sController cancelCommissioningForNodeID:@999 error:NULL]; | ||
| } | ||
|
|
||
| - (void)testShutdownBlePowerOffRaceUAF | ||
| { | ||
| // Attempt a PASE connection over BLE, this will call BleConnectionDelegateImpl::NewConnection() | ||
| MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithSetupPasscode:@54321 discriminator:@0xb1e]; | ||
| payload.discoveryCapabilities = MTRDiscoveryCapabilitiesBLE; | ||
| NSError * error; | ||
| XCTAssertTrue([sController setupCommissioningSessionWithPayload:payload newNodeID:@999 error:&error], | ||
| "setupCommissioningSessionWithPayload failed: %@", error); | ||
|
|
||
| // Create a race between shutdown and a CBManager callback that used to provoke a UAF. | ||
| // Note that on the order of 100 iterations can be necessary to reproduce the crash. | ||
| __block atomic_int tasks = 2; | ||
| dispatch_semaphore_t done = dispatch_semaphore_create(0); | ||
|
|
||
| dispatch_block_t shutdown = ^{ | ||
| // Shut down the controller. This causes the SetupCodePairer to call | ||
| // BleConnectionDelegateImpl::CancelConnection(), then the SetupCodePairer | ||
| // is deallocated along with the DeviceCommissioner | ||
| [sController shutdown]; | ||
| sController = nil; | ||
| if (atomic_fetch_sub(&tasks, 1) == 1) { | ||
| dispatch_semaphore_signal(done); | ||
| } | ||
| }; | ||
| dispatch_block_t powerOff = ^{ | ||
| // Cause CBPeripheralManager to signal a state change that | ||
| // triggers a callback to the SetupCodePairer | ||
| self.class.mockCoreBluetooth.state = CBManagerStatePoweredOff; | ||
| if (atomic_fetch_sub(&tasks, 1) == 1) { | ||
| dispatch_semaphore_signal(done); | ||
| } | ||
| }; | ||
|
|
||
| dispatch_queue_t pool = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); | ||
| dispatch_async(pool, shutdown); | ||
| dispatch_async(pool, powerOff); | ||
| dispatch_wait(done, DISPATCH_TIME_FOREVER); | ||
| } | ||
|
|
||
| @end | ||
|
|
||
| NS_ASSUME_NONNULL_END | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.