| // |
| // 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 "Channel/Sources/EDOChannelMultiplexer.h" |
| |
| #import "Channel/Sources/EDOBlockingQueue.h" |
| #import "Channel/Sources/EDOChannel.h" |
| #import "Channel/Sources/EDOHostPort.h" |
| #import "Channel/Sources/EDOSocket.h" |
| #import "Channel/Sources/EDOSocketChannel.h" |
| #import "Channel/Sources/EDOSocketPort.h" |
| |
| static const int64_t kEDOConnectTimeout = 3 * NSEC_PER_SEC; |
| static const int64_t kEDOGetChannelTimeout = 5 * NSEC_PER_SEC; |
| |
| @implementation EDOChannelMultiplexer { |
| /** The listen socket for the multiplexer to handle incoming forwarders. */ |
| EDOSocket *_listenSocket; |
| /** The connected channels that are ready to forward. */ |
| EDOBlockingQueue<id<EDOChannel>> *_connectedChannels; |
| } |
| |
| - (instancetype)init { |
| self = [super init]; |
| if (self) { |
| _connectedChannels = [[EDOBlockingQueue alloc] init]; |
| } |
| return self; |
| } |
| |
| - (NSUInteger)numberOfChannels { |
| return _connectedChannels.count; |
| } |
| |
| - (BOOL)start:(EDOHostPort *)port error:(NSError **)error { |
| __weak EDOChannelMultiplexer *weakSelf = self; |
| NSData *deviceIdentifier = [EDOHostPort.deviceIdentifier dataUsingEncoding:NSUTF8StringEncoding]; |
| _listenSocket = [EDOSocket |
| listenWithTCPPort:port.port |
| queue:nil |
| connectedBlock:^(EDOSocket *socket, NSError *serviceError) { |
| EDOSocketChannel *channel = [EDOSocketChannel channelWithSocket:socket]; |
| |
| // Sends the deviceIdentifier to ACK the connection. |
| [channel sendData:deviceIdentifier withCompletionHandler:nil]; |
| |
| dispatch_semaphore_t connectWait = dispatch_semaphore_create(0); |
| dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, kEDOConnectTimeout); |
| [channel receiveDataWithHandler:^(id<EDOChannel> _, NSData *data, NSError *error) { |
| EDOChannelMultiplexer *strongSelf = weakSelf; |
| // An device ACK message from the forwarder to finish the handshaking, from now on, |
| // the channel can be used for forwarding. |
| if (strongSelf && data) { |
| // TODO(haowoo): The data should contain the forwarder info, but we ignore for now. |
| [strongSelf->_connectedChannels appendObject:channel]; |
| } else { |
| [channel invalidate]; |
| } |
| dispatch_semaphore_signal(connectWait); |
| }]; |
| if (dispatch_semaphore_wait(connectWait, timeout) != 0) { |
| [channel invalidate]; |
| } |
| }]; |
| UInt16 listenPort = _listenSocket.socketPort.port; |
| _port = [[EDOHostPort alloc] initWithPort:listenPort name:nil deviceSerialNumber:nil]; |
| return _listenSocket != nil; |
| } |
| |
| - (void)stop { |
| [_listenSocket invalidate]; |
| _connectedChannels = [[EDOBlockingQueue alloc] init]; |
| _port = nil; |
| } |
| |
| - (id<EDOChannel>)channelWithPort:(EDOHostPort *)hostPort |
| timeout:(NSTimeInterval)timeout |
| error:(NSError **)error { |
| dispatch_time_t channelTimeout = dispatch_time(DISPATCH_TIME_NOW, kEDOGetChannelTimeout); |
| EDOSocketChannel *channel = [_connectedChannels lastObjectWithTimeout:channelTimeout]; |
| if (!channel) { |
| return nil; |
| } |
| |
| __block BOOL connected = NO; |
| dispatch_semaphore_t connectWait = dispatch_semaphore_create(0); |
| NSData *deviceIdentifier = [EDOHostPort.deviceIdentifier dataUsingEncoding:NSUTF8StringEncoding]; |
| // Sends the host post and waits for the confirmation |
| [channel sendData:hostPort.data withCompletionHandler:nil]; |
| [channel receiveDataWithHandler:^(id<EDOChannel> _, NSData *data, NSError *error) { |
| // The forwarder should respond with the deviceIdentifier that is sent when setting up the |
| // connection. |
| if ([deviceIdentifier isEqualToData:data]) { |
| connected = YES; |
| } |
| dispatch_semaphore_signal(connectWait); |
| }]; |
| dispatch_time_t connectTimeout = dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC); |
| dispatch_semaphore_wait(connectWait, connectTimeout); |
| if (!connected) { |
| [channel invalidate]; |
| return nil; |
| } else { |
| return channel.valid ? channel : nil; |
| } |
| } |
| |
| @end |