FBDeviceControl/Management/FBAMDServiceConnection.m (332 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBAMDServiceConnection.h"
#import "FBAFCConnection.h"
#import "FBDeviceControlError.h"
typedef uint32_t HeaderIntType;
static const NSUInteger HeaderLength = sizeof(HeaderIntType);
// There's an upper limit on the number of bytes we can read at once
static size_t ReadBufferSize = 1024 * 4;
@interface FBAMDServiceConnection ()
- (ssize_t)send:(const void *)buffer size:(size_t)size;
- (ssize_t)receive:(void *)buffer size:(size_t)size;
@end
@interface FBAMDServiceConnection_FileReader : NSObject <FBFileReader>
@property (nonatomic, strong, readonly) id<FBDataConsumer> consumer;
@property (nonatomic, strong, readonly) FBAMDServiceConnection *connection;
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@property (nonatomic, strong, readonly) FBMutableFuture<NSNumber *> *finishedReadingMutable;
@end
@implementation FBAMDServiceConnection_FileReader
@synthesize state = _state;
- (instancetype)initWithServiceConnection:(FBAMDServiceConnection *)connection consumer:(id<FBDataConsumer>)consumer queue:(dispatch_queue_t)queue
{
self = [super init];
if (!self) {
return nil;
}
_connection = connection;
_consumer = consumer;
_queue = queue;
_state = FBFileReaderStateNotStarted;
_finishedReadingMutable = FBMutableFuture.future;
return self;
}
- (FBFuture<NSNull *> *)startReading
{
if (self.state != FBFileReaderStateNotStarted) {
return [[FBDeviceControlError
describeFormat:@"Cannot start reading in state %lu", (unsigned long)self.state]
failFuture];
}
FBAMDServiceConnection *connection = self.connection;
id<FBDataConsumer> consumer = self.consumer;
dispatch_async(self.queue, ^{
void *buffer = alloca(ReadBufferSize);
while (self.state == FBFileReaderStateReading && self.finishedReadingMutable.state == FBFutureStateRunning) {
ssize_t readBytes = [connection receive:buffer size:ReadBufferSize];
if (readBytes < 1) {
break;
}
NSData *data = [[NSData alloc] initWithBytes:buffer length:(size_t) readBytes];
[consumer consumeData:data];
}
[consumer consumeEndOfFile];
self->_state = FBFileReaderStateFinishedReadingNormally;
});
_state = FBFileReaderStateReading;
return FBFuture.empty;
}
- (FBFuture<NSNumber *> *)stopReading
{
if (self.state == FBFileReaderStateNotStarted) {
return [[FBDeviceControlError
describe:@"Cannot stop reading when reading has not started"]
failFuture];
}
if (self.state != FBFileReaderStateReading) {
return self.finishedReadingMutable;
}
_state = FBFileReaderStateFinishedReadingByCancellation;
[self.finishedReadingMutable resolveWithResult:@(FBFileReaderStateFinishedReadingByCancellation)];
return self.finishedReadingMutable;
}
- (FBFuture<NSNumber *> *)finishedReadingWithTimeout:(NSTimeInterval)timeout
{
return [[[self
finishedReading]
timeout:timeout waitingFor:@"Process Reading to Finish"]
onQueue:self.queue handleError:^(NSError *_) {
return [self stopReading];
}];
}
- (FBFuture<NSNumber *> *)finishedReading
{
return self.finishedReadingMutable;
}
@end
@implementation FBAMDServiceConnection
#pragma mark Initializers
+ (instancetype)connectionWithName:(NSString *)name connection:(AMDServiceConnectionRef)connection device:(AMDeviceRef)device calls:(AMDCalls)calls logger:(id<FBControlCoreLogger>)logger
{
// Use Raw transfer when there's no Secure Context, otherwise we must use the service connection wrapping.
AMSecureIOContext secureIOContext = calls.ServiceConnectionGetSecureIOContext(connection);
[logger logFormat:@"Constructing service connection for %@ %@ Secure", name, secureIOContext ? @"is" : @"is not"];
return [[FBAMDServiceConnection alloc] initWithName:name connection:connection device:device calls:calls logger:logger];
}
- (instancetype)initWithName:(NSString *)name connection:(AMDServiceConnectionRef)connection device:(AMDeviceRef)device calls:(AMDCalls)calls logger:(id<FBControlCoreLogger>)logger;
{
self = [super init];
if (!self) {
return nil;
}
_name = name;
_connection = connection;
_device = device;
_calls = calls;
_logger = logger;
return self;
}
#pragma mark NSObject
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ %@", self.name, self.connection];
}
#pragma mark plist Messaging
- (BOOL)sendMessage:(id)message error:(NSError **)error
{
int result = self.calls.ServiceConnectionSendMessage(self.connection, (__bridge CFPropertyListRef)(message), kCFPropertyListBinaryFormat_v1_0, NULL, NULL, NULL);
if (result != 0) {
NSString *errorDescription = CFBridgingRelease(self.calls.CopyErrorText(result));
return [[FBDeviceControlError
describeFormat:@"Failed to send message %@ (%@ code %d)", errorDescription, message, result]
failBool:error];
}
return YES;
}
- (id)receiveMessageWithError:(NSError **)error
{
CFTypeRef message = NULL;
int result = self.calls.ServiceConnectionReceiveMessage(self.connection, &message, NULL, NULL, NULL, NULL);
if (result != 0) {
NSString *errorDescription = CFBridgingRelease(self.calls.CopyErrorText(result));
return [[FBDeviceControlError
describeFormat:@"Failed to receive message (%@): code %d", errorDescription, result]
fail:error];
}
return CFBridgingRelease(message);
}
- (id)sendAndReceiveMessage:(id)message error:(NSError **)error
{
if (![self sendMessage:message error:error]) {
return nil;
}
return [self receiveMessageWithError:error];
}
#pragma mark Lifecycle
- (BOOL)invalidateWithError:(NSError **)error
{
if (!_connection) {
return [[FBDeviceControlError
describe:@"No connection to invalidate"]
failBool:error];
}
NSString *connectionDescription = CFBridgingRelease(CFCopyDescription(self.connection));
[self.logger logFormat:@"Invalidating Connection %@", connectionDescription];
int status = self.calls.ServiceConnectionInvalidate(self.connection);
if (status != 0) {
NSString *errorDescription = CFBridgingRelease(self.calls.CopyErrorText(status));
return [[FBDeviceControlError
describeFormat:@"Failed to invalidate connection %@ with error %@", connectionDescription, errorDescription]
failBool:error];
}
[self.logger logFormat:@"Invalidated connection %@", connectionDescription];
// AMDServiceConnectionInvalidate does not release the connection.
CFRelease(_connection);
_connection = NULL;
return YES;
}
#pragma mark AFC
- (FBAFCConnection *)asAFCConnectionWithCalls:(AFCCalls)calls callback:(AFCNotificationCallback)callback logger:(id<FBControlCoreLogger>)logger
{
AFCConnectionRef afcConnection = calls.Create(
0x0,
self.calls.ServiceConnectionGetSocket(self.connection),
0x0,
callback,
0x0
);
// We need to apply the Secure Context if it's present on the service connection.
AMSecureIOContext secureIOContext = self.calls.ServiceConnectionGetSecureIOContext(self.connection);;
if (secureIOContext != NULL) {
calls.SetSecureContext(afcConnection, secureIOContext);
}
return [[FBAFCConnection alloc] initWithConnection:afcConnection calls:calls logger:self.logger];
}
#pragma mark FBAMDServiceConnectionTransfer Implementation
// There's an upper limit on the number of bytes we can receive at once
static size_t SendBufferSize = 1024 * 4;
- (BOOL)send:(NSData *)data error:(NSError **)error
{
// Keep track of the number of bytes we can send.
size_t bytesRemaining = data.length;
// Start a loop that ends when there's no more bytes to send
while (bytesRemaining > 0) {
// Send the bytes now
NSRange sendRange = NSMakeRange(data.length - data.length, MIN(SendBufferSize, bytesRemaining));
NSData *chunkData = [data subdataWithRange:sendRange];
ssize_t result = [self send:chunkData.bytes size:chunkData.length];
// A negative return indicates error.
if (result == -1) {
return [[FBDeviceControlError
describeFormat:@"Failure in send of %zu bytes: %s", chunkData.length, strerror(errno)]
failBool:error];
}
// End of file.
if (result == 0) {
break;
}
// Check an over-write to prevent unsigned integer overflow.
size_t sentBytes = (size_t) result;
if (sentBytes > bytesRemaining) {
return [[FBDeviceControlError
describeFormat:@"Failure in send: Sent %zu bytes but only %zu bytes remaining", sentBytes, bytesRemaining]
failBool:error];
}
// Otherwise keep going and decrement the number of remaining bytes to send.
bytesRemaining -= sentBytes;
}
// Check that we've sent the right number of bytes.
if (bytesRemaining != 0) {
return [[FBDeviceControlError
describeFormat:@"Failed to send %zu bytes, %zu remaining", data.length, bytesRemaining]
failBool:error];
}
return YES;
}
- (BOOL)sendWithLengthHeader:(NSData *)data error:(NSError **)error
{
HeaderIntType length = (HeaderIntType) data.length;
HeaderIntType lengthWire = OSSwapHostToBigInt32(length); // The host (native) length should be converted endianness of remote (ARM/Apple Silicon).
NSData *lengthData = [[NSData alloc] initWithBytes:&lengthWire length:HeaderLength];
// Write the length data.
if (![self send:lengthData error:error]) {
return NO;
}
// Then send the actual payload.
if (![self send:data error:error]) {
return NO;
}
return YES;
}
- (BOOL)sendUnsignedInt32:(uint32_t)value error:(NSError **)error
{
NSData *data = [[NSData alloc] initWithBytes:&value length:sizeof(uint32_t)];
return [self send:data error:error];
}
- (NSData *)receive:(size_t)size error:(NSError **)error
{
// Create a buffer that contains the data to return and how to append it from the enumerator
NSMutableData *data = NSMutableData.data;
void(^enumerator)(NSData *) = ^(NSData *chunk){
[data appendData:[chunk copy]];
};
// Start the byte recieve.
BOOL success = [self enumateReceiveOfLength:size chunkSize:ReadBufferSize enumerator:enumerator error:error];
if (!success) {
return nil;
}
return data;
}
- (BOOL)receive:(size_t)size toFile:(NSFileHandle *)fileHandle error:(NSError **)error
{
void(^enumerator)(NSData *) = ^(NSData *chunk){
[fileHandle writeData:chunk];
};
return [self enumateReceiveOfLength:size chunkSize:ReadBufferSize enumerator:enumerator error:error];
}
- (BOOL)receive:(void *)destination ofSize:(size_t)size error:(NSError **)error
{
NSData *data = [self receive:size error:error];
if (!data) {
return NO;
}
memcpy(destination, data.bytes, data.length);
return YES;
}
- (NSData *)receiveUpTo:(size_t)size error:(NSError **)error
{
// Create a buffer that contains the data
void *buffer = alloca(size);
// Read the underlying bytes.
ssize_t result = [self receive:buffer size:size];
// End of file.
if (result == 0) {
return NSData.data;
}
// A negative return indicates an error
if (result == -1) {
return [[FBDeviceControlError
describeFormat:@"Failure in receive of up to %zu bytes: %s", size, strerror(errno)]
fail:error];
}
size_t readBytes = (size_t) result;
return [[NSData alloc] initWithBytes:buffer length:readBytes];
}
- (BOOL)receiveUnsignedInt32:(uint32_t *)valueOut error:(NSError **)error
{
return [self receive:valueOut ofSize:sizeof(uint32_t) error:error];
}
- (BOOL)receiveUnsignedInt64:(uint64_t *)valueOut error:(NSError **)error
{
return [self receive:valueOut ofSize:sizeof(uint64_t) error:error];
}
- (id<FBFileReader>)readFromConnectionWritingToConsumer:(id<FBDataConsumer>)consumer onQueue:(dispatch_queue_t)queue
{
return [[FBAMDServiceConnection_FileReader alloc] initWithServiceConnection:self consumer:consumer queue:queue];
}
- (id<FBDataConsumer, FBDataConsumerLifecycle>)writeWithConsumerWritingOnQueue:(dispatch_queue_t)queue
{
return [FBBlockDataConsumer asynchronousDataConsumerOnQueue:queue consumer:^(NSData *data) {
[self send:data error:nil];
}];
}
#pragma mark Private
- (ssize_t)send:(const void *)buffer size:(size_t)size
{
return self.calls.ServiceConnectionSend(self.connection, buffer, size);
}
- (ssize_t)receive:(void *)buffer size:(size_t)size
{
return self.calls.ServiceConnectionReceive(self.connection, buffer, size);
}
- (BOOL)enumateReceiveOfLength:(size_t)size chunkSize:(size_t)chunkSize enumerator:(void(^)(NSData *))enumerator error:(NSError **)error
{
// Create a buffer that contains the incremental enumerated data.
void *buffer = alloca(chunkSize);
// Start reading in a loop, until there's no more bytes to read.
size_t bytesRemaining = size;
while (bytesRemaining > 0) {
// Don't read more bytes than are remaining.
size_t maxReadBytes = MIN(chunkSize, bytesRemaining);
ssize_t result = [self receive:buffer size:maxReadBytes];
// End of file.
if (result == 0) {
break;
}
// A negative return indicates an error
if (result == -1) {
return [[FBDeviceControlError
describeFormat:@"Failure in receive of %zu bytes: %s", maxReadBytes, strerror(errno)]
failBool:error];
}
// Check an over-read to prevent unsigned integer overflow.
size_t readBytes = (size_t) result;
if (readBytes > bytesRemaining) {
return [[FBDeviceControlError
describeFormat:@"Failure in receive: Read %zu bytes but only %zu bytes remaining", readBytes, bytesRemaining]
failBool:error];
}
// Decrement the number of bytes to read and pass it to the callback
bytesRemaining -= readBytes;
NSData *readData = [[NSData alloc] initWithBytesNoCopy:buffer length:readBytes freeWhenDone:NO];
enumerator(readData);
}
// Check that we've read the right number of bytes.
if (bytesRemaining != 0) {
return [[FBDeviceControlError
describeFormat:@"Failed to receive %zu bytes, %zu remaining to read and eof reached.", size, bytesRemaining]
failBool:error];
}
return YES;
}
@end