diff --git a/Peertalk Example/PTAppDelegate.m b/Peertalk Example/PTAppDelegate.m index 03473b1..ee78efd 100644 --- a/Peertalk Example/PTAppDelegate.m +++ b/Peertalk Example/PTAppDelegate.m @@ -49,17 +49,13 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Configure the output NSTextView we use for UI feedback outputTextView_.textContainerInset = NSMakeSize(15.0, 10.0); - consoleTextAttributes_ = [NSDictionary dictionaryWithObjectsAndKeys: - [NSFont fontWithName:@"helvetica" size:16.0], NSFontAttributeName, - [NSColor lightGrayColor], NSForegroundColorAttributeName, - nil]; - consoleStatusTextAttributes_ = [NSDictionary dictionaryWithObjectsAndKeys: - [NSFont fontWithName:@"menlo" size:11.0], NSFontAttributeName, - [NSColor darkGrayColor], NSForegroundColorAttributeName, - nil]; + consoleTextAttributes_ = @{NSFontAttributeName: [NSFont fontWithName:@"helvetica" size:16.0], + NSForegroundColorAttributeName: [NSColor lightGrayColor]}; + consoleStatusTextAttributes_ = @{NSFontAttributeName: [NSFont fontWithName:@"menlo" size:11.0], + NSForegroundColorAttributeName: [NSColor darkGrayColor]}; // Configure the input NSTextField we use for UI input - [inputTextField_ setFont:[NSFont fontWithDescriptor:[[consoleTextAttributes_ objectForKey:NSFontAttributeName] fontDescriptor] size:14.0]]; + inputTextField_.font = [NSFont fontWithDescriptor:[consoleTextAttributes_[NSFontAttributeName] fontDescriptor] size:14.0]; [self.window makeFirstResponder:inputTextField_]; // Start listening for device attached/detached notifications @@ -72,7 +68,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [self presentMessage:@"Ready for action — connecting at will." isStatus:YES]; // Start pinging - [self ping]; + //[self ping]; } @@ -103,7 +99,7 @@ - (void)presentMessage:(NSString*)message isStatus:(BOOL)isStatus { [NSAnimationContext beginGrouping]; [NSAnimationContext currentContext].duration = 0.15; [NSAnimationContext currentContext].timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - NSClipView* clipView = [[self.outputTextView enclosingScrollView] contentView]; + NSClipView* clipView = (self.outputTextView).enclosingScrollView.contentView; NSPoint newOrigin = clipView.bounds.origin; newOrigin.y += 5.0; // hack A 1/2 [clipView setBoundsOrigin:newOrigin]; // hack A 2/2 @@ -143,13 +139,13 @@ - (void)setConnectedChannel:(PTChannel*)connectedChannel { - (void)pongWithTag:(uint32_t)tagno error:(NSError*)error { - NSNumber *tag = [NSNumber numberWithUnsignedInt:tagno]; - NSMutableDictionary *pingInfo = [pings_ objectForKey:tag]; + NSNumber *tag = @(tagno); + NSMutableDictionary *pingInfo = pings_[tag]; if (pingInfo) { NSDate *now = [NSDate date]; - [pingInfo setObject:now forKey:@"date ended"]; + pingInfo[@"date ended"] = now; [pings_ removeObjectForKey:tag]; - NSLog(@"Ping total roundtrip time: %.3f ms", [now timeIntervalSinceDate:[pingInfo objectForKey:@"date created"]]*1000.0); + NSLog(@"Ping total roundtrip time: %.3f ms", [now timeIntervalSinceDate:pingInfo[@"date created"]]*1000.0); } } @@ -160,12 +156,12 @@ - (void)ping { pings_ = [NSMutableDictionary dictionary]; } uint32_t tagno = [connectedChannel_.protocol newTag]; - NSNumber *tag = [NSNumber numberWithUnsignedInt:tagno]; + NSNumber *tag = @(tagno); NSMutableDictionary *pingInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSDate date], @"date created", nil]; - [pings_ setObject:pingInfo forKey:tag]; + pings_[tag] = pingInfo; [connectedChannel_ sendFrameOfType:PTExampleFrameTypePing tag:tagno withPayload:nil callback:^(NSError *error) { [self performSelector:@selector(ping) withObject:nil afterDelay:1.0]; - [pingInfo setObject:[NSDate date] forKey:@"date sent"]; + pingInfo[@"date sent"] = [NSDate date]; if (error) { [pings_ removeObjectForKey:tag]; } @@ -227,22 +223,21 @@ - (void)startListeningForDevices { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { - NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + NSNumber *deviceID = (note.userInfo)[@"DeviceID"]; //NSLog(@"PTUSBDeviceDidAttachNotification: %@", note.userInfo); NSLog(@"PTUSBDeviceDidAttachNotification: %@", deviceID); dispatch_async(notConnectedQueue_, ^{ if (!connectingToDeviceID_ || ![deviceID isEqualToNumber:connectingToDeviceID_]) { [self disconnectFromCurrentChannel]; - connectingToDeviceID_ = deviceID; - connectedDeviceProperties_ = [note.userInfo objectForKey:@"Properties"]; + connectingToDeviceID_ = deviceID; connectedDeviceProperties_ = (note.userInfo)[@"Properties"]; [self enqueueConnectToUSBDevice]; } }); }]; [nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { - NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + NSNumber *deviceID = (note.userInfo)[@"DeviceID"]; //NSLog(@"PTUSBDeviceDidDetachNotification: %@", note.userInfo); NSLog(@"PTUSBDeviceDidDetachNotification: %@", deviceID); @@ -308,7 +303,9 @@ - (void)connectToLocalIPv4Port { - (void)enqueueConnectToUSBDevice { dispatch_async(notConnectedQueue_, ^{ dispatch_async(dispatch_get_main_queue(), ^{ - [self connectToUSBDevice]; + if (connectingToDeviceID_) { + [self connectToUSBDevice]; + } }); }); } diff --git a/README.md b/README.md index 84674c3..9417c2c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ PeerTalk is an iOS and Mac Cocoa library for communicating over USB. 4. Tested and designed for libdispatch (aka Grand Central Dispatch). +5. Now compatible with Bonjour® services and NSStream-based programs + Grab the goods from [https://github.com/rsms/peertalk](https://github.com/rsms/peertalk) @@ -62,3 +64,71 @@ It _should_ work. Demo video: [http://www.youtube.com/watch?v=kQPWy8N0mBg](http://www.youtube.com/watch?v=kQPWy8N0mBg) + +## Using peertalk with Bonjour + +Peertalk can now be used to connect from a macOS application to a Bonjour service running on a USB-attached iOS device. It only involves a small modification of your existing Bonjour code. + +When you want to connect to a Bonjour service, you generally use a `NSNetService` object associated with a class implementing the `NSNetServiceDelegate`protocol. Then you *resolve* the Bonjour service: + +```` +// self implements the NSNetServiceDelegate protocol +NSString *serviceName = ...; +NSString *serviceType = @"_music._tcp"; // or any custom service type prided by your iOS app +NSNetService *service; + +service = [[NSNetService alloc] initWithDomain:@"local." type: serviceType name:serviceName]; +service.delegate = self; +[service resolveWithTimeout:5.0]; +```` + +Then you implement the delegate method `netServiceDidResolveAddress`: + +```` +- (void)netServiceDidResolveAddress:(NSNetService *)netService +{ + // netService has been succesfuly resolved, its hostname and port are now set + + // First try to connect to the service using a USB link + [PTUSBHub.sharedHub connectToDeviceWithHostName: netService.hostName port:(int)netService.port + onStart:^(NSError *error, NSInputStream *inStream, NSOutputStream *outStream) { + + if ((inStream == nil) || (outStream == nil)) { + // USB connection did not succeed or was not supported: connect to the resolved service via standard Bonjour mechanism + [netService getInputStream:&inStream outputStream:&outStream]; + } + + if ((inStream != nil) && (outStream != nil)) { + // ... Use the NSStreams for communicating with the service + } + }]; + +} +```` + +### In the iOS app providing the Bonjour service + +For this mechanism to work, **peertalk** needs a way to know the Bonjour hostname of the USB-connected iOS device providing the Bonjour service. + +This is achieved by running a `PTHostNameProvider` in your iOS application, typically in the AppDelegate: + +```` +#import "AppDelegate.h" +#import "PTHostnameProvider.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + [PTHostNameProvider start]; + + // Other inits ... + return YES; +} + +// ... + +@end + +```` + diff --git a/peertalk.xcodeproj/project.pbxproj b/peertalk.xcodeproj/project.pbxproj index a9e303e..0cbbff5 100644 --- a/peertalk.xcodeproj/project.pbxproj +++ b/peertalk.xcodeproj/project.pbxproj @@ -58,6 +58,11 @@ EE158A911CBD412900A3E3F0 /* PTChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACFD2D5151D36220081ACF5 /* PTChannel.m */; }; EE158A921CBD412900A3E3F0 /* PTProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A88FA5D150D61DE00FC3647 /* PTProtocol.m */; }; EE158A931CBD412900A3E3F0 /* PTUSBHub.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A88FA5F150D61DE00FC3647 /* PTUSBHub.m */; }; + FD3344791EC3042C00FD641D /* PTHostnameProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = FD3344771EC3042C00FD641D /* PTHostnameProvider.h */; }; + FD33447C1EC305D000FD641D /* PTHostnameProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = FD3344781EC3042C00FD641D /* PTHostnameProvider.m */; }; + FD33447D1EC305E700FD641D /* PTHostnameProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = FD3344781EC3042C00FD641D /* PTHostnameProvider.m */; }; + FD33447E1EC3073A00FD641D /* PTHostnameProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = FD3344781EC3042C00FD641D /* PTHostnameProvider.m */; }; + FD33447F1EC3074400FD641D /* PTHostnameProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = FD3344781EC3042C00FD641D /* PTHostnameProvider.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -104,7 +109,7 @@ 6A64AAEF1526050D0065BF86 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 6A64AAF11526050D0065BF86 /* Peertalk iOS Example-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Peertalk iOS Example-Prefix.pch"; sourceTree = ""; }; 6A64AAF21526050D0065BF86 /* PTAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTAppDelegate.h; sourceTree = ""; }; - 6A64AAF31526050D0065BF86 /* PTAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTAppDelegate.m; sourceTree = ""; }; + 6A64AAF31526050D0065BF86 /* PTAppDelegate.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = PTAppDelegate.m; sourceTree = ""; }; 6A64AAF61526050D0065BF86 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPhone.storyboard; sourceTree = ""; }; 6A64AAF91526050D0065BF86 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPad.storyboard; sourceTree = ""; }; 6A64AAFB1526050D0065BF86 /* PTViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTViewController.h; sourceTree = ""; }; @@ -130,6 +135,8 @@ EE158A5B1CBD3EB600A3E3F0 /* Peertalk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Peertalk.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE158A681CBD3ED700A3E3F0 /* Peertalk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Peertalk.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE158A7C1CBD402600A3E3F0 /* Peertalk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Peertalk.h; sourceTree = ""; }; + FD3344771EC3042C00FD641D /* PTHostnameProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTHostnameProvider.h; sourceTree = ""; }; + FD3344781EC3042C00FD641D /* PTHostnameProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTHostnameProvider.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -302,6 +309,8 @@ 6A88FA5D150D61DE00FC3647 /* PTProtocol.m */, 6A88FA5E150D61DE00FC3647 /* PTUSBHub.h */, 6A88FA5F150D61DE00FC3647 /* PTUSBHub.m */, + FD3344771EC3042C00FD641D /* PTHostnameProvider.h */, + FD3344781EC3042C00FD641D /* PTHostnameProvider.m */, ); path = peertalk; sourceTree = ""; @@ -335,6 +344,7 @@ files = ( 6A88FA60150D61DE00FC3647 /* PTProtocol.h in Headers */, 6A88FA62150D61DE00FC3647 /* PTUSBHub.h in Headers */, + FD3344791EC3042C00FD641D /* PTHostnameProvider.h in Headers */, 6ACFD2D6151D36220081ACF5 /* PTChannel.h in Headers */, 5E2C5024171F46A6008A9752 /* PTPrivate.h in Headers */, EE158A7D1CBD402600A3E3F0 /* Peertalk.h in Headers */, @@ -480,6 +490,10 @@ attributes = { LastUpgradeCheck = 0800; TargetAttributes = { + 6A64AAE11526050D0065BF86 = { + DevelopmentTeam = 7V6VCE4Z4V; + ProvisioningStyle = Manual; + }; EE158A5A1CBD3EB600A3E3F0 = { CreatedOnToolsVersion = 7.3; }; @@ -579,6 +593,7 @@ 6A64AAFD1526050D0065BF86 /* PTViewController.m in Sources */, 6A64AB0815260E5C0065BF86 /* PTChannel.m in Sources */, 6A64AB0915260E600065BF86 /* PTProtocol.m in Sources */, + FD33447C1EC305D000FD641D /* PTHostnameProvider.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -587,6 +602,7 @@ buildActionMask = 2147483647; files = ( 6A88FA61150D61DE00FC3647 /* PTProtocol.m in Sources */, + FD33447E1EC3073A00FD641D /* PTHostnameProvider.m in Sources */, 6A88FA63150D61DE00FC3647 /* PTUSBHub.m in Sources */, 6ACFD2D7151D36220081ACF5 /* PTChannel.m in Sources */, ); @@ -605,6 +621,7 @@ buildActionMask = 2147483647; files = ( EE158A8E1CBD412900A3E3F0 /* PTChannel.m in Sources */, + FD33447F1EC3074400FD641D /* PTHostnameProvider.m in Sources */, EE158A901CBD412900A3E3F0 /* PTUSBHub.m in Sources */, EE158A8F1CBD412900A3E3F0 /* PTProtocol.m in Sources */, ); @@ -615,6 +632,7 @@ buildActionMask = 2147483647; files = ( EE158A911CBD412900A3E3F0 /* PTChannel.m in Sources */, + FD33447D1EC305E700FD641D /* PTHostnameProvider.m in Sources */, EE158A931CBD412900A3E3F0 /* PTUSBHub.m in Sources */, EE158A921CBD412900A3E3F0 /* PTProtocol.m in Sources */, ); @@ -727,6 +745,7 @@ isa = XCBuildConfiguration; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = 7V6VCE4Z4V; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", @@ -738,6 +757,8 @@ MACOSX_DEPLOYMENT_TARGET = ""; PRODUCT_BUNDLE_IDENTIFIER = "me.rsms.peertalk.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "d94a7d2e-a9d8-4123-8d0d-e2dc2e060926"; + PROVISIONING_PROFILE_SPECIFIER = "iOS Dev Provisioning Profile"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; @@ -748,6 +769,7 @@ isa = XCBuildConfiguration; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", @@ -760,6 +782,7 @@ OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; PRODUCT_BUNDLE_IDENTIFIER = "me.rsms.peertalk.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/peertalk/PTHostnameProvider.h b/peertalk/PTHostnameProvider.h new file mode 100644 index 0000000..18ee900 --- /dev/null +++ b/peertalk/PTHostnameProvider.h @@ -0,0 +1,41 @@ +// +// PTHostnameProvider.h +// +// Copyright (c) 2017 Jean-Luc Jumpertz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#define kPTHostnameProviderPort 22101 + +// Hostname response protocol +// Bytes 0-1: message length in network order (big-endian) +// Bytes 2-7: magic string "Host: " +// Bytes 8-...: null-terminated dns/bonjour hostname, like "My-iPhone.local." + +#define kPTHostnameProviderResponseMagicString "Host: " + +#define kPTHostnameProviderResponseOffsetMagicString 2 +#define kPTHostnameProviderResponseOffsetHostname 8 + +@interface PTHostNameProvider: NSObject + ++ (void) start; + +@end + diff --git a/peertalk/PTHostnameProvider.m b/peertalk/PTHostnameProvider.m new file mode 100644 index 0000000..fe0b388 --- /dev/null +++ b/peertalk/PTHostnameProvider.m @@ -0,0 +1,265 @@ +// +// PTHostnameProvider.m +// +// Copyright (c) 2017 Jean-Luc Jumpertz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import "PTHostnameProvider.h" + +#include +#include +#include +#include +#include +#import + +#if TARGET_OS_IPHONE +#import +#endif + +@implementation PTHostNameProvider +{ + dispatch_source_t _dispatchSource; + dispatch_queue_t _handlerQueue; +} + +static PTHostNameProvider* _hostnameProvider = nil; + ++ (void) start; +{ + if (_hostnameProvider == nil) { + _hostnameProvider = [PTHostNameProvider new]; + } +} + +- (instancetype) init +{ + self = [super init]; + if (self != nil) { + + _handlerQueue = dispatch_queue_create("com.celedev.CIMP2P.PTHostNameProvider", DISPATCH_QUEUE_SERIAL); + +#if TARGET_OS_IPHONE + // In iOS, monitor the applications state notification and suspend / resume the advertising for the P2P connection as appropriate + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleApplicationWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleApplicationDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification object:nil]; + + if (UIApplication.sharedApplication.applicationState != UIApplicationStateBackground) { + + [self startHostnameServer]; + } +#else + [self startHostnameServer]; +#endif + } + return self; +} + +- (void) handleApplicationWillEnterForeground:(NSNotification*)notification +{ + [self startHostnameServer]; +} + +- (void) handleApplicationDidEnterBackground:(NSNotification*)notification +{ + [self stopListening]; +} + +- (void) startHostnameServer +{ + [self listenOnPort:kPTHostnameProviderPort IPv4Address:INADDR_LOOPBACK withConnectionHandler:^(dispatch_io_t acceptedConnectionChannel) { + + // Send a single message on the channel and close it + [self sendHostnameMessageOnChannel:acceptedConnectionChannel withCompletion:^(NSError *error) { + + dispatch_io_close(acceptedConnectionChannel, 0); + }]; + + } error:NULL]; +} + +- (void) sendHostnameMessageOnChannel:(dispatch_io_t)dispatchChannel withCompletion:(void(^)(NSError* error))completion +{ + // Get the host name + const int kMaxHostNameSize = 255; + char hostName[kMaxHostNameSize + 8]; // Room to append ".local." and trailing null char + if (gethostname(hostName, kMaxHostNameSize) == 0) { + hostName[kMaxHostNameSize] = '\0'; + strcat(hostName, ".local."); + } + else { + hostName[0] = '\0'; + } + + // Build and send the message + + size_t hostNameLength = strlen(hostName); + if (hostNameLength > 0) { + size_t messageSize = kPTHostnameProviderResponseOffsetHostname + hostNameLength + 1; + char* messageBytes = malloc(messageSize); + messageBytes [0] = (messageSize >> 8) & 0xff; + messageBytes [1] = messageSize & 0xff; + memcpy(&messageBytes[kPTHostnameProviderResponseOffsetMagicString], kPTHostnameProviderResponseMagicString, strlen(kPTHostnameProviderResponseMagicString)); + strcpy(&messageBytes[kPTHostnameProviderResponseOffsetHostname], hostName); + + dispatch_data_t messageData = dispatch_data_create(messageBytes, messageSize, _handlerQueue, DISPATCH_DATA_DESTRUCTOR_FREE); + + dispatch_io_write(dispatchChannel, 0, messageData, _handlerQueue, ^(bool done, dispatch_data_t _Nullable data, int errorNum) { + if (done) { + NSError* writeError = nil; + if (errorNum != 0) { + writeError = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errorNum userInfo:nil]; + } + completion (writeError); + } + }); + } + else { + // No host name + completion(nil); + } +} + +- (BOOL) listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address withConnectionHandler:(void(^)(dispatch_io_t acceptedConnectionChannel))handler error:(NSError**)error +{ + NSError* listenError = nil; + + if (_dispatchSource != nil) { + [self stopListening]; + } + + // Create socket + dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + listenError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; + } + + // Connect socket + struct sockaddr_in addr; + bzero((char *)&addr, sizeof(addr)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + //addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + //addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_addr.s_addr = htonl(address); + + socklen_t socklen = sizeof(addr); + + int on = 1; + + if ((listenError == nil) && (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)) { + listenError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; + } + + if ((listenError == nil) && (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)) { + listenError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; + } + + if ((listenError == nil) && (bind(fd, (struct sockaddr*)&addr, socklen) != 0)) { + listenError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; + } + + if ((listenError == nil) && (listen(fd, 512) != 0)) { + listenError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; + } + + if (listenError == nil) { + _dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, _handlerQueue); + + dispatch_source_set_event_handler(_dispatchSource, ^{ + unsigned long nconns = dispatch_source_get_data(_dispatchSource); + while ([self acceptIncomingConnectionOnServerSocket:fd withHandler:handler] && --nconns); + }); + + dispatch_source_set_cancel_handler(_dispatchSource, ^{ + // Captures *self*, effectively holding a reference to *self* until cancelled. + _dispatchSource = nil; + close(fd); + }); + + dispatch_resume(_dispatchSource); + //NSLog(@"%@ opened on fd #%d", self, fd); + } + + if (listenError != nil) { + if (fd != -1) { + close(fd); + } + if (error != NULL) { + *error = listenError; + } + } + return (listenError == nil); +} + +- (BOOL) stopListening +{ + BOOL wasStopNeeded = NO; + + if (_dispatchSource != nil) { + dispatch_source_cancel(_dispatchSource); + _dispatchSource = nil; + wasStopNeeded = YES; + } + return wasStopNeeded; +} + +- (BOOL) acceptIncomingConnectionOnServerSocket:(dispatch_fd_t)serverSocketFD withHandler:(void(^)(dispatch_io_t acceptedConnectionChannel))handler +{ + BOOL isConnectionAccepted = YES; + + struct sockaddr_in addr; + socklen_t addrLen = sizeof(addr); + dispatch_fd_t clientSocketFD = accept(serverSocketFD, (struct sockaddr*)&addr, &addrLen); + + isConnectionAccepted = (clientSocketFD != -1); + + if (isConnectionAccepted) { + // prevent SIGPIPE + int on = 1; + setsockopt(clientSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); + + if (fcntl(clientSocketFD, F_SETFL, O_NONBLOCK) == -1) { + close(clientSocketFD); + isConnectionAccepted = NO; + } + } + + if (isConnectionAccepted) { + + dispatch_io_t acceptedConnectionChannel = dispatch_io_create(DISPATCH_IO_STREAM, clientSocketFD, _handlerQueue, ^(int error) { + close(clientSocketFD); + }); + + if (acceptedConnectionChannel != nil) { + handler (acceptedConnectionChannel); + } + } + + return isConnectionAccepted; +} + + +@end diff --git a/peertalk/PTUSBHub.h b/peertalk/PTUSBHub.h index 89aaf64..f764ce8 100644 --- a/peertalk/PTUSBHub.h +++ b/peertalk/PTUSBHub.h @@ -1,3 +1,27 @@ +// Peertalk +// +// Copyright (c) 2012 Rasmus Andersson +// +// Connection by hostname Copyright (c) 2017 Jean-Luc Jumpertz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + #include #import @@ -34,16 +58,21 @@ FOUNDATION_EXPORT NSString * const PTUSBDeviceDidDetachNotification; FOUNDATION_EXPORT NSString * const PTUSBHubErrorDomain; // Error codes returned with NSError.code for NSError domain PTUSBHubErrorDomain -typedef enum { - PTUSBHubErrorBadDevice = 2, +typedef NS_ENUM(unsigned int, PTUSBHubError) { + PTUSBHubErrorInvalidCommand = 1, + PTUSBHubErrorUnknownDevice = 2, PTUSBHubErrorConnectionRefused = 3, -} PTUSBHubError; + PTUSBHubErrorInvalidResponse = 4, +}; @interface PTUSBHub : NSObject // Shared, implicitly opened hub. + (PTUSBHub*)sharedHub; +/// Create the sharedHub instance and start monitoring USB- attached / detached devices ++ (void) startMonitoringAttachedDevices; + // Connect to a TCP *port* on a device, while the actual transport is over USB. // Upon success, *error* is nil and *channel* is a duplex I/O channel. // You can retrieve the underlying file descriptor using @@ -59,24 +88,20 @@ typedef enum { // argument is non-nil, the channel closed because of an error. Pass NULL for no // callback. // -- (void)connectToDevice:(NSNumber*)deviceID +- (void)connectToDevice:(NSNumber*)deviceID port:(int)port onStart:(void(^)(NSError *error, dispatch_io_t channel))onStart onEnd:(void(^)(NSError *error))onEnd; -// Start listening for devices. You only need to invoke this method on custom -// instances to start receiving notifications. The shared instance returned from -// +sharedHub is always in listening mode. -// -// *onStart* is called either when the system failed to start listening, in -// which case the error argument is non-nil, or when the receiver is listening. -// Pass NULL for no callback. -// -// *onEnd* is called when listening stopped. If the error argument is non-nil, -// listening stopped because of an error. Pass NULL for no callback. -// -- (void)listenOnQueue:(dispatch_queue_t)queue - onStart:(void(^)(NSError*))onStart - onEnd:(void(^)(NSError*))onEnd; + +/// Connect to a Bonjour service on a device, while the actual transport is over USB. +/// The Bonjour service is supposed to be resolved, so the client only knows the service +/// hostname and port. In order to be compatible with the standard way of connecting to +/// NetServices, a pair of NSStreams is provided in the callback. +/// And because NSStreams have their onw closing mechanism, no 'onEnd' callback is provided in this case. +- (void) connectToDeviceWithHostName:(NSString*)deviceHostname + port:(int)port + onStart:(void(^)(NSError*, NSInputStream*, NSOutputStream*))onStart; + @end diff --git a/peertalk/PTUSBHub.m b/peertalk/PTUSBHub.m index afb3225..4cf9363 100644 --- a/peertalk/PTUSBHub.m +++ b/peertalk/PTUSBHub.m @@ -1,5 +1,28 @@ +// Peertalk +// +// Copyright (c) 2012 Rasmus Andersson +// +// Connection by hostname Copyright (c) 2017 Jean-Luc Jumpertz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + #import "PTUSBHub.h" -#import "PTPrivate.h" #include #include @@ -7,56 +30,70 @@ #include #include +#import "PTHostnameProvider.h" + +#ifdef OS_OBJECT_USE_OBJC +#define PT_PRECISE_LIFETIME_UNUSED __attribute__((objc_precise_lifetime, unused)) +#else +#define PT_PRECISE_LIFETIME_UNUSED +#endif + +#ifdef DEBUG +#define NSLogDebug(...) NSLog(__VA_ARGS__) +#else +#define NSLogDebug(...) +#endif + NSString * const PTUSBHubErrorDomain = @"PTUSBHubError"; 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 +101,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 +121,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); } @@ -120,14 +157,7 @@ static void usbmux_packet_free(usbmux_packet_t *upacket) { // 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_; -} +@interface PTUSBChannel : NSObject // 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 @@ -156,26 +186,47 @@ - (void)stop; @end -@interface PTUSBChannel (Private) +@interface PTUSBChannel () +{ + dispatch_io_t dispatchChannel_; + dispatch_fd_t _channelFileDescriptor; // -1 if the channel is not open or if the fd ownership has been transfered to a client + dispatch_queue_t queue_; + uint32_t nextPacketTag_; + NSMutableDictionary *responseHandlers_; + BOOL autoReadPackets_; + BOOL isReadingPackets_; +} + (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; + +- (BOOL) transferChannelToClientUsingBlock:(void(^)(dispatch_io_t dispatchChannel)) block; +- (BOOL) transferChannelToClientAsNSStreamsUsingBlock:(void(^)(NSInputStream* inputStream, NSOutputStream* outputStream)) block ; + @end +@interface PTAttachedDevice : NSObject + +@property NSNumber* deviceId; +@property NSString* hostName; +@property NSDictionary * deviceInfo; +@property dispatch_data_t hostnameMessageData; + +- (instancetype) initWithDeviceInfo:(NSDictionary*)attachedDeviceInfo; + +@end + + + @interface PTUSBHub () { - PTUSBChannel *channel_; + PTUSBChannel *broadcastPacketsChannel_; + NSMutableSet* attachedDevices_; } -- (void)handleBroadcastPacket:(NSDictionary*)packet; @end @@ -183,79 +234,272 @@ @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]; + }); + return gSharedHub; } ++ (void) startMonitoringAttachedDevices +{ + // Create the shared hub + [self sharedHub]; +} - (id)init { - if (!(self = [super init])) return nil; - - return self; + self = [super init]; + if (self != nil) { + attachedDevices_ = [NSMutableSet new]; + [self startListeningForBroadcastPackets]; + } + 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) startListeningForBroadcastPackets +{ + broadcastPacketsChannel_ = [PTUSBChannel new]; + NSError *error = nil; + if ([broadcastPacketsChannel_ openOnQueue:dispatch_get_main_queue() error:&error onEnd:nil]) { + + [broadcastPacketsChannel_ listenWithBroadcastHandler:^(NSDictionary *packet) { [self handleBroadcastPacket:packet]; } + callback:^(NSError *error) { NSLog(@"PTUSBHub failed to initialize: %@", error); }]; + } else { + NSLog(@"PTUSBHub failed to initialize: %@", error); + } } +- (void)connectToDevice:(NSNumber*)deviceID port:(int)port onConnected:(void(^)(NSError*, PTUSBChannel*))onConnected onEnd:(void(^)(NSError*))onEnd +{ + PTUSBChannel *usbChannel = [PTUSBChannel new]; + NSError *error = nil; + + if ([usbChannel openOnQueue:dispatch_get_main_queue() error:&error onEnd:onEnd]) { + + NSDictionary *connectToDevicePacket = [PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeConnect + payload:@{ @"DeviceID": deviceID, + @"PortNumber": @(htons(port & 0xffff)) }]; + + [usbChannel sendRequest:connectToDevicePacket callback:^(NSError *error, NSDictionary *responsePacket) { + + onConnected (error, usbChannel); + }]; + } + else { + onConnected (error, nil); + } +} - (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) ); - }]; -} - - -- (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); - } + + [self connectToDevice:deviceID port:port onConnected:^(NSError *error, PTUSBChannel *usbChannel) { + + if (error == nil) { + // The client takes ownership of the dispatch channel connected to the device + BOOL isChannelValidForClient = [usbChannel transferChannelToClientUsingBlock:^(dispatch_io_t dispatchChannel) { + + onStart(nil, dispatchChannel); + }]; + + if (! isChannelValidForClient) { + NSError* error = [NSError errorWithDomain:PTUSBHubErrorDomain code:PTUSBHubErrorInvalidCommand + userInfo:@{ NSLocalizedDescriptionKey: @"Error when connecting to the device" }]; + onStart(error, nil); + } + } + else { + onStart(error, nil); + } + + } onEnd:onEnd]; +} + +- (void) connectToDeviceWithHostName:(NSString*)deviceHostname port:(int)port onStart:(void(^)(NSError*, NSInputStream*, NSOutputStream*))onStart +{ + void (^onDeviceConnectedBlock)(NSError *error, PTUSBChannel *usbChannel) = ^(NSError *error, PTUSBChannel *usbChannel) { + + if (error == nil) { + // The client takes ownership of the dispatch channel connected to the device + BOOL isChannelValidForClient = [usbChannel transferChannelToClientAsNSStreamsUsingBlock:^(NSInputStream *inputStream, NSOutputStream *outputStream) { + + onStart (nil, inputStream, outputStream); + }]; + + if (! isChannelValidForClient) { + NSError* error = [NSError errorWithDomain:PTUSBHubErrorDomain code:PTUSBHubErrorInvalidCommand + userInfo:@{ NSLocalizedDescriptionKey: @"Error when connecting to the device" }]; + onStart(error, nil, nil); + } + } + else { + onStart(error, nil, nil); + } + }; + + __block PTAttachedDevice* targetDevice = nil; + [attachedDevices_ enumerateObjectsUsingBlock:^(PTAttachedDevice * _Nonnull device, BOOL * _Nonnull stop) { + if ((device.hostName != nil) && ([device.hostName caseInsensitiveCompare:deviceHostname] == NSOrderedSame)) { + targetDevice = device; + *stop = YES; + } + }]; + + if (targetDevice != nil) { + [self connectToDevice:targetDevice.deviceId port:port onConnected:onDeviceConnectedBlock onEnd:nil]; + } + else { + // Try to get the missing hostnames of attached devices + NSMutableSet* queriedDevices = [NSMutableSet new]; + [attachedDevices_ enumerateObjectsUsingBlock:^(PTAttachedDevice * _Nonnull device, BOOL * _Nonnull stop) { + if (device.hostName == nil) { + [queriedDevices addObject:device.deviceId]; + } + }]; + + if (queriedDevices.count > 0) { + [queriedDevices enumerateObjectsUsingBlock:^(NSNumber* _Nonnull deviceId, BOOL * _Nonnull stop) { + + [self getHostNameOfAttachedDeviceWithId:deviceId completion:^(NSError *error, PTAttachedDevice *queriedDevice) { + + if (queriedDevices.count > 0) { + [queriedDevices removeObject:deviceId]; + + if ((error == nil) && ([queriedDevice.hostName caseInsensitiveCompare:deviceHostname] == NSOrderedSame)) { + + // This is the target device + [self connectToDevice:queriedDevice.deviceId port:port onConnected:onDeviceConnectedBlock onEnd:nil]; + + // Empy the queriedDevices set to prevent a connection to more than one device + [queriedDevices removeAllObjects]; + } + else if (queriedDevices.count == 0) { + // No match + NSError* error = [NSError errorWithDomain:PTUSBHubErrorDomain code:PTUSBHubErrorUnknownDevice + userInfo:@{ NSLocalizedDescriptionKey: @"No attached device with the specified host name" }]; + onStart(error, nil, nil); + } + } + }]; + }]; + } + else { + // Unknown hostname + NSError* error = [NSError errorWithDomain:PTUSBHubErrorDomain code:PTUSBHubErrorUnknownDevice + userInfo:@{ NSLocalizedDescriptionKey: @"No attached device with the specified host name" }]; + onStart(error, nil, nil); + } + } } +- (void)handleBroadcastPacket:(NSDictionary*)packet +{ + NSString *messageType = packet[@"MessageType"]; + + if ([@"Attached" isEqualToString:messageType]) { + PTAttachedDevice* attachedDevice = [[PTAttachedDevice alloc] initWithDeviceInfo:packet[@"Properties"]]; + [attachedDevices_ addObject:attachedDevice]; + [[NSNotificationCenter defaultCenter] postNotificationName:PTUSBDeviceDidAttachNotification object:self userInfo:packet]; + + } else if ([@"Detached" isEqualToString:messageType]) { + NSNumber* detachedDeviceId = packet[@"DeviceID"]; + if (detachedDeviceId != nil) { + PTAttachedDevice* detachedDevice = [self attachedDeviceWithId:detachedDeviceId]; + if (detachedDevice != nil) { + [attachedDevices_ removeObject:detachedDevice]; + [[NSNotificationCenter defaultCenter] postNotificationName:PTUSBDeviceDidDetachNotification object:self userInfo:packet]; + } + } + + } else { + NSLogDebug(@"Warning: Unhandled broadcast message: %@", packet); + } +} + +- (PTAttachedDevice*) attachedDeviceWithId:(NSNumber*)deviceID +{ + __block PTAttachedDevice* deviceWithId = nil; + [attachedDevices_ enumerateObjectsUsingBlock:^(PTAttachedDevice * _Nonnull device, BOOL * _Nonnull stop) { + if ([device.deviceId isEqual:deviceID]) { + deviceWithId = device; + *stop = YES; + } + }]; + return deviceWithId; +} + +- (void) getHostNameOfAttachedDeviceWithId:(NSNumber*)deviceID completion:(void(^)(NSError* error, PTAttachedDevice* attachedDeviceWithId))completion +{ + PTAttachedDevice* attachedDevice = [self attachedDeviceWithId:deviceID]; + + [self connectToDevice:deviceID port:kPTHostnameProviderPort onStart:^(NSError *connectError, dispatch_io_t channel) { + + if (connectError == nil) { + // Read the hostname message + + attachedDevice.hostnameMessageData = nil; + + dispatch_io_read(channel, 0, SIZE_MAX, dispatch_get_main_queue(), ^(bool done, dispatch_data_t _Nullable data, int error) { + + if (data != nil) { + dispatch_data_t hostnameMessageData = attachedDevice.hostnameMessageData; + + if (hostnameMessageData == nil) { + hostnameMessageData = data; + } + else { + hostnameMessageData = dispatch_data_create_concat(hostnameMessageData, data); + } + + attachedDevice.hostnameMessageData = hostnameMessageData; + + if (dispatch_data_get_size(hostnameMessageData) > kPTHostnameProviderResponseOffsetHostname) { + + const char *messageBuffer = NULL; + size_t bufferSize = 0; + PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(hostnameMessageData, (const void **)&messageBuffer, &bufferSize); + + size_t messageSize = (messageBuffer[0] << 8) + messageBuffer[1]; + if ((bufferSize == messageSize) + && (strncmp(&messageBuffer[kPTHostnameProviderResponseOffsetMagicString], kPTHostnameProviderResponseMagicString, + strlen(kPTHostnameProviderResponseMagicString)) == 0)) { + + // The message is fully received and the magic string matches: get the hostname + attachedDevice.hostName = [[NSString alloc] initWithBytes:&messageBuffer[kPTHostnameProviderResponseOffsetHostname] + length:messageSize - kPTHostnameProviderResponseOffsetHostname - 1 + encoding:NSUTF8StringEncoding]; + + if (completion != nil) { + completion (nil, attachedDevice); + } + + } + } + } + + NSError* readError = nil; + if (error != 0) { + readError = [NSError errorWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]; + } + + if (done) { + attachedDevice.hostnameMessageData = nil; + dispatch_io_close(channel, 0); // This retains the channel until the read is complete + + if ((readError != nil) || (attachedDevice.hostName.length == 0)) { + completion (readError, attachedDevice); + } + } + }); + } + else { + // Could not connect to device with the provided id and port + completion (connectError, attachedDevice); + } + + } onEnd:nil]; +} @end @@ -264,307 +508,379 @@ - (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[@"CFBundleName"]; + bundleVersion = [infoDict[@"CFBundleVersion"] description]; + } + }); + + if (bundleName) { + packet = @{@"MessageType": messageType, + @"ProgName": bundleName, + @"ClientVersionString": bundleVersion}; + } else { + packet = @{@"MessageType": messageType}; } - }); - - 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; + self = [super init]; + if (self !=nil) { + autoReadPackets_ = NO; + _channelFileDescriptor = -1; + } + return self; } - (void)dealloc { - //NSLog(@"dealloc %@", self); - if (channel_) { -#if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(channel_); + //NSLogDebug(@"dealloc %@", self); + +#ifndef OS_OBJECT_USE_OBJC + if (dispatchChannel_) { + dispatch_release(channel_); + dispatchChannel_ = nil; + } #endif - channel_ = nil; - } -} - - -- (BOOL)valid { - return !!channel_; + + if (_channelFileDescriptor != -1) { + close (_channelFileDescriptor); + _channelFileDescriptor = -1; + } } - (dispatch_io_t)dispatchChannel { - return channel_; + return dispatchChannel_; } - (dispatch_fd_t)fileDescriptor { - return dispatch_io_get_descriptor(channel_); + return dispatch_io_get_descriptor(dispatchChannel_); } - - (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(dispatchChannel_ == 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; + } + + _channelFileDescriptor = fd; + + dispatchChannel_ = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue_, ^(int errorNumber) { + + if (onEnd) { + NSError* error = nil; + if (errorNumber != 0) { + error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:nil]; + } + + onEnd(error); + } + }); + + return YES; +} - return YES; +- (BOOL) transferChannelToClientUsingBlock:(void(^)(dispatch_io_t dispatchChannel)) block +{ + dispatch_fd_t fd = _channelFileDescriptor; + + BOOL isTransferValid = (fd != -1); + + if (isTransferValid) { + dispatch_io_t clientChannel = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue_, ^(int error) { + close(fd); + }); + + block(clientChannel); + + _channelFileDescriptor = -1; // Release ownership of the file descriptor + } + + return isTransferValid; +} + +- (BOOL) transferChannelToClientAsNSStreamsUsingBlock:(void(^)(NSInputStream* inputStream, NSOutputStream* outputStream)) block +{ + dispatch_fd_t fd = _channelFileDescriptor; + + BOOL isTransferDone = NO; + + if (fd != -1) { + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + CFStreamCreatePairWithSocket(NULL, fd, &readStream, &writeStream); + + if ((readStream != nil) && (writeStream != nil)) { + + NSInputStream* inputStream = (__bridge_transfer NSInputStream*) readStream; + NSOutputStream* outputStream = (__bridge_transfer NSOutputStream*) writeStream; + + block(inputStream, outputStream); + + isTransferDone = YES; + _channelFileDescriptor = -1; + } + } + + return isTransferDone; } - (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 != 0) { + callback(error); + } + }]; } -- (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error { - if (!*error) { - NSNumber *n = [packet objectForKey:@"Number"]; +- (NSError*) errorFromPlistResponse:(NSDictionary*)packet { + + NSError* error = nil; - if (!n) { - *error = [NSError errorWithDomain:PTUSBHubErrorDomain code:(n ? n.integerValue : 0) userInfo:nil]; - return NO; + NSNumber *replyCodeObject = packet[@"Number"]; + + if ([replyCodeObject isKindOfClass:[NSNumber class]]) { + + USBMuxReplyCode replyCode = (USBMuxReplyCode)replyCodeObject.integerValue; + if (replyCode != 0) { + NSString *errmessage; + 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: errmessage = @"Unspecified error"; + } + error = [NSError errorWithDomain:PTUSBHubErrorDomain code:replyCode userInfo:@{NSLocalizedDescriptionKey: errmessage}]; + } } - - 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; + else { + error = [NSError errorWithDomain:PTUSBHubErrorDomain code:PTUSBHubErrorInvalidResponse userInfo:nil]; } - } - return YES; + + return error; } - (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; + } + if (callback != nil) { + // TODO: timeout un-triggered callbacks in responseQueue_ + if (!responseHandlers_) responseHandlers_ = [NSMutableDictionary new]; + + responseHandlers_[@(tag)] = ^(NSError* error, NSDictionary* responseData) { + + if (error == nil) { + error = [self errorFromPlistResponse:responseData]; + } + callback(error, responseData); + }; + } + }]; + + // 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 + //NSLogDebug(@"Received broadcast: %@", packet); + if (broadcastHandler) broadcastHandler(packet); + } else if (responseHandlers_) { + // Reply + NSNumber *key = @(packetTag); + void(^requestCallback)(NSError*,NSDictionary*) = responseHandlers_[key]; + if (requestCallback) { + [responseHandlers_ removeObjectForKey:key]; + requestCallback(error, packet); + } else { + NSLogDebug(@"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); -#if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(map_data); + dispatch_io_read(dispatchChannel_, 0, sizeof(ref_upacket.size), queue_, ^(bool done, dispatch_data_t data, int error) { + //NSLogDebug(@"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 + assert(buffer_size == sizeof(ref_upacket.size)); + memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size); +#ifndef OS_OBJECT_USE_OBJC + 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); -#if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(map_data); + + // 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(dispatchChannel_, offset, upacket->size - sizeof(ref_upacket.size), queue_, ^(bool done, dispatch_data_t data, int error) { + //NSLogDebug(@"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:PTUSBHubErrorInvalidResponse 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); +#ifndef OS_OBJECT_USE_OBJC + 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); + + //NSLogDebug(@"[PT] Received usbmux_packet: size= %u, protocol= %d, type= %d, tag= %d", upacket->size, upacket->protocol, upacket->type, upacket->tag); + + // We only support plist protocol + if (upacket->protocol != USBMuxPacketProtocolPlist) { + NSError* error = [[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:PTUSBHubErrorInvalidResponse + userInfo:@{NSLocalizedDescriptionKey: @"Unexpected package protocol"}]; + callback(error, nil, upacket->tag); + NSLogDebug(@"[PT] Received usbmux_packet: packet protocol is not plist - protocol= %d, type= %d, tag= %d payload: %@", upacket->protocol, upacket->type, upacket->tag, [NSData dataWithBytes:upacket length: upacket->size]); + usbmux_packet_free(upacket); + return; + } + + // Only one type of packet in the plist protocol + if (upacket->type != USBMuxPacketTypePlistPayload) { + NSError* error = [[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:PTUSBHubErrorInvalidResponse + userInfo:@{NSLocalizedDescriptionKey: @"Unexpected package type"}]; + callback(error, nil, upacket->tag); + NSLogDebug(@"[PT] Received usbmux_packet: plist packet is not of type USBMuxPacketTypePlistPayload - type= %d, tag= %d payload: %@", upacket->type, upacket->tag, [[NSString alloc] initWithData:[NSData dataWithBytes:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket)] encoding:NSUTF8StringEncoding]); + usbmux_packet_free(upacket); + return; + } + + // Try to decode any payload as plist + NSError *err = nil; + NSDictionary *messagePayload = nil; + if (usbmux_packet_payload_size(upacket) > 0) { + messagePayload = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err]; + } + + // NSLogDebug(@"[PT] Received usbmux_packet: plist packet %@", messagePayload); + + // Invoke callback + callback(err, messagePayload, upacket->tag); + usbmux_packet_free(upacket); + }); }); - }); } @@ -574,50 +890,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); - } - }); -#if PT_DISPATCH_RETAIN_RELEASE - dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write + off_t offset = 0; + dispatch_io_write(dispatchChannel_, offset, data, queue_, ^(bool done, dispatch_data_t data, int _errno) { + //NSLogDebug(@"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); + } + }); +#ifndef OS_OBJECT_USE_OBJC + dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write #endif } @@ -625,41 +941,68 @@ - (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(dispatchChannel_, 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 (dispatchChannel_) { + dispatch_io_close(dispatchChannel_, 0); + } } - (void)stop { - if (channel_) { - dispatch_io_close(channel_, DISPATCH_IO_STOP); - } + if (dispatchChannel_) { + dispatch_io_close(dispatchChannel_, DISPATCH_IO_STOP); + } +} + +@end + +#pragma mark - + +@implementation PTAttachedDevice + +- (instancetype) initWithDeviceInfo:(NSDictionary*)attachedDeviceInfo +{ + self = [super init]; + if (self != nil) { + _deviceId = attachedDeviceInfo [@"DeviceID"]; + _deviceInfo = attachedDeviceInfo; + } + return self; +} + +- (BOOL) isEqual:(id)object +{ + return ((self == object) + || ([object isKindOfClass:[PTAttachedDevice class]] && [self.deviceId isEqual:((PTAttachedDevice*)object).deviceId])); +} + +- (NSUInteger) hash +{ + return self.deviceId.hash; } @end