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