mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 07:36:10 -04:00
[NC Apple coverage] Add unit tests for Nearby Apple's BleMedium.
PiperOrigin-RevId: 820484319
This commit is contained in:
committed by
Copybara-Service
parent
5023547e61
commit
d08ca79b89
@@ -145,6 +145,7 @@ objc_library(
|
||||
# Prevent Objective-C++ headers from being pulled into swift.
|
||||
aspect_hints = ["//tools/build_defs/swift:no_module"],
|
||||
deps = [
|
||||
":Shared",
|
||||
":bluetooth_adapter_v2",
|
||||
":comm",
|
||||
"//internal/platform:base",
|
||||
|
||||
@@ -35,6 +35,9 @@ NSData *_Nullable GNCMd5Data(NSData *data);
|
||||
/// Generates an MD5 hash (16 bytes) from a string.
|
||||
NSData *_Nullable GNCMd5String(NSString *string);
|
||||
|
||||
/// Converts NSData to a hex string with a "0x" prefix.
|
||||
NSString *GNCConvertDataToHexString(NSData *_Nullable data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
@@ -41,4 +41,20 @@ NSData *GNCMd5String(NSString *string) {
|
||||
return GNCMd5Data([string dataUsingEncoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
|
||||
NSString *GNCConvertDataToHexString(NSData *_Nullable data) {
|
||||
NSUInteger dataLength = data.length;
|
||||
if (dataLength == 0) {
|
||||
return @"0x";
|
||||
}
|
||||
|
||||
const unsigned char *dataBuffer = (const unsigned char *)data.bytes;
|
||||
NSMutableString *hexString = [NSMutableString stringWithCapacity:dataLength * 2];
|
||||
|
||||
for (NSUInteger i = 0; i < dataLength; ++i) {
|
||||
[hexString appendFormat:@"%02lx", (unsigned long)dataBuffer[i]];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"0x%@", hexString];
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -33,6 +33,8 @@ objc_library(
|
||||
"GNCBLEL2CAPServerTest.m",
|
||||
"GNCBLEL2CAPStreamTest.m",
|
||||
"GNCBLEMediumTest.m",
|
||||
"GNCFakeBLEGATTServer.m",
|
||||
"GNCFakeBLEMedium.m",
|
||||
"GNCFakeCentralManager.m",
|
||||
"GNCFakePeripheral.m",
|
||||
"GNCFakePeripheralManager.m",
|
||||
@@ -52,6 +54,8 @@ objc_library(
|
||||
"GNCBLEL2CAPClient+Testing.h",
|
||||
"GNCBLEL2CAPFakeInputOutputStream.h",
|
||||
"GNCBLEMedium+Testing.h",
|
||||
"GNCFakeBLEGATTServer.h",
|
||||
"GNCFakeBLEMedium.h",
|
||||
"GNCFakeCentralManager.h",
|
||||
"GNCFakePeripheral.h",
|
||||
"GNCFakePeripheralManager.h",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GNCFakeBLEGATTServer : GNCBLEGATTServer
|
||||
|
||||
// Add properties to control fake behavior if needed.
|
||||
@property(nonatomic, nullable) NSError *stopError;
|
||||
@property(nonatomic) BOOL isStopped;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEGATTServer.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation GNCFakeBLEGATTServer
|
||||
|
||||
- (void)stopWithCompletionHandler:(nullable void (^)(NSError *_Nullable))completionHandler {
|
||||
self.isStopped = YES;
|
||||
if (completionHandler) {
|
||||
completionHandler(self.stopError);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEMedium.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEGATTServer.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GNCFakeBLEMedium : GNCBLEMedium
|
||||
|
||||
// Properties to control fake behavior.
|
||||
@property(nonatomic, nullable) NSError *startAdvertisingError;
|
||||
@property(nonatomic, nullable) NSError *stopAdvertisingError;
|
||||
@property(nonatomic, nullable) NSError *startScanningError;
|
||||
@property(nonatomic, nullable) NSError *stopScanningError;
|
||||
@property(nonatomic, nullable) NSError *resumeScanningError;
|
||||
@property(nonatomic, nullable) NSError *startGATTServerError;
|
||||
@property(nonatomic, nullable) NSError *connectToGATTServerError;
|
||||
@property(nonatomic, nullable) NSError *openServerSocketError;
|
||||
@property(nonatomic, nullable) NSError *openL2CAPServerSocketError;
|
||||
@property(nonatomic, nullable) NSError *openL2CAPChannelError;
|
||||
|
||||
@property(nonatomic, nullable) GNCBLEGATTServer *fakeGATTServer;
|
||||
@property(nonatomic, nullable) GNCBLEGATTClient *fakeGATTClient;
|
||||
@property(nonatomic, nullable) GNCBLEL2CAPStream *fakeL2CAPStream;
|
||||
@property(nonatomic) uint16_t fakePSM;
|
||||
|
||||
@property(nonatomic, nullable) id<GNCPeripheral> lastConnectedPeripheral;
|
||||
@property(nonatomic, nullable) GNCGATTDisconnectionHandler lastDisconnectionHandler;
|
||||
|
||||
@property(nonatomic, nullable) GNCAdvertisementFoundHandler advertisementFoundHandler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEMedium.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEMedium.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation GNCFakeBLEMedium
|
||||
|
||||
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)advertisementData
|
||||
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler {
|
||||
if (completionHandler) {
|
||||
completionHandler(self.startAdvertisingError);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAdvertisingWithCompletionHandler:(nullable GNCStopAdvertisingCompletionHandler)completionHandler {
|
||||
if (completionHandler) {
|
||||
completionHandler(self.stopAdvertisingError);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startScanningForService:(CBUUID *)serviceUUID
|
||||
advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler
|
||||
completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler {
|
||||
self.advertisementFoundHandler = advertisementFoundHandler;
|
||||
if (completionHandler) {
|
||||
completionHandler(self.startScanningError);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startScanningForMultipleServices:(NSArray<CBUUID *> *)serviceUUIDs
|
||||
advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler
|
||||
completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler {
|
||||
self.advertisementFoundHandler = advertisementFoundHandler;
|
||||
if (completionHandler) {
|
||||
completionHandler(self.startScanningError);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopScanningWithCompletionHandler:(nullable GNCStopScanningCompletionHandler)completionHandler {
|
||||
if (completionHandler) {
|
||||
completionHandler(self.stopScanningError);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resumeMediumScanning:(nullable GNCStartScanningCompletionHandler)completionHandler {
|
||||
if (completionHandler) {
|
||||
completionHandler(self.resumeScanningError);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startGATTServerWithCompletionHandler:(nullable GNCGATTServerCompletionHandler)completionHandler {
|
||||
if (completionHandler) {
|
||||
completionHandler(self.fakeGATTServer, self.startGATTServerError);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connectToGATTServerForPeripheral:(id<GNCPeripheral>)peripheral
|
||||
disconnectionHandler:(nullable GNCGATTDisconnectionHandler)disconnectionHandler
|
||||
completionHandler:(nullable GNCGATTConnectionCompletionHandler)completionHandler {
|
||||
self.lastConnectedPeripheral = peripheral;
|
||||
self.lastDisconnectionHandler = disconnectionHandler;
|
||||
if (completionHandler) {
|
||||
if (!self.connectToGATTServerError) {
|
||||
if (!self.fakeGATTClient) {
|
||||
// Create a default fake client if one isn't provided.
|
||||
self.fakeGATTClient = [[GNCBLEGATTClient alloc] initWithPeripheral:peripheral
|
||||
requestDisconnectionHandler:^(id<GNCPeripheral> p) {
|
||||
// Do nothing in fake.
|
||||
}];
|
||||
}
|
||||
completionHandler(self.fakeGATTClient, nil);
|
||||
} else {
|
||||
completionHandler(nil, self.connectToGATTServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)openL2CAPServerWithPSMPublishedCompletionHandler:
|
||||
(GNCOpenL2CAPServerPSMPublishedCompletionHandler)psmPublishedCompletionHandler
|
||||
channelOpenedCompletionHandler:
|
||||
(GNCOpenL2CAPServerChannelOpendCompletionHandler)channelOpenedCompletionHandler
|
||||
peripheralManager:
|
||||
(nullable id<GNCPeripheralManager>)peripheralManager {
|
||||
if (psmPublishedCompletionHandler) {
|
||||
psmPublishedCompletionHandler(self.fakePSM, self.openL2CAPServerSocketError);
|
||||
}
|
||||
// In the fake, we don't have a real channel opened event.
|
||||
}
|
||||
|
||||
- (void)openL2CAPChannelWithPSM:(CBL2CAPPSM)PSM
|
||||
peripheral:(id<GNCPeripheral>)peripheral
|
||||
completionHandler:(nullable GNCOpenL2CAPStreamCompletionHandler)completionHandler {
|
||||
self.lastConnectedPeripheral = peripheral;
|
||||
if (completionHandler) {
|
||||
completionHandler(self.fakeL2CAPStream, self.openL2CAPChannelError);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)supportsExtendedAdvertisements {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -37,6 +37,7 @@ objc_library(
|
||||
"GNCUtilsTest.m",
|
||||
"GNCWifiLanMediumTest.mm",
|
||||
"UtilsTest.mm",
|
||||
"ble_medium_test.mm",
|
||||
"ble_peripheral_test.mm",
|
||||
"ble_socket_test.mm",
|
||||
],
|
||||
|
||||
@@ -153,4 +153,22 @@
|
||||
@"MD5 hash for an empty string did not match the expected value.");
|
||||
}
|
||||
|
||||
- (void)testConvertDataToHexString {
|
||||
// Test with nil data.
|
||||
XCTAssertEqualObjects(GNCConvertDataToHexString(nil), @"0x",
|
||||
@"Hex string for nil data should be '0x'.");
|
||||
|
||||
// Test with empty data.
|
||||
NSData *emptyData = [NSData data];
|
||||
XCTAssertEqualObjects(GNCConvertDataToHexString(emptyData), @"0x",
|
||||
@"Hex string for empty data should be '0x'.");
|
||||
|
||||
// Test with sample data.
|
||||
const unsigned char bytes[] = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
NSData *sampleData = [NSData dataWithBytes:bytes length:sizeof(bytes)];
|
||||
NSString *expectedHexString = @"0xdeadbeef";
|
||||
XCTAssertEqualObjects(GNCConvertDataToHexString(sampleData), expectedHexString,
|
||||
@"Hex string for sample data did not match the expected value.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_medium.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEMedium.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEMedium.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
|
||||
#include "internal/platform/implementation/apple/ble_utils.h"
|
||||
#include "internal/platform/implementation/ble.h"
|
||||
|
||||
// TODO(b/293336684): Add tests for Weave sockets, AdvertisementFoundHandler, and more edge cases.
|
||||
|
||||
static NSString *const kTestServiceUUIDString = @"0000FE2C-0000-1000-8000-00805F9B34FB";
|
||||
static const char *const kTestServiceID = "TestServiceID";
|
||||
|
||||
@interface GNCBLEMediumCPPTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBLEMediumCPPTest {
|
||||
GNCFakeBLEMedium *_fakeGNCBLEMedium;
|
||||
std::unique_ptr<nearby::apple::BleMedium> _medium;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
_fakeGNCBLEMedium = [[GNCFakeBLEMedium alloc] init];
|
||||
_medium = std::make_unique<nearby::apple::BleMedium>((GNCBLEMedium *)_fakeGNCBLEMedium);
|
||||
}
|
||||
|
||||
#pragma mark - Advertising Tests
|
||||
|
||||
- (void)testStartAdvertising_Success {
|
||||
nearby::api::ble::BleAdvertisementData advertising_data;
|
||||
nearby::api::ble::AdvertiseParameters advertise_set_parameters;
|
||||
|
||||
bool result = _medium->StartAdvertising(advertising_data, advertise_set_parameters);
|
||||
|
||||
XCTAssertTrue(result);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertising_Failure {
|
||||
nearby::api::ble::BleAdvertisementData advertising_data;
|
||||
nearby::api::ble::AdvertiseParameters advertise_set_parameters;
|
||||
_fakeGNCBLEMedium.startAdvertisingError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
|
||||
|
||||
bool result = _medium->StartAdvertising(advertising_data, advertise_set_parameters);
|
||||
|
||||
XCTAssertFalse(result);
|
||||
}
|
||||
|
||||
- (void)testStopAdvertising_Success {
|
||||
bool result = _medium->StopAdvertising();
|
||||
|
||||
XCTAssertTrue(result);
|
||||
}
|
||||
|
||||
- (void)testStopAdvertising_Failure {
|
||||
_fakeGNCBLEMedium.stopAdvertisingError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
|
||||
|
||||
bool result = _medium->StopAdvertising();
|
||||
|
||||
XCTAssertFalse(result);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingAsync_Success {
|
||||
nearby::api::ble::BleAdvertisementData advertising_data;
|
||||
nearby::api::ble::AdvertiseParameters advertise_set_parameters;
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Advertising started"];
|
||||
nearby::api::ble::BleMedium::AdvertisingCallback callback = {
|
||||
.start_advertising_result = std::function<void(absl::Status)>(^(absl::Status status) {
|
||||
XCTAssertTrue(status.ok());
|
||||
[expectation fulfill];
|
||||
}),
|
||||
};
|
||||
|
||||
auto session =
|
||||
_medium->StartAdvertising(advertising_data, advertise_set_parameters, std::move(callback));
|
||||
|
||||
XCTAssertNotEqual(session.get(), nullptr);
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingAsync_Failure {
|
||||
nearby::api::ble::BleAdvertisementData advertising_data;
|
||||
nearby::api::ble::AdvertiseParameters advertise_set_parameters;
|
||||
_fakeGNCBLEMedium.startAdvertisingError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Advertising failed to start"];
|
||||
nearby::api::ble::BleMedium::AdvertisingCallback callback = {
|
||||
.start_advertising_result = std::function<void(absl::Status)>(^(absl::Status status) {
|
||||
XCTAssertFalse(status.ok());
|
||||
[expectation fulfill];
|
||||
}),
|
||||
};
|
||||
|
||||
auto session =
|
||||
_medium->StartAdvertising(advertising_data, advertise_set_parameters, std::move(callback));
|
||||
|
||||
XCTAssertNotEqual(session.get(), nullptr);
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
}
|
||||
|
||||
#pragma mark - Scanning Tests
|
||||
|
||||
- (void)testStartScanning_Success {
|
||||
nearby::Uuid service_uuid(0, 0);
|
||||
nearby::api::ble::TxPowerLevel tx_power_level = nearby::api::ble::TxPowerLevel::kUltraLow;
|
||||
|
||||
auto session = _medium->StartScanning(service_uuid, tx_power_level,
|
||||
nearby::api::ble::BleMedium::ScanningCallback{});
|
||||
|
||||
XCTAssertNotEqual(session.get(), nullptr);
|
||||
}
|
||||
|
||||
- (void)testStartScanning_Failure {
|
||||
nearby::Uuid service_uuid(0, 0);
|
||||
nearby::api::ble::TxPowerLevel tx_power_level = nearby::api::ble::TxPowerLevel::kUltraLow;
|
||||
_fakeGNCBLEMedium.startScanningError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
|
||||
|
||||
auto session = _medium->StartScanning(service_uuid, tx_power_level,
|
||||
nearby::api::ble::BleMedium::ScanningCallback{});
|
||||
|
||||
XCTAssertEqual(session.get(), nullptr);
|
||||
}
|
||||
|
||||
- (void)testStartMultipleServicesScanning_Success {
|
||||
std::vector<nearby::Uuid> service_uuids = {nearby::Uuid(0, 0)};
|
||||
nearby::api::ble::TxPowerLevel tx_power_level = nearby::api::ble::TxPowerLevel::kUltraLow;
|
||||
|
||||
bool result = _medium->StartMultipleServicesScanning(service_uuids, tx_power_level, {});
|
||||
|
||||
XCTAssertTrue(result);
|
||||
}
|
||||
|
||||
- (void)testStartMultipleServicesScanning_Failure {
|
||||
std::vector<nearby::Uuid> service_uuids = {nearby::Uuid(0, 0)};
|
||||
nearby::api::ble::TxPowerLevel tx_power_level = nearby::api::ble::TxPowerLevel::kUltraLow;
|
||||
_fakeGNCBLEMedium.startScanningError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
|
||||
|
||||
bool result = _medium->StartMultipleServicesScanning(service_uuids, tx_power_level, {});
|
||||
|
||||
XCTAssertFalse(result);
|
||||
}
|
||||
|
||||
- (void)testStopScanning_Success {
|
||||
bool result = _medium->StopScanning();
|
||||
|
||||
XCTAssertTrue(result);
|
||||
}
|
||||
|
||||
- (void)testStopScanning_Failure {
|
||||
_fakeGNCBLEMedium.stopScanningError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
|
||||
|
||||
bool result = _medium->StopScanning();
|
||||
|
||||
XCTAssertFalse(result);
|
||||
}
|
||||
|
||||
- (void)testPauseResumeScanning_Success {
|
||||
XCTAssertTrue(_medium->PauseMediumScanning());
|
||||
XCTAssertTrue(_medium->ResumeMediumScanning());
|
||||
}
|
||||
|
||||
- (void)testPauseResumeScanning_Failure {
|
||||
_fakeGNCBLEMedium.stopScanningError = [NSError errorWithDomain:@"test" code:1 userInfo:nil];
|
||||
XCTAssertFalse(_medium->PauseMediumScanning());
|
||||
|
||||
_fakeGNCBLEMedium.resumeScanningError = [NSError errorWithDomain:@"test" code:2 userInfo:nil];
|
||||
XCTAssertFalse(_medium->ResumeMediumScanning());
|
||||
}
|
||||
|
||||
// TODO(b/293336684): Add tests for async StartScanning.
|
||||
|
||||
#pragma mark - GATT Server Tests
|
||||
|
||||
- (void)testStartGattServer_Success {
|
||||
_fakeGNCBLEMedium.fakeGATTServer = [[GNCFakeBLEGATTServer alloc] init];
|
||||
|
||||
auto gatt_server = _medium->StartGattServer({});
|
||||
|
||||
XCTAssertNotEqual(gatt_server.get(), nullptr);
|
||||
}
|
||||
|
||||
- (void)testStartGattServer_Failure {
|
||||
_fakeGNCBLEMedium.startGATTServerError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
|
||||
|
||||
auto gatt_server = _medium->StartGattServer({});
|
||||
|
||||
XCTAssertEqual(gatt_server.get(), nullptr);
|
||||
}
|
||||
|
||||
#pragma mark - GATT Client Tests
|
||||
|
||||
- (void)testConnectToGattServer_PeripheralNotFound {
|
||||
auto gatt_client =
|
||||
_medium->ConnectToGattServer(99999, nearby::api::ble::TxPowerLevel::kUltraLow, {});
|
||||
|
||||
XCTAssertEqual(gatt_client.get(), nullptr);
|
||||
}
|
||||
|
||||
- (void)testConnectToGattServer_Success {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Advertisement found callback should be called."];
|
||||
nearby::api::ble::BleMedium::ScanCallback callback = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
[expectation](nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
[expectation fulfill];
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback));
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
|
||||
auto gatt_client = _medium->ConnectToGattServer(fakePeripheral.identifier.hash,
|
||||
nearby::api::ble::TxPowerLevel::kUltraLow, {});
|
||||
|
||||
XCTAssertNotEqual(gatt_client.get(), nullptr);
|
||||
}
|
||||
|
||||
- (void)testConnectToGattServer_Failure {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Advertisement found callback should be called."];
|
||||
nearby::api::ble::BleMedium::ScanCallback callback = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
[expectation](nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
[expectation fulfill];
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback));
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
_fakeGNCBLEMedium.connectToGATTServerError = [NSError errorWithDomain:@"test"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
auto gatt_client = _medium->ConnectToGattServer(fakePeripheral.identifier.hash,
|
||||
nearby::api::ble::TxPowerLevel::kUltraLow, {});
|
||||
|
||||
XCTAssertEqual(gatt_client.get(), nullptr);
|
||||
}
|
||||
|
||||
#pragma mark - L2CAP Server Tests
|
||||
|
||||
- (void)testOpenL2capServerSocket_Success {
|
||||
_fakeGNCBLEMedium.fakePSM = 123;
|
||||
|
||||
auto l2cap_server = _medium->OpenL2capServerSocket(kTestServiceID);
|
||||
|
||||
XCTAssertNotEqual(l2cap_server.get(), nullptr);
|
||||
// XCTAssertEqual(l2cap_server->GetPSM(), 123); // Needs friend class or accessor
|
||||
}
|
||||
|
||||
- (void)testOpenL2capServerSocket_Failure {
|
||||
_fakeGNCBLEMedium.openL2CAPServerSocketError = [NSError errorWithDomain:@"test"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
auto l2cap_server = _medium->OpenL2capServerSocket(kTestServiceID);
|
||||
|
||||
XCTAssertEqual(l2cap_server.get(), nullptr);
|
||||
}
|
||||
|
||||
#pragma mark - L2CAP Client Tests
|
||||
|
||||
- (void)testConnectOverL2cap_NotFound {
|
||||
auto l2cap_socket = _medium->ConnectOverL2cap(
|
||||
123, kTestServiceID, nearby::api::ble::TxPowerLevel::kUltraLow, 99999, nullptr);
|
||||
|
||||
XCTAssertEqual(l2cap_socket.get(), nullptr);
|
||||
}
|
||||
|
||||
#pragma mark - Server Socket Tests
|
||||
|
||||
- (void)testOpenServerSocket_Success {
|
||||
XCTSkip(@"TODO(b/293336684): Requires more capable GNSPeripheralManager fakes for full testing.");
|
||||
auto server_socket = _medium->OpenServerSocket(kTestServiceID);
|
||||
|
||||
XCTAssertNotEqual(server_socket.get(), nullptr);
|
||||
}
|
||||
|
||||
// TODO(b/293336684): Add failure test case for OpenServerSocket when GNSPeripheralManager fakes
|
||||
// are more capable.
|
||||
|
||||
#pragma mark - Other Tests
|
||||
|
||||
- (void)testIsExtendedAdvertisementsAvailable {
|
||||
XCTAssertFalse(_medium->IsExtendedAdvertisementsAvailable());
|
||||
}
|
||||
|
||||
- (void)testRetrieveBlePeripheralIdFromNativeId {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Advertisement found callback should be called."];
|
||||
nearby::api::ble::BleMedium::ScanCallback callback = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
[expectation](nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
[expectation fulfill];
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback));
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
|
||||
// Test with a known UUID
|
||||
std::optional<nearby::api::ble::BlePeripheral::UniqueId> result1 =
|
||||
_medium->RetrieveBlePeripheralIdFromNativeId(
|
||||
[fakePeripheral.identifier.UUIDString UTF8String]);
|
||||
XCTAssertTrue(result1.has_value());
|
||||
XCTAssertEqual(result1.value(), fakePeripheral.identifier.hash);
|
||||
|
||||
// Test with an unknown but valid UUID
|
||||
NSString *unknownUUIDString = [[NSUUID alloc] init].UUIDString;
|
||||
std::optional<nearby::api::ble::BlePeripheral::UniqueId> result2 =
|
||||
_medium->RetrieveBlePeripheralIdFromNativeId([unknownUUIDString UTF8String]);
|
||||
XCTAssertTrue(result2.has_value()); // Valid UUID format should return an ID.
|
||||
XCTAssertNotEqual(result2.value(), fakePeripheral.identifier.hash);
|
||||
|
||||
std::optional<nearby::api::ble::BlePeripheral::UniqueId> result3 =
|
||||
_medium->RetrieveBlePeripheralIdFromNativeId("invalid-uuid-string");
|
||||
XCTAssertFalse(result3.has_value());
|
||||
}
|
||||
|
||||
#pragma mark - HandleAdvertisementFound Tests
|
||||
|
||||
- (void)testHandleAdvertisementFound_NewPeripheral {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Advertisement found callback"];
|
||||
|
||||
nearby::api::ble::BleMedium::ScanCallback callback = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
XCTAssertEqual(peripheral_id, fakePeripheral.identifier.hash);
|
||||
XCTAssertEqual(advertisement.service_data.size(), 1);
|
||||
CBUUID *serviceUUID =
|
||||
nearby::apple::CBUUID128FromCPP(advertisement.service_data.begin()->first);
|
||||
XCTAssertEqualObjects(serviceUUID.UUIDString, kTestServiceUUIDString);
|
||||
[expectation fulfill];
|
||||
})};
|
||||
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback));
|
||||
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
}
|
||||
|
||||
- (void)testHandleAdvertisementFound_KnownPeripheral_NewData {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData1 =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test1" length:5]};
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData2 =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test2" length:5]};
|
||||
|
||||
XCTestExpectation *expectation1 = [self expectationWithDescription:@"Callback 1"];
|
||||
nearby::api::ble::BleMedium::ScanCallback callback1 = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
[expectation1 fulfill];
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback1));
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData1);
|
||||
}
|
||||
[self waitForExpectations:@[ expectation1 ] timeout:1.0];
|
||||
|
||||
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Callback 2"];
|
||||
nearby::api::ble::BleMedium::ScanCallback callback2 = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
XCTAssertEqual(peripheral_id, fakePeripheral.identifier.hash);
|
||||
XCTAssertEqual(advertisement.service_data.size(), 1);
|
||||
CBUUID *serviceUUID =
|
||||
nearby::apple::CBUUID128FromCPP(advertisement.service_data.begin()->first);
|
||||
XCTAssertEqualObjects(serviceUUID.UUIDString, kTestServiceUUIDString);
|
||||
NSData *data = [NSData dataWithBytes:advertisement.service_data.begin()->second.data()
|
||||
length:advertisement.service_data.begin()->second.size()];
|
||||
XCTAssertEqualObjects(data,
|
||||
serviceData2[[CBUUID UUIDWithString:kTestServiceUUIDString]]);
|
||||
[expectation2 fulfill];
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback2));
|
||||
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData2);
|
||||
}
|
||||
|
||||
[self waitForExpectations:@[ expectation2 ] timeout:1.0];
|
||||
}
|
||||
|
||||
- (void)testHandleAdvertisementFound_KnownPeripheral_SameData_WithinThreshold {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
|
||||
|
||||
__block XCTestExpectation *expectation1 = [self expectationWithDescription:@"Callback 1"];
|
||||
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Callback 2"];
|
||||
expectation2.inverted = YES; // Should NOT be called.
|
||||
|
||||
nearby::api::ble::BleMedium::ScanCallback callback = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
if ([expectation1.description isEqualToString:@"Callback 1"]) {
|
||||
[expectation1 fulfill];
|
||||
expectation1 = nil; // Prevent double fulfillment
|
||||
} else {
|
||||
[expectation2 fulfill];
|
||||
}
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback));
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
[self waitForExpectations:@[ expectation1 ] timeout:1.0];
|
||||
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
|
||||
[self waitForExpectations:@[ expectation2 ] timeout:1.0];
|
||||
}
|
||||
|
||||
- (void)testHandleAdvertisementFound_KnownPeripheral_SameData_OutsideThreshold {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData =
|
||||
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
|
||||
|
||||
XCTestExpectation *expectation1 = [self expectationWithDescription:@"Callback 1"];
|
||||
nearby::api::ble::BleMedium::ScanCallback callback1 = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
[expectation1 fulfill];
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback1));
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
[self waitForExpectations:@[ expectation1 ] timeout:1.0];
|
||||
|
||||
[NSThread sleepForTimeInterval:2.1]; // Wait for longer than the threshold
|
||||
|
||||
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Callback 2"];
|
||||
nearby::api::ble::BleMedium::ScanCallback callback2 = {
|
||||
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
|
||||
nearby::api::ble::BleAdvertisementData)>(
|
||||
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
|
||||
const nearby::api::ble::BleAdvertisementData &advertisement) {
|
||||
[expectation2 fulfill];
|
||||
})};
|
||||
_medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow,
|
||||
std::move(callback2));
|
||||
|
||||
if (_fakeGNCBLEMedium.advertisementFoundHandler) {
|
||||
_fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData);
|
||||
}
|
||||
|
||||
[self waitForExpectations:@[ expectation2 ] timeout:1.0];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -47,6 +47,8 @@ namespace apple {
|
||||
class BleMedium : public api::ble::BleMedium {
|
||||
public:
|
||||
BleMedium();
|
||||
// For testing only.
|
||||
explicit BleMedium(GNCBLEMedium *medium);
|
||||
~BleMedium() override;
|
||||
|
||||
// Async interface for StartAdvertising.
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#import "internal/platform/implementation/apple/ble_socket.h"
|
||||
#import "internal/platform/implementation/apple/bluetooth_adapter_v2.h"
|
||||
#import "internal/platform/implementation/apple/utils.h"
|
||||
#import "internal/platform/implementation/apple/GNCUtils.h"
|
||||
|
||||
static NSString *const kWeaveServiceUUID = @"FEF3";
|
||||
static const char *const kConnectionCallbackQueueLabel =
|
||||
@@ -64,27 +65,10 @@ static NSTimeInterval const kAdvertisementPacketsMapExpirationTimeInterval = 600
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
namespace {
|
||||
|
||||
NSString *ConvertDataToHexString(NSData *data) {
|
||||
NSUInteger dataLength = data.length;
|
||||
if (dataLength == 0) {
|
||||
return @"0x";
|
||||
}
|
||||
BleMedium::BleMedium() : BleMedium([[GNCBLEMedium alloc] init]) {}
|
||||
|
||||
const unsigned char *dataBuffer = (const unsigned char *)data.bytes;
|
||||
NSMutableString *hexString = [NSMutableString stringWithCapacity:dataLength * 2];
|
||||
|
||||
for (NSUInteger i = 0; i < dataLength; ++i) {
|
||||
[hexString appendFormat:@"%02lx", (unsigned long)dataBuffer[i]];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"0x%@", hexString];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BleMedium::BleMedium() : medium_([[GNCBLEMedium alloc] init]) {
|
||||
BleMedium::BleMedium(GNCBLEMedium *medium) : medium_(medium) {
|
||||
connection_callback_queue_ =
|
||||
dispatch_queue_create(kConnectionCallbackQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
@@ -184,7 +168,7 @@ void BleMedium::HandleAdvertisementFound(id<GNCPeripheral> peripheral,
|
||||
for (NSData *service_data in serviceData.allValues) {
|
||||
GNCLoggerDebug(@"Reporting the advertisement packet to upper layer for unique_id: %llu, %@, "
|
||||
@"advertisement_data: %@.",
|
||||
unique_id, peripheral, ConvertDataToHexString(service_data));
|
||||
unique_id, peripheral, GNCConvertDataToHexString(service_data));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -211,6 +195,9 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
|
||||
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
|
||||
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError *blockError = nil;
|
||||
|
||||
[medium_ startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData) {
|
||||
@@ -218,13 +205,30 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
|
||||
[this, peripheral, serviceData] { HandleAdvertisementFound(peripheral, serviceData); });
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
blockError = error;
|
||||
if (scanning_cb_.start_scanning_result) {
|
||||
scanning_cb_.start_scanning_result(
|
||||
error == nil ? absl::OkStatus()
|
||||
: absl::InternalError(error.localizedDescription.UTF8String));
|
||||
}
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, kApiTimeoutInSeconds * NSEC_PER_SEC);
|
||||
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
|
||||
GNCLoggerError(@"Start scanning operation timed out.");
|
||||
if (scanning_cb_.start_scanning_result) {
|
||||
scanning_cb_.start_scanning_result(absl::DeadlineExceededError("Start scanning timed out"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (blockError) {
|
||||
GNCLoggerError(@"Failed to start scanning: %@", blockError);
|
||||
// The start_scanning_result callback was already called in the completionHandler with the error.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<ScanningSession>(ScanningSession{.stop_scanning = [this] {
|
||||
return StopScanning() ? absl::OkStatus() : absl::InternalError("Failed to stop scanning");
|
||||
}});
|
||||
|
||||
Reference in New Issue
Block a user