[NC Apple coverage] Add unit tests for GNCMBleUtils.

PiperOrigin-RevId: 808614106
This commit is contained in:
Edwin Wu
2025-09-18 09:26:32 -07:00
committed by Copybara-Service
parent ae94fa2cdc
commit 8ded7ccfca
8 changed files with 270 additions and 14 deletions
@@ -74,7 +74,7 @@ NSData *GNCMGenerateBLEFramesIntroductionPacket(NSData *serviceIDHash);
* Parses the packet for Ble SocketControlFrame introduction packet and returns
* serviceIdHash if succeed.
*/
NSData *GNCMParseBLEFramesIntroductionPacket(NSData *data);
NSData *_Nullable GNCMParseBLEFramesIntroductionPacket(NSData *_Nullable data);
/** Creates the disconnection packet for Ble SocketControlFrame. */
NSData *GNCMGenerateBLEFramesDisconnectionPacket(NSData *serviceIDHash);
@@ -83,7 +83,7 @@ NSData *GNCMGenerateBLEFramesDisconnectionPacket(NSData *serviceIDHash);
NSData *GNCMGenerateBLEFramesPacketAcknowledgementPacket(NSData *serviceIDHash, int receivedSize);
/** Parses the BLE L2CAP packet from the data. */
GNCMBLEL2CAPPacket *_Nullable GNCMParseBLEL2CAPPacket(NSData *data);
GNCMBLEL2CAPPacket *_Nullable GNCMParseBLEL2CAPPacket(NSData *_Nullable data);
/** Creates the BLE L2CAP packet from the command and data. */
NSData *_Nullable GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommand command, NSData *_Nullable data);
@@ -63,9 +63,11 @@ NSData *GNCMGenerateBLEFramesIntroductionPacket(NSData *serviceIDHash) {
return packet;
}
NSData *_Nullable GNCMParseBLEFramesIntroductionPacket(NSData *data) {
NSData *_Nullable GNCMParseBLEFramesIntroductionPacket(NSData *_Nullable data) {
if (!data) return nil;
::location::nearby::mediums::SocketControlFrame socket_control_frame;
NSUInteger prefixLength = sizeof(kGNCMControlPacketServiceIDHash);
if (data.length <= prefixLength) return nil;
NSData *packet = [data subdataWithRange:NSMakeRange(prefixLength, data.length - prefixLength)];
if (socket_control_frame.ParseFromArray(packet.bytes, (int)packet.length)) {
if (socket_control_frame.type() ==
@@ -132,9 +134,12 @@ NSData *GNCMGenerateBLEFramesPacketAcknowledgementPacket(NSData *serviceIDHash,
@end
// TODO: b/399815436 - Add unit tests for this function.
GNCMBLEL2CAPPacket *_Nullable GNCMParseBLEL2CAPPacket(NSData *data) {
if (data.length < 1) {
GNCMBLEL2CAPPacket *_Nullable GNCMParseBLEL2CAPPacket(NSData *_Nullable data) {
const NSUInteger kCommandLength = 1;
const NSUInteger kLengthFieldLength = 2;
const NSUInteger kHeaderLength = kCommandLength + kLengthFieldLength;
if (!data || data.length < kCommandLength) {
return nil;
}
@@ -148,26 +153,29 @@ GNCMBLEL2CAPPacket *_Nullable GNCMParseBLEL2CAPPacket(NSData *data) {
// Extract data
NSData *packetData = nil;
if (receivedDataLength > 3) {
if (receivedDataLength > kHeaderLength) {
// Extract data length (2 bytes, big endian)
int dataLength = (bytes[1] << 8) | bytes[2];
// Validate data length
if (dataLength != (int)(receivedDataLength - 3)) {
if (dataLength != (int)(receivedDataLength - kHeaderLength)) {
GNCLoggerError(@"[NEARBY] Data length mismatch. Expected: %d, Actual: %lu", dataLength,
receivedDataLength - 3);
receivedDataLength - kHeaderLength);
return nil;
}
if (dataLength > 0) {
packetData = [NSData dataWithBytes:&bytes[3] length:dataLength];
packetData = [NSData dataWithBytes:&bytes[kHeaderLength] length:dataLength];
} else {
packetData = nil;
}
} else if (receivedDataLength > kCommandLength) {
// Header is incomplete
GNCLoggerError(@"[NEARBY] Incomplete L2CAP packet header.");
return nil;
}
return [[GNCMBLEL2CAPPacket alloc] initWithCommand:command data:packetData];
}
// TODO: b/399815436 - Add unit tests for this function.
NSData *_Nullable GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommand command, NSData *_Nullable data) {
if (!IsSupportedCommand(command)) {
GNCLoggerError(@"[NEARBY] Invalid command to generate packet: %lu", command);
@@ -33,10 +33,11 @@ objc_library(
"GNCBLEL2CAPServerTest.m",
"GNCBLEL2CAPStreamTest.m",
"GNCBLEMediumTest.m",
"GNCBLEUtilsTest.mm",
"GNCFakeCentralManager.m",
"GNCFakePeripheral.m",
"GNCFakePeripheralManager.m",
"GNCFakeSocket.m",
"GNCMBleUtilsTest.m",
"GNCMFakeConnection.mm",
"NSData+GNCBase85Test.m",
"NSData+GNCWebSafeBase64Test.m",
@@ -50,6 +51,7 @@ objc_library(
"GNCFakeCentralManager.h",
"GNCFakePeripheral.h",
"GNCFakePeripheralManager.h",
"GNCFakeSocket.h",
"GNCMFakeConnection.h",
],
deps = [
@@ -58,9 +60,11 @@ objc_library(
"//internal/platform/implementation/apple", # buildcleaner: keep
"//internal/platform/implementation/apple:ble_v2",
"//internal/platform/implementation/apple/Mediums/BLE",
"//internal/platform/implementation/apple/Mediums/BLE/Sockets:Shared",
"//third_party/apple_frameworks:CoreBluetooth",
"//third_party/apple_frameworks:Foundation",
"//third_party/apple_frameworks:XCTest",
"//third_party/objective_c/ocmock/v3:OCMock",
],
)
@@ -0,0 +1,35 @@
// 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/Sockets/Source/Shared/GNSSocket.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** A fake implementation of @c GNSSocket to inject for testing. */
@interface GNCFakeSocket : NSObject
/** The socket's delegate. */
@property(nonatomic, weak) id<GNSSocketDelegate> delegate;
/** Simulates a socket connection event. */
- (void)simulateSocketDidConnect;
/** Simulates a socket disconnection event. */
- (void)simulateSocketDidDisconnectWithError:(nullable NSError *)error;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,33 @@
// 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/GNCFakeSocket.h"
NS_ASSUME_NONNULL_BEGIN
@implementation GNCFakeSocket
@synthesize delegate = _delegate;
- (void)simulateSocketDidConnect {
[self.delegate socketDidConnect:(GNSSocket *)self];
}
- (void)simulateSocketDidDisconnectWithError:(nullable NSError *)error {
[self.delegate socket:(GNSSocket *)self didDisconnectWithError:error];
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,175 @@
// 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/GNCMBleUtils.h"
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeSocket.h"
static const NSTimeInterval kTimeout = 1.0;
static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5s internal timeout
@interface GNCMBleUtilsTest : XCTestCase
@end
@implementation GNCMBleUtilsTest
- (void)testServiceIDHash {
NSString *serviceID = @"TestServiceID";
NSData *hash = GNCMServiceIDHash(serviceID);
XCTAssertNotNil(hash);
XCTAssertEqual(hash.length, GNCMBleAdvertisementLengthServiceIDHash);
// Check for consistency
NSData *hash2 = GNCMServiceIDHash(serviceID);
XCTAssertEqualObjects(hash, hash2);
}
- (void)testGenerateAndParseBLEFramesIntroductionPacket {
NSData *serviceIDHash = [@"123" dataUsingEncoding:NSUTF8StringEncoding];
NSData *packet = GNCMGenerateBLEFramesIntroductionPacket(serviceIDHash);
XCTAssertNotNil(packet);
NSData *parsedHash = GNCMParseBLEFramesIntroductionPacket(packet);
XCTAssertEqualObjects(parsedHash, serviceIDHash);
}
- (void)testParseBLEFramesIntroductionPacketFailure_NilData {
NSData *parsedHash = GNCMParseBLEFramesIntroductionPacket(nil);
XCTAssertNil(parsedHash);
}
- (void)testParseBLEFramesIntroductionPacketFailure_ShortData {
NSData *invalidPacket = [@"12" dataUsingEncoding:NSUTF8StringEncoding];
NSData *parsedHash = GNCMParseBLEFramesIntroductionPacket(invalidPacket);
XCTAssertNil(parsedHash);
}
- (void)testGenerateBLEFramesDisconnectionPacket {
NSData *serviceIDHash = [@"ABC" dataUsingEncoding:NSUTF8StringEncoding];
NSData *packet = GNCMGenerateBLEFramesDisconnectionPacket(serviceIDHash);
XCTAssertNotNil(packet);
XCTAssertTrue(packet.length > serviceIDHash.length);
}
- (void)testGenerateBLEFramesPacketAcknowledgementPacket {
NSData *serviceIDHash = [@"DEF" dataUsingEncoding:NSUTF8StringEncoding];
int receivedSize = 1024;
NSData *packet = GNCMGenerateBLEFramesPacketAcknowledgementPacket(serviceIDHash, receivedSize);
XCTAssertNotNil(packet);
XCTAssertTrue(packet.length > serviceIDHash.length);
}
- (void)testGenerateAndParseBLEL2CAPPacketWithData {
GNCMBLEL2CAPCommand command = GNCMBLEL2CAPCommandRequestAdvertisement;
NSData *data = [@"test_data" dataUsingEncoding:NSUTF8StringEncoding];
NSData *packetData = GNCMGenerateBLEL2CAPPacket(command, data);
XCTAssertNotNil(packetData);
GNCMBLEL2CAPPacket *packet = GNCMParseBLEL2CAPPacket(packetData);
XCTAssertNotNil(packet);
XCTAssertEqual(packet.command, command);
XCTAssertEqualObjects(packet.data, data);
}
- (void)testGenerateAndParseBLEL2CAPPacketWithoutData {
GNCMBLEL2CAPCommand command = GNCMBLEL2CAPCommandRequestAdvertisementFinish;
NSData *packetData = GNCMGenerateBLEL2CAPPacket(command, nil);
XCTAssertNotNil(packetData);
GNCMBLEL2CAPPacket *packet = GNCMParseBLEL2CAPPacket(packetData);
XCTAssertNotNil(packet);
XCTAssertEqual(packet.command, command);
XCTAssertNil(packet.data);
}
- (void)testParseBLEL2CAPPacketInvalidData_Nil {
GNCMBLEL2CAPPacket *packet = GNCMParseBLEL2CAPPacket(nil);
XCTAssertNil(packet);
}
- (void)testParseBLEL2CAPPacketInvalidData_Empty {
GNCMBLEL2CAPPacket *packet = GNCMParseBLEL2CAPPacket([[NSData alloc] init]);
XCTAssertNil(packet);
}
- (void)testParseBLEL2CAPPacketShortData_NoLength {
uint8_t commandByte = (uint8_t)GNCMBLEL2CAPCommandRequestAdvertisement;
NSData *shortPacketData = [NSData dataWithBytes:&commandByte length:1];
GNCMBLEL2CAPPacket *packet = GNCMParseBLEL2CAPPacket(shortPacketData);
XCTAssertNotNil(packet);
XCTAssertEqual(packet.command, GNCMBLEL2CAPCommandRequestAdvertisement);
XCTAssertNil(packet.data);
}
- (void)testParseBLEL2CAPPacketShortData_IncompleteLength {
uint8_t bytes[] = {GNCMBLEL2CAPCommandRequestAdvertisement, 0x01};
NSData *shortPacketData = [NSData dataWithBytes:bytes length:sizeof(bytes)];
GNCMBLEL2CAPPacket *packet = GNCMParseBLEL2CAPPacket(shortPacketData);
XCTAssertNil(packet);
}
- (void)testParseBLEL2CAPPacketShortData_IncompleteData {
uint8_t bytes[] = {GNCMBLEL2CAPCommandRequestAdvertisement, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04};
NSData *shortPacketData = [NSData dataWithBytes:bytes length:sizeof(bytes)];
GNCMBLEL2CAPPacket *packet = GNCMParseBLEL2CAPPacket(shortPacketData);
XCTAssertNil(packet);
}
- (void)testWaitForConnection_Success {
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
XCTestExpectation *expectation = [self expectationWithDescription:@"Connection success"];
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
XCTAssertTrue(flag);
[expectation fulfill];
});
// Simulate the connection callback
[fakeSocket simulateSocketDidConnect];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}
- (void)testWaitForConnection_Failure_Disconnect {
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Connection failed on disconnect"];
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
XCTAssertFalse(flag);
[expectation fulfill];
});
// Simulate the disconnection callback
[fakeSocket simulateSocketDidDisconnectWithError:nil];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}
- (void)testWaitForConnection_Failure_Timeout {
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
// No delegate call - let it time out
XCTestExpectation *expectation =
[self expectationWithDescription:@"Connection failed on timeout"];
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
XCTAssertFalse(flag);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:kWaitForConnectionTimeout handler:nil];
}
@end
@@ -25,6 +25,7 @@ objc_library(
testonly = True,
srcs = [
"GNCAwdlMediumTest.mm",
"GNCBLEUtilsTest.mm",
"GNCCryptoTest.mm",
"GNCDeviceInfoTest.mm",
"GNCMultiThreadExecutorTest.mm",
@@ -41,6 +42,7 @@ objc_library(
],
deps = [
"//internal/platform:base",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation/apple", # buildcleaner: keep
@@ -92,8 +92,7 @@ using ByteArray = ::nearby::ByteArray;
- (void)testCBAttributePermissionsFromCPPSinglePermission {
CBAttributePermissions expected = CBAttributePermissionsReadable;
CBAttributePermissions actual =
::nearby::apple::CBAttributePermissionsFromCPP(Permission::kRead);
CBAttributePermissions actual = ::nearby::apple::CBAttributePermissionsFromCPP(Permission::kRead);
XCTAssertEqual(actual, expected);
}