diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index e2ee439bbdb8ea..229c6cca656d26 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -116,11 +116,10 @@ jobs: export TEST_RUNNER_ASAN_OPTIONS=__CURRENT_VALUE__:detect_stack_use_after_return=1 - # Disable BLE (CHIP_IS_BLE=NO) because the app does not have the permission to use it and that may crash the CI. xcodebuild test -target "Matter" -scheme "Matter Framework Tests" \ -resultBundlePath /tmp/darwin/framework-tests/TestResults.xcresult \ -sdk macosx ${{ matrix.options.arguments }} \ - CHIP_IS_BLE=NO GCC_PREPROCESSOR_DEFINITIONS='${inherited} ${{ matrix.options.defines }}' \ + GCC_PREPROCESSOR_DEFINITIONS='${inherited} ${{ matrix.options.defines }}' \ > >(tee /tmp/darwin/framework-tests/darwin-tests.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-err.log >&2) - name: Generate Summary if: always() diff --git a/src/controller/python/chip/ble/darwin/Scanning.mm b/src/controller/python/chip/ble/darwin/Scanning.mm index 564a984e094369..404008fbddd0cf 100644 --- a/src/controller/python/chip/ble/darwin/Scanning.mm +++ b/src/controller/python/chip/ble/darwin/Scanning.mm @@ -1,7 +1,7 @@ #include #include #include -#include +#include #import @@ -45,7 +45,7 @@ - (id)initWithContext:(PyObject *)context { self = [super init]; if (self) { - self.shortServiceUUID = [MTRUUIDHelper GetShortestServiceUUID:&chip::Ble::CHIP_BLE_SVC_ID]; + self.shortServiceUUID = chip::DeviceLayer::Internal::CBUUIDFromBleUUID(chip::Ble::CHIP_BLE_SVC_ID); _workQueue = dispatch_queue_create("com.chip.python.ble.work_queue", DISPATCH_QUEUE_SERIAL); _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _workQueue); @@ -78,7 +78,7 @@ - (void)centralManager:(CBCentralManager *)central NSDictionary * servicesData = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey]; for (CBUUID * serviceUUID in servicesData) { - if (![serviceUUID.data isEqualToData:_shortServiceUUID.data]) { + if (![serviceUUID isEqualTo:_shortServiceUUID]) { continue; } NSData * serviceData = [servicesData objectForKey:serviceUUID]; diff --git a/src/darwin/Framework/CHIP/MTRCommissionableBrowser.mm b/src/darwin/Framework/CHIP/MTRCommissionableBrowser.mm index 8a111a6a019bdb..0bbdc12c188059 100644 --- a/src/darwin/Framework/CHIP/MTRCommissionableBrowser.mm +++ b/src/darwin/Framework/CHIP/MTRCommissionableBrowser.mm @@ -26,9 +26,11 @@ #include #include #include +#include using namespace chip::Dnssd; using namespace chip::DeviceLayer; +using namespace chip::DeviceLayer::Internal; #if CONFIG_NETWORK_LAYER_BLE #include @@ -39,6 +41,8 @@ using namespace chip::Tracing::DarwinFramework; +@class CBPeripheral; + @implementation MTRCommissionableBrowserResultInterfaces @end @@ -48,6 +52,7 @@ @interface MTRCommissionableBrowserResult () @property (nonatomic) NSNumber * productID; @property (nonatomic) NSNumber * discriminator; @property (nonatomic) BOOL commissioningMode; +@property (nonatomic, strong, nullable) CBPeripheral * peripheral; @end @implementation MTRCommissionableBrowserResult @@ -302,6 +307,7 @@ void OnBleScanAdd(BLE_CONNECTION_OBJECT connObj, const ChipBLEDeviceIdentificati result.discriminator = @(info.GetDeviceDiscriminator()); result.commissioningMode = YES; result.params = chip::MakeOptional(chip::Controller::SetUpCodePairerParameters(connObj, false /* connected */)); + result.peripheral = CBPeripheralFromBleConnObject(connObj); // avoid params holding a dangling pointer MATTER_LOG_METRIC(kMetricBLEDevicesAdded, ++mBLEDevicesAdded); diff --git a/src/darwin/Framework/CHIPTests/MTRBleTests.m b/src/darwin/Framework/CHIPTests/MTRBleTests.m new file mode 100644 index 00000000000000..8ec6698ced0bf5 --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTRBleTests.m @@ -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 +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTRBleTests : MTRTestCase +@end + +@interface TestBrowserDelegate : NSObject +@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 diff --git a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m index d2b5f797e4514b..e5e8354f8bf794 100644 --- a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m +++ b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m @@ -17,6 +17,7 @@ #import +#import "MTRMockCB.h" #import "MTRTestCase+ServerAppRunner.h" #import "MTRTestCase.h" #import "MTRTestKeys.h" @@ -24,21 +25,20 @@ // Fixture 1: chip-all-clusters-app --KVS "$(mktemp -t chip-test-kvs)" --interface-id -1 -static const uint16_t kLocalPort = 5541; static const uint16_t kTestVendorId = 0xFFF1u; -static const __auto_type kTestProductIds = @[ @(0x8000u), @(0x8001u) ]; -static const __auto_type kTestDiscriminators = @[ @(2000), @(3839u), @(3840u) ]; +static const __auto_type kTestProductIds = @[ @(0x8000u), @(0x8001u), @(0x8002u) ]; +static const __auto_type kTestDiscriminators = @[ @(2000), @(3839u), @(3840u), @(0xb1e) ]; static const uint16_t kDiscoverDeviceTimeoutInSeconds = 10; -static const uint16_t kExpectedDiscoveredDevicesCount = 3; +static const uint16_t kExpectedDiscoveredDevicesCount = 4; // Singleton controller we use. static MTRDeviceController * sController = nil; -static NSString * kInstanceNameKey = @"instanceName"; -static NSString * kVendorIDKey = @"vendorID"; -static NSString * kProductIDKey = @"productID"; -static NSString * kDiscriminatorKey = @"discriminator"; -static NSString * kCommissioningModeKey = @"commissioningMode"; +static NSString * const kInstanceNameKey = @"instanceName"; +static NSString * const kVendorIDKey = @"vendorID"; +static NSString * const kProductIDKey = @"productID"; +static NSString * const kDiscriminatorKey = @"discriminator"; +static NSString * const kCommissioningModeKey = @"commissioningMode"; static NSDictionary * ResultSnapshot(MTRCommissionableBrowserResult * result) { @@ -112,7 +112,9 @@ - (void)controller:(MTRDeviceController *)controller didFindCommissionableDevice __auto_type discriminator = device.discriminator; __auto_type commissioningMode = device.commissioningMode; - XCTAssertEqual(instanceName.length, 16); // The instance name is random, so just ensure the len is right. + if (![instanceName isEqual:@"BLE"]) { + XCTAssertEqual(instanceName.length, 16); // The instance name is random, so just ensure the len is right. + } XCTAssertEqualObjects(vendorId, @(kTestVendorId)); XCTAssertTrue([kTestProductIds containsObject:productId]); XCTAssertTrue([kTestDiscriminators containsObject:discriminator]); @@ -162,28 +164,9 @@ + (void)setUp { [super setUp]; - __auto_type * factory = [MTRDeviceControllerFactory sharedInstance]; - XCTAssertNotNil(factory); - - __auto_type * storage = [[MTRTestStorage alloc] init]; - __auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:storage]; - factoryParams.port = @(kLocalPort); - - BOOL ok = [factory startControllerFactory:factoryParams error:nil]; - XCTAssertTrue(ok); - - __auto_type * testKeys = [[MTRTestKeys alloc] init]; - XCTAssertNotNil(testKeys); - - __auto_type * params = [[MTRDeviceControllerStartupParams alloc] initWithIPK:testKeys.ipk fabricID:@(1) nocSigner:testKeys]; - params.vendorID = @(kTestVendorId); - - MTRDeviceController * controller = [factory createControllerOnNewFabric:params error:nil]; - XCTAssertNotNil(controller); - - sController = controller; + sController = [MTRTestCase createControllerOnTestFabric]; - // Start the helper apps our tests use. + // Start the helper apps our tests use. Note these payloads match kTestDiscriminators etc. for (NSString * payload in @[ @"MT:Y.K90SO527JA0648G00", @"MT:-24J0AFN00I40648G00", @@ -197,10 +180,10 @@ + (void)setUp + (void)tearDown { - MTRDeviceController * controller = sController; - XCTAssertNotNil(controller); - [controller shutdown]; - XCTAssertFalse([controller isRunning]); + XCTAssertNotNil(sController); + [sController shutdown]; + XCTAssertFalse([sController isRunning]); + sController = nil; [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; @@ -211,6 +194,7 @@ - (void)setUp { [super setUp]; [self setContinueAfterFailure:NO]; + [self.class.mockCoreBluetooth reset]; } - (void)test001_StartBrowseAndStopBrowse @@ -218,11 +202,26 @@ - (void)test001_StartBrowseAndStopBrowse __auto_type delegate = [[DeviceScannerDelegate alloc] init]; dispatch_queue_t dispatchQueue = dispatch_queue_create("com.chip.discover", DISPATCH_QUEUE_SERIAL); + XCTestExpectation * bleScanExpectation = [self expectationWithDescription:@"did start BLE scan"]; + self.class.mockCoreBluetooth.onScanForPeripheralsWithServicesOptions = ^(NSArray * _Nullable serviceUUIDs, NSDictionary * _Nullable options) { + XCTAssertEqual(serviceUUIDs.count, 1); + [bleScanExpectation fulfill]; + }; + // Start browsing XCTAssertTrue([sController startBrowseForCommissionables:delegate queue:dispatchQueue]); + [self waitForExpectations:@[ bleScanExpectation ] timeout:1]; + + XCTestExpectation * bleStopExpectation = [self expectationWithDescription:@"did stop BLE scan"]; + self.class.mockCoreBluetooth.onStopScan = ^{ + [bleStopExpectation fulfill]; + }; + // Stop browsing XCTAssertTrue([sController stopBrowseForCommissionables]); + + [self waitForExpectations:@[ bleStopExpectation ] timeout:1]; } - (void)test002_StartBrowseAndStopBrowseMultipleTimes @@ -264,12 +263,18 @@ - (void)test004_StartBrowseWhileBrowsing XCTAssertTrue([sController stopBrowseForCommissionables]); } -- (void)test005_StartBrowseGetCommissionableOverMdns +- (void)test005_StartBrowseGetCommissionableOverMdnsAndBle { __auto_type expectation = [self expectationWithDescription:@"Commissionable devices Found"]; __auto_type delegate = [[DeviceScannerDelegate alloc] initWithExpectation:expectation]; dispatch_queue_t dispatchQueue = dispatch_queue_create("com.chip.discover", DISPATCH_QUEUE_SERIAL); + // Mock a commissionable device advertising over BLE + [self.class.mockCoreBluetooth addMockCommissionableMatterDeviceWithIdentifier:[NSUUID UUID] + vendorID:@(kTestVendorId) + productID:@0x8002 + discriminator:@0xb1e]; + // Start browsing XCTAssertTrue([sController startBrowseForCommissionables:delegate queue:dispatchQueue]); diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRMockCB.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRMockCB.h new file mode 100644 index 00000000000000..022b7b876b156e --- /dev/null +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRMockCB.h @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2025 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 +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Instantiating this class will intercept any future calls that +/// allocate `CBCentralManager` objects to return mock objects instead. +/// Only one instance of this class should exist at any one time. +@interface MTRMockCB : NSObject + +/// Stops mock `CBCentralManager` allocations and disables existing mocks. +/// Failure to call this method may result in leaks or other issues. +- (void)stopMocking; + +/// Resets `on*` hooks and mocked peripherals but does not stop mocking. +- (void)reset; + +// Adds a mocked peripheral and causes any mocked `CBCentralManager` +// instances to discover this as a `CBPeripheral`, if they are scanning. +// The provided identifier becomes the `identifier` of the `CBPeripheral`. +- (void)addMockPeripheralWithIdentifier:(NSUUID *)identifier + services:(NSArray *)services + advertisementData:(nullable NSDictionary *)advertisementData; + +// Convenience version of `addMockPeripheralWithIdentifier:...` that +// advertises the relevant service with advertisement data as defined +// in the "Matter BLE Service Data payload format" section of the spec. +- (void)addMockCommissionableMatterDeviceWithIdentifier:(NSUUID *)identifier + vendorID:(NSNumber *)vendorID + productID:(NSNumber *)productID + discriminator:(NSNumber *)discriminator; + +- (void)removeMockPeripheralWithIdentifier:(NSUUID *)identifier; + +/// Mocked state. Defaults to CBManagerStatePoweredOn. +@property (readwrite, assign) CBManagerState state; + +@property (readwrite, strong, nullable) void (^onScanForPeripheralsWithServicesOptions) + (NSArray * _Nullable serviceUUIDs, NSDictionary * _Nullable options); + +@property (readwrite, strong, nullable) void (^onStopScan)(void); + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRMockCB.m b/src/darwin/Framework/CHIPTests/TestHelpers/MTRMockCB.m new file mode 100644 index 00000000000000..0098e27bda53cf --- /dev/null +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRMockCB.m @@ -0,0 +1,690 @@ +/** + * 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 "MTRDefines_Internal.h" + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTRMockCBPeripheralDetails : NSObject + +- (instancetype)initWithIdentifier:(NSUUID *)identifier; + +@property (readonly, nonatomic, weak) MTRMockCB * mock; +@property (readonly, nonatomic, strong) NSUUID * identifier; +@property (readonly, nonatomic, copy) NSString * name; +@property (readonly, nonatomic, copy) NSDictionary * advertisementData; + +@property (nonatomic, assign) CBPeripheralState state; +@property (nonatomic, nullable, copy) NSDictionary * extraAdvertisementData; +@property (nonatomic, copy) NSArray * services; + +@end + +@interface MTRMockCBCentralManager : NSObject + +- (instancetype)_initWithMock:(MTRMockCB *)mock; +- (void)_didUpdateState; +- (void)_maybeDiscoverPeripheral:(MTRMockCBPeripheralDetails *)details; + +// MARK: CBManager + +@property (nonatomic, assign, readonly) CBManagerState state; +@property (nonatomic, assign, readonly) CBManagerAuthorization authorization; +@property (class, nonatomic, assign, readonly) CBManagerAuthorization authorization; + +// MARK: CBCentralManager + +@property (nonatomic, weak, nullable) id delegate; +@property (nonatomic, assign, readonly) BOOL isScanning; + ++ (BOOL)supportsFeatures:(CBCentralManagerFeature)features; + +- (instancetype)init; +- (instancetype)initWithDelegate:(nullable id)delegate + queue:(nullable dispatch_queue_t)queue; +- (instancetype)initWithDelegate:(nullable id)delegate + queue:(nullable dispatch_queue_t)queue + options:(nullable NSDictionary *)options; + +- (NSArray *)retrievePeripheralsWithIdentifiers:(NSArray *)identifiers; +- (NSArray *)retrieveConnectedPeripheralsWithServices:(NSArray *)serviceUUIDs; +- (void)scanForPeripheralsWithServices:(nullable NSArray *)serviceUUIDs options:(nullable NSDictionary *)options; +- (void)stopScan; +- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary *)options; +- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral; +- (void)registerForConnectionEventsWithOptions:(nullable NSDictionary *)options; + +@end + +@interface MTRMockCBPeripheral : NSObject + +- (instancetype)_initWithDetails:(MTRMockCBPeripheralDetails *)details manager:(MTRMockCBCentralManager *)manager; + +@property (readonly, strong, nonatomic) MTRMockCBCentralManager * manager; // not API, but used by BlePlatformDelegateImpl via KVC + +// MARK: CBPeer + +@property (readonly, nonatomic) NSUUID * identifier; + +// MARK: CBPeripheral + +@property (weak, nonatomic, nullable) id delegate; +@property (retain, readonly, nullable) NSString * name; +@property (retain, readonly, nullable) NSNumber * RSSI; +@property (readonly) CBPeripheralState state; +@property (retain, readonly, nullable) NSArray * services; +@property (readonly) BOOL canSendWriteWithoutResponse; +@property (readonly) BOOL ancsAuthorized; + +- (void)readRSSI; +- (void)discoverServices:(nullable NSArray *)serviceUUIDs; +- (void)discoverIncludedServices:(nullable NSArray *)includedServiceUUIDs forService:(CBService *)service; +- (void)discoverCharacteristics:(nullable NSArray *)characteristicUUIDs forService:(CBService *)service; +- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic; +- (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type; +- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type; +- (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic; +- (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic; +- (void)readValueForDescriptor:(CBDescriptor *)descriptor; +- (void)writeValue:(NSData *)data forDescriptor:(CBDescriptor *)descriptor; +- (void)openL2CAPChannel:(CBL2CAPPSM)PSM; + +@end + +static NSString * CBManagerStateAsString(CBManagerState state) +{ + switch (state) { + case CBManagerStateUnknown: + return @"CBManagerStateUnknown"; + case CBManagerStateResetting: + return @"CBManagerStateResetting"; + case CBManagerStateUnsupported: + return @"CBManagerStateUnsupported"; + case CBManagerStateUnauthorized: + return @"CBManagerStateUnauthorized"; + case CBManagerStatePoweredOff: + return @"CBManagerStatePoweredOff"; + case CBManagerStatePoweredOn: + return @"CBManagerStatePoweredOn"; + } + return [NSString stringWithFormat:@"CBManagerState(%ld)", (long) state]; +} + +@implementation MTRMockCB { +@package + os_log_t _log; + dispatch_queue_t _queue; + os_block_t _invalidate; + NSHashTable * _managers; + NSMutableDictionary * _peripherals; + CBManagerState _state; +} + +static void InterceptClassMethod(__strong os_block_t * inOutCleanup, Class cls, SEL sel, id block) +{ + Method method = class_getClassMethod(cls, sel); // may return an inherited method + if (!method) { + NSString * reason = [NSString stringWithFormat:@"+[%@ %@] does not exist", + NSStringFromClass(cls), NSStringFromSelector(sel)]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; + } + IMP originalImp = method_getImplementation(method); + + // Try to add the method first, in case it came from a super class. + // Note we need to pass the meta-class to class_addMethod(). + IMP newImp = imp_implementationWithBlock(block); + if (class_addMethod(object_getClass(cls), sel, newImp, method_getTypeEncoding(method))) { + method = class_getClassMethod(cls, sel); // look up again so we clean up the method we added + } else { + method_setImplementation(method, newImp); + } + + os_block_t nextCleanup = *inOutCleanup; + *inOutCleanup = ^{ + // This isn't 100% correct if we added an override of a super-class method, because + // there is no API for removing a method. Instead we directly point it at the + // inherited implementation; this is good enough for our purposes here. + method_setImplementation(method, originalImp); + imp_removeBlock(newImp); // otherwise the block might leak + (void) block; // keep an obvious reference to avoid `leaks` false positives before cleanup + nextCleanup(); + }; +} + +- (instancetype)init +{ + self = [super init]; + _log = os_log_create("com.csa.matter", "mock"); + + static atomic_flag sInitialized = ATOMIC_FLAG_INIT; + if (atomic_flag_test_and_set(&sInitialized)) { + os_log_error(_log, "CoreBluetooth mocking is already enabled"); + return nil; + } + + mtr_weakify(self); + _invalidate = ^{ + mtr_strongify(self); + self->_invalidate = nil; + atomic_flag_clear(&sInitialized); + }; + + _queue = dispatch_queue_create("mock.cb", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + dispatch_queue_set_specific(_queue, (__bridge void *) self, @YES, nil); // mark our queue + + _managers = [NSHashTable weakObjectsHashTable]; + _peripherals = [[NSMutableDictionary alloc] init]; + _state = CBManagerStatePoweredOn; + + os_log(_log, "Enabling CoreBluetooth mocking"); + + // Replace implementations of class methods we need to mock. We don't need to intercept + // any instance methods directly, because we're returning a mock object from `alloc`. + InterceptClassMethod(&_invalidate, CBCentralManager.class, @selector(alloc), ^id NS_RETURNS_RETAINED(void) { + mtr_strongify(self); + return self ? [[MTRMockCBCentralManager alloc] _initWithMock:self] : nil; + }); + InterceptClassMethod(&_invalidate, CBCentralManager.class, @selector(supportsFeatures:), ^BOOL(CBCentralManagerFeature features) { + return [MTRMockCBCentralManager supportsFeatures:features]; + }); + InterceptClassMethod(&_invalidate, CBManager.class, @selector(authorization), ^CBManagerAuthorization(void) { + return [MTRMockCBCentralManager authorization]; + }); + + return self; +} + +- (void)sync:(void (^NS_NOESCAPE)(BOOL isValid))block +{ + // Allow `sync` to work like a recursive lock for convenience. + if (dispatch_get_specific((__bridge void *) self) != NULL) { + block(_invalidate != nil); + } else { + dispatch_sync(_queue, ^{ + block(_invalidate != nil); + }); + } +} + +- (BOOL)isValid +{ + __block BOOL result; + [self sync:^(BOOL isValid) { + result = isValid; + }]; + return result; +} + +- (void)reset +{ + [self sync:^(BOOL isValid) { + [_peripherals removeAllObjects]; + _onScanForPeripheralsWithServicesOptions = nil; + _onStopScan = nil; + }]; +} + +- (void)stopMocking +{ + [self sync:^(BOOL isValid) { + if (isValid) { + os_log(_log, "Disabling CoreBluetooth mocking"); + + _invalidate(); + _invalidate = nil; + + NSArray * managers = [_managers allObjects]; + _managers = nil; + _peripherals = nil; + + if (_state != CBManagerStatePoweredOff) { + _state = CBManagerStatePoweredOff; + for (MTRMockCBCentralManager * manager in managers) { + [manager _didUpdateState]; + } + } + } + }]; +} + +- (CBManagerState)state +{ + __block CBManagerState result; + [self sync:^(BOOL isValid) { + result = _state; + }]; + return result; +} + +- (void)setState:(CBManagerState)state +{ + [self sync:^(BOOL isValid) { + if (isValid && state != _state) { + _state = state; + for (MTRMockCBCentralManager * manager in _managers) { + [manager _didUpdateState]; + } + } + }]; +} + +- (void)addMockPeripheralWithIdentifier:(NSUUID *)identifier + services:(nonnull NSArray *)services + advertisementData:(nullable NSDictionary *)advertisementData +{ + [self sync:^(BOOL isValid) { + if (isValid) { + MTRMockCBPeripheralDetails * details = _peripherals[identifier]; + if (!details) { + details = [[MTRMockCBPeripheralDetails alloc] initWithIdentifier:identifier]; + _peripherals[identifier] = details; + } + details.services = services; + details.extraAdvertisementData = advertisementData; + + for (MTRMockCBCentralManager * manager in _managers) { + [manager _maybeDiscoverPeripheral:details]; + } + } + }]; +} + +- (void)addMockCommissionableMatterDeviceWithIdentifier:(NSUUID *)identifier + vendorID:(NSNumber *)vendorID + productID:(NSNumber *)productID + discriminator:(NSNumber *)discriminator +{ + // Note: CBUUID transparently expands 16 or 32 bit UUIDs to 128 bit as required, + // however the current BLEConnectionDelegateImpl has its own comparison logic + // that does not treat short and long UUIDs as equivalent and only works with + // short UUIDs. The full UUID is "0000fff6-0000-1000-8000-00805f9b34fb". + CBUUID * matterServiceUUID = [CBUUID UUIDWithString:@"fff6"]; + + // See "Matter BLE Service Data payload format" in the spec, all fields little endian. + const uint8_t serviceDataBytes[] = { + 0x00, // OpCode 0x00 (Commissionable) + discriminator.unsignedIntValue & 0xff, (discriminator.unsignedIntValue >> 8) & 0xf, + vendorID.unsignedIntValue & 0xff, (vendorID.unsignedIntValue >> 8) & 0xff, + productID.unsignedIntValue & 0xff, (productID.unsignedIntValue >> 8) & 0xff, + 0x00, // Flags + }; + NSData * matterServiceData = [NSData dataWithBytes:serviceDataBytes length:sizeof(serviceDataBytes)]; + + [self addMockPeripheralWithIdentifier:[NSUUID UUID] + services:@[ matterServiceUUID ] + advertisementData:@{ CBAdvertisementDataServiceDataKey : @ { matterServiceUUID : matterServiceData } }]; +} + +- (void)removeMockPeripheralWithIdentifier:(NSUUID *)identifier +{ + [self sync:^(BOOL isValid) { + if (isValid) { + _peripherals[identifier] = nil; + } + }]; +} + +@end + +@implementation MTRMockCBPeripheralDetails + +- (instancetype)initWithIdentifier:(NSUUID *)identifier +{ + self = [super init]; + _identifier = identifier; + return self; +} + +- (NSString *)name +{ + return _identifier.UUIDString; +} + +- (NSDictionary *)advertisementData +{ + NSMutableDictionary * data = [[NSMutableDictionary alloc] init]; + data[CBAdvertisementDataLocalNameKey] = self.name; + data[CBAdvertisementDataServiceUUIDsKey] = self.services; + data[CBAdvertisementDataIsConnectable] = @YES; + if (_extraAdvertisementData) { + [data addEntriesFromDictionary:_extraAdvertisementData]; + } + return [data copy]; +} + +- (BOOL)matchesAnyServices:(nullable NSArray *)serviceUUIDs +{ + if (!serviceUUIDs) { + return YES; // special case + } + for (CBUUID * expected in serviceUUIDs) { + if ([self.services containsObject:expected]) { + return YES; + } + } + return NO; +} + +@end + +@implementation MTRMockCBCentralManager { +@package + MTRMockCB * _mock; // retain cycle (broken by stopMocking) + BOOL _initialized; + + id __weak _Nullable _delegate; + dispatch_queue_t _delegateQueue; + NSDictionary * _Nullable _options; + + NSArray * _Nullable _scanServiceUUIDs; + NSDictionary * _Nullable _scanOptions; +} + +- (instancetype)_initWithMock:(MTRMockCB *)mock +{ + self = [super init]; + _mock = mock; + return self; +} + +- (BOOL)isKindOfClass:(Class)aClass +{ + return [super isKindOfClass:aClass] || aClass == CBManager.class || aClass == CBCentralManager.class; +} + +- (NSString *)description +{ + __block NSString * result; + [_mock sync:^(BOOL isValid) { + result = [NSString stringWithFormat:@"<%@ %p %@ %@>", + self.class, self, CBManagerStateAsString(self.state), isValid ? @"valid" : @"defunct"]; + }]; + return result; +} + +// MARK: CBManager + ++ (CBManagerAuthorization)authorization +{ + return CBManagerAuthorizationAllowedAlways; +} + +- (CBManagerAuthorization)authorization +{ + return [[self class] authorization]; +} + +- (CBManagerState)state +{ + return _mock.state; +} + +// MARK: CBCentralManager + ++ (BOOL)supportsFeatures:(CBCentralManagerFeature)features +{ + return NO; +} + +- (instancetype)init +{ + return [self initWithDelegate:nil queue:nil options:nil]; +} + +- (instancetype)initWithDelegate:(nullable id)delegate + queue:(nullable dispatch_queue_t)queue +{ + return [self initWithDelegate:delegate queue:queue options:nil]; +} + +- (instancetype)initWithDelegate:(nullable id)delegate + queue:(nullable dispatch_queue_t)queue + options:(nullable NSDictionary *)options +{ + XCTAssertFalse(_initialized); + _initialized = YES; + + _delegate = delegate; + _delegateQueue = queue ?: dispatch_get_main_queue(); + _options = options; + + [_mock sync:^(BOOL isValid) { + if (isValid) { + [_mock->_managers addObject:self]; + [self _didUpdateState]; + } + }]; + return self; +} + +- (NSArray *)retrievePeripheralsWithIdentifiers:(NSArray *)identifiers +{ + return nil; +} + +- (NSArray *)retrieveConnectedPeripheralsWithServices:(NSArray *)serviceUUIDs +{ + return nil; +} + +- (void)scanForPeripheralsWithServices:(nullable NSArray *)serviceUUIDs + options:(nullable NSDictionary *)options +{ + [_mock sync:^(BOOL isValid) { + _scanServiceUUIDs = [serviceUUIDs copy]; + _scanOptions = [options copy]; + _isScanning = YES; + + if (isValid) { + __auto_type callback = _mock.onScanForPeripheralsWithServicesOptions; + if (callback) { + callback(serviceUUIDs, options); + } + + for (MTRMockCBPeripheralDetails * details in _mock->_peripherals.allValues) { + [self _maybeDiscoverPeripheral:details]; + } + } + }]; +} + +- (void)stopScan +{ + [_mock sync:^(BOOL isValid) { + _scanServiceUUIDs = nil; + _scanOptions = nil; + _isScanning = NO; + + if (isValid) { + __auto_type callback = _mock.onStopScan; + if (callback) { + callback(); + } + } + }]; +} + +- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary *)options +{ +} + +- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral +{ +} + +- (void)registerForConnectionEventsWithOptions:(nullable NSDictionary *)options +{ +} + +// MARK: Internals + +- (void)_didUpdateState +{ + os_log(_mock->_log, "%@ didUpdateState", self); + dispatch_async(_delegateQueue, ^{ + [self->_delegate centralManagerDidUpdateState:(id) self]; + }); +} + +- (void)_maybeDiscoverPeripheral:(MTRMockCBPeripheralDetails *)details +{ + if (_isScanning && [details matchesAnyServices:_scanServiceUUIDs] && + [_delegate respondsToSelector:@selector(centralManager:didDiscoverPeripheral:advertisementData:RSSI:)]) { + // TODO: Cache CBPeripheral mocks as long as the client keeps them alive? + MTRMockCBPeripheral * peripheral = [[MTRMockCBPeripheral alloc] _initWithDetails:details manager:self]; + NSDictionary * advertisementData = details.advertisementData; + os_log(_mock->_log, "%@ didDiscoverPeripheral %@", self, peripheral); + dispatch_async(_delegateQueue, ^{ + [self->_delegate centralManager:(id) self + didDiscoverPeripheral:(id) peripheral + advertisementData:advertisementData + RSSI:@127 /* Reserved for "RSSI not available" */]; + }); + } +} + +@end + +@implementation MTRMockCBPeripheral { + MTRMockCBPeripheralDetails * _details; + MTRMockCBCentralManager * _manager; +} + +- (instancetype)_initWithDetails:(MTRMockCBPeripheralDetails *)details manager:(MTRMockCBCentralManager *)manager +{ + self = [super init]; + _details = details; + _manager = manager; + return self; +} + +- (BOOL)isKindOfClass:(Class)aClass +{ + return [super isKindOfClass:aClass] || aClass == CBPeer.class || aClass == CBPeripheral.class; +} + +- (NSString *)description +{ + __block NSString * result; + [_manager->_mock sync:^(BOOL isValid) { + result = [NSString stringWithFormat:@"<%@ %p %@ %@>", self.class, self, self.identifier, isValid ? @"valid" : @"defunct"]; + }]; + return result; +} + +// MARK: CBPeer + +- (BOOL)isEqual:(id)object +{ + return [object class] == [self class] && [((MTRMockCBPeripheral *) object).identifier isEqualTo:self.identifier]; +} + +- (NSUInteger)hash +{ + return self.identifier.hash; +} + +- (id)copyWithZone:(nullable NSZone *)zone +{ + return self; // identifier is not mutable (and this is what CBPeer does) +} + +- (NSUUID *)identifier +{ + return _details.identifier; +} + +// MARK: CBPeripheral + +- (nullable NSString *)name +{ + return _details.identifier.UUIDString; +} + +- (nullable NSNumber *)RSSI +{ + return nil; +} + +- (CBPeripheralState)state +{ + return _details.state; +} + +- (nullable NSArray *)services +{ + return nil; +} + +- (void)readRSSI +{ +} + +- (void)discoverServices:(nullable NSArray *)serviceUUIDs +{ +} + +- (void)discoverIncludedServices:(nullable NSArray *)includedServiceUUIDs forService:(CBService *)service +{ +} + +- (void)discoverCharacteristics:(nullable NSArray *)characteristicUUIDs forService:(CBService *)service +{ +} + +- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic +{ +} + +- (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type +{ + return 512; +} + +- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type +{ +} + +- (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic +{ +} + +- (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic +{ +} + +- (void)readValueForDescriptor:(CBDescriptor *)descriptor +{ +} + +- (void)writeValue:(NSData *)data forDescriptor:(CBDescriptor *)descriptor +{ +} + +- (void)openL2CAPChannel:(CBL2CAPPSM)PSM +{ +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h index 3b404f6f05b1f4..d698ea8a959e16 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h @@ -22,14 +22,27 @@ #define HAVE_NSTASK 1 #endif +@class MTRDeviceController; +@class MTRMockCB; + NS_ASSUME_NONNULL_BEGIN @interface MTRTestCase : XCTestCase + // It would be nice to do the leak-detection automatically, but running "leaks" // on every single sub-test is slow, and some of our tests seem to have leaks // outside Matter.framework. So have it be opt-in for now, and improve later. @property (nonatomic) BOOL detectLeaks; +// Creates a device controller on a new fabric with test keys and test storage. ++ (MTRDeviceController *)createControllerOnTestFabric; + +// Provides access to the mock CoreBlueooth instance managed automatically by +// this class. Bluetooth mocking is enabled for all tests (even those that don't +// actively interact with it) to avoid issues with accessing the real Bluetooth +// implementation in CI. +@property (class, readonly) MTRMockCB * mockCoreBluetooth; + #if HAVE_NSTASK /** * Create an NSTask for the given path. Path should be relative to the Matter diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm index 915df782be4f43..02be960cc2b0a7 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm @@ -14,14 +14,20 @@ * limitations under the License. */ +#import "MTRTestCase.h" + +#import "MTRMockCB.h" +#import "MTRTestKeys.h" +#import "MTRTestStorage.h" + #include #include -#import "MTRTestCase.h" - #if HAVE_NSTASK // Tasks that are not scoped to a specific test, but rather to a specific test suite. -static NSMutableSet * runningCrossTestTasks; +static NSMutableSet * sRunningCrossTestTasks; + +static MTRMockCB * sMockCB; static void ClearTaskSet(NSMutableSet * __strong & tasks) { @@ -43,16 +49,26 @@ + (void)setUp { [super setUp]; + sMockCB = [[MTRMockCB alloc] init]; + #if HAVE_NSTASK - runningCrossTestTasks = [[NSMutableSet alloc] init]; + sRunningCrossTestTasks = [[NSMutableSet alloc] init]; #endif // HAVE_NSTASK } + (void)tearDown { #if HAVE_NSTASK - ClearTaskSet(runningCrossTestTasks); + ClearTaskSet(sRunningCrossTestTasks); #endif // HAVE_NSTASK + + [sMockCB stopMocking]; + sMockCB = nil; +} + ++ (MTRMockCB *)mockCoreBluetooth +{ + return sMockCB; } - (void)setUp @@ -106,6 +122,22 @@ - (void)tearDown [super tearDown]; } ++ (id)createControllerOnTestFabric +{ + __auto_type * storage = [[MTRTestStorage alloc] init]; + __auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:storage]; + __auto_type * factory = MTRDeviceControllerFactory.sharedInstance; + XCTAssertTrue([factory startControllerFactory:factoryParams error:nil]); + + __auto_type * testKeys = [[MTRTestKeys alloc] init]; + __auto_type * params = [[MTRDeviceControllerStartupParams alloc] initWithIPK:testKeys.ipk fabricID:@1 nocSigner:testKeys]; + params.vendorID = @0xFFF1; + MTRDeviceController * controller = [factory createControllerOnNewFabric:params error:nil]; + XCTAssertNotNil(controller); + + return controller; +} + #if HAVE_NSTASK - (NSTask *)createTaskForPath:(NSString *)path { @@ -147,7 +179,7 @@ + (void)launchTask:(NSTask *)task { [self doLaunchTask:task]; - [runningCrossTestTasks addObject:task]; + [sRunningCrossTestTasks addObject:task]; } #endif // HAVE_NSTASK diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 940b1eb2fbc55c..6bdeae34c6ff46 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -132,10 +132,12 @@ 3DA1A3552ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DA1A3522ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.h */; }; 3DA1A3562ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3DA1A3532ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm */; }; 3DA1A3582ABABF6A004F0BB9 /* MTRAsyncWorkQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DA1A3572ABABF69004F0BB9 /* MTRAsyncWorkQueueTests.m */; }; + 3DB9DAE52D67EE5A00704FAB /* MTRBleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB9DAE42D67EE5A00704FAB /* MTRBleTests.m */; }; 3DECCB6E29347D2D00585AEC /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DECCB6D29347D2C00585AEC /* Security.framework */; }; 3DECCB702934AECD00585AEC /* MTRLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DECCB6F2934AC1C00585AEC /* MTRLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DECCB722934AFE200585AEC /* MTRLogging.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3DECCB712934AFE200585AEC /* MTRLogging.mm */; }; 3DECCB742934C21B00585AEC /* MTRDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DECCB732934C21B00585AEC /* MTRDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3DF5219E2D62C3E5008F8E52 /* MTRMockCB.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DF5219D2D62C3E5008F8E52 /* MTRMockCB.m */; }; 3DFCB3292966684500332B35 /* MTRCertificateInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DFCB3282966684500332B35 /* MTRCertificateInfoTests.m */; }; 3DFCB32C29678C9500332B35 /* MTRConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DFCB32B29678C9500332B35 /* MTRConversion.h */; }; 51029DF6293AA6100087AFB0 /* MTROperationalCertificateIssuer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51029DF5293AA6100087AFB0 /* MTROperationalCertificateIssuer.mm */; }; @@ -653,18 +655,21 @@ 3DA1A3522ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRAsyncWorkQueue.h; sourceTree = ""; }; 3DA1A3532ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRAsyncWorkQueue.mm; sourceTree = ""; }; 3DA1A3572ABABF69004F0BB9 /* MTRAsyncWorkQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRAsyncWorkQueueTests.m; sourceTree = ""; }; + 3DB9DAE42D67EE5A00704FAB /* MTRBleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRBleTests.m; sourceTree = ""; }; + 3DB9DAE92D754C7200704FAB /* BleUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BleUtils.h; sourceTree = ""; }; + 3DB9DAEA2D754C7200704FAB /* BleUtils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BleUtils.mm; sourceTree = ""; }; 3DECCB6D29347D2C00585AEC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; 3DECCB6F2934AC1C00585AEC /* MTRLogging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRLogging.h; sourceTree = ""; }; 3DECCB712934AFE200585AEC /* MTRLogging.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRLogging.mm; sourceTree = ""; }; 3DECCB732934C21B00585AEC /* MTRDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDefines.h; sourceTree = ""; }; - 3DF521682D5E90BE008F8E52 /* BleApplicationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BleApplicationDelegate.h; sourceTree = ""; }; + 3DF521682D5E90BE008F8E52 /* BleApplicationDelegateImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BleApplicationDelegateImpl.h; sourceTree = ""; }; 3DF521692D5E90BE008F8E52 /* BleApplicationDelegateImpl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BleApplicationDelegateImpl.mm; sourceTree = ""; }; - 3DF5216A2D5E90BE008F8E52 /* BleConnectionDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BleConnectionDelegate.h; sourceTree = ""; }; + 3DF5216A2D5E90BE008F8E52 /* BleConnectionDelegateImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BleConnectionDelegateImpl.h; sourceTree = ""; }; 3DF5216B2D5E90BE008F8E52 /* BleConnectionDelegateImpl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BleConnectionDelegateImpl.mm; sourceTree = ""; }; 3DF5216C2D5E90BE008F8E52 /* BLEManagerImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BLEManagerImpl.h; sourceTree = ""; }; 3DF5216D2D5E90BE008F8E52 /* BLEManagerImpl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BLEManagerImpl.cpp; sourceTree = ""; }; 3DF5216E2D5E90BE008F8E52 /* BlePlatformConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BlePlatformConfig.h; sourceTree = ""; }; - 3DF5216F2D5E90BE008F8E52 /* BlePlatformDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BlePlatformDelegate.h; sourceTree = ""; }; + 3DF5216F2D5E90BE008F8E52 /* BlePlatformDelegateImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BlePlatformDelegateImpl.h; sourceTree = ""; }; 3DF521702D5E90BE008F8E52 /* BlePlatformDelegateImpl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BlePlatformDelegateImpl.mm; sourceTree = ""; }; 3DF521712D5E90BE008F8E52 /* BleScannerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BleScannerDelegate.h; sourceTree = ""; }; 3DF521722D5E90BE008F8E52 /* BUILD.gn */ = {isa = PBXFileReference; lastKnownFileType = text; path = BUILD.gn; sourceTree = ""; }; @@ -695,8 +700,6 @@ 3DF5218B2D5E90BE008F8E52 /* Logging.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Logging.mm; sourceTree = ""; }; 3DF5218C2D5E90BE008F8E52 /* MdnsError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MdnsError.h; sourceTree = ""; }; 3DF5218D2D5E90BE008F8E52 /* MdnsError.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MdnsError.cpp; sourceTree = ""; }; - 3DF5218E2D5E90BE008F8E52 /* MTRUUIDHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRUUIDHelper.h; sourceTree = ""; }; - 3DF5218F2D5E90BE008F8E52 /* MTRUUIDHelperImpl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRUUIDHelperImpl.mm; sourceTree = ""; }; 3DF521902D5E90BE008F8E52 /* NetworkCommissioningDriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkCommissioningDriver.h; sourceTree = ""; }; 3DF521912D5E90BE008F8E52 /* PlatformManagerImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformManagerImpl.h; sourceTree = ""; }; 3DF521922D5E90BE008F8E52 /* PlatformManagerImpl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PlatformManagerImpl.cpp; sourceTree = ""; }; @@ -709,6 +712,8 @@ 3DF521992D5E90BE008F8E52 /* Tracing.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Tracing.mm; sourceTree = ""; }; 3DF5219A2D5E90BE008F8E52 /* UserDefaults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserDefaults.h; sourceTree = ""; }; 3DF5219B2D5E90BE008F8E52 /* UserDefaults.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UserDefaults.mm; sourceTree = ""; }; + 3DF5219C2D62C3E5008F8E52 /* MTRMockCB.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRMockCB.h; sourceTree = ""; }; + 3DF5219D2D62C3E5008F8E52 /* MTRMockCB.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRMockCB.m; sourceTree = ""; }; 3DFCB3282966684500332B35 /* MTRCertificateInfoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRCertificateInfoTests.m; sourceTree = ""; }; 3DFCB32A2966827F00332B35 /* MTRDefines_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDefines_Internal.h; sourceTree = ""; }; 3DFCB32B29678C9500332B35 /* MTRConversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRConversion.h; sourceTree = ""; }; @@ -1400,16 +1405,18 @@ isa = PBXGroup; children = ( 3DF521722D5E90BE008F8E52 /* BUILD.gn */, - 3DF521682D5E90BE008F8E52 /* BleApplicationDelegate.h */, + 3DF521682D5E90BE008F8E52 /* BleApplicationDelegateImpl.h */, 3DF521692D5E90BE008F8E52 /* BleApplicationDelegateImpl.mm */, - 3DF5216A2D5E90BE008F8E52 /* BleConnectionDelegate.h */, + 3DF5216A2D5E90BE008F8E52 /* BleConnectionDelegateImpl.h */, 3DF5216B2D5E90BE008F8E52 /* BleConnectionDelegateImpl.mm */, 3DF5216C2D5E90BE008F8E52 /* BLEManagerImpl.h */, 3DF5216D2D5E90BE008F8E52 /* BLEManagerImpl.cpp */, 3DF5216E2D5E90BE008F8E52 /* BlePlatformConfig.h */, - 3DF5216F2D5E90BE008F8E52 /* BlePlatformDelegate.h */, + 3DF5216F2D5E90BE008F8E52 /* BlePlatformDelegateImpl.h */, 3DF521702D5E90BE008F8E52 /* BlePlatformDelegateImpl.mm */, 3DF521712D5E90BE008F8E52 /* BleScannerDelegate.h */, + 3DB9DAE92D754C7200704FAB /* BleUtils.h */, + 3DB9DAEA2D754C7200704FAB /* BleUtils.mm */, 3DF521732D5E90BE008F8E52 /* CHIPDevicePlatformConfig.h */, 3DF521742D5E90BE008F8E52 /* CHIPDevicePlatformEvent.h */, 3DF521752D5E90BE008F8E52 /* CHIPPlatformConfig.h */, @@ -1437,8 +1444,6 @@ 3DF5218B2D5E90BE008F8E52 /* Logging.mm */, 3DF5218C2D5E90BE008F8E52 /* MdnsError.h */, 3DF5218D2D5E90BE008F8E52 /* MdnsError.cpp */, - 3DF5218E2D5E90BE008F8E52 /* MTRUUIDHelper.h */, - 3DF5218F2D5E90BE008F8E52 /* MTRUUIDHelperImpl.mm */, 3DF521902D5E90BE008F8E52 /* NetworkCommissioningDriver.h */, 3DF521912D5E90BE008F8E52 /* PlatformManagerImpl.h */, 3DF521922D5E90BE008F8E52 /* PlatformManagerImpl.cpp */, @@ -1480,6 +1485,8 @@ 75B0D01C2B71B46F002074DD /* MTRDeviceTestDelegate.h */, 75B0D01D2B71B47F002074DD /* MTRDeviceTestDelegate.m */, 75139A6C2B7FE19100E3A919 /* MTRTestDeclarations.h */, + 3DF5219C2D62C3E5008F8E52 /* MTRMockCB.h */, + 3DF5219D2D62C3E5008F8E52 /* MTRMockCB.m */, ); path = TestHelpers; sourceTree = ""; @@ -1796,6 +1803,7 @@ 3DA1A3572ABABF69004F0BB9 /* MTRAsyncWorkQueueTests.m */, 3D3928D62BBCEA3D00CDEBB2 /* MTRAvailabilityTests.m */, 51669AEF2913204400F4AA36 /* MTRBackwardsCompatTests.m */, + 3DB9DAE42D67EE5A00704FAB /* MTRBleTests.m */, 3DFCB3282966684500332B35 /* MTRCertificateInfoTests.m */, 517BF3F2282B62CB00A8B7DB /* MTRCertificateTests.m */, 51339B1E2A0DA64D00C798C1 /* MTRCertificateValidityTests.m */, @@ -2594,6 +2602,7 @@ 8874C1322B69C7060084BEFD /* MTRMetricsTests.m in Sources */, 1E5801C328941C050033A199 /* MTRTestOTAProvider.m in Sources */, 5A6FEC9D27B5E48900F25F42 /* MTRXPCProtocolTests.m in Sources */, + 3DB9DAE52D67EE5A00704FAB /* MTRBleTests.m in Sources */, 1EE0805E2A44875E008A03C2 /* MTRCommissionableBrowserTests.m in Sources */, 518D3F832AA132DC008E0007 /* MTRTestPerControllerStorage.m in Sources */, 51339B1F2A0DA64D00C798C1 /* MTRCertificateValidityTests.m in Sources */, @@ -2605,6 +2614,7 @@ 75B0D01E2B71B47F002074DD /* MTRDeviceTestDelegate.m in Sources */, 3D0C484B29DA4FA0006D811F /* MTRErrorTests.m in Sources */, 3DA1A3582ABABF6A004F0BB9 /* MTRAsyncWorkQueueTests.m in Sources */, + 3DF5219E2D62C3E5008F8E52 /* MTRMockCB.m in Sources */, 51742B4A29CB5FC1009974FE /* MTRTestResetCommissioneeHelper.m in Sources */, 5AE6D4E427A99041001F2493 /* MTRDeviceTests.m in Sources */, 510CECA8297F72970064E0B3 /* MTROperationalCertificateIssuerTests.m in Sources */, diff --git a/src/platform/Darwin/BLEManagerImpl.cpp b/src/platform/Darwin/BLEManagerImpl.cpp index 8ff634c52cb1ed..080d75ed1a861b 100644 --- a/src/platform/Darwin/BLEManagerImpl.cpp +++ b/src/platform/Darwin/BLEManagerImpl.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2025 Project CHIP Authors * Copyright (c) 2018 Nest Labs, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,9 +26,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE @@ -43,61 +43,29 @@ Global BLEManagerImpl::sInstance; CHIP_ERROR BLEManagerImpl::_Init() { - CHIP_ERROR err; + ChipLogDetail(DeviceLayer, "Initializing BLE Manager"); - ChipLogDetail(DeviceLayer, "%s", __FUNCTION__); - - // Initialize the Chip BleLayer. - BleApplicationDelegateImpl * appDelegate = new BleApplicationDelegateImpl(); - BleConnectionDelegateImpl * connDelegate = new BleConnectionDelegateImpl(); - BlePlatformDelegateImpl * platformDelegate = new BlePlatformDelegateImpl(); - - mApplicationDelegate = appDelegate; - mConnectionDelegate = connDelegate; - mPlatformDelegate = platformDelegate; - - err = BleLayer::Init(platformDelegate, connDelegate, appDelegate, &DeviceLayer::SystemLayer()); - - if (CHIP_NO_ERROR != err) - { - _Shutdown(); - } - - return err; + // Initialize the CHIP BleLayer. The application, connection, and platform delegate + // implementations are all stateless classes that we inherit from privately. + return BleLayer::Init(this, this, this, &DeviceLayer::SystemLayer()); } void BLEManagerImpl::_Shutdown() { - if (mApplicationDelegate) - { - delete mApplicationDelegate; - mApplicationDelegate = nullptr; - } - - if (mConnectionDelegate) - { - delete mConnectionDelegate; - mConnectionDelegate = nullptr; - } - - if (mPlatformDelegate) - { - delete mPlatformDelegate; - mPlatformDelegate = nullptr; - } + // Nothing to do } CHIP_ERROR BLEManagerImpl::StartScan(BleScannerDelegate * delegate, BleScanMode mode) { - VerifyOrReturnError(mConnectionDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE); - static_cast(mConnectionDelegate)->StartScan(delegate, mode); + VerifyOrReturnError(BleLayer::IsInitialized(), CHIP_ERROR_INCORRECT_STATE); + BleConnectionDelegateImpl::StartScan(delegate, mode); return CHIP_NO_ERROR; } CHIP_ERROR BLEManagerImpl::StopScan() { - VerifyOrReturnError(mConnectionDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE); - static_cast(mConnectionDelegate)->StopScan(); + VerifyOrReturnError(BleLayer::IsInitialized(), CHIP_ERROR_INCORRECT_STATE); + BleConnectionDelegateImpl::StopScan(); return CHIP_NO_ERROR; } diff --git a/src/platform/Darwin/BLEManagerImpl.h b/src/platform/Darwin/BLEManagerImpl.h index 194dadb2e79529..48439b9b21cf4d 100644 --- a/src/platform/Darwin/BLEManagerImpl.h +++ b/src/platform/Darwin/BLEManagerImpl.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2025 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. @@ -23,8 +23,12 @@ #pragma once +#include #include #include +#include +#include +#include #include #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE @@ -38,14 +42,18 @@ using namespace chip::Ble; /** * Concrete implementation of the BLEManagerImpl singleton object for the Darwin platforms. */ -class BLEManagerImpl final : public BLEManager, private BleLayer +class BLEManagerImpl final : public BLEManager, + private BleLayer, + private BleApplicationDelegateImpl, + private BleConnectionDelegateImpl, + private BlePlatformDelegateImpl { // Allow the BLEManager interface class to delegate method calls to // the implementation methods provided by this class. friend BLEManager; public: - CHIP_ERROR ConfigureBle(uint32_t aNodeId, bool aIsCentral) { return CHIP_NO_ERROR; } + CHIP_ERROR ConfigureBle(uint32_t bleDeviceId, bool aIsCentral) { return CHIP_NO_ERROR; } CHIP_ERROR StartScan(BleScannerDelegate * delegate, BleScanMode mode = BleScanMode::kDefault); CHIP_ERROR StopScan(); @@ -70,10 +78,6 @@ class BLEManagerImpl final : public BLEManager, private BleLayer friend BLEManagerImpl & BLEMgrImpl(void); static Global sInstance; - - BleConnectionDelegate * mConnectionDelegate = nullptr; - BlePlatformDelegate * mPlatformDelegate = nullptr; - BleApplicationDelegate * mApplicationDelegate = nullptr; }; /** diff --git a/src/platform/Darwin/BUILD.gn b/src/platform/Darwin/BUILD.gn index e263c8f398c495..57ad9c7821db33 100644 --- a/src/platform/Darwin/BUILD.gn +++ b/src/platform/Darwin/BUILD.gn @@ -124,14 +124,14 @@ static_library("Darwin") { if (chip_enable_ble) { sources += [ - "BleApplicationDelegate.h", + "BleApplicationDelegateImpl.h", "BleApplicationDelegateImpl.mm", - "BleConnectionDelegate.h", + "BleConnectionDelegateImpl.h", "BleConnectionDelegateImpl.mm", - "BlePlatformDelegate.h", + "BlePlatformDelegateImpl.h", "BlePlatformDelegateImpl.mm", - "MTRUUIDHelper.h", - "MTRUUIDHelperImpl.mm", + "BleUtils.h", + "BleUtils.mm", ] } } diff --git a/src/platform/Darwin/BleApplicationDelegate.h b/src/platform/Darwin/BleApplicationDelegateImpl.h similarity index 86% rename from src/platform/Darwin/BleApplicationDelegate.h rename to src/platform/Darwin/BleApplicationDelegateImpl.h index 72aba4e65b397b..e9d985ab0e80c8 100644 --- a/src/platform/Darwin/BleApplicationDelegate.h +++ b/src/platform/Darwin/BleApplicationDelegateImpl.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2025 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. @@ -26,7 +26,7 @@ namespace Internal { class BleApplicationDelegateImpl : public Ble::BleApplicationDelegate { public: - virtual void NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT connObj); + void NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT connObj) override; }; } // namespace Internal diff --git a/src/platform/Darwin/BleApplicationDelegateImpl.mm b/src/platform/Darwin/BleApplicationDelegateImpl.mm index 883f2a2bcd8916..01fc44e46581ee 100644 --- a/src/platform/Darwin/BleApplicationDelegateImpl.mm +++ b/src/platform/Darwin/BleApplicationDelegateImpl.mm @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2025 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. @@ -25,10 +25,7 @@ #endif #include -#include - -using namespace ::chip; -using namespace ::chip::Ble; +#include namespace chip { namespace DeviceLayer { diff --git a/src/platform/Darwin/BleConnectionDelegate.h b/src/platform/Darwin/BleConnectionDelegateImpl.h similarity index 96% rename from src/platform/Darwin/BleConnectionDelegate.h rename to src/platform/Darwin/BleConnectionDelegateImpl.h index 585624ae407dfa..bb0afd4df64cde 100644 --- a/src/platform/Darwin/BleConnectionDelegate.h +++ b/src/platform/Darwin/BleConnectionDelegateImpl.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2025 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. diff --git a/src/platform/Darwin/BleConnectionDelegateImpl.mm b/src/platform/Darwin/BleConnectionDelegateImpl.mm index 7b5d68bab13523..d29525ea60d951 100644 --- a/src/platform/Darwin/BleConnectionDelegateImpl.mm +++ b/src/platform/Darwin/BleConnectionDelegateImpl.mm @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2020-2025 Project CHIP Authors * Copyright (c) 2015-2017 Nest Labs, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,24 +28,26 @@ #include #include #include -#include +#include #include +#include #include #include #include -#import "MTRUUIDHelper.h" +#import + #import "PlatformMetricKeys.h" using namespace chip::Ble; using namespace chip::DeviceLayer; +using namespace chip::DeviceLayer::Internal; using namespace chip::Tracing::DarwinPlatform; constexpr uint64_t kScanningWithDiscriminatorTimeoutInSeconds = 60; constexpr uint64_t kPreWarmScanTimeoutInSeconds = 120; constexpr uint64_t kCachePeripheralTimeoutInSeconds = static_cast(CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX / 1000.0 * 8.0 * 0.625); -constexpr char kBleWorkQueueName[] = "org.csa-iot.matter.framework.ble.workqueue"; typedef NS_ENUM(uint8_t, BleConnectionMode) { kUndefined = 0, @@ -56,25 +58,23 @@ typedef NS_ENUM(uint8_t, BleConnectionMode) { @interface BleConnection : NSObject -@property (strong, nonatomic) dispatch_queue_t chipWorkQueue; -@property (strong, nonatomic) dispatch_queue_t workQueue; +@property (strong, nonatomic) dispatch_queue_t workQueue; // the CHIP work queue @property (strong, nonatomic) CBCentralManager * centralManager; @property (strong, nonatomic) CBPeripheral * peripheral; -@property (strong, nonatomic) CBUUID * shortServiceUUID; @property (nonatomic, readonly, nullable) dispatch_source_t timer; @property (nonatomic, readonly) BleConnectionMode currentMode; @property (strong, nonatomic) NSMutableDictionary * cachedPeripherals; -@property (unsafe_unretained, nonatomic) bool found; -@property (unsafe_unretained, nonatomic) chip::SetupDiscriminator deviceDiscriminator; -@property (unsafe_unretained, nonatomic) void * appState; -@property (unsafe_unretained, nonatomic) BleConnectionDelegate::OnConnectionCompleteFunct onConnectionComplete; -@property (unsafe_unretained, nonatomic) BleConnectionDelegate::OnConnectionErrorFunct onConnectionError; -@property (unsafe_unretained, nonatomic) chip::DeviceLayer::BleScannerDelegate * scannerDelegate; -@property (unsafe_unretained, nonatomic) chip::Ble::BleLayer * mBleLayer; - -- (id)initWithQueue:(dispatch_queue_t)queue; -- (id)initWithDelegate:(chip::DeviceLayer::BleScannerDelegate *)delegate prewarm:(bool)prewarm queue:(dispatch_queue_t)queue; -- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator queue:(dispatch_queue_t)queue; +@property (assign, nonatomic) bool found; +@property (assign, nonatomic) chip::SetupDiscriminator deviceDiscriminator; +@property (assign, nonatomic) void * appState; +@property (assign, nonatomic) BleConnectionDelegate::OnConnectionCompleteFunct onConnectionComplete; +@property (assign, nonatomic) BleConnectionDelegate::OnConnectionErrorFunct onConnectionError; +@property (assign, nonatomic) chip::DeviceLayer::BleScannerDelegate * scannerDelegate; +@property (assign, nonatomic) chip::Ble::BleLayer * mBleLayer; + +- (instancetype)initWithDelegate:(chip::DeviceLayer::BleScannerDelegate *)delegate prewarm:(bool)prewarm; +- (instancetype)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator; + - (void)setBleLayer:(chip::Ble::BleLayer *)bleLayer; - (void)start; - (void)stop; @@ -92,7 +92,6 @@ - (void)removePeripheralsFromCache; namespace DeviceLayer { namespace Internal { BleConnection * ble; - dispatch_queue_t bleWorkQueue; void BleConnectionDelegateImpl::NewConnection( Ble::BleLayer * bleLayer, void * appState, const SetupDiscriminator & inDeviceDiscriminator) @@ -103,30 +102,24 @@ - (void)removePeripheralsFromCache; SetupDiscriminator deviceDiscriminator = inDeviceDiscriminator; ChipLogProgress(Ble, "ConnectionDelegate NewConnection with discriminator"); - if (!bleWorkQueue) { - bleWorkQueue = dispatch_queue_create(kBleWorkQueueName, DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); - } - - dispatch_async(bleWorkQueue, ^{ - // If the previous connection delegate was not a try to connect to something, just reuse it instead of - // creating a brand new connection but update the discriminator and the ble layer members. - if (ble and ![ble isConnecting]) { - [ble setBleLayer:bleLayer]; - ble.appState = appState; - ble.onConnectionComplete = OnConnectionComplete; - ble.onConnectionError = OnConnectionError; - [ble updateWithDiscriminator:deviceDiscriminator]; - return; - } - - [ble stop]; - ble = [[BleConnection alloc] initWithDiscriminator:deviceDiscriminator queue:bleWorkQueue]; + // If the previous connection delegate was not a try to connect to something, just reuse it instead of + // creating a brand new connection but update the discriminator and the ble layer members. + if (ble and ![ble isConnecting]) { [ble setBleLayer:bleLayer]; ble.appState = appState; ble.onConnectionComplete = OnConnectionComplete; ble.onConnectionError = OnConnectionError; - ble.centralManager = [ble.centralManager initWithDelegate:ble queue:bleWorkQueue]; - }); + [ble updateWithDiscriminator:deviceDiscriminator]; + return; + } + + [ble stop]; + ble = [[BleConnection alloc] initWithDiscriminator:deviceDiscriminator]; + [ble setBleLayer:bleLayer]; + ble.appState = appState; + ble.onConnectionComplete = OnConnectionComplete; + ble.onConnectionError = OnConnectionError; + ble.centralManager = [ble.centralManager initWithDelegate:ble queue:ble.workQueue]; } void BleConnectionDelegateImpl::NewConnection(Ble::BleLayer * bleLayer, void * appState, BLE_CONNECTION_OBJECT connObj) @@ -134,31 +127,25 @@ - (void)removePeripheralsFromCache; assertChipStackLockedByCurrentThread(); ChipLogProgress(Ble, "ConnectionDelegate NewConnection with conn obj: %p", connObj); - - if (!bleWorkQueue) { - bleWorkQueue = dispatch_queue_create(kBleWorkQueueName, DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); - } - - CBPeripheral * peripheral = (__bridge CBPeripheral *) connObj; // bridge (and retain) before dispatching - dispatch_async(bleWorkQueue, ^{ - // The BLE_CONNECTION_OBJECT represent a CBPeripheral object. In order for it to be valid the central - // manager needs to still be running. - if (!ble || [ble isConnecting]) { - if (OnConnectionError) { - auto workQueue = chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(); - dispatch_async(workQueue, ^{ - OnConnectionError(appState, CHIP_ERROR_INCORRECT_STATE); - }); - } - return; + CBPeripheral * peripheral = CBPeripheralFromBleConnObject(connObj); + + // The BLE_CONNECTION_OBJECT represents a CBPeripheral object. In order for it to be valid the central + // manager needs to still be running. + if (!ble || [ble isConnecting]) { + if (OnConnectionError) { + // Avoid calling back prior to returning + dispatch_async(PlatformMgrImpl().GetWorkQueue(), ^{ + OnConnectionError(appState, CHIP_ERROR_INCORRECT_STATE); + }); } + return; + } - [ble setBleLayer:bleLayer]; - ble.appState = appState; - ble.onConnectionComplete = OnConnectionComplete; - ble.onConnectionError = OnConnectionError; - [ble updateWithPeripheral:peripheral]; - }); + [ble setBleLayer:bleLayer]; + ble.appState = appState; + ble.onConnectionComplete = OnConnectionComplete; + ble.onConnectionError = OnConnectionError; + [ble updateWithPeripheral:peripheral]; } void BleConnectionDelegateImpl::StartScan(BleScannerDelegate * delegate, BleScanMode mode) @@ -168,39 +155,33 @@ - (void)removePeripheralsFromCache; bool prewarm = (mode == BleScanMode::kPreWarm); ChipLogProgress(Ble, "ConnectionDelegate StartScan (%s)", (prewarm ? "pre-warm" : "default")); - if (!bleWorkQueue) { - bleWorkQueue = dispatch_queue_create(kBleWorkQueueName, DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); - } - - dispatch_async(bleWorkQueue, ^{ - // Pre-warming is best-effort, don't cancel an ongoing scan or connection attempt - if (prewarm && ble) { - // TODO: Once we get rid of the separate BLE queue we can just return CHIP_ERROR_BUSY. - // That will also allow these cases to be distinguished in our metric. - ChipLogProgress(Ble, "Not starting pre-warm scan, an operation is already in progress"); - if (delegate) { - dispatch_async(PlatformMgrImpl().GetWorkQueue(), ^{ - delegate->OnBleScanStopped(); - }); - } - return; + // Pre-warming is best-effort, don't cancel an ongoing scan or connection attempt + if (prewarm && ble) { + // TODO: Once we get rid of the separate BLE queue we can just return CHIP_ERROR_BUSY. + // That will also allow these cases to be distinguished in our metric. + ChipLogProgress(Ble, "Not starting pre-warm scan, an operation is already in progress"); + if (delegate) { + dispatch_async(PlatformMgrImpl().GetWorkQueue(), ^{ + delegate->OnBleScanStopped(); + }); } + return; + } - // If the previous connection delegate was not a try to connect to something, just reuse it instead of - // creating a brand new connection but update the discriminator and the ble layer members. - if (ble and ![ble isConnecting]) { - [ble updateWithDelegate:delegate prewarm:prewarm]; - return; - } + // If the previous connection delegate was not a try to connect to something, just reuse it instead of + // creating a brand new connection but update the discriminator and the ble layer members. + if (ble and ![ble isConnecting]) { + [ble updateWithDelegate:delegate prewarm:prewarm]; + return; + } - [ble stop]; - ble = [[BleConnection alloc] initWithDelegate:delegate prewarm:prewarm queue:bleWorkQueue]; - // Do _not_ set onConnectionComplete and onConnectionError - // here. The connection callbacks we have expect an appState - // that we do not have here, and in any case connection - // complete/error make no sense for a scan. - ble.centralManager = [ble.centralManager initWithDelegate:ble queue:bleWorkQueue]; - }); + [ble stop]; + ble = [[BleConnection alloc] initWithDelegate:delegate prewarm:prewarm]; + // Do _not_ set onConnectionComplete and onConnectionError + // here. The connection callbacks we have expect an appState + // that we do not have here, and in any case connection + // complete/error make no sense for a scan. + ble.centralManager = [ble.centralManager initWithDelegate:ble queue:ble.workQueue]; } void BleConnectionDelegateImpl::StopScan() @@ -218,16 +199,8 @@ - (void)removePeripheralsFromCache; CHIP_ERROR BleConnectionDelegateImpl::DoCancel() { assertChipStackLockedByCurrentThread(); - if (bleWorkQueue == nil) { - return CHIP_NO_ERROR; - } - - dispatch_async(bleWorkQueue, ^{ - [ble stop]; - ble = nil; - }); - - bleWorkQueue = nil; + [ble stop]; + ble = nil; return CHIP_NO_ERROR; } } // namespace Internal @@ -239,15 +212,16 @@ @interface BleConnection () @property (nonatomic, readonly) int32_t totalDevicesRemoved; @end -@implementation BleConnection +@implementation BleConnection { + CBUUID * _chipServiceUUID; +} -- (id)initWithQueue:(dispatch_queue_t)queue +- (instancetype)init { self = [super init]; if (self) { - self.shortServiceUUID = [MTRUUIDHelper GetShortestServiceUUID:&chip::Ble::CHIP_BLE_SVC_ID]; - _chipWorkQueue = chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(); - _workQueue = queue; + _chipServiceUUID = CBUUIDFromBleUUID(chip::Ble::CHIP_BLE_SVC_ID); + _workQueue = chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(); _centralManager = [CBCentralManager alloc]; _found = false; _cachedPeripherals = [[NSMutableDictionary alloc] init]; @@ -258,9 +232,9 @@ - (id)initWithQueue:(dispatch_queue_t)queue return self; } -- (id)initWithDelegate:(chip::DeviceLayer::BleScannerDelegate *)delegate prewarm:(bool)prewarm queue:(dispatch_queue_t)queue +- (instancetype)initWithDelegate:(chip::DeviceLayer::BleScannerDelegate *)delegate prewarm:(bool)prewarm { - self = [self initWithQueue:queue]; + self = [self init]; if (self) { _scannerDelegate = delegate; if (prewarm) { @@ -274,9 +248,9 @@ - (id)initWithDelegate:(chip::DeviceLayer::BleScannerDelegate *)delegate prewarm return self; } -- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator queue:(dispatch_queue_t)queue +- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator { - self = [self initWithQueue:queue]; + self = [self init]; if (self) { _deviceDiscriminator = deviceDiscriminator; _currentMode = kConnecting; @@ -318,26 +292,23 @@ - (void)clearTimer // All our callback dispatch must happen on _chipWorkQueue - (void)dispatchConnectionError:(CHIP_ERROR)error { - dispatch_async(_chipWorkQueue, ^{ - if (self.onConnectionError != nil) { - self.onConnectionError(self.appState, error); - } - }); + if (self.onConnectionError != nil) { + self.onConnectionError(self.appState, error); + } } - (void)dispatchConnectionComplete:(CBPeripheral *)peripheral { - dispatch_async(_chipWorkQueue, ^{ - if (self.onConnectionComplete != nil) { - self.onConnectionComplete(self.appState, (__bridge void *) peripheral); - } - }); + if (self.onConnectionComplete != nil) { + self.onConnectionComplete(self.appState, BleConnObjectFromCBPeripheral(peripheral)); + } } // Start CBCentralManagerDelegate - (void)centralManagerDidUpdateState:(CBCentralManager *)central { + assertChipStackLockedByCurrentThread(); MATTER_LOG_METRIC(kMetricBLECentralManagerState, static_cast(central.state)); switch (central.state) { @@ -370,15 +341,9 @@ - (void)centralManager:(CBCentralManager *)central advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { - NSDictionary * servicesData = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey]; - NSData * serviceData; - for (CBUUID * serviceUUID in servicesData) { - if ([serviceUUID.data isEqualToData:_shortServiceUUID.data]) { - serviceData = [servicesData objectForKey:serviceUUID]; - break; - } - } + assertChipStackLockedByCurrentThread(); + NSData * serviceData = advertisementData[CBAdvertisementDataServiceDataKey][_chipServiceUUID]; if (!serviceData) { return; } @@ -443,8 +408,10 @@ - (BOOL)checkDiscriminator:(uint16_t)discriminator - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { + assertChipStackLockedByCurrentThread(); MATTER_LOG_METRIC_END(kMetricBLEConnectPeripheral); MATTER_LOG_METRIC_BEGIN(kMetricBLEDiscoveredServices); + [peripheral setDelegate:self]; [peripheral discoverServices:nil]; [self stopScanning]; @@ -456,6 +423,8 @@ - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPerip - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { + assertChipStackLockedByCurrentThread(); + if (nil != error) { ChipLogError(Ble, "BLE:Error finding Chip Service in the device: [%s]", [error.localizedDescription UTF8String]); } @@ -463,7 +432,7 @@ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)err MATTER_LOG_METRIC_END(kMetricBLEDiscoveredServices, CHIP_ERROR(chip::ChipError::Range::kOS, static_cast(error.code))); for (CBService * service in peripheral.services) { - if ([service.UUID.data isEqualToData:_shortServiceUUID.data] && !self.found) { + if ([service.UUID isEqual:_chipServiceUUID] && !self.found) { MATTER_LOG_METRIC_BEGIN(kMetricBLEDiscoveredCharacteristics); [peripheral discoverCharacteristics:nil forService:service]; self.found = true; @@ -480,6 +449,7 @@ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)err - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { + assertChipStackLockedByCurrentThread(); MATTER_LOG_METRIC_END(kMetricBLEDiscoveredCharacteristics, CHIP_ERROR(chip::ChipError::Range::kOS, static_cast(error.code))); if (nil != error) { @@ -495,20 +465,17 @@ - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { + assertChipStackLockedByCurrentThread(); + if (nil == error) { - chip::Ble::ChipBleUUID svcId; - chip::Ble::ChipBleUUID charId; - [BleConnection fillServiceWithCharacteristicUuids:characteristic svcId:&svcId charId:&charId]; - dispatch_async(_chipWorkQueue, ^{ - _mBleLayer->HandleWriteConfirmation((__bridge void *) peripheral, &svcId, &charId); - }); + ChipBleUUID svcId = BleUUIDFromCBUUD(characteristic.service.UUID); + ChipBleUUID charId = BleUUIDFromCBUUD(characteristic.UUID); + _mBleLayer->HandleWriteConfirmation(BleConnObjectFromCBPeripheral(peripheral), &svcId, &charId); } else { ChipLogError( Ble, "BLE:Error writing Characteristics in Chip service on the device: [%s]", [error.localizedDescription UTF8String]); - dispatch_async(_chipWorkQueue, ^{ - MATTER_LOG_METRIC(kMetricBLEWriteChrValueFailed, BLE_ERROR_GATT_WRITE_FAILED); - _mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_WRITE_FAILED); - }); + MATTER_LOG_METRIC(kMetricBLEWriteChrValueFailed, BLE_ERROR_GATT_WRITE_FAILED); + _mBleLayer->HandleConnectionError(BleConnObjectFromCBPeripheral(peripheral), BLE_ERROR_GATT_WRITE_FAILED); } } @@ -516,34 +483,31 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { + assertChipStackLockedByCurrentThread(); + bool isNotifying = characteristic.isNotifying; if (nil == error) { - chip::Ble::ChipBleUUID svcId; - chip::Ble::ChipBleUUID charId; - [BleConnection fillServiceWithCharacteristicUuids:characteristic svcId:&svcId charId:&charId]; - - dispatch_async(_chipWorkQueue, ^{ - if (isNotifying) { - _mBleLayer->HandleSubscribeComplete((__bridge void *) peripheral, &svcId, &charId); - } else { - _mBleLayer->HandleUnsubscribeComplete((__bridge void *) peripheral, &svcId, &charId); - } - }); + ChipBleUUID svcId = BleUUIDFromCBUUD(characteristic.service.UUID); + ChipBleUUID charId = BleUUIDFromCBUUD(characteristic.UUID); + if (isNotifying) { + _mBleLayer->HandleSubscribeComplete(BleConnObjectFromCBPeripheral(peripheral), &svcId, &charId); + } else { + _mBleLayer->HandleUnsubscribeComplete(BleConnObjectFromCBPeripheral(peripheral), &svcId, &charId); + } } else { ChipLogError(Ble, "BLE:Error subscribing/unsubcribing some characteristic on the device: [%s]", [error.localizedDescription UTF8String]); - dispatch_async(_chipWorkQueue, ^{ - if (isNotifying) { - MATTER_LOG_METRIC(kMetricBLEUpdateNotificationStateForChrFailed, BLE_ERROR_GATT_WRITE_FAILED); - // we're still notifying, so we must failed the unsubscription - _mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_UNSUBSCRIBE_FAILED); - } else { - // we're not notifying, so we must failed the subscription - MATTER_LOG_METRIC(kMetricBLEUpdateNotificationStateForChrFailed, BLE_ERROR_GATT_SUBSCRIBE_FAILED); - _mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_SUBSCRIBE_FAILED); - } - }); + + if (isNotifying) { + MATTER_LOG_METRIC(kMetricBLEUpdateNotificationStateForChrFailed, BLE_ERROR_GATT_WRITE_FAILED); + // we're still notifying, so we must failed the unsubscription + _mBleLayer->HandleConnectionError(BleConnObjectFromCBPeripheral(peripheral), BLE_ERROR_GATT_UNSUBSCRIBE_FAILED); + } else { + // we're not notifying, so we must failed the subscription + MATTER_LOG_METRIC(kMetricBLEUpdateNotificationStateForChrFailed, BLE_ERROR_GATT_SUBSCRIBE_FAILED); + _mBleLayer->HandleConnectionError(BleConnObjectFromCBPeripheral(peripheral), BLE_ERROR_GATT_SUBSCRIBE_FAILED); + } } } @@ -551,34 +515,31 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { + assertChipStackLockedByCurrentThread(); + if (nil == error) { - chip::Ble::ChipBleUUID svcId; - chip::Ble::ChipBleUUID charId; - [BleConnection fillServiceWithCharacteristicUuids:characteristic svcId:&svcId charId:&charId]; + ChipBleUUID svcId = BleUUIDFromCBUUD(characteristic.service.UUID); + ChipBleUUID charId = BleUUIDFromCBUUD(characteristic.UUID); auto * value = characteristic.value; // read immediately before dispatching - dispatch_async(_chipWorkQueue, ^{ - // build a inet buffer from the rxEv and send to blelayer. - auto msgBuf = chip::System::PacketBufferHandle::NewWithData(value.bytes, value.length); - - if (msgBuf.IsNull()) { - ChipLogError(Ble, "Failed at allocating buffer for incoming BLE data"); - MATTER_LOG_METRIC(kMetricBLEUpdateValueForChrFailed, CHIP_ERROR_NO_MEMORY); - _mBleLayer->HandleConnectionError((__bridge void *) peripheral, CHIP_ERROR_NO_MEMORY); - } else if (!_mBleLayer->HandleIndicationReceived((__bridge void *) peripheral, &svcId, &charId, std::move(msgBuf))) { - // since this error comes from device manager core - // we assume it would do the right thing, like closing the connection - ChipLogError(Ble, "Failed at handling incoming BLE data"); - MATTER_LOG_METRIC(kMetricBLEUpdateValueForChrFailed, CHIP_ERROR_INCORRECT_STATE); - } - }); + // build a inet buffer from the rxEv and send to blelayer. + auto msgBuf = chip::System::PacketBufferHandle::NewWithData(value.bytes, value.length); + + if (msgBuf.IsNull()) { + ChipLogError(Ble, "Failed at allocating buffer for incoming BLE data"); + MATTER_LOG_METRIC(kMetricBLEUpdateValueForChrFailed, CHIP_ERROR_NO_MEMORY); + _mBleLayer->HandleConnectionError(BleConnObjectFromCBPeripheral(peripheral), CHIP_ERROR_NO_MEMORY); + } else if (!_mBleLayer->HandleIndicationReceived(BleConnObjectFromCBPeripheral(peripheral), &svcId, &charId, std::move(msgBuf))) { + // since this error comes from device manager core + // we assume it would do the right thing, like closing the connection + ChipLogError(Ble, "Failed at handling incoming BLE data"); + MATTER_LOG_METRIC(kMetricBLEUpdateValueForChrFailed, CHIP_ERROR_INCORRECT_STATE); + } } else { ChipLogError( Ble, "BLE:Error receiving indication of Characteristics on the device: [%s]", [error.localizedDescription UTF8String]); - dispatch_async(_chipWorkQueue, ^{ - MATTER_LOG_METRIC(kMetricBLEUpdateValueForChrFailed, BLE_ERROR_GATT_INDICATE_FAILED); - _mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_INDICATE_FAILED); - }); + MATTER_LOG_METRIC(kMetricBLEUpdateValueForChrFailed, BLE_ERROR_GATT_INDICATE_FAILED); + _mBleLayer->HandleConnectionError(BleConnObjectFromCBPeripheral(peripheral), BLE_ERROR_GATT_INDICATE_FAILED); } } @@ -603,30 +564,20 @@ - (void)stop [self stopScanning]; [self removePeripheralsFromCache]; - if (!_centralManager && !_peripheral) { - return; + if (_peripheral) { + // Close all BLE connections before we release CB objects + _mBleLayer->CloseAllBleConnections(); + _peripheral = nil; } - // Properly closing the underlying ble connections needs to happens - // on the chip work queue. At the same time the SDK is trying to - // properly unsubscribe and shutdown the connection, so if we nullify - // the centralManager and the peripheral members too early it won't be - // able to reach those. - // This is why closing connections happens as 2 async steps. - dispatch_async(_chipWorkQueue, ^{ - if (_peripheral) { - _mBleLayer->CloseAllBleConnections(); - } + if (_centralManager) { + _centralManager.delegate = nil; + _centralManager = nil; + } - dispatch_async(_workQueue, ^{ - _centralManager.delegate = nil; - _centralManager = nil; - _peripheral = nil; - if (chip::DeviceLayer::Internal::ble == self) { - chip::DeviceLayer::Internal::ble = nil; - } - }); - }); + if (chip::DeviceLayer::Internal::ble == self) { + chip::DeviceLayer::Internal::ble = nil; + } } - (void)_resetCounters @@ -647,7 +598,7 @@ - (void)startScanning [self _resetCounters]; auto scanOptions = @{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }; - [_centralManager scanForPeripheralsWithServices:@[ _shortServiceUUID ] options:scanOptions]; + [_centralManager scanForPeripheralsWithServices:@[ _chipServiceUUID ] options:scanOptions]; } - (void)stopScanning @@ -680,9 +631,7 @@ - (void)detachScannerDelegate auto * existingDelegate = _scannerDelegate; if (existingDelegate) { _scannerDelegate = nullptr; - dispatch_async(_chipWorkQueue, ^{ - existingDelegate->OnBleScanStopped(); - }); + existingDelegate->OnBleScanStopped(); } } @@ -693,11 +642,9 @@ - (void)updateWithDelegate:(chip::DeviceLayer::BleScannerDelegate *)delegate pre if (delegate) { for (CBPeripheral * cachedPeripheral in _cachedPeripherals) { NSData * serviceData = _cachedPeripherals[cachedPeripheral][@"data"]; - dispatch_async(_chipWorkQueue, ^{ - ChipBLEDeviceIdentificationInfo info; - memcpy(&info, [serviceData bytes], sizeof(info)); - delegate->OnBleScanAdd((__bridge void *) cachedPeripheral, info); - }); + ChipBLEDeviceIdentificationInfo info; + memcpy(&info, [serviceData bytes], sizeof(info)); + delegate->OnBleScanAdd(BleConnObjectFromCBPeripheral(cachedPeripheral), info); } _scannerDelegate = delegate; } @@ -768,12 +715,10 @@ - (void)addPeripheralToCache:(CBPeripheral *)peripheral data:(NSData *)data ChipLogProgress(Ble, "Adding peripheral %p to the cache", peripheral); auto delegate = _scannerDelegate; if (delegate) { - dispatch_async(_chipWorkQueue, ^{ - ChipBLEDeviceIdentificationInfo info; - auto bytes = (const uint8_t *) [data bytes]; - memcpy(&info, bytes, sizeof(info)); - delegate->OnBleScanAdd((__bridge void *) peripheral, info); - }); + ChipBLEDeviceIdentificationInfo info; + auto bytes = (const uint8_t *) [data bytes]; + memcpy(&info, bytes, sizeof(info)); + delegate->OnBleScanAdd(BleConnObjectFromCBPeripheral(peripheral), info); } timeoutTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _workQueue); @@ -820,9 +765,7 @@ - (void)removePeripheralFromCache:(CBPeripheral *)peripheral auto delegate = _scannerDelegate; if (delegate) { - dispatch_async(_chipWorkQueue, ^{ - delegate->OnBleScanRemove((__bridge void *) peripheral); - }); + delegate->OnBleScanRemove(BleConnObjectFromCBPeripheral(peripheral)); } } } @@ -834,55 +777,6 @@ - (void)removePeripheralsFromCache } } -/** - * private static method to copy service and characteristic UUIDs from CBCharacteristic to a pair of ChipBleUUID objects. - * this is used in calls into Chip layer to decouple it from CoreBluetooth - * - * @param[in] characteristic the source characteristic - * @param[in] svcId the destination service UUID - * @param[in] charId the destination characteristic UUID - * - */ -+ (void)fillServiceWithCharacteristicUuids:(CBCharacteristic *)characteristic - svcId:(chip::Ble::ChipBleUUID *)svcId - charId:(chip::Ble::ChipBleUUID *)charId -{ - static const size_t FullUUIDLength = 16; - if ((FullUUIDLength != sizeof(charId->bytes)) || (FullUUIDLength != sizeof(svcId->bytes)) - || (FullUUIDLength != characteristic.UUID.data.length)) { - // we're dead. we expect the data length to be the same (16-byte) across the board - ChipLogError(Ble, "UUID of characteristic is incompatible"); - return; - } - - memcpy(charId->bytes, characteristic.UUID.data.bytes, sizeof(charId->bytes)); - memset(svcId->bytes, 0, sizeof(svcId->bytes)); - - // Expand service UUID back to 16-byte long as that's what the BLE Layer expects - // this is a buffer pre-filled with BLE service UUID Base - // byte 0 to 3 are reserved for shorter versions of BLE service UUIDs - // For 4-byte service UUIDs, all bytes from 0 to 3 are used - // For 2-byte service UUIDs, byte 0 and 1 shall be 0 - uint8_t serviceFullUUID[FullUUIDLength] - = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; - - switch (characteristic.service.UUID.data.length) { - case 2: - // copy the 2-byte service UUID onto the right offset - memcpy(serviceFullUUID + 2, characteristic.service.UUID.data.bytes, 2); - break; - case 4: - // flow through - case 16: - memcpy(serviceFullUUID, characteristic.service.UUID.data.bytes, characteristic.service.UUID.data.length); - break; - default: - // we're dead. we expect the data length to be the same (16-byte) across the board - ChipLogError(Ble, "Service UUIDs are incompatible"); - } - memcpy(svcId->bytes, serviceFullUUID, sizeof(svcId->bytes)); -} - - (void)setBleLayer:(chip::Ble::BleLayer *)bleLayer { _mBleLayer = bleLayer; diff --git a/src/platform/Darwin/BlePlatformConfig.h b/src/platform/Darwin/BlePlatformConfig.h index a27bfa28784de8..096af0478dd99a 100644 --- a/src/platform/Darwin/BlePlatformConfig.h +++ b/src/platform/Darwin/BlePlatformConfig.h @@ -26,6 +26,10 @@ // ==================== Platform Adaptations ==================== +#define BLE_CONNECTION_OBJECT void * // actually __unsafe_unretained CBPeripheral * +#define BLE_CONNECTION_UNINITIALIZED nullptr +#define BLE_USES_DEVICE_EVENTS 0 + // ========== Platform-specific Configuration Overrides ========= /* none so far */ diff --git a/src/platform/Darwin/BlePlatformDelegate.h b/src/platform/Darwin/BlePlatformDelegateImpl.h similarity index 71% rename from src/platform/Darwin/BlePlatformDelegate.h rename to src/platform/Darwin/BlePlatformDelegateImpl.h index d53f9007ca1b59..256f36fe733a86 100644 --- a/src/platform/Darwin/BlePlatformDelegate.h +++ b/src/platform/Darwin/BlePlatformDelegateImpl.h @@ -20,9 +20,6 @@ #include #include -using ::chip::Ble::ChipBleUUID; -using ::chip::System::PacketBufferHandle; - namespace chip { namespace DeviceLayer { namespace Internal { @@ -30,16 +27,16 @@ namespace Internal { class BlePlatformDelegateImpl : public Ble::BlePlatformDelegate { public: - CHIP_ERROR SubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, - const ChipBleUUID * charId) override; - CHIP_ERROR UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, - const ChipBleUUID * charId) override; + CHIP_ERROR SubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; + CHIP_ERROR UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; CHIP_ERROR CloseConnection(BLE_CONNECTION_OBJECT connObj) override; uint16_t GetMTU(BLE_CONNECTION_OBJECT connObj) const override; - CHIP_ERROR SendIndication(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId, - PacketBufferHandle pBuf) override; - CHIP_ERROR SendWriteRequest(BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId, - PacketBufferHandle pBuf) override; + CHIP_ERROR SendIndication(BLE_CONNECTION_OBJECT connObj, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; + CHIP_ERROR SendWriteRequest(BLE_CONNECTION_OBJECT connObj, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; }; } // namespace Internal diff --git a/src/platform/Darwin/BlePlatformDelegateImpl.mm b/src/platform/Darwin/BlePlatformDelegateImpl.mm index d967ea41d2a686..1b5fe31c58f3bf 100644 --- a/src/platform/Darwin/BlePlatformDelegateImpl.mm +++ b/src/platform/Darwin/BlePlatformDelegateImpl.mm @@ -27,76 +27,61 @@ #include #include -#include +#include +#include -#import "MTRUUIDHelper.h" +#import -using namespace ::chip; -using namespace ::chip::Ble; -using ::chip::System::PacketBufferHandle; +using namespace chip::Ble; +using chip::System::PacketBufferHandle; namespace chip { namespace DeviceLayer { namespace Internal { - CHIP_ERROR BlePlatformDelegateImpl::SubscribeCharacteristic( - BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId) - { - CHIP_ERROR err = BLE_ERROR_GATT_SUBSCRIBE_FAILED; - if (nullptr == svcId || nullptr == charId) { - return err; - } - - CBUUID * serviceId = [MTRUUIDHelper GetShortestServiceUUID:svcId]; - CBUUID * characteristicId = [CBUUID UUIDWithData:[NSData dataWithBytes:charId->bytes length:sizeof(charId->bytes)]]; - CBPeripheral * peripheral = (__bridge CBPeripheral *) connObj; - - for (CBService * service in peripheral.services) { - if ([service.UUID.data isEqualToData:serviceId.data]) { - for (CBCharacteristic * characteristic in service.characteristics) { - if ([characteristic.UUID.data isEqualToData:characteristicId.data]) { - err = CHIP_NO_ERROR; - [peripheral setNotifyValue:true forCharacteristic:characteristic]; - break; + namespace { + CBCharacteristic * FindCharacteristic(CBPeripheral * peripheral, const ChipBleUUID * svcId, const ChipBleUUID * charId) + { + VerifyOrReturnValue(svcId != nullptr && charId != nullptr, nil); + CBUUID * cbSvcId = CBUUIDFromBleUUID(*svcId); + for (CBService * service in peripheral.services) { + if ([service.UUID isEqual:cbSvcId]) { + CBUUID * cbCharId = CBUUIDFromBleUUID(*charId); + for (CBCharacteristic * characteristic in service.characteristics) { + if ([characteristic.UUID isEqual:cbCharId]) { + return characteristic; + } } + break; } } + return nil; } + } - return err; + CHIP_ERROR BlePlatformDelegateImpl::SubscribeCharacteristic( + BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId) + { + CBPeripheral * peripheral = CBPeripheralFromBleConnObject(connObj); + CBCharacteristic * characteristic = FindCharacteristic(peripheral, svcId, charId); + VerifyOrReturnError(characteristic != nil, BLE_ERROR_GATT_SUBSCRIBE_FAILED); + [peripheral setNotifyValue:YES forCharacteristic:characteristic]; + return CHIP_NO_ERROR; } CHIP_ERROR BlePlatformDelegateImpl::UnsubscribeCharacteristic( BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId) { - CHIP_ERROR err = BLE_ERROR_GATT_UNSUBSCRIBE_FAILED; - if (nullptr == svcId || nullptr == charId) { - return err; - } - - CBUUID * serviceId = [MTRUUIDHelper GetShortestServiceUUID:svcId]; - CBUUID * characteristicId = characteristicId = [CBUUID UUIDWithData:[NSData dataWithBytes:charId->bytes - length:sizeof(charId->bytes)]]; - CBPeripheral * peripheral = (__bridge CBPeripheral *) connObj; - - for (CBService * service in peripheral.services) { - if ([service.UUID.data isEqualToData:serviceId.data]) { - for (CBCharacteristic * characteristic in service.characteristics) { - if ([characteristic.UUID.data isEqualToData:characteristicId.data]) { - err = CHIP_NO_ERROR; - [peripheral setNotifyValue:false forCharacteristic:characteristic]; - break; - } - } - } - } - - return err; + CBPeripheral * peripheral = CBPeripheralFromBleConnObject(connObj); + CBCharacteristic * characteristic = FindCharacteristic(peripheral, svcId, charId); + VerifyOrReturnError(characteristic != nil, BLE_ERROR_GATT_UNSUBSCRIBE_FAILED); + [peripheral setNotifyValue:NO forCharacteristic:characteristic]; + return CHIP_NO_ERROR; } CHIP_ERROR BlePlatformDelegateImpl::CloseConnection(BLE_CONNECTION_OBJECT connObj) { - CBPeripheral * peripheral = (__bridge CBPeripheral *) connObj; + CBPeripheral * peripheral = CBPeripheralFromBleConnObject(connObj); // CoreBluetooth API requires a CBCentralManager to close a connection which is a property of the peripheral. CBCentralManager * manager = (CBCentralManager *) [peripheral valueForKey:@"manager"]; @@ -108,7 +93,7 @@ uint16_t BlePlatformDelegateImpl::GetMTU(BLE_CONNECTION_OBJECT connObj) const { - CBPeripheral * peripheral = (__bridge CBPeripheral *) connObj; + CBPeripheral * peripheral = CBPeripheralFromBleConnObject(connObj); // The negotiated mtu length is a property of the peripheral. uint16_t mtuLength = [[peripheral valueForKey:@"mtuLength"] unsignedShortValue]; @@ -126,32 +111,12 @@ CHIP_ERROR BlePlatformDelegateImpl::SendWriteRequest( BLE_CONNECTION_OBJECT connObj, const ChipBleUUID * svcId, const ChipBleUUID * charId, PacketBufferHandle pBuf) { - CHIP_ERROR err = BLE_ERROR_GATT_WRITE_FAILED; - if (nullptr == svcId || nullptr == charId || pBuf.IsNull()) { - return err; - } - - CBUUID * serviceId = [MTRUUIDHelper GetShortestServiceUUID:svcId]; - CBUUID * characteristicId = [CBUUID UUIDWithData:[NSData dataWithBytes:charId->bytes length:sizeof(charId->bytes)]]; - NSData * data = [NSData dataWithBytes:pBuf->Start() length:pBuf->DataLength()]; - CBPeripheral * peripheral = (__bridge CBPeripheral *) connObj; - - for (CBService * service in peripheral.services) { - if ([service.UUID.data isEqualToData:serviceId.data]) { - for (CBCharacteristic * characteristic in service.characteristics) { - if ([characteristic.UUID.data isEqualToData:characteristicId.data]) { - err = CHIP_NO_ERROR; - [peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; - break; - } - } - } - } - - // Going out of scope releases delegate's reference to pBuf. pBuf will be freed when both platform delegate and Chip - // stack free their references to it. We release pBuf's reference here since its payload bytes were copied into a new - // NSData object - return err; + CBPeripheral * peripheral = CBPeripheralFromBleConnObject(connObj); + CBCharacteristic * characteristic = FindCharacteristic(peripheral, svcId, charId); + VerifyOrReturnError(characteristic != nil && !pBuf.IsNull(), BLE_ERROR_GATT_WRITE_FAILED); + NSData * data = [NSData dataWithBytes:pBuf->Start() length:pBuf->DataLength()]; // copies data, pBuf can be freed + [peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; + return CHIP_NO_ERROR; } } // namespace Internal diff --git a/src/platform/Darwin/BleUtils.h b/src/platform/Darwin/BleUtils.h new file mode 100644 index 00000000000000..13d7b4e88aecfb --- /dev/null +++ b/src/platform/Darwin/BleUtils.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2025 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. + */ + +#pragma once + +#include + +@class CBPeripheral; +@class CBUUID; + +namespace chip { +namespace DeviceLayer { + namespace Internal { + + inline CBPeripheral * CBPeripheralFromBleConnObject(BLE_CONNECTION_OBJECT connObj) + { + return (__bridge CBPeripheral *) connObj; + } + + inline BLE_CONNECTION_OBJECT BleConnObjectFromCBPeripheral(CBPeripheral * peripheral) + { + return (__bridge void *) peripheral; + } + + // Creates a CBUUID from a ChipBleUUID + CBUUID * CBUUIDFromBleUUID(Ble::ChipBleUUID const & uuid); + + // Creates a ChipBleUUID from a CBUUID, expanding 16 or 32 bit UUIDs if necessary. + Ble::ChipBleUUID BleUUIDFromCBUUD(CBUUID * uuid); + + } +} +} diff --git a/src/platform/Darwin/BleUtils.mm b/src/platform/Darwin/BleUtils.mm new file mode 100644 index 00000000000000..5ca9f4a3946c67 --- /dev/null +++ b/src/platform/Darwin/BleUtils.mm @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2025 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 "BleUtils.h" + +#import +#import + +using namespace chip::Ble; + +namespace chip { +namespace DeviceLayer { + namespace Internal { + + CBUUID * CBUUIDFromBleUUID(ChipBleUUID const & uuid) + { + return [CBUUID UUIDWithData:[NSData dataWithBytes:uuid.bytes length:sizeof(uuid.bytes)]]; + } + + ChipBleUUID BleUUIDFromCBUUD(CBUUID * uuid) + { + // CBUUID handles the expansion to 128 bit automatically internally, + // but doesn't expose the expanded UUID in the `data` property, so + // we have to re-implement the expansion here. + // The Base UUID 00000000-0000-1000-8000-00805F9B34FB is defined in the BLE spec. + constexpr ChipBleUUID baseUuid = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB } }; + NSData * uuidData = uuid.data; + switch (uuidData.length) { + case 2: { + ChipBleUUID outUuid = baseUuid; + memcpy(outUuid.bytes + 2, uuidData.bytes, 2); + return outUuid; + } + case 4: { + ChipBleUUID outUuid = baseUuid; + memcpy(outUuid.bytes, uuidData.bytes, 4); + return outUuid; + } + case 16: { + ChipBleUUID outUuid; + memcpy(outUuid.bytes, uuidData.bytes, 16); + return outUuid; + } + default: { + NSCAssert(NO, @"Invalid CBUUID.data: %@", uuidData); + return ChipBleUUID {}; + } + } + } + + } +} +} diff --git a/src/platform/Darwin/MTRUUIDHelper.h b/src/platform/Darwin/MTRUUIDHelper.h deleted file mode 100644 index faa0af95809f88..00000000000000 --- a/src/platform/Darwin/MTRUUIDHelper.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Copyright (c) 2020 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. - */ - -#pragma once - -#include - -#import - -@interface MTRUUIDHelper : NSObject -+ (CBUUID *)GetShortestServiceUUID:(const chip::Ble::ChipBleUUID *)svcId; -@end diff --git a/src/platform/Darwin/MTRUUIDHelperImpl.mm b/src/platform/Darwin/MTRUUIDHelperImpl.mm deleted file mode 100644 index 8f1b863a760cf8..00000000000000 --- a/src/platform/Darwin/MTRUUIDHelperImpl.mm +++ /dev/null @@ -1,44 +0,0 @@ -/* - * - * Copyright (c) 2020 Project CHIP Authors - * Copyright (c) 2015-2017 Nest Labs, Inc. - * - * 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. - */ - -#if !__has_feature(objc_arc) -#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -#import "MTRUUIDHelper.h" - -@implementation MTRUUIDHelper - -+ (CBUUID *)GetShortestServiceUUID:(const chip::Ble::ChipBleUUID *)svcId -{ - // shorten the 16-byte UUID reported by BLE Layer to shortest possible, 2 or 4 bytes - // this is the BLE Service UUID Base. If a 16-byte service UUID partially matches with these 12 bytes, - // it can be shortened to 2 or 4 bytes. - static const uint8_t bleBaseUUID[12] = { 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; - if (0 == memcmp(svcId->bytes + 4, bleBaseUUID, sizeof(bleBaseUUID))) { - // okay, let's try to shorten it - if ((0 == svcId->bytes[0]) && (0 == svcId->bytes[1])) { - // the highest 2 bytes are both 0, so we just need 2 bytes - return [CBUUID UUIDWithData:[NSData dataWithBytes:(svcId->bytes + 2) length:2]]; - } // we need to use 4 bytes - return [CBUUID UUIDWithData:[NSData dataWithBytes:svcId->bytes length:4]]; - } - // it cannot be shortened as it doesn't match with the BLE Service UUID Base - return [CBUUID UUIDWithData:[NSData dataWithBytes:svcId->bytes length:16]]; -} -@end