blob: 2185a99b6b37cca79e2e66a341058a13c1a80685 [file] [log] [blame] [edit]
//
// Copyright 2018 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 "Device/Sources/EDODeviceChannel.h"
#include <sys/socket.h>
#include <sys/un.h>
#import "Device/Sources/EDOUSBMuxUtil.h"
@implementation EDODeviceChannel {
dispatch_io_t _dispatchChannel;
dispatch_queue_t _queue;
}
+ (instancetype)channelWithError:(NSError **)error {
EDODeviceChannel *channel = [[EDODeviceChannel alloc] initInternal];
if ([channel openWithError:error]) {
return channel;
}
return nil;
}
- (instancetype)initInternal {
self = [super init];
if (self) {
_queue = dispatch_queue_create("com.google.edo.deviceChannel", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (dispatch_io_t)releaseAsDispatchIO {
@synchronized(self) {
dispatch_io_t channel = _dispatchChannel;
_dispatchChannel = nil;
return channel;
}
}
- (void)receivePacketWithHandler:(EDODevicePacketReceivedHandler)handler {
// Read the first `size` bytes off the channel_
dispatch_io_read(
_dispatchChannel, 0, [EDOUSBMuxUtil sizeOfPayloadSize], _queue,
^(bool done, dispatch_data_t sizeData, int error) {
if (!done) {
return;
}
if (error) {
handler(nil, [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]);
return;
}
// Read size of incoming usbmux_packet_t
uint32_t packetLength = 0;
char *buffer = NULL;
size_t bufferSize = 0;
NS_VALID_UNTIL_END_OF_SCOPE dispatch_data_t mapData =
dispatch_data_create_map(sizeData, (const void **)&buffer, &bufferSize);
// NS_VALID_UNTIL_END_OF_SCOPE guarantees 'mapData' isn't released before
// memcpy has a chance to do its thing
if (bufferSize == 0) {
// The packet could be dropped by usbmuxd when there are more than ~50 packets in transit.
NSError *error = [NSError
errorWithDomain:NSPOSIXErrorDomain
code:ECONNREFUSED
userInfo:@{NSLocalizedDescriptionKey : @"The packet was dropped by usbmuxd."}];
handler(nil, error);
return;
}
NSAssert(bufferSize == [EDOUSBMuxUtil sizeOfPayloadSize],
@"Buffer size is different from the size field.");
NSAssert(sizeof(packetLength) == [EDOUSBMuxUtil sizeOfPayloadSize],
@"PacketLength has different size from the size field.");
memcpy((void *)&(packetLength), (const void *)buffer, bufferSize);
// Read rest of the incoming usbmux packet
size_t offset = [EDOUSBMuxUtil sizeOfPayloadSize];
dispatch_io_read(
self->_dispatchChannel, 0, packetLength - offset, self->_queue,
^(bool done, dispatch_data_t data, int error) {
if (!done) {
return;
}
if (error) {
handler(nil, [[NSError alloc] initWithDomain:NSPOSIXErrorDomain
code:error
userInfo:nil]);
return;
}
if (packetLength > kEDOPacketMaxPayloadSize) {
handler(nil, [[NSError alloc] initWithDomain:EDODeviceErrorDomain
code:1
userInfo:@{
NSLocalizedDescriptionKey :
@"Received a packet that is too large"
}]);
return;
}
dispatch_data_t totalData = dispatch_data_create_concat(sizeData, data);
NSError *packetError;
NSDictionary *payloadDict =
[EDOUSBMuxUtil payloadDictionaryFromPacketData:totalData error:&packetError];
// Invoke completion handler
handler(payloadDict, packetError);
});
});
}
- (void)sendPacket:(NSDictionary *)packet completion:(EDODevicePacketSentHandler)completion {
NSError *error = nil;
dispatch_data_t data = [EDOUSBMuxUtil createPacketDataWithPayload:packet error:&error];
if (!data) {
if (completion) {
completion(error);
}
} else {
dispatch_io_write(
_dispatchChannel, 0, data, _queue, ^(bool done, dispatch_data_t data, int _errno) {
if (!done) {
return;
}
if (completion) {
NSError *err = nil;
if (_errno) {
err = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil];
}
completion(err);
}
});
}
}
#pragma mark - Private
- (BOOL)openWithError:(NSError **)error {
if (_dispatchChannel) {
return YES;
}
// Create socket
dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
if (error) {
*error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
}
return NO;
}
// prevent SIGPIPE
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
// Connect socket
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/var/run/usbmuxd");
socklen_t socklen = sizeof(addr);
if (connect(fd, (struct sockaddr *)&addr, socklen) == -1) {
if (error) {
*error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
}
return NO;
}
_dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, fd, _queue, ^(int error) {
if (error == 0) {
close(fd);
}
});
return _dispatchChannel != NULL;
}
@end