diff --git a/FBDeviceControl/Management/FBAMDServiceConnection.h b/FBDeviceControl/Management/FBAMDServiceConnection.h index 09303b3a2..324f89695 100644 --- a/FBDeviceControl/Management/FBAMDServiceConnection.h +++ b/FBDeviceControl/Management/FBAMDServiceConnection.h @@ -112,6 +112,8 @@ NS_ASSUME_NONNULL_BEGIN /** Synchronously receive bytes from the connection. + This call will block until 'size' is met. + If a read fails before the 'size' is met, this call will fail. @param size the number of bytes to read. @param error an error out for any error that occurs. @@ -119,6 +121,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSData *)receive:(size_t)size error:(NSError **)error; +/** + Synchronously receive up to 'size' bytes in the connection + This call will return an empty NSData when end of file is reached. + + @param size the number of bytes to read up to. + @param error an error out for any error that occurs. + @return the data. + */ +- (NSData *)receiveUpTo:(size_t)size error:(NSError **)error; + /** Synchronously receive bytes from the connection, writing to a file handle. diff --git a/FBDeviceControl/Management/FBAMDServiceConnection.m b/FBDeviceControl/Management/FBAMDServiceConnection.m index b3a7da44a..68cb7fcd9 100644 --- a/FBDeviceControl/Management/FBAMDServiceConnection.m +++ b/FBDeviceControl/Management/FBAMDServiceConnection.m @@ -327,6 +327,26 @@ - (BOOL)receive:(void *)destination ofSize:(size_t)size error:(NSError **)error 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]; diff --git a/FBDeviceControl/Management/FBDeviceDebugServer.m b/FBDeviceControl/Management/FBDeviceDebugServer.m index bde621389..53ae0a9d3 100644 --- a/FBDeviceControl/Management/FBDeviceDebugServer.m +++ b/FBDeviceControl/Management/FBDeviceDebugServer.m @@ -13,61 +13,87 @@ @interface FBDeviceDebugServer_TwistedPairFiles : NSObject -@property (nonatomic, assign, readonly) int source; -@property (nonatomic, strong, readonly) FBAMDServiceConnection *sink; -@property (nonatomic, strong, readonly) dispatch_queue_t queue; -@property (nonatomic, strong, readonly) dispatch_queue_t sinkWriteQueue; -@property (nonatomic, strong, readonly) dispatch_queue_t sinkReadQueue; - -@property (nonatomic, strong, nullable, readwrite) id sourceWriter; -@property (nonatomic, strong, nullable, readwrite) id sourceReader; -@property (nonatomic, strong, nullable, readwrite) id sinkWriter; -@property (nonatomic, strong, nullable, readwrite) id sinkReader; +@property (nonatomic, assign, readonly) int socket; +@property (nonatomic, strong, readonly) FBAMDServiceConnection *connection; +@property (nonatomic, strong, readonly) id logger; +@property (nonatomic, strong, readonly) dispatch_queue_t socketToConnectionQueue; +@property (nonatomic, strong, readonly) dispatch_queue_t connectionToSocketQueue; @end @implementation FBDeviceDebugServer_TwistedPairFiles -- (instancetype)initWithSource:(int)source sink:(FBAMDServiceConnection *)sink queue:(dispatch_queue_t)queue +- (instancetype)initWithSocket:(int)socket connection:(FBAMDServiceConnection *)connection logger:(id)logger { self = [super init]; if (!self) { return nil; } - _source = source; - _sink = sink; - _queue = queue; - _sinkWriteQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver_sink_write", DISPATCH_QUEUE_SERIAL); - _sinkReadQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver_sink_read", DISPATCH_QUEUE_SERIAL); + _socket = socket; + _connection = connection; + _logger = logger; + _socketToConnectionQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver.socket_to_connection", DISPATCH_QUEUE_SERIAL); + _connectionToSocketQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver.connection_to_socket", DISPATCH_QUEUE_SERIAL); return self; } -- (FBFuture *> *)start +static size_t const ConnectionReadSizeLimit = 1024; + +- (FBFuture *)startWithError:(NSError **)error { - NSError *error = nil; - id sourceWriter = [FBFileWriter asyncWriterWithFileDescriptor:self.source closeOnEndOfFile:NO error:&error]; - if (!sourceWriter) { - return [FBFuture futureWithError:error]; + if (@available(macOS 10.15, *)) { + id logger = self.logger; + int socket = self.socket; + NSFileHandle *socketReadHandle = [[NSFileHandle alloc] initWithFileDescriptor:socket closeOnDealloc:NO]; + NSFileHandle *socketWriteHandle = [[NSFileHandle alloc] initWithFileDescriptor:socket closeOnDealloc:NO]; + FBAMDServiceConnection *connection = self.connection; + FBMutableFuture *socketReadCompleted = FBMutableFuture.future; + FBMutableFuture *connectionReadCompleted = FBMutableFuture.future; + dispatch_async(self.socketToConnectionQueue, ^{ + while (socketReadCompleted.state == FBFutureStateRunning && connectionReadCompleted.state == FBFutureStateRunning) { + NSError *innerError = nil; + NSData *data = [socketReadHandle availableData]; + if (data.length == 0) { + [logger log:@"Socket read reached end of file"]; + break; + } + if (![connection send:data error:&innerError]) { + [logger logFormat:@"Sending data to remote debugserver failed: %@", innerError]; + break; + } + } + [logger logFormat:@"Exiting socket %d read loop", socket]; + [socketReadCompleted resolveWithResult:NSNull.null]; + }); + dispatch_async(self.connectionToSocketQueue, ^{ + while (socketReadCompleted.state == FBFutureStateRunning && connectionReadCompleted.state == FBFutureStateRunning) { + NSError *innerError = nil; + NSData *data = [connection receiveUpTo:ConnectionReadSizeLimit error:&innerError]; + if (data.length == 0) { + [logger logFormat:@"debugserver read ended: %@", innerError]; + break; + } + if (![socketWriteHandle writeData:data error:&innerError]) { + [logger logFormat:@"Socket write failed: %@", innerError]; + break; + } + } + [logger logFormat:@"Exiting connection %@ read loop", connection]; + [connectionReadCompleted resolveWithResult:NSNull.null]; + }); + return [[FBFuture + futureWithFutures:@[ + socketReadCompleted, + connectionReadCompleted, + ]] + onQueue:self.connectionToSocketQueue notifyOfCompletion:^(id _) { + [logger logFormat:@"Closing socket file descriptor %d", socket]; + close(socket); + }]; } - self.sourceWriter = sourceWriter; - self.sinkWriter = [self.sink writeWithConsumerWritingOnQueue:self.sinkWriteQueue]; - self.sourceReader = [FBFileReader readerWithFileDescriptor:self.source closeOnEndOfFile:NO consumer:self.sinkWriter logger:nil]; - self.sinkReader = [self.sink readFromConnectionWritingToConsumer:self.sourceWriter onQueue:self.sinkReadQueue]; - return [[FBFuture - futureWithFutures:@[ - [self.sourceReader startReading], - [self.sinkReader startReading], - ]] - onQueue:self.queue map:^(id _) { - return [[FBFuture - race:@[ - self.sourceReader.finishedReading, - self.sinkReader.finishedReading, - ]] - mapReplace:NSNull.null]; - }]; + return nil; } @end @@ -131,17 +157,18 @@ - (void)socketServer:(FBSocketServer *)server clientConnected:(struct in6_addr)a return; } [self.logger log:@"Client connected, connecting all file handles"]; - self.twistedPair = [[FBDeviceDebugServer_TwistedPairFiles alloc] initWithSource:fileDescriptor sink:self.serviceConnection queue:self.queue]; - [[[self.twistedPair - start] - onQueue:self.queue fmap:^(FBFuture *finished) { - [self.logger log:@"File handles connected"]; - return finished; - }] - onQueue:self.queue notifyOfCompletion:^(id _) { - [self.logger log:@"Client Disconnected"]; - self.twistedPair = nil; - }]; + FBDeviceDebugServer_TwistedPairFiles *twistedPair = [[FBDeviceDebugServer_TwistedPairFiles alloc] initWithSocket:fileDescriptor connection:self.serviceConnection logger:self.logger]; + NSError *error = nil; + FBFuture *completed = [twistedPair startWithError:&error]; + if (!completed) { + [self.logger logFormat:@"Failed to start connection %@", error]; + return; + } + [completed onQueue:self.queue notifyOfCompletion:^(id _) { + [self.logger log:@"Client Disconnected"]; + self.twistedPair = nil; + }]; + self.twistedPair = twistedPair; } #pragma mark FBiOSTargetOperation