mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Add GNCBLEL2CAPConnection
PiperOrigin-RevId: 750016094
This commit is contained in:
committed by
Copybara-Service
parent
adb38095d8
commit
af148603b6
@@ -0,0 +1,52 @@
|
||||
// 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/GNCMConnection.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class GNCBLEL2CAPStream;
|
||||
|
||||
/**
|
||||
* A GNCBLEL2CAPConnection implementation for BLE L2CAP.
|
||||
* This class is thread-safe.
|
||||
*/
|
||||
@interface GNCBLEL2CAPConnection : NSObject
|
||||
@property(nonatomic) GNCMConnectionHandlers *connectionHandlers;
|
||||
|
||||
/*
|
||||
* Creates a |GNCBLEL2CAPConnection|.
|
||||
*
|
||||
* @param stream A |GNCL2CAPStream| instance.
|
||||
* @param serviceID A string that uniquely identifies the service.
|
||||
* @param incomingConnection A flag to indicate the connection is incoming.
|
||||
* @param callbackQueue The queue on which all callbacks are made.
|
||||
*/
|
||||
+ (instancetype)connectionWithStream:(GNCBLEL2CAPStream *)stream
|
||||
serviceID:(nullable NSString *)serviceID
|
||||
incomingConnection:(BOOL)incomingConnection
|
||||
callbackQueue:(dispatch_queue_t)callbackQueue;
|
||||
|
||||
/**
|
||||
* Sends data to the remote device.
|
||||
*
|
||||
* @param payload The data to send.
|
||||
* @param completion A block that is called when the data has been sent. The block takes a BOOL
|
||||
* parameter indicating whether the data was sent successfully.
|
||||
*/
|
||||
- (void)sendData:(NSData *)payload completion:(void (^)(BOOL))completion;
|
||||
|
||||
@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/GNCBLEL2CAPConnection.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleUtils.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/GNCLeaks.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
enum { kL2CAPPacketLength = 4 };
|
||||
|
||||
static char *const kGNCBLEL2CAPConnectionQueueLabel = "com.google.nearby.GNCBLEL2CAPConnection";
|
||||
|
||||
static NSData *PrefixDataWithServiceIDHash(NSData *serviceIDHash, NSData *data) {
|
||||
NSMutableData *combinedData = [NSMutableData dataWithCapacity:serviceIDHash.length + data.length];
|
||||
|
||||
[combinedData appendData:serviceIDHash];
|
||||
[combinedData appendData:data];
|
||||
|
||||
return combinedData;
|
||||
}
|
||||
|
||||
static NSData *PrefixLengthData(NSData *data) {
|
||||
uint32_t dataLength = (uint32_t)data.length;
|
||||
uint32_t lengthBigEndian = CFSwapInt32HostToBig(dataLength);
|
||||
|
||||
NSMutableData *packet = [NSMutableData dataWithCapacity:sizeof(uint32_t)];
|
||||
[packet appendBytes:&lengthBigEndian length:sizeof(uint32_t)];
|
||||
[packet appendData:data];
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
@interface GNCBLEL2CAPConnection () <GNCBLEL2CAPStreamDelegate>
|
||||
@property(nonatomic) dispatch_queue_t selfQueue;
|
||||
@property(nonatomic) GNCBLEL2CAPStream *stream;
|
||||
@property(nonatomic) NSData *serviceIDHash;
|
||||
@property(nonatomic) dispatch_queue_t callbackQueue;
|
||||
@property(nonatomic) BOOL incomingConnection;
|
||||
@end
|
||||
|
||||
@implementation GNCBLEL2CAPConnection
|
||||
|
||||
+ (instancetype)connectionWithStream:(GNCBLEL2CAPStream *)stream
|
||||
serviceID:(NSString *)serviceID
|
||||
incomingConnection:(BOOL)incomingConnection
|
||||
callbackQueue:(dispatch_queue_t)callbackQueue {
|
||||
GNCBLEL2CAPConnection *connection = [[GNCBLEL2CAPConnection alloc] init];
|
||||
[connection setStream:stream];
|
||||
stream.delegate = connection;
|
||||
[connection setServiceIDHash:GNCMServiceIDHash(serviceID)];
|
||||
[connection setCallbackQueue:callbackQueue];
|
||||
[connection
|
||||
setSelfQueue:dispatch_queue_create(kGNCBLEL2CAPConnectionQueueLabel,
|
||||
dispatch_queue_attr_make_with_qos_class(
|
||||
DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, -1))];
|
||||
[connection setIncomingConnection:incomingConnection];
|
||||
return connection;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_stream close];
|
||||
GNCVerifyDealloc(_stream, 2); // assert if not deallocated
|
||||
}
|
||||
|
||||
#pragma mark GNCBLEL2CAPConnection
|
||||
|
||||
- (void)sendData:(NSData *)data completion:(void (^)(BOOL))completion {
|
||||
dispatch_async(_selfQueue, ^{
|
||||
NSData *packet;
|
||||
|
||||
// Prefix the service ID hash.
|
||||
packet = PrefixLengthData(PrefixDataWithServiceIDHash(_serviceIDHash, data));
|
||||
GTMLoggerInfo(@"[NEARBY] GNCBLEL2CAPConnection data to be sent: %@", [packet description]);
|
||||
[_stream sendData:packet
|
||||
completionBlock:^(BOOL result) {
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
completion(result);
|
||||
});
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark GNCBLEL2CAPStreamDelegate
|
||||
|
||||
- (void)stream:(GNCBLEL2CAPStream *)stream didReceiveData:(NSData *)data {
|
||||
GTMLoggerDebug(@"[NEARBY] BLEL2CAPConnection didReceiveData, data: %@", [data description]);
|
||||
|
||||
NSData *realData = [self extractRealDataFromData:data];
|
||||
if (realData == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the L2CAP packet.
|
||||
// TODO: b/399815436 - Refactor the validation logic to connections layer.
|
||||
|
||||
// Extract the service ID prefix from each data packet and validate it.
|
||||
NSUInteger prefixLength = _serviceIDHash.length;
|
||||
if (![[realData subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
|
||||
GTMLoggerInfo(@"[NEARBY]: Received wrong data packet and discarded");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(_selfQueue, ^{
|
||||
if (_connectionHandlers.payloadHandler) {
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
_connectionHandlers.payloadHandler([NSData
|
||||
dataWithData:[realData subdataWithRange:NSMakeRange(prefixLength,
|
||||
realData.length - prefixLength)]]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stream:(GNCBLEL2CAPStream *)stream didDisconnectWithError:(NSError *_Nullable)error {
|
||||
dispatch_async(_selfQueue, ^{
|
||||
if (_connectionHandlers.disconnectedHandler) {
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
_connectionHandlers.disconnectedHandler();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (NSData *_Nullable)extractRealDataFromData:(NSData *)data {
|
||||
if (data.length < kL2CAPPacketLength) {
|
||||
GTMLoggerError(@"[NEARBY] Packet length mismatch. Expected: > %d, Actual: %lu",
|
||||
kL2CAPPacketLength, data.length);
|
||||
return nil;
|
||||
}
|
||||
int realDataLength = CFSwapInt32BigToHost(
|
||||
*(int *)([[data subdataWithRange:NSMakeRange(0, kL2CAPPacketLength)] bytes]));
|
||||
if (realDataLength != (data.length - kL2CAPPacketLength)) {
|
||||
GTMLoggerError(@"[NEARBY] Data length mismatch. Expected: %d, Actual: %lu",
|
||||
realDataLength, data.length - kL2CAPPacketLength);
|
||||
return nil;
|
||||
}
|
||||
return [data subdataWithRange:NSMakeRange(kL2CAPPacketLength, data.length - kL2CAPPacketLength)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class GNCBLEL2CAPStream;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Block invoked when the stream is closed.
|
||||
@@ -22,6 +24,23 @@ typedef void (^GNCBLEL2CAPStreamClosedBlock)(void);
|
||||
/// Block invoked when |data| is received from the remote device on the L2CAP connection.
|
||||
typedef void (^GNCBLEL2CAPControllerReceivedDataBlock)(NSData *data);
|
||||
|
||||
/** Delegate for @c GNCBLEL2CAPStream. */
|
||||
@protocol GNCBLEL2CAPStreamDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Called when data is received from the remote device on the L2CAP connection.
|
||||
*
|
||||
* @param data The data received from the remote device.
|
||||
*/
|
||||
- (void)stream:(GNCBLEL2CAPStream *)stream didReceiveData:(NSData *)data;
|
||||
|
||||
/**
|
||||
* Called when the stream is closed.
|
||||
*/
|
||||
- (void)stream:(GNCBLEL2CAPStream *)stream didDisconnectWithError:(NSError *_Nullable)error;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Abstraction to take in two streams returned from L2CAP controller and provide a simplified
|
||||
* interface to send and receive data from the streams.
|
||||
@@ -29,6 +48,9 @@ typedef void (^GNCBLEL2CAPControllerReceivedDataBlock)(NSData *data);
|
||||
*/
|
||||
@interface GNCBLEL2CAPStream : NSObject
|
||||
|
||||
/// Delegate for @c GNCBLEL2CAPStream
|
||||
@property(nonatomic, weak) id<GNCBLEL2CAPStreamDelegate> delegate;
|
||||
|
||||
/// Returns an instance of @c GNCBLEL2CAPStream which sends and receives data on |inputStream|
|
||||
/// and |outputStream|.
|
||||
/// Invokes |closedBlock| if stream closed signal is received when reading data.
|
||||
|
||||
@@ -320,6 +320,8 @@
|
||||
}
|
||||
|
||||
dispatch_async(_receivedDataQueue, ^{
|
||||
[_delegate stream:self didReceiveData:data];
|
||||
// TODO: b/399815436 - Remove below once the delegate is implemented.
|
||||
self->_receivedDataBlock(data);
|
||||
});
|
||||
} else if (bytesRead < 0) {
|
||||
@@ -328,6 +330,7 @@
|
||||
GTMLoggerDebug(@"[NEARBY] End of stream reached. Disconnecting");
|
||||
// This indicates the L2CAP socket is closed. Notifying the owner so that it can tear down this
|
||||
// stream and update its own state.
|
||||
[_delegate stream:self didDisconnectWithError:nil];
|
||||
_closedBlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ objc_library(
|
||||
"BLEv2/GNCBLEGATTClient.m",
|
||||
"BLEv2/GNCBLEGATTServer.m",
|
||||
"BLEv2/GNCBLEL2CAPClient.m",
|
||||
"BLEv2/GNCBLEL2CAPConnection.m",
|
||||
"BLEv2/GNCBLEL2CAPServer.m",
|
||||
"BLEv2/GNCBLEL2CAPStream.m",
|
||||
"BLEv2/GNCBLEMedium.m",
|
||||
@@ -55,6 +56,7 @@ objc_library(
|
||||
"BLEv2/GNCBLEGATTClient.h",
|
||||
"BLEv2/GNCBLEGATTServer.h",
|
||||
"BLEv2/GNCBLEL2CAPClient.h",
|
||||
"BLEv2/GNCBLEL2CAPConnection.h",
|
||||
"BLEv2/GNCBLEL2CAPServer.h",
|
||||
"BLEv2/GNCBLEL2CAPStream.h",
|
||||
"BLEv2/GNCBLEMedium.h",
|
||||
|
||||
Reference in New Issue
Block a user