mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Implement L2CAPServer
PiperOrigin-RevId: 733593766
This commit is contained in:
committed by
Copybara-Service
parent
b6dc3348a4
commit
18f52de400
@@ -0,0 +1,39 @@
|
||||
// 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 <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* An object that publishes the @c PSM value.
|
||||
*
|
||||
* @note The public APIs of this class are thread safe.
|
||||
*/
|
||||
@interface GNCBLEL2CAPServer : NSObject
|
||||
|
||||
// Represents a PSM (Protocol/Service Multiplexer) value for an L2CAP channel.
|
||||
@property(atomic, readonly) CBL2CAPPSM PSM;
|
||||
|
||||
/**
|
||||
* Retrieves the L2CAP channel.
|
||||
*
|
||||
* @return The L2CAP channel, or nil if the channel is not available.
|
||||
*/
|
||||
- (nullable CBL2CAPChannel *)getChannel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,158 @@
|
||||
// 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/BLEv2/GNCBLEL2CAPServer.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheralManager.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static char *const kGNCBLEL2CAPServerQueueLabel = "com.nearby.GNCBLEL2CAPServer";
|
||||
|
||||
@interface GNCBLEL2CAPServer () <GNCPeripheralManagerDelegate>
|
||||
@property(atomic, readwrite) CBL2CAPPSM PSM;
|
||||
@end
|
||||
|
||||
@implementation GNCBLEL2CAPServer {
|
||||
dispatch_queue_t _queue;
|
||||
id<GNCPeripheralManager> _peripheralManager;
|
||||
|
||||
// The L2CAP channel that is used to send and receive data.
|
||||
CBL2CAPChannel *_channel;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = dispatch_queue_create(kGNCBLEL2CAPServerQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil queue:_queue];
|
||||
// Set for @c GNCPeripheralManager to be able to forward callbacks.
|
||||
_peripheralManager.peripheralDelegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// This is private and should only be used for tests. The provided peripheral manager must call
|
||||
// delegate methods on the main queue.
|
||||
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = dispatch_get_main_queue();
|
||||
_peripheralManager = peripheralManager;
|
||||
// Set for @c GNCPeripheralManager to be able to forward callbacks.
|
||||
_peripheralManager.peripheralDelegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable CBL2CAPChannel *)getChannel {
|
||||
return _channel;
|
||||
}
|
||||
|
||||
#pragma mark - GNCPeripheralManagerDelegate
|
||||
|
||||
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
|
||||
dispatch_assert_queue(_queue);
|
||||
if (_peripheralManager.state == CBManagerStatePoweredOn) {
|
||||
[_peripheralManager publishL2CAPChannelWithEncryption:NO];
|
||||
}
|
||||
if (_peripheralManager.state == CBManagerStatePoweredOff) {
|
||||
[_peripheralManager unpublishL2CAPChannel:_PSM];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
if (error) {
|
||||
GTMLoggerError(@"Failed to publish L2CAP channel: %@", error);
|
||||
}
|
||||
GTMLoggerInfo(@"Published L2CAP channel with PSM: %d", PSM);
|
||||
_PSM = PSM;
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
if (error) {
|
||||
GTMLoggerError(@"Failed to unpublish L2CAP channel: %@", error);
|
||||
}
|
||||
_PSM = 0;
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
if (error) {
|
||||
GTMLoggerError(@"Failed to open L2CAP channel: %@", error);
|
||||
return;
|
||||
}
|
||||
GTMLoggerInfo(@"Opened L2CAP channel with PSM: %d", channel.PSM);
|
||||
_channel = channel;
|
||||
// TODO: b/399815436 - Implement to wrap up l2cap channel.
|
||||
}
|
||||
|
||||
#pragma mark - CBPeripheralManagerDelegate
|
||||
|
||||
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManagerDidUpdateState:peripheral];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManager:peripheral didPublishL2CAPChannel:PSM error:error];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManager:peripheral didUnpublishL2CAPChannel:PSM error:error];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManager:peripheral didOpenL2CAPChannel:channel error:error];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)tearDown {
|
||||
if (_PSM > 0) {
|
||||
[_peripheralManager unpublishL2CAPChannel:_PSM];
|
||||
}
|
||||
_peripheralManager.peripheralDelegate = nil;
|
||||
_peripheralManager = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -118,6 +118,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)stopAdvertising;
|
||||
|
||||
/**
|
||||
* Publishes an L2CAP channel with optional encryption.
|
||||
*
|
||||
* @param encryptionRequired A boolean value indicating whether encryption is required for the L2CAP channel.
|
||||
*/
|
||||
- (void)publishL2CAPChannelWithEncryption:(BOOL)encryptionRequired;
|
||||
|
||||
/**
|
||||
* Unpublishes an L2CAP channel.
|
||||
*
|
||||
* @param PSM The @c PSM (Protocol/Service Multiplexer) value assigned to the published channel.
|
||||
*/
|
||||
- (void)unpublishL2CAPChannel:(CBL2CAPPSM)PSM;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
@@ -156,6 +170,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @param peripheral The peripheral manager that is starting advertising.
|
||||
* @param error The reason the call failed, or @c nil if no error occurred.
|
||||
*/
|
||||
@optional
|
||||
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
@@ -171,6 +186,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @param service The service added to the local GATT database.
|
||||
* @param error The reason the call failed, or @c nil if no error occurred.
|
||||
*/
|
||||
@optional
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error;
|
||||
@@ -186,9 +202,49 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @param request A @c CBATTRequest object that represents a request to read a characteristic’s
|
||||
* value.
|
||||
*/
|
||||
@optional
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request;
|
||||
|
||||
/**
|
||||
* Called when an L2CAP channel has been published by the peripheral.
|
||||
*
|
||||
* @param peripheral The peripheral that published the L2CAP channel.
|
||||
* @param PSM The @c PSM (Protocol/Service Multiplexer) value assigned to the published channel.
|
||||
* @param error An error object indicating why the channel failed to publish, or @c nil if
|
||||
* successful.
|
||||
*/
|
||||
@optional
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
/**
|
||||
* Called when an L2CAP channel has been unpublished by the peripheral.
|
||||
*
|
||||
* @param peripheral The peripheral that unpublished the L2CAP channel.
|
||||
* @param PSM The @c PSM (Protocol/Service Multiplexer) value assigned to the unpublished channel.
|
||||
* @param error An error object indicating why the channel failed to unpublish, or @c nil if
|
||||
* successful.
|
||||
*/
|
||||
@optional
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(NSError *)error;
|
||||
|
||||
/**
|
||||
* Called when an L2CAP channel has been opened by the peripheral.
|
||||
*
|
||||
* @param peripheral The peripheral that opened the L2CAP channel.
|
||||
* @param channel The opened L2CAP channel, or nil if an error occurred.
|
||||
* @param error An error object indicating why the channel failed to
|
||||
* open, or @c nil if successful.
|
||||
*/
|
||||
@optional
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
@interface CBPeripheralManager () <GNCPeripheralManager>
|
||||
|
||||
@@ -25,6 +25,7 @@ objc_library(
|
||||
"BLEv2/GNCBLEGATTCharacteristic.m",
|
||||
"BLEv2/GNCBLEGATTClient.m",
|
||||
"BLEv2/GNCBLEGATTServer.m",
|
||||
"BLEv2/GNCBLEL2CAPServer.m",
|
||||
"BLEv2/GNCBLEMedium.m",
|
||||
"BLEv2/GNCCentralManager.m",
|
||||
"BLEv2/GNCPeripheral.m",
|
||||
@@ -48,6 +49,7 @@ objc_library(
|
||||
"BLEv2/GNCBLEGATTCharacteristic.h",
|
||||
"BLEv2/GNCBLEGATTClient.h",
|
||||
"BLEv2/GNCBLEGATTServer.h",
|
||||
"BLEv2/GNCBLEL2CAPServer.h",
|
||||
"BLEv2/GNCBLEMedium.h",
|
||||
"BLEv2/GNCCentralManager.h",
|
||||
"BLEv2/GNCPeripheral.h",
|
||||
|
||||
@@ -28,6 +28,8 @@ objc_library(
|
||||
"GNCBLEGATTClientTest.m",
|
||||
"GNCBLEGATTServer+Testing.h",
|
||||
"GNCBLEGATTServerTest.m",
|
||||
"GNCBLEL2CAPServer+Testing.h",
|
||||
"GNCBLEL2CAPServerTest.m",
|
||||
"GNCBLEMedium+Testing.h",
|
||||
"GNCBLEMediumTest.m",
|
||||
"GNCBLEUtilsTest.mm",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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/BLEv2/GNCBLEL2CAPServer.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GNCPeripheralManager;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GNCBLEL2CAPServer (Testing)
|
||||
|
||||
/**
|
||||
* Creates a L2CAP server with a provided peripheral manager.
|
||||
*
|
||||
* This is only exposed for testing and can be used to inject a fake peripheral manager.
|
||||
*
|
||||
* @param peripheralManager The peripheral manager instance.
|
||||
*/
|
||||
- (instancetype)initWithPeripheralManager:(id<GNCPeripheralManager>)peripheralManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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/BLEv2/GNCBLEL2CAPServer.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Tests/GNCBLEL2CAPServer+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.h"
|
||||
|
||||
static const int kPSM = 192;
|
||||
|
||||
@interface GNCBLEL2CAPServerTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBLEL2CAPServerTest
|
||||
|
||||
#pragma mark - Publish L2CAP channel
|
||||
|
||||
- (void)testPublishL2CAPChannel {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
fakePeripheralManager.PSM = kPSM;
|
||||
GNCBLEL2CAPServer *l2capServer =
|
||||
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTAssertEqual([l2capServer PSM], kPSM);
|
||||
XCTAssertNotNil([l2capServer getChannel]);
|
||||
}
|
||||
|
||||
- (void)testFailedToPublishL2CAPChannel {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
fakePeripheralManager.didPublishL2CAPChannelError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
fakePeripheralManager.PSM = kPSM;
|
||||
GNCBLEL2CAPServer *l2capServer =
|
||||
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTAssertNotEqual([l2capServer PSM], kPSM);
|
||||
XCTAssertNil([l2capServer getChannel]);
|
||||
}
|
||||
|
||||
- (void)testUnPublishL2CAPChannel {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
fakePeripheralManager.PSM = kPSM;
|
||||
GNCBLEL2CAPServer *l2capServer =
|
||||
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOff];
|
||||
|
||||
XCTAssertEqual([l2capServer PSM], 0);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -35,6 +35,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/** The data being advertised. */
|
||||
@property(nonatomic, nullable, readonly) NSDictionary<NSString *, id> *advertisementData;
|
||||
|
||||
/** The PSM of the L2CAP channel. */
|
||||
@property(nonatomic) CBL2CAPPSM PSM;
|
||||
|
||||
/** Expectation fulfilled when peripheral responds to a request with success. */
|
||||
@property(nonatomic, readonly) XCTestExpectation *respondToRequestSuccessExpectation;
|
||||
|
||||
@@ -59,6 +62,24 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *didStartAdvertisingError;
|
||||
|
||||
/**
|
||||
* Similates a @c publishL2CAPChannel: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling @c
|
||||
* publishL2CAPChannel: and will call the @c gnc_peripheralManager:didPublishL2CAPChannel:error:
|
||||
* delegate method with the provided error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *didPublishL2CAPChannelError;
|
||||
|
||||
/**
|
||||
* Similates a @c unpublishL2CAPChannel: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling @c
|
||||
* unpublishL2CAPChannel: and will call the @c gnc_peripheralManager:didUnpublishL2CAPChannel:error:
|
||||
* delegate method with the provided error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *didUnPublishL2CAPChannelError;
|
||||
|
||||
/**
|
||||
* Simulates a state update event.
|
||||
*
|
||||
|
||||
@@ -122,6 +122,27 @@
|
||||
_isAdvertising = false;
|
||||
}
|
||||
|
||||
- (void)publishL2CAPChannelWithEncryption:(BOOL)encryptionRequired {
|
||||
CBL2CAPPSM localPSM = 0;
|
||||
if (!_didPublishL2CAPChannelError) {
|
||||
localPSM = _PSM;
|
||||
}
|
||||
[peripheralDelegate gnc_peripheralManager:self
|
||||
didPublishL2CAPChannel:localPSM
|
||||
error:_didPublishL2CAPChannelError];
|
||||
if (!_didPublishL2CAPChannelError) {
|
||||
[peripheralDelegate gnc_peripheralManager:self
|
||||
didOpenL2CAPChannel:[[CBL2CAPChannel alloc] init]
|
||||
error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unpublishL2CAPChannel:(CBL2CAPPSM)PSM {
|
||||
[peripheralDelegate gnc_peripheralManager:self
|
||||
didUnpublishL2CAPChannel:_PSM
|
||||
error:_didUnPublishL2CAPChannelError];
|
||||
}
|
||||
|
||||
#pragma mark - Testing Helpers
|
||||
|
||||
- (NSArray<CBService *> *)services {
|
||||
|
||||
Reference in New Issue
Block a user