Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 351e6c9538
Fetching contributors…

Cannot retrieve contributors at this time

executable file 621 lines (481 sloc) 20.463 kb
#import "PTUSBHub.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <err.h>
NSString *PTUSBHubErrorDomain = @"PTUSBHubError";
typedef uint32_t USBMuxPacketType;
enum {
USBMuxPacketTypeResult = 1,
USBMuxPacketTypeConnect = 2,
USBMuxPacketTypeListen = 3,
USBMuxPacketTypeDeviceAdd = 4,
USBMuxPacketTypeDeviceRemove = 5,
// ? = 6,
// ? = 7,
USBMuxPacketTypePlistPayload = 8,
};
typedef uint32_t USBMuxPacketProtocol;
enum {
USBMuxPacketProtocolBinary = 0,
USBMuxPacketProtocolPlist = 1,
};
typedef uint32_t USBMuxReplyCode;
enum {
USBMuxReplyCodeOK = 0,
USBMuxReplyCodeBadCommand = 1,
USBMuxReplyCodeBadDevice = 2,
USBMuxReplyCodeConnectionRefused = 3,
// ? = 4,
// ? = 5,
USBMuxReplyCodeBadVersion = 6,
};
typedef struct usbmux_packet {
uint32_t size;
USBMuxPacketProtocol protocol;
USBMuxPacketType type;
uint32_t tag;
char data[0];
} usbmux_packet_t;
static uint32_t usbmux_packet_payload_size(usbmux_packet_t *upacket) {
return upacket->size - sizeof(usbmux_packet_t);
}
static void *usbmux_packet_payload(usbmux_packet_t *upacket) {
return (void*)upacket->data;
}
static void usbmux_packet_set_payload(usbmux_packet_t *upacket, const void *payload, uint32_t payloadLength) {
memcpy(usbmux_packet_payload(upacket), payload, payloadLength);
}
static usbmux_packet_t *usbmux_packet_alloc(CFIndex payloadSize) {
assert(payloadSize <= UINT32_MAX); // protocol limitation
uint32_t payloadLength = (uint32_t)payloadSize;
uint32_t upacketLength = sizeof(usbmux_packet_t) + payloadLength;
assert(sizeof(usbmux_packet_t) == 16);
usbmux_packet_t *upacket = CFAllocatorAllocate(kCFAllocatorDefault, upacketLength, 0);
memset(upacket, 0, sizeof(usbmux_packet_t));
upacket->size = upacketLength;
return upacket;
}
static usbmux_packet_t *usbmux_packet_create(USBMuxPacketProtocol protocol, USBMuxPacketType type, uint32_t tag, const void *payload, NSUInteger payloadSize) {
usbmux_packet_t *upacket = usbmux_packet_alloc(payloadSize);
if (!upacket)
return NULL;
upacket->protocol = protocol;
upacket->type = type;
upacket->tag = tag;
if (payload && payloadSize) {
usbmux_packet_set_payload(upacket, payload, payloadSize);
}
return upacket;
}
static void usbmux_packet_free(usbmux_packet_t *upacket) {
CFAllocatorDeallocate(kCFAllocatorDefault, upacket);
}
NSString *PTUSBDeviceDidAttachNotification = @"PTUSBDeviceDidAttachNotification";
NSString *PTUSBDeviceDidDetachNotification = @"PTUSBDeviceDidDetachNotification";
static NSString *kPlistPacketTypeListen = @"Listen";
static NSString *kPlistPacketTypeConnect = @"Connect";
// Represents a channel of communication between the host process and a remote
// (device) system. In practice, a PTUSBChannel is connected to a usbmuxd
// endpoint which is configured to either listen for device changes (the
// PTUSBHub's channel is usually configured as a device notification listener) or
// configured as a TCP bridge (e.g. channels returned from PTUSBHub's
// connectToDevice:port:callback:). You should not create channels yourself, but
// let PTUSBHub provide you with already configured channels.
@interface PTUSBChannel : NSObject {
dispatch_io_t channel_;
dispatch_queue_t queue_;
uint32_t nextPacketTag_;
NSMutableDictionary *responseQueue_;
BOOL autoReadPackets_;
BOOL isReadingPackets_;
}
// The underlying dispatch I/O channel. This is handy if you want to handle your
// own I/O logic without PTUSBChannel. Remember to dispatch_retain() the channel
// if you plan on using it as it might be released from the PTUSBChannel at any
// point in time.
@property (readonly) dispatch_io_t dispatchChannel;
// The underlying file descriptor.
@property (readonly) dispatch_fd_t fileDescriptor;
// Send data
- (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback;
- (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback;
// Read data
- (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback;
// Close the channel, preventing further reads and writes, but letting currently
// queued reads and writes finish.
- (void)cancel;
// Close the channel, preventing further reads and writes, immediately
// terminating any ongoing reads and writes.
- (void)stop;
@end
@interface PTUSBChannel (Private)
+ (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload;
- (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError *error))onEnd;
- (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback;
- (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error;
- (uint32_t)nextPacketTag;
- (void)sendPacketOfType:(USBMuxPacketType)type overProtocol:(USBMuxPacketProtocol)protocol tag:(uint32_t)tag payload:(NSData*)payload callback:(void(^)(NSError*))callback;
- (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError *error))callback;
- (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError *error, NSDictionary *responsePacket))callback;
- (void)scheduleReadPacketWithCallback:(void(^)(NSError *error, NSDictionary *packet, uint32_t packetTag))callback;
- (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler;
- (void)setNeedsReadingPacket;
@end
@interface PTUSBHub () {
PTUSBChannel *channel_;
}
- (void)handleBroadcastPacket:(NSDictionary*)packet;
@end
@implementation PTUSBHub
+ (PTUSBHub*)sharedHub {
static PTUSBHub *gSharedHub;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gSharedHub = [PTUSBHub new];
[gSharedHub listenOnQueue:dispatch_get_main_queue() onStart:^(NSError *error) {
if (error) {
NSLog(@"PTUSBHub failed to initialize: %@", error);
}
} onEnd:nil];
});
return gSharedHub;
}
- (id)init {
if (!(self = [super init])) return nil;
return self;
}
- (void)listenOnQueue:(dispatch_queue_t)queue onStart:(void(^)(NSError*))onStart onEnd:(void(^)(NSError*))onEnd {
if (channel_) {
if (onStart) onStart(nil);
return;
}
channel_ = [PTUSBChannel new];
NSError *error = nil;
if ([channel_ openOnQueue:queue error:&error onEnd:onEnd]) {
[channel_ listenWithBroadcastHandler:^(NSDictionary *packet) { [self handleBroadcastPacket:packet]; } callback:onStart];
} else if (onStart) {
onStart(error);
}
}
- (void)connectToDevice:(NSNumber*)deviceID port:(int)port onStart:(void(^)(NSError*, dispatch_io_t))onStart onEnd:(void(^)(NSError*))onEnd {
PTUSBChannel *channel = [PTUSBChannel new];
NSError *error = nil;
if (![channel openOnQueue:dispatch_get_current_queue() error:&error onEnd:onEnd]) {
onStart(error, nil);
return;
}
port = ((port<<8) & 0xFF00) | (port>>8); // limit
NSDictionary *packet = [PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeConnect
payload:[NSDictionary dictionaryWithObjectsAndKeys:
deviceID, @"DeviceID",
[NSNumber numberWithInt:port], @"PortNumber",
nil]];
[channel sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) {
NSError *error = error_;
[channel errorFromPlistResponse:responsePacket error:&error];
onStart(error, (error ? nil : channel.dispatchChannel) );
}];
}
- (void)handleBroadcastPacket:(NSDictionary*)packet {
NSString *messageType = [packet objectForKey:@"MessageType"];
if ([@"Attached" isEqualToString:messageType]) {
[[NSNotificationCenter defaultCenter] postNotificationName:PTUSBDeviceDidAttachNotification object:self userInfo:packet];
} else if ([@"Detached" isEqualToString:messageType]) {
[[NSNotificationCenter defaultCenter] postNotificationName:PTUSBDeviceDidDetachNotification object:self userInfo:packet];
} else {
NSLog(@"Warning: Unhandled broadcast message: %@", packet);
}
}
@end
#pragma mark -
@implementation PTUSBChannel
+ (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload {
NSDictionary *packet = nil;
static NSString *bundleName = nil;
static NSString *bundleVersion = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary;
if (infoDict) {
bundleName = [infoDict objectForKey:@"CFBundleName"];
bundleVersion = [[infoDict objectForKey:@"CFBundleVersion"] description];
}
});
if (bundleName) {
packet = [NSDictionary dictionaryWithObjectsAndKeys:
messageType, @"MessageType",
bundleName, @"ProgName",
bundleVersion, @"ClientVersionString",
nil];
} else {
packet = [NSDictionary dictionaryWithObjectsAndKeys:@"Listen", @"MessageType", nil];
}
if (payload) {
NSMutableDictionary *mpacket = [NSMutableDictionary dictionaryWithDictionary:payload];
[mpacket addEntriesFromDictionary:packet];
packet = mpacket;
}
return packet;
}
- (id)init {
if (!(self = [super init])) return nil;
return self;
}
- (void)dealloc {
//NSLog(@"dealloc %@", self);
if (channel_) {
dispatch_release(channel_);
channel_ = nil;
}
}
- (BOOL)valid {
return !!channel_;
}
- (dispatch_io_t)dispatchChannel {
return channel_;
}
- (dispatch_fd_t)fileDescriptor {
return dispatch_io_get_descriptor(channel_);
}
- (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError*))onEnd {
assert(channel_ == nil);
queue_ = queue ? queue : dispatch_get_current_queue();
// 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;
}
channel_ = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue_, ^(int error) {
close(fd);
if (onEnd) {
onEnd(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]);
}
});
return YES;
}
- (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback {
autoReadPackets_ = YES;
[self scheduleReadPacketWithBroadcastHandler:broadcastHandler];
NSDictionary *packet = [PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeListen payload:nil];
[self sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) {
if (!callback)
return;
NSError *error = error_;
[self errorFromPlistResponse:responsePacket error:&error];
callback(error);
}];
}
- (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error {
if (!*error) {
NSNumber *n = [packet objectForKey:@"Number"];
if (!n) {
*error = [NSError errorWithDomain:PTUSBHubErrorDomain code:(n ? n.integerValue : 0) userInfo:nil];
return NO;
}
USBMuxReplyCode replyCode = n.integerValue;
if (replyCode != 0) {
NSString *errmessage = @"Unspecified error";
switch (replyCode) {
case USBMuxReplyCodeBadCommand: errmessage = @"illegal command"; break;
case USBMuxReplyCodeBadDevice: errmessage = @"unknown device"; break;
case USBMuxReplyCodeConnectionRefused: errmessage = @"connection refused"; break;
case USBMuxReplyCodeBadVersion: errmessage = @"invalid version"; break;
default: break;
}
*error = [NSError errorWithDomain:PTUSBHubErrorDomain code:replyCode userInfo:[NSDictionary dictionaryWithObject:errmessage forKey:NSLocalizedDescriptionKey]];
return NO;
}
}
return YES;
}
- (uint32_t)nextPacketTag {
return ++nextPacketTag_;
}
- (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError*, NSDictionary*))callback {
uint32_t tag = [self nextPacketTag];
[self sendPacket:packet tag:tag callback:^(NSError *error) {
if (error) {
callback(error, nil);
return;
}
// TODO: timeout un-triggered callbacks in responseQueue_
if (!responseQueue_) responseQueue_ = [NSMutableDictionary new];
[responseQueue_ setObject:callback forKey:[NSNumber numberWithUnsignedInt:tag]];
}];
// We are awaiting a response
[self setNeedsReadingPacket];
}
- (void)setNeedsReadingPacket {
if (!isReadingPackets_) {
[self scheduleReadPacketWithBroadcastHandler:nil];
}
}
- (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler {
assert(isReadingPackets_ == NO);
[self scheduleReadPacketWithCallback:^(NSError *error, NSDictionary *packet, uint32_t packetTag) {
// Interpret the package we just received
if (packetTag == 0) {
// Broadcast message
//NSLog(@"Received broadcast: %@", packet);
if (broadcastHandler) broadcastHandler(packet);
} else if (responseQueue_) {
// Reply
NSNumber *key = [NSNumber numberWithUnsignedInt:packetTag];
void(^requestCallback)(NSError*,NSDictionary*) = [responseQueue_ objectForKey:key];
if (requestCallback) {
[responseQueue_ removeObjectForKey:key];
requestCallback(error, packet);
} else {
NSLog(@"Warning: Ignoring reply packet for which there is no registered callback. Packet => %@", packet);
}
}
// Schedule reading another incoming package
if (autoReadPackets_) {
[self scheduleReadPacketWithBroadcastHandler:broadcastHandler];
}
}];
}
- (void)scheduleReadPacketWithCallback:(void(^)(NSError*, NSDictionary*, uint32_t))callback {
static usbmux_packet_t ref_upacket;
isReadingPackets_ = YES;
dispatch_io_read(channel_, 0, sizeof(ref_upacket.size), queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read 0,4: done=%d data=%p error=%d", done, data, error);
if (!done)
return;
if (error) {
isReadingPackets_ = NO;
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0);
return;
}
// Read size of incoming usbmux_packet_t
uint32_t upacket_len = 0;
char *buffer = NULL;
size_t buffer_size = 0;
dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
assert(buffer_size == sizeof(ref_upacket.size));
memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size);
dispatch_release(map_data);
// Allocate a new usbmux_packet_t for the expected size
uint32_t payloadLength = upacket_len - sizeof(usbmux_packet_t);
usbmux_packet_t *upacket = usbmux_packet_alloc(payloadLength);
// Read rest of the incoming usbmux_packet_t
off_t offset = sizeof(ref_upacket.size);
dispatch_io_read(channel_, offset, upacket->size - offset, queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read X,Y: done=%d data=%p error=%d", done, data, error);
if (!done)
return;
isReadingPackets_ = NO;
if (error) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0);
usbmux_packet_free(upacket);
return;
}
// Copy read bytes onto our usbmux_packet_t
char *buffer = NULL;
size_t buffer_size = 0;
dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
assert(buffer_size == upacket->size - offset);
memcpy(((void *)(upacket))+offset, (const void *)buffer, buffer_size);
dispatch_release(map_data);
// We only support plist protocol
if (upacket->protocol != USBMuxPacketProtocolPlist) {
callback([[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package protocol" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag);
usbmux_packet_free(upacket);
return;
}
// Only one type of packet in the plist protocol
if (upacket->type != USBMuxPacketTypePlistPayload) {
callback([[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package type" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag);
usbmux_packet_free(upacket);
return;
}
// Try to decode any payload as plist
NSError *err = nil;
NSDictionary *dict = nil;
if (usbmux_packet_payload_size(upacket)) {
dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err];
}
// Invoke callback
callback(err, dict, upacket->tag);
usbmux_packet_free(upacket);
});
});
}
- (void)sendPacketOfType:(USBMuxPacketType)type overProtocol:(USBMuxPacketProtocol)protocol tag:(uint32_t)tag payload:(NSData*)payload callback:(void(^)(NSError*))callback {
usbmux_packet_t *upacket = usbmux_packet_create(protocol, type, tag, payload ? payload.bytes : nil, payload ? payload.length : 0);
dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, queue_, ^{
// Free packet when data is freed
usbmux_packet_free(upacket);
});
//NSData *data1 = [NSData dataWithBytesNoCopy:(void*)upacket length:upacket->size freeWhenDone:NO];
//[data1 writeToFile:[NSString stringWithFormat:@"/Users/rsms/c-packet-%u.data", tag] atomically:NO];
[self sendDispatchData:data callback:callback];
}
- (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError*))callback {
NSError *error = nil;
// NSPropertyListBinaryFormat_v1_0
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packet format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
if (!plistData) {
callback(error);
} else {
[self sendPacketOfType:USBMuxPacketTypePlistPayload overProtocol:USBMuxPacketProtocolPlist tag:tag payload:plistData callback:callback];
}
}
- (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback {
off_t offset = 0;
dispatch_io_write(channel_, offset, data, queue_, ^(bool done, dispatch_data_t data, int _errno) {
//NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, error);
if (!done)
return;
if (callback) {
NSError *err = nil;
if (_errno) err = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil];
callback(err);
}
});
dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write
}
- (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback {
dispatch_data_t ddata = dispatch_data_create((const void*)data.bytes, data.length, queue_, ^{
// trick to have the block capture and retain the data
[data length];
});
[self sendDispatchData:ddata callback:callback];
}
- (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback {
dispatch_io_read(channel_, offset, length, queue_, ^(bool done, dispatch_data_t data, int _errno) {
if (!done)
return;
NSError *error = nil;
if (_errno != 0) {
error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil];
}
callback(error, data);
});
}
- (void)cancel {
if (channel_) {
dispatch_io_close(channel_, 0);
}
}
- (void)stop {
if (channel_) {
dispatch_io_close(channel_, DISPATCH_IO_STOP);
}
}
@end
Jump to Line
Something went wrong with that request. Please try again.