From b60c7ec5845bfbf9ed9dacdc09c250ddb203b640 Mon Sep 17 00:00:00 2001 From: Jacob Jennings Date: Wed, 9 Aug 2017 14:45:56 -0700 Subject: [PATCH] Fixed -[PTUSBHUB scheduleReadPacketWithCallback:] crash --- peertalk/PTUSBHub.m | 810 ++++++++++++++++++++++---------------------- 1 file changed, 410 insertions(+), 400 deletions(-) diff --git a/peertalk/PTUSBHub.m b/peertalk/PTUSBHub.m index afb3225..d6902ab 100644 --- a/peertalk/PTUSBHub.m +++ b/peertalk/PTUSBHub.m @@ -11,52 +11,52 @@ typedef uint32_t USBMuxPacketType; enum { - USBMuxPacketTypeResult = 1, - USBMuxPacketTypeConnect = 2, - USBMuxPacketTypeListen = 3, - USBMuxPacketTypeDeviceAdd = 4, - USBMuxPacketTypeDeviceRemove = 5, - // ? = 6, - // ? = 7, - USBMuxPacketTypePlistPayload = 8, + USBMuxPacketTypeResult = 1, + USBMuxPacketTypeConnect = 2, + USBMuxPacketTypeListen = 3, + USBMuxPacketTypeDeviceAdd = 4, + USBMuxPacketTypeDeviceRemove = 5, + // ? = 6, + // ? = 7, + USBMuxPacketTypePlistPayload = 8, }; typedef uint32_t USBMuxPacketProtocol; enum { - USBMuxPacketProtocolBinary = 0, - USBMuxPacketProtocolPlist = 1, + USBMuxPacketProtocolBinary = 0, + USBMuxPacketProtocolPlist = 1, }; typedef uint32_t USBMuxReplyCode; enum { - USBMuxReplyCodeOK = 0, - USBMuxReplyCodeBadCommand = 1, - USBMuxReplyCodeBadDevice = 2, - USBMuxReplyCodeConnectionRefused = 3, - // ? = 4, - // ? = 5, - USBMuxReplyCodeBadVersion = 6, + 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]; + uint32_t size; + USBMuxPacketProtocol protocol; + USBMuxPacketType type; + uint32_t tag; + char data[0]; } __attribute__((__packed__)) usbmux_packet_t; static const uint32_t kUsbmuxPacketMaxPayloadSize = UINT32_MAX - (uint32_t)sizeof(usbmux_packet_t); static uint32_t usbmux_packet_payload_size(usbmux_packet_t *upacket) { - return upacket->size - sizeof(usbmux_packet_t); + return upacket->size - sizeof(usbmux_packet_t); } static void *usbmux_packet_payload(usbmux_packet_t *upacket) { - return (void*)upacket->data; + return (void*)upacket->data; } @@ -64,17 +64,17 @@ static void usbmux_packet_set_payload(usbmux_packet_t *upacket, const void *payload, uint32_t payloadLength) { - memcpy(usbmux_packet_payload(upacket), payload, payloadLength); + memcpy(usbmux_packet_payload(upacket), payload, payloadLength); } static usbmux_packet_t *usbmux_packet_alloc(uint32_t payloadSize) { - assert(payloadSize <= kUsbmuxPacketMaxPayloadSize); - uint32_t upacketSize = sizeof(usbmux_packet_t) + payloadSize; - usbmux_packet_t *upacket = CFAllocatorAllocate(kCFAllocatorDefault, upacketSize, 0); - memset(upacket, 0, sizeof(usbmux_packet_t)); - upacket->size = upacketSize; - return upacket; + assert(payloadSize <= kUsbmuxPacketMaxPayloadSize); + uint32_t upacketSize = sizeof(usbmux_packet_t) + payloadSize; + usbmux_packet_t *upacket = CFAllocatorAllocate(kCFAllocatorDefault, upacketSize, 0); + memset(upacket, 0, sizeof(usbmux_packet_t)); + upacket->size = upacketSize; + return upacket; } @@ -84,25 +84,25 @@ static void usbmux_packet_set_payload(usbmux_packet_t *upacket, const void *payload, uint32_t 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, (uint32_t)payloadSize); - } - - return upacket; + 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, (uint32_t)payloadSize); + } + + return upacket; } static void usbmux_packet_free(usbmux_packet_t *upacket) { - CFAllocatorDeallocate(kCFAllocatorDefault, upacket); + CFAllocatorDeallocate(kCFAllocatorDefault, upacket); } @@ -121,12 +121,12 @@ static void usbmux_packet_free(usbmux_packet_t *upacket) { // 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_; + 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 @@ -173,7 +173,7 @@ - (void)setNeedsReadingPacket; @interface PTUSBHub () { - PTUSBChannel *channel_; + PTUSBChannel *channel_; } - (void)handleBroadcastPacket:(NSDictionary*)packet; @end @@ -183,77 +183,77 @@ @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; + 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; + 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); - } + 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_main_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) ); - }]; + PTUSBChannel *channel = [PTUSBChannel new]; + NSError *error = nil; + + if (![channel openOnQueue:dispatch_get_main_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); - } + 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); + } } @@ -264,307 +264,316 @@ - (void)handleBroadcastPacket:(NSDictionary*)packet { @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]; + 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:messageType, @"MessageType", nil]; } - }); - - if (bundleName) { - packet = [NSDictionary dictionaryWithObjectsAndKeys: - messageType, @"MessageType", - bundleName, @"ProgName", - bundleVersion, @"ClientVersionString", - nil]; - } else { - packet = [NSDictionary dictionaryWithObjectsAndKeys:messageType, @"MessageType", nil]; - } - - if (payload) { - NSMutableDictionary *mpacket = [NSMutableDictionary dictionaryWithDictionary:payload]; - [mpacket addEntriesFromDictionary:packet]; - packet = mpacket; - } - - return packet; + + if (payload) { + NSMutableDictionary *mpacket = [NSMutableDictionary dictionaryWithDictionary:payload]; + [mpacket addEntriesFromDictionary:packet]; + packet = mpacket; + } + + return packet; } - (id)init { - if (!(self = [super init])) return nil; - - return self; + if (!(self = [super init])) return nil; + + return self; } - (void)dealloc { - //NSLog(@"dealloc %@", self); - if (channel_) { + //NSLog(@"dealloc %@", self); + if (channel_) { #if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(channel_); + dispatch_release(channel_); #endif - channel_ = nil; - } + channel_ = nil; + } } - (BOOL)valid { - return !!channel_; + return !!channel_; } - (dispatch_io_t)dispatchChannel { - return channel_; + return channel_; } - (dispatch_fd_t)fileDescriptor { - return dispatch_io_get_descriptor(channel_); + return dispatch_io_get_descriptor(channel_); } - (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError*))onEnd { - assert(queue != nil); - assert(channel_ == nil); - queue_ = 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]); + assert(queue != nil); + assert(channel_ == nil); + queue_ = 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; } - }); - - return YES; + + // 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; + autoReadPackets_ = YES; + [self scheduleReadPacketWithBroadcastHandler:broadcastHandler]; - NSError *error = error_; - [self errorFromPlistResponse:responsePacket error:&error]; + NSDictionary *packet = [PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeListen payload:nil]; - callback(error); - }]; + [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; + if (!*error) { + NSNumber *n = [packet objectForKey:@"Number"]; + + if (!n) { + *error = [NSError errorWithDomain:PTUSBHubErrorDomain code:(n ? n.integerValue : 0) userInfo:nil]; + return NO; + } + + USBMuxReplyCode replyCode = (USBMuxReplyCode)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; + } } - - USBMuxReplyCode replyCode = (USBMuxReplyCode)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; + return YES; } - (uint32_t)nextPacketTag { - return ++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]; + 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]; - } + 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); - } - } + assert(isReadingPackets_ == NO); - // Schedule reading another incoming package - if (autoReadPackets_) { - [self scheduleReadPacketWithBroadcastHandler:broadcastHandler]; - } - }]; + [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; - } + static usbmux_packet_t ref_upacket; + isReadingPackets_ = YES; - // Read size of incoming usbmux_packet_t - uint32_t upacket_len = 0; - char *buffer = NULL; - size_t buffer_size = 0; - PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size); // objc_precise_lifetime guarantees 'map_data' isn't released before memcpy has a chance to do its thing - assert(buffer_size == sizeof(ref_upacket.size)); - memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size); + 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; + PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size); // objc_precise_lifetime guarantees 'map_data' isn't released before memcpy has a chance to do its thing + memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size); #if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(map_data); + dispatch_release(map_data); #endif - - // Allocate a new usbmux_packet_t for the expected size - uint32_t payloadLength = upacket_len - (uint32_t)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; - } - - if (upacket_len > kUsbmuxPacketMaxPayloadSize) { - callback( - [[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:1 userInfo:@{ - NSLocalizedDescriptionKey:@"Received a packet that is too large"}], - nil, - 0 - ); - usbmux_packet_free(upacket); - return; - } - - // Copy read bytes onto our usbmux_packet_t - char *buffer = NULL; - size_t buffer_size = 0; - PT_PRECISE_LIFETIME_UNUSED 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); + /* + * This change addresses the crash that's been plaguing PT for quite some time now. The infamous issue supposedly closed here: https://github.com/rsms/peertalk/issues/34 + * I was able to reproduce this fairly regularly after allowing an iPhone not running PeerTalk to connect to a macOS client listening for devices and then disconnecting it repeatedly. + * The main issue was the assertion that run-time value of buffer_size necessarily equals the compile-time constant UINT32_MAX (which was, explicitly stated, sizeof(ref_upacket.size)). + * When buffer_size was zero, this assertion failed and crashed the application, yet there was no real *problem* with buffer_size being zero. It makes more sense to recover gracefully in this case. + */ + if (buffer_size == 0 && done) { // If buffer_size is zero, no data was sent in the `data` param of this dispatch_io block; if done is true, maybe we just return? + if (callback) callback(nil, nil, 0); + return; + } + + // Allocate a new usbmux_packet_t for the expected size + uint32_t payloadLength = upacket_len - (uint32_t)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; + } + + if (upacket_len > kUsbmuxPacketMaxPayloadSize) { + callback( + [[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:1 userInfo:@{ + NSLocalizedDescriptionKey:@"Received a packet that is too large"}], + nil, + 0 + ); + usbmux_packet_free(upacket); + return; + } + + // Copy read bytes onto our usbmux_packet_t + char *buffer = NULL; + size_t buffer_size = 0; + PT_PRECISE_LIFETIME_UNUSED 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); #if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(map_data); + dispatch_release(map_data); #endif - - // 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); + + // 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); + }); }); - }); } @@ -574,50 +583,50 @@ - (void)sendPacketOfType:(USBMuxPacketType)type payload:(NSData*)payload callback:(void(^)(NSError*))callback { - assert(payload.length <= kUsbmuxPacketMaxPayloadSize); - usbmux_packet_t *upacket = usbmux_packet_create( - protocol, - type, - tag, - payload ? payload.bytes : nil, - (uint32_t)(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]; + assert(payload.length <= kUsbmuxPacketMaxPayloadSize); + usbmux_packet_t *upacket = usbmux_packet_create( + protocol, + type, + tag, + payload ? payload.bytes : nil, + (uint32_t)(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]; - } + 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); - } - }); + 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); + } + }); #if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write + dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write #endif } @@ -625,41 +634,42 @@ - (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callb #pragma clang diagnostic ignored "-Wunused-getter-return-value" - (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]; + 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]; } #pragma clang diagnostic pop - (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); - }); + 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); - } + if (channel_) { + dispatch_io_close(channel_, 0); + } } - (void)stop { - if (channel_) { - dispatch_io_close(channel_, DISPATCH_IO_STOP); - } + if (channel_) { + dispatch_io_close(channel_, DISPATCH_IO_STOP); + } } @end +