blob: 1d9d962a890413450a3c17a51912c6a2bf35e6f2 [file] [log] [blame]
//
// Copyright 2019 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
//
// http://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 "DeviceForwarder/Sources/EDODeviceForwardersManager.h"
#import "Channel/Sources/EDOChannelErrors.h"
#import "Channel/Sources/EDOChannelForwarder.h"
#import "Channel/Sources/EDOHostPort.h"
#import "Channel/Sources/EDOSocket.h"
#import "Channel/Sources/EDOSocketChannel.h"
#import "Device/Sources/EDODeviceConnector.h"
/**
* The number of tries the manager attempts to connect to the multiplexer at start.
*
* @note The manager can start trying before the device starts the multiplexer, so we have a fair
* number of tries in case.
*/
static const int kEDODeviceConnectMaxRetries = 15;
static const int kEDODeviceRetryIntervalInSeconds = 1;
@implementation EDODeviceForwardersManager {
/** The set of forwarders that connect to the multiplexer or forward the channel. */
NSMutableSet<EDOChannelForwarder *> *_forwarders;
}
- (instancetype)initWithDeviceUUID:(NSString *)deviceUUID
port:(UInt16)port
numOfForwarders:(NSUInteger)numOfForwarders {
NSParameterAssert(deviceUUID.length > 0);
NSParameterAssert(port > 0);
NSParameterAssert(numOfForwarders > 0);
self = [super init];
if (self) {
_deviceUUID = deviceUUID;
_port = port;
_numOfForwarders = numOfForwarders;
_forwarders = [[NSMutableSet alloc] init];
}
return self;
}
- (void)startWithCompletionBlock:(void (^)(EDODeviceForwardersManager *))block {
@synchronized(_forwarders) {
[_forwarders removeAllObjects];
}
_deviceIdentifier = nil;
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
__weak EDODeviceForwardersManager *weakSelf = self;
__block int retryCount = kEDODeviceConnectMaxRetries;
__block __weak void (^weakRetryBlock)(void);
void (^retryBlock)(void) = ^{
EDODeviceForwardersManager *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
NSString *deviceIdentifier = [strongSelf startForwarding];
if (deviceIdentifier) {
[strongSelf willChangeValueForKey:@"deviceIdentifier"];
strongSelf->_deviceIdentifier = deviceIdentifier;
[strongSelf didChangeValueForKey:@"deviceIdentifier"];
// Start one additional forwarder to handle concurrent requests.
[strongSelf startForwarding];
block(strongSelf);
} else {
retryCount--;
if (retryCount > 0) {
dispatch_time_t when =
dispatch_time(DISPATCH_TIME_NOW, kEDODeviceRetryIntervalInSeconds * NSEC_PER_SEC);
dispatch_after(when, queue, weakRetryBlock);
} else {
NSLog(@"Fail to connect to the multiplexer after %d retries.", kEDODeviceConnectMaxRetries);
block(strongSelf);
}
}
};
retryBlock = [retryBlock copy];
weakRetryBlock = retryBlock;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), queue, retryBlock);
}
/**
* Starts a new forwarder to connect to the multiplexer and ready for forwarding.
*
* @note It only starts a new forwarder if it doesn't exceed the limit.
* @return The device identifier from the device's multiplexer.
*/
- (NSString *)startForwarding {
EDOChannelForwarder *forwarder;
@synchronized(_forwarders) {
if (_numOfForwarders <= _forwarders.count) {
return nil;
}
forwarder = [[EDOChannelForwarder alloc] initWithConnectBlock:self.multiplexerConnectBlock
hostConnectBlock:self.hostConnectBlock];
[_forwarders addObject:forwarder];
}
__weak EDODeviceForwardersManager *weakSelf = self;
EDOForwarderErrorHandler errorHandler = ^(EDOForwarderError errorCode) {
EDODeviceForwardersManager *strongSelf = weakSelf;
if (strongSelf) {
@synchronized(strongSelf->_forwarders) {
[strongSelf->_forwarders removeObject:forwarder];
}
}
// We re-establish the forwarder if the port fails to connect so the multiplexer can set up a
// new connection.
if (errorCode == EDOForwarderErrorPortConnection) {
[strongSelf startForwarding];
} else {
// For any other errors, we don't retry and just kill it.
}
};
NSData *deviceData = [forwarder startWithErrorHandler:errorHandler];
if (!deviceData) {
@synchronized(_forwarders) {
[_forwarders removeObject:forwarder];
}
return nil;
} else {
return [[NSString alloc] initWithData:deviceData encoding:NSUTF8StringEncoding];
}
}
/** Gets a block that sets up the connection to the multiplexer. */
- (EDOMultiplexerConnectBlock)multiplexerConnectBlock {
NSString *deviceUUID = _deviceUUID;
UInt16 port = _port;
EDOMultiplexerConnectBlock block = ^{
NSError *error;
id<EDOChannel> channel;
dispatch_io_t deviceChannel = [EDODeviceConnector.sharedConnector connectToDevice:deviceUUID
onPort:port
error:&error];
if (deviceChannel && !error) {
channel = [[EDOSocketChannel alloc] initWithDispatchIO:deviceChannel];
}
return channel;
};
return block;
}
/** Gets a block that handles the incoming EDOHostPort to forward. */
- (EDOHostChannelConnectBlock)hostConnectBlock {
__weak EDODeviceForwardersManager *weakSelf = self;
return ^(EDOHostPort *port) {
id<EDOChannel> channel;
NSError *connectionError;
if (port.connectsDevice) {
dispatch_io_t deviceChannel =
[EDODeviceConnector.sharedConnector connectToDevice:port.deviceSerialNumber
onPort:port.port
error:&connectionError];
if (!connectionError) {
channel = [[EDOSocketChannel alloc] initWithDispatchIO:deviceChannel];
}
} else {
EDOSocket *socket = [EDOSocket socketWithTCPPort:port.port queue:nil error:&connectionError];
if (socket) {
channel = [EDOSocketChannel channelWithSocket:socket];
}
}
if (connectionError) {
// TODO(haowoo): we only log the error for now as we don't return the error back to the
// multiplexer just yet.
NSLog(@"Error when connecting to %@, %@", port, connectionError);
} else {
// This forwarder is being used now, we need to create a new one for the new request.
[weakSelf startForwarding];
}
return channel;
};
}
@end