[NC Apple coverage] Refactor CBManagerStateString and add tests for BLE

Sockets utilities and Weave packets.

PiperOrigin-RevId: 802825276
This commit is contained in:
Edwin Wu
2025-09-03 20:23:01 -07:00
committed by Copybara-Service
parent 1b169516cb
commit 6e1fd931b7
7 changed files with 323 additions and 37 deletions
@@ -18,27 +18,10 @@
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralPeerManager+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralPeerManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils+Private.h"
NS_ASSUME_NONNULL_BEGIN
static NSString *CBManagerStateString(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 Unknown(%ld)", (long)state];
}
@interface GNSCentralManager () {
NSString *_advertisedName;
CBUUID *_advertisedServiceUUID;
@@ -17,29 +17,11 @@
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils+Private.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
static NSString *CBManagerStateString(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 @"CBManagerState Unknown";
}
// http://b/28875581 On iOS, under some misterious conditions, the Bluetooth daemon (BTServer
// process) continously crashes when attempting to register Bluetooth services. In fact, if the
// BTServer process is killed, the OS spins a new instace of this process after 10 seconds.
@@ -44,4 +44,13 @@ NSError *GNSErrorWithCode(GNSError errorCode);
*/
NSString *GNSCharacteristicName(NSString *uuid);
/**
* Returns a human readable name based on a CBManagerState.
*
* @param state CBManagerState
*
* @return Name of the CBManagerState.
*/
NSString *CBManagerStateString(CBManagerState state);
NS_ASSUME_NONNULL_END
@@ -99,4 +99,22 @@ NSString *GNSCharacteristicName(NSString *uuid) {
return @"UnknownChar";
}
NSString *CBManagerStateString(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 @"CBManagerState Unknown";
}
NS_ASSUME_NONNULL_END
@@ -46,6 +46,7 @@ objc_library(
testonly = 1,
srcs = [
"Shared/GNSSocketTest.m",
"Shared/GNSUtilsTest.m",
"Shared/GNSWeavePacketTest.m",
],
deps = [
@@ -0,0 +1,90 @@
// 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 <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils.h"
NS_ASSUME_NONNULL_BEGIN
@interface GNSUtilsTest : XCTestCase
@end
@implementation GNSUtilsTest
- (void)testGNSErrorWithCode {
// Test all error codes to ensure they have descriptions
NSArray<NSNumber *> *allErrorCodes = @[
@(GNSErrorNoConnection),
@(GNSErrorLostConnection),
@(GNSErrorOperationInProgress),
@(GNSErrorMissingService),
@(GNSErrorMissingCharacteristics),
@(GNSErrorPeripheralDidRefuseConnection),
@(GNSErrorCancelPendingSocketRequested),
@(GNSErrorNewInviteToConnectReceived),
@(GNSErrorWeaveErrorPacketReceived),
@(GNSErrorUnsupportedWeaveProtocolVersion),
@(GNSErrorUnexpectedWeaveControlPacket),
@(GNSErrorParsingWeavePacket),
@(GNSErrorWrongWeavePacketCounter),
@(GNSErrorWeaveDataTransferInProgress),
@(GNSErrorParsingWeavePacketTooSmall),
@(GNSErrorParsingWeavePacketTooLarge),
@(GNSErrorConnectionTimedOut),
];
for (NSNumber *errorCodeNum in allErrorCodes) {
GNSError errorCode = [errorCodeNum integerValue];
NSError *currentError = GNSErrorWithCode(errorCode);
XCTAssertEqualObjects(currentError.domain, kGNSSocketsErrorDomain);
XCTAssertEqual(currentError.code, errorCode);
XCTAssertNotNil(currentError.localizedDescription, @"Description missing for error code %ld",
(long)errorCode);
XCTAssertTrue(currentError.localizedDescription.length > 0,
@"Description empty for error code %ld", (long)errorCode);
}
// Test the assertion for GNSErrorNoError
XCTAssertThrows(GNSErrorWithCode(GNSErrorNoError),
@"Should throw an assertion for GNSErrorNoError");
}
- (void)testGNSCharacteristicName {
XCTAssertEqualObjects(GNSCharacteristicName(kGNSWeaveToPeripheralCharUUIDString),
@"ToPeripheralChar");
XCTAssertEqualObjects(GNSCharacteristicName(kGNSWeaveFromPeripheralCharUUIDString),
@"FromPeripheralChar");
XCTAssertEqualObjects(GNSCharacteristicName(kGNSPairingCharUUIDString), @"PairingChar");
XCTAssertEqualObjects(GNSCharacteristicName(@"UNKNOWN_UUID"), @"UnknownChar");
XCTAssertEqualObjects(GNSCharacteristicName(@""), @"UnknownChar");
}
- (void)testCBManagerStateString {
XCTAssertEqualObjects(CBManagerStateString(CBManagerStateUnknown), @"CBManagerStateUnknown");
XCTAssertEqualObjects(CBManagerStateString(CBManagerStateResetting), @"CBManagerStateResetting");
XCTAssertEqualObjects(CBManagerStateString(CBManagerStateUnsupported),
@"CBManagerStateUnsupported");
XCTAssertEqualObjects(CBManagerStateString(CBManagerStateUnauthorized),
@"CBManagerStateUnauthorized");
XCTAssertEqualObjects(CBManagerStateString(CBManagerStatePoweredOff),
@"CBManagerStatePoweredOff");
XCTAssertEqualObjects(CBManagerStateString(CBManagerStatePoweredOn), @"CBManagerStatePoweredOn");
// Test with an out-of-range value
XCTAssertEqualObjects(CBManagerStateString((CBManagerState)100), @"CBManagerState Unknown");
}
@end
NS_ASSUME_NONNULL_END
@@ -16,8 +16,47 @@
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSWeavePacket.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils.h"
// Fake handler for testing visitWithHandler
@interface GNSFakeWeavePacketHandler : NSObject <GNSWeavePacketHandler>
@property(nonatomic, nullable) GNSWeaveConnectionRequestPacket *lastConnectionRequestPacket;
@property(nonatomic, nullable) GNSWeaveConnectionConfirmPacket *lastConnectionConfirmPacket;
@property(nonatomic, nullable) GNSWeaveErrorPacket *lastErrorPacket;
@property(nonatomic, nullable) GNSWeaveDataPacket *lastDataPacket;
@property(nonatomic, nullable) id lastContext;
@property(nonatomic) int handleConnectionRequestCalledCount;
@property(nonatomic) int handleConnectionConfirmCalledCount;
@property(nonatomic) int handleErrorCalledCount;
@property(nonatomic) int handleDataCalledCount;
@end
@implementation GNSFakeWeavePacketHandler
- (void)handleConnectionRequestPacket:(GNSWeaveConnectionRequestPacket *)packet
context:(nullable id)context {
self.lastConnectionRequestPacket = packet;
self.lastContext = context;
self.handleConnectionRequestCalledCount++;
}
- (void)handleConnectionConfirmPacket:(GNSWeaveConnectionConfirmPacket *)packet
context:(nullable id)context {
self.lastConnectionConfirmPacket = packet;
self.lastContext = context;
self.handleConnectionConfirmCalledCount++;
}
- (void)handleErrorPacket:(GNSWeaveErrorPacket *)packet context:(nullable id)context {
self.lastErrorPacket = packet;
self.lastContext = context;
self.handleErrorCalledCount++;
}
- (void)handleDataPacket:(GNSWeaveDataPacket *)packet context:(nullable id)context {
self.lastDataPacket = packet;
self.lastContext = context;
self.handleDataCalledCount++;
}
@end
@interface GNSWeavePacketTest : XCTestCase {
NSData *_largeNonEmptyData;
NSData *_smallNonEmptyData;
@@ -307,4 +346,168 @@
XCTAssertEqualObjects(connectionConfirmPacket.data, smallPayload);
}
- (void)testConnectionRequestVisitWithHandler {
GNSWeaveConnectionRequestPacket *requestPacket =
[[GNSWeaveConnectionRequestPacket alloc] initWithMinVersion:1
maxVersion:1
maxPacketSize:20
data:nil];
GNSFakeWeavePacketHandler *handler = [[GNSFakeWeavePacketHandler alloc] init];
id context = [[NSObject alloc] init];
XCTAssertTrue([requestPacket visitWithHandler:handler context:context]);
XCTAssertEqual(handler.handleConnectionRequestCalledCount, 1);
XCTAssertEqual(handler.lastConnectionRequestPacket, requestPacket);
XCTAssertEqual(handler.lastContext, context);
XCTAssertEqual(handler.handleConnectionConfirmCalledCount, 0);
XCTAssertEqual(handler.handleErrorCalledCount, 0);
XCTAssertEqual(handler.handleDataCalledCount, 0);
}
- (void)testConnectionConfirmVisitWithHandler {
GNSWeaveConnectionConfirmPacket *confirmPacket =
[[GNSWeaveConnectionConfirmPacket alloc] initWithVersion:1 packetSize:20 data:nil];
GNSFakeWeavePacketHandler *handler = [[GNSFakeWeavePacketHandler alloc] init];
id context = [[NSObject alloc] init];
XCTAssertTrue([confirmPacket visitWithHandler:handler context:context]);
XCTAssertEqual(handler.handleConnectionConfirmCalledCount, 1);
XCTAssertEqual(handler.lastConnectionConfirmPacket, confirmPacket);
XCTAssertEqual(handler.lastContext, context);
XCTAssertEqual(handler.handleConnectionRequestCalledCount, 0);
XCTAssertEqual(handler.handleErrorCalledCount, 0);
XCTAssertEqual(handler.handleDataCalledCount, 0);
}
- (void)testErrorVisitWithHandler {
GNSWeaveErrorPacket *errorPacket = [[GNSWeaveErrorPacket alloc] initWithPacketCounter:3];
GNSFakeWeavePacketHandler *handler = [[GNSFakeWeavePacketHandler alloc] init];
id context = [[NSObject alloc] init];
XCTAssertTrue([errorPacket visitWithHandler:handler context:context]);
XCTAssertEqual(handler.handleErrorCalledCount, 1);
XCTAssertEqual(handler.lastErrorPacket, errorPacket);
XCTAssertEqual(handler.lastContext, context);
XCTAssertEqual(handler.handleConnectionRequestCalledCount, 0);
XCTAssertEqual(handler.handleConnectionConfirmCalledCount, 0);
XCTAssertEqual(handler.handleDataCalledCount, 0);
}
- (void)testDataVisitWithHandler {
GNSWeaveDataPacket *dataPacket =
[[GNSWeaveDataPacket alloc] initWithPacketCounter:4
firstPacket:YES
lastPacket:YES
data:_smallNonEmptyData];
GNSFakeWeavePacketHandler *handler = [[GNSFakeWeavePacketHandler alloc] init];
id context = [[NSObject alloc] init];
XCTAssertTrue([dataPacket visitWithHandler:handler context:context]);
XCTAssertEqual(handler.handleDataCalledCount, 1);
XCTAssertEqual(handler.lastDataPacket, dataPacket);
XCTAssertEqual(handler.lastContext, context);
XCTAssertEqual(handler.handleConnectionRequestCalledCount, 0);
XCTAssertEqual(handler.handleConnectionConfirmCalledCount, 0);
XCTAssertEqual(handler.handleErrorCalledCount, 0);
}
- (void)testVisitWithHandlerNoMatch {
GNSWeaveConnectionRequestPacket *requestPacket =
[[GNSWeaveConnectionRequestPacket alloc] initWithMinVersion:1
maxVersion:1
maxPacketSize:20
data:nil];
// Handler that implements nothing from the protocol
id<GNSWeavePacketHandler> emptyHandler = (id<GNSWeavePacketHandler>)[[NSObject alloc] init];
XCTAssertFalse([requestPacket visitWithHandler:emptyHandler context:nil]);
}
// Additional tests for parsing edge cases
- (void)testParseConnectionRequestMinSize {
UInt8 header = (1 << 7) + 0; // Connection Request, counter 0
NSMutableData *payload = [NSMutableData data];
UInt16 val = 0;
// Min payload size is 6 bytes
[payload appendBytes:&val length:sizeof(val)];
[payload appendBytes:&val length:sizeof(val)];
[payload appendBytes:&val length:sizeof(val)];
XCTAssertEqual(payload.length, 6);
NSError *error = nil;
GNSWeavePacket *packet =
[GNSWeavePacket parseData:[self weavePacketWithHeader:header data:payload] error:&error];
XCTAssertNotNil(packet);
XCTAssertNil(error);
XCTAssertTrue([packet isKindOfClass:[GNSWeaveConnectionRequestPacket class]]);
// Too small
NSData *tooSmallPayload = [payload subdataWithRange:NSMakeRange(0, 5)];
packet = [GNSWeavePacket parseData:[self weavePacketWithHeader:header data:tooSmallPayload]
error:&error];
XCTAssertNil(packet);
XCTAssertEqual(error.code, GNSErrorParsingWeavePacketTooSmall);
}
- (void)testParseConnectionConfirmMinSize {
UInt8 header = (1 << 7) + 1; // Connection Confirm, counter 0
NSMutableData *payload = [NSMutableData data];
UInt16 version = 1;
UInt16 packetSize = kGNSMinSupportedPacketSize;
// Min payload size is 4 bytes
UInt16 versionBigEndian = CFSwapInt16HostToBig(version);
UInt16 packetSizeBigEndian = CFSwapInt16HostToBig(packetSize);
[payload appendBytes:&versionBigEndian length:sizeof(versionBigEndian)];
[payload appendBytes:&packetSizeBigEndian length:sizeof(packetSizeBigEndian)];
XCTAssertEqual(payload.length, 4);
NSError *error = nil;
GNSWeavePacket *packet =
[GNSWeavePacket parseData:[self weavePacketWithHeader:header data:payload] error:&error];
XCTAssertNotNil(packet);
XCTAssertNil(error);
XCTAssertTrue([packet isKindOfClass:[GNSWeaveConnectionConfirmPacket class]]);
// Too small
NSData *tooSmallPayload = [payload subdataWithRange:NSMakeRange(0, 3)];
packet = [GNSWeavePacket parseData:[self weavePacketWithHeader:header data:tooSmallPayload]
error:&error];
XCTAssertNil(packet);
XCTAssertEqual(error.code, GNSErrorParsingWeavePacketTooSmall);
}
- (void)testParseErrorPacketSize {
UInt8 header = (1 << 7) + 2; // Error, counter 0
// Error packet has no payload
NSError *error = nil;
GNSWeavePacket *packet = [GNSWeavePacket parseData:[self weavePacketWithHeader:header data:nil]
error:&error];
XCTAssertNotNil(packet);
XCTAssertNil(error);
// Should still be valid with empty data
packet = [GNSWeavePacket parseData:[self weavePacketWithHeader:header data:[NSData data]]
error:&error];
XCTAssertNotNil(packet);
XCTAssertNil(error);
// Invalid with payload
packet = [GNSWeavePacket parseData:[self weavePacketWithHeader:header data:_smallNonEmptyData]
error:nil];
// This doesn't cause an error in the current parseData, it just ignores the extra data.
// Depending on spec, this might be desired or not. Assuming it's fine.
XCTAssertNotNil(packet);
}
- (void)testMaxPacketCounter {
XCTAssertThrows([[GNSWeaveErrorPacket alloc] initWithPacketCounter:8],
@"Should assert for packet counter >= 8");
XCTAssertThrows([[GNSWeaveDataPacket alloc] initWithPacketCounter:8
firstPacket:YES
lastPacket:YES
data:_smallNonEmptyData],
@"Should assert for packet counter >= 8");
}
@end