├── Client ├── KKConnectorError.h ├── PTChannel+KKConnector.h ├── KKConnectorPort.m ├── KKConnectorPort.h ├── KKConnectorRequestKeeper.m ├── KKConnectorError.m ├── KKConnectorRequestKeeper.h ├── PTChannel+KKConnector.m ├── KKConnectorClient.h ├── KKConnectorApp.h ├── KKConnectorApp.m └── KKConnectorClient.m ├── Shared ├── KKConnectorRequest.h ├── KKConnectorResponse.h ├── Peertalk │ ├── PTPrivate.h │ ├── PTUSBHub.h │ ├── PTProtocol.h │ ├── PTChannel.h │ ├── PTProtocol.m │ ├── PTChannel.m │ └── PTUSBHub.m ├── KKConnectorRequest.m ├── KKConnectorResponse.m └── KKConnectorDefines.h ├── KKConnectorClient.podspec ├── KKConnectorServer.podspec ├── Server ├── KKConnectorServer.h ├── KKConnectorServerRequestHandler.h ├── KKConnectorServerRequestHandler.m └── KKConnectorServer.m └── LICENSE /Client/KKConnectorError.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorError.h 3 | // KKConnectorClient 4 | // 5 | // Created by 李凯 on 2020/11/14. 6 | // 7 | 8 | #import 9 | 10 | @interface KKConnectorError : NSObject 11 | 12 | + (NSError *)inner; 13 | + (NSError *)noConnect; 14 | + (NSError *)repeatingCommand; 15 | + (NSError *)requestTimeout; 16 | + (NSError *)protocolVersionNotMatched; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Shared/KKConnectorRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorRequest.h 3 | // KKConnectorServer 4 | // 5 | // Created by 李凯 on 2020/11/10. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface KKConnectorRequest : NSObject 13 | 14 | @property(nonatomic, copy) NSString *header; 15 | 16 | @property(nonatomic, strong, nullable) id body; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /Client/PTChannel+KKConnector.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTChannel+KKConnectorClient.h 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/13. 6 | // 7 | 8 | #import "PTChannel.h" 9 | 10 | @class KKConnectorRequestKeeper; 11 | 12 | @interface PTChannel (KKConnector) 13 | 14 | /// 已经发送但尚未收到全部回复的请求 15 | @property(nonatomic, strong) NSMutableArray *keepers; 16 | 17 | - (KKConnectorRequestKeeper *)queryKeeperWithTag:(uint32_t)tag; 18 | 19 | - (KKConnectorRequestKeeper *)queryKeeperWithHeader:(NSString *)header; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /KKConnectorClient.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'KKConnectorClient' 3 | spec.version = '1.0.7' 4 | spec.license = 'MIT' 5 | spec.summary = 'Transfer data between macOS ans iOS' 6 | spec.homepage = 'https://github.com/hughkli/KKConnector.git' 7 | spec.author = 'Li Kai' 8 | spec.source = { :git => 'https://github.com/hughkli/KKConnector.git', :tag => spec.version.to_s } 9 | spec.requires_arc = true 10 | spec.source_files = 'Shared/**/*', 'Client/**/*' 11 | spec.osx.deployment_target = '10.14' 12 | end 13 | -------------------------------------------------------------------------------- /Client/KKConnectorPort.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorPort.m 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/13. 6 | // 7 | 8 | #import "KKConnectorPort.h" 9 | 10 | @implementation KKConnectorSimulatorPort 11 | 12 | - (NSString *)description { 13 | return [NSString stringWithFormat:@"number:%@", @(self.portNumber)]; 14 | } 15 | 16 | @end 17 | 18 | @implementation KKConnectorUSBPort 19 | 20 | - (NSString *)description { 21 | return [NSString stringWithFormat:@"number:%@, deviceID:%@, connectedChannel:%@", @(self.portNumber), self.deviceID, self.connectedChannel]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /KKConnectorServer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'KKConnectorServer' 3 | spec.version = '1.0.7' 4 | spec.license = 'MIT' 5 | spec.summary = 'Transfer data between macOS ans iOS' 6 | spec.homepage = 'https://github.com/hughkli/KKConnector.git' 7 | spec.author = 'Li Kai' 8 | spec.source = { :git => 'https://github.com/hughkli/KKConnector.git', :tag => spec.version.to_s } 9 | spec.requires_arc = true 10 | spec.source_files = 'Shared/**/*', 'Server/**/*' 11 | spec.ios.deployment_target = '9.0' 12 | spec.macos.deployment_target = '10.14' 13 | end 14 | -------------------------------------------------------------------------------- /Client/KKConnectorPort.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorPort.h 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/13. 6 | // 7 | 8 | #import 9 | 10 | @class PTChannel; 11 | 12 | @interface KKConnectorSimulatorPort : NSObject 13 | 14 | @property(nonatomic, assign) int portNumber; 15 | 16 | @property(nonatomic, strong) PTChannel *connectedChannel; 17 | 18 | @end 19 | 20 | @interface KKConnectorUSBPort : NSObject 21 | 22 | @property(nonatomic, assign) int portNumber; 23 | 24 | @property(nonatomic, strong) NSNumber *deviceID; 25 | 26 | @property(nonatomic, strong) PTChannel *connectedChannel; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Shared/KKConnectorResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorResponse.h 3 | // KKConnectorServer 4 | // 5 | // Created by 李凯 on 2020/11/10. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface KKConnectorResponse : NSObject 13 | 14 | @property(nonatomic, assign) int totalSize; 15 | @property(nonatomic, assign) int thisSize; 16 | 17 | @property(nonatomic, assign) BOOL hasError; 18 | @property(nonatomic, assign) int errorCode; 19 | @property(nonatomic, copy, nullable) NSString *errorDescription; 20 | 21 | @property(nonatomic, strong, nullable) NSObject *body; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /Shared/Peertalk/PTPrivate.h: -------------------------------------------------------------------------------- 1 | #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (!defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0)) || \ 2 | (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && (!defined(__MAC_10_8) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_8)) 3 | #define PT_DISPATCH_RETAIN_RELEASE 1 4 | #else 5 | #define PT_DISPATCH_RETAIN_RELEASE 0 6 | #endif 7 | 8 | #if PT_DISPATCH_RETAIN_RELEASE 9 | #define PT_PRECISE_LIFETIME 10 | #define PT_PRECISE_LIFETIME_UNUSED 11 | #else 12 | #define PT_PRECISE_LIFETIME __attribute__((objc_precise_lifetime)) 13 | #define PT_PRECISE_LIFETIME_UNUSED __attribute__((objc_precise_lifetime, unused)) 14 | #endif 15 | -------------------------------------------------------------------------------- /Shared/KKConnectorRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorRequest.m 3 | // KKConnectorServer 4 | // 5 | // Created by 李凯 on 2020/11/10. 6 | // 7 | 8 | #import "KKConnectorRequest.h" 9 | 10 | @implementation KKConnectorRequest 11 | 12 | - (instancetype)initWithCoder:(NSCoder *)coder { 13 | self = [super init]; 14 | if (self) { 15 | self.header = [coder decodeObjectForKey:@"header"]; 16 | self.body = [coder decodeObjectForKey:@"body"]; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)encodeWithCoder:(NSCoder *)coder { 22 | [coder encodeObject:self.header forKey:@"header"]; 23 | [coder encodeObject:self.body forKey:@"body"]; 24 | } 25 | 26 | + (BOOL)supportsSecureCoding { 27 | return YES; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Client/KKConnectorRequestKeeper.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorRequestKeeper.m 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/13. 6 | // 7 | 8 | #import "KKConnectorRequestKeeper.h" 9 | #import "KKConnectorRequestKeeper.h" 10 | 11 | @implementation KKConnectorRequestKeeper 12 | 13 | - (void)resetTimeoutCount { 14 | [self endTimeoutCount]; 15 | if (self.timeoutInterval > 0) { 16 | [self performSelector:@selector(_handleTimeout) withObject:nil afterDelay:self.timeoutInterval]; 17 | } else { 18 | NSAssert(NO, @"timeoutInterval 为 0"); 19 | } 20 | } 21 | 22 | - (void)endTimeoutCount { 23 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 24 | } 25 | 26 | - (void)_handleTimeout { 27 | if (self.timeoutBlock) { 28 | self.timeoutBlock(self); 29 | } 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Server/KKConnectorServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKSConnectionManager.h 3 | // KKServer 4 | // 5 | // Created by 李凯 on 2020/11/9. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @class KKConnectorServerRequestHandler; 13 | 14 | @protocol KKConnectorServerDelegate 15 | 16 | /// 当 Client 端发送的是 Push 类型的消息时(不需要回复)则 handler 为 nil 17 | - (void)connectorServerDidReceiveRequestHeader:(NSString *)header body:(nullable id)body handler:(nullable KKConnectorServerRequestHandler *)handler; 18 | 19 | @end 20 | 21 | @interface KKConnectorServer : NSObject 22 | 23 | + (instancetype)sharedInstance; 24 | 25 | - (void)registerAppID:(unsigned int)appID protocolVersion:(NSString *)protocolVersion delegate:(id)delegate; 26 | 27 | /// 主动向 Client 端推送数据,不保证可以送达,Server 端也不会收到任何回复 28 | - (void)pushWithAppID:(unsigned int)appID header:(NSString *)header body:(nullable id)body; 29 | 30 | @end 31 | 32 | NS_ASSUME_NONNULL_END 33 | -------------------------------------------------------------------------------- /Client/KKConnectorError.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorError.m 3 | // KKConnectorClient 4 | // 5 | // Created by 李凯 on 2020/11/14. 6 | // 7 | 8 | #import "KKConnectorError.h" 9 | #import "KKConnectorDefines.h" 10 | 11 | @implementation KKConnectorError 12 | 13 | + (NSError *)inner { 14 | return [NSError errorWithDomain:KKConnectorErrorDomain code:KKConnectorError_Inner userInfo:nil]; 15 | } 16 | 17 | + (NSError *)noConnect { 18 | return [NSError errorWithDomain:KKConnectorErrorDomain code:KKConnectorError_NoConnect userInfo:nil]; 19 | } 20 | 21 | + (NSError *)repeatingCommand { 22 | return [NSError errorWithDomain:KKConnectorErrorDomain code:KKConnectorError_RepeatingCommand userInfo:nil]; 23 | } 24 | 25 | + (NSError *)requestTimeout { 26 | return [NSError errorWithDomain:KKConnectorErrorDomain code:KKConnectorError_RequestTimeout userInfo:nil]; 27 | } 28 | 29 | + (NSError *)protocolVersionNotMatched { 30 | return [NSError errorWithDomain:KKConnectorErrorDomain code:KKConnectorError_ProtocolVersionNotMatched userInfo:nil]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Server/KKConnectorServerRequestHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorServerRequestHandler.h 3 | // KKConnectorServer 4 | // 5 | // Created by 李凯 on 2020/11/10. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface KKConnectorServerRequestHandler : NSObject 13 | 14 | - (instancetype)initWithAppID:(uint32_t)appID requestTag:(uint32_t)tag; 15 | 16 | /// 回复一个错误,客户端收到后会中止整个请求 17 | /// 可以使用 errorCode 和 errorDescription 来描述错误,客户端可以分别从收到的 NSError 的 code 与 localizedDescription 属性中取出来 18 | /// @Warning 业务不要使用 -7000001 ~ -7000020 范围内的值作为 errorCode,因为它们已经被 KKConnector 内部占用了 19 | - (void)errorWithCode:(int)errorCode description:(nullable NSString *)errorDescription; 20 | 21 | /// 如果只需要一次 response 则用这个方法 22 | - (void)just:(NSObject * _Nullable)data; 23 | 24 | /// 如果数据量比较大需要拆成多次 response,则先调用 startWithTotalSize 设置总大小,然后调用 respond:thisSize: 分别返回,当客户端收到的 size 的总和达到最开始设置的 totalSize 时则认定回复完成 25 | - (void)startWithTotalSize:(int)size; 26 | - (void)respond:(NSObject *)data thisSize:(int)size; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Li Kai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Client/KKConnectorRequestKeeper.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorRequestKeeper.h 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/13. 6 | // 7 | 8 | #import 9 | 10 | @class KKConnectorRequestKeeper; 11 | 12 | @interface KKConnectorRequestKeeper : NSObject 13 | 14 | @property(nonatomic, copy) NSString *header; 15 | 16 | @property(nonatomic, assign) uint32_t tag; 17 | 18 | @property(nonatomic, assign) NSUInteger receivedDataCount; 19 | 20 | /// 每一次收到返回数据时都会调用到这里。换句话说,如果一个请求含有多次返回,则这个 block 会被调用多次 21 | @property(nonatomic, copy) void (^succBlock)(NSObject *); 22 | 23 | /// 收到全局返回数据时会调用到这里,如果失败了则不会调用到这里。 24 | @property(nonatomic, copy) void (^completionBlock)(void); 25 | 26 | /// 遇到错误时会调用到这里 27 | @property(nonatomic, copy) void (^failBlock)(NSError *); 28 | 29 | /** 30 | 调用 resetTimeoutCount 开始倒计时,如果 timeoutInterval 时间内没有通过 endTimeoutCount 结束倒计时,则 timeoutBlock 会被调用 31 | */ 32 | 33 | /// 超时时间,必须先设置该属性 34 | @property(nonatomic, assign) NSTimeInterval timeoutInterval; 35 | /// 超时后,该 block 会被调用 36 | @property(nonatomic, copy) void (^timeoutBlock)(KKConnectorRequestKeeper *request); 37 | /// 开始或重设倒计时 38 | - (void)resetTimeoutCount; 39 | /// 结束倒计时。在试图销毁该 LKConnectionRequest 对象前必须先调用该方法结束倒计时 40 | - (void)endTimeoutCount; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Shared/KKConnectorResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorResponse.m 3 | // KKConnectorServer 4 | // 5 | // Created by 李凯 on 2020/11/10. 6 | // 7 | 8 | #import "KKConnectorResponse.h" 9 | 10 | @implementation KKConnectorResponse 11 | 12 | - (instancetype)initWithCoder:(NSCoder *)coder { 13 | if (self = [super init]) { 14 | self.totalSize = [coder decodeIntForKey:@"totalSize"]; 15 | self.thisSize = [coder decodeIntForKey:@"thisSize"]; 16 | self.hasError = [coder decodeBoolForKey:@"hasError"]; 17 | self.errorCode = [coder decodeIntForKey:@"errorCode"]; 18 | self.errorDescription = [coder decodeObjectForKey:@"errorDescription"]; 19 | self.body = [coder decodeObjectForKey:@"body"]; 20 | } 21 | return self; 22 | } 23 | 24 | + (BOOL)supportsSecureCoding { 25 | return YES; 26 | } 27 | 28 | - (void)encodeWithCoder:(NSCoder *)coder { 29 | [coder encodeInt:self.totalSize forKey:@"totalSize"]; 30 | [coder encodeInt:self.thisSize forKey:@"thisSize"]; 31 | [coder encodeBool:self.hasError forKey:@"hasError"]; 32 | [coder encodeInt:self.errorCode forKey:@"errorCode"]; 33 | [coder encodeObject:self.errorDescription forKey:@"errorDescription"]; 34 | [coder encodeObject:self.body forKey:@"body"]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Client/PTChannel+KKConnector.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTChannel+KKConnectorClient.m 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/13. 6 | // 7 | 8 | #import "PTChannel+KKConnector.h" 9 | #import 10 | #import "KKConnectorRequestKeeper.h" 11 | 12 | @implementation PTChannel (KKConnector) 13 | 14 | static char kAssociatedObjectKey_KKConnector_PTChannelKeepers; 15 | - (void)setKeepers:(NSMutableArray *)keepers { 16 | objc_setAssociatedObject(self, &kAssociatedObjectKey_KKConnector_PTChannelKeepers, keepers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 17 | } 18 | 19 | - (NSMutableArray *)keepers { 20 | id result = objc_getAssociatedObject(self, &kAssociatedObjectKey_KKConnector_PTChannelKeepers); 21 | return result; 22 | } 23 | 24 | 25 | - (KKConnectorRequestKeeper *)queryKeeperWithTag:(uint32_t)tag { 26 | for (KKConnectorRequestKeeper *keeper in self.keepers) { 27 | if (keeper.tag == tag) { 28 | return keeper; 29 | } 30 | } 31 | return nil; 32 | } 33 | 34 | - (KKConnectorRequestKeeper *)queryKeeperWithHeader:(NSString *)header { 35 | for (KKConnectorRequestKeeper *keeper in self.keepers) { 36 | if ([keeper.header isEqualToString:header]) { 37 | return keeper; 38 | } 39 | } 40 | return nil; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Client/KKConnectorClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorClient.h 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/10. 6 | // 7 | 8 | #import 9 | 10 | @class KKConnectorApp; 11 | 12 | /** 13 | iOS 是 Server 端,macOS 是 Client 端 14 | 15 | - 把 app 退到后台不会 kill 掉 server channel,仍旧会占用着端口(但可能无法执行代码) 16 | - 把 app kill 掉后,server channel 也会被 kill,端口占用会被释放 17 | 18 | - 一台电脑上的所有模拟器里的所有 app 共享同一批端口,比如依次启动“模拟器 A 的 app1”、“模拟器 A 的 app2”、“模拟器 B 的 app3”,则它们依次会占用 47164、47165、47166 这几个端口 19 | - 一台真机上的所有 app 共享同一批端口,比如依次启动“真机 A 的 app1”、“真机 A 的 app2”、“真机 B 的 app3”,则它们依次会占用 47175、47176、47175(注意不是 47177)这几个端口 20 | 21 | */ 22 | 23 | NS_ASSUME_NONNULL_BEGIN 24 | 25 | @protocol KKConnectorClientDelegate 26 | 27 | - (BOOL)validateKKConnectorServerProtocolVersion:(NSString *)serverProtocolVersion; 28 | 29 | - (void)kkConnectorClientDidReceivePushWithHeader:(NSString *)header body:(nullable id)body; 30 | 31 | 32 | @end 33 | 34 | @interface KKConnectorClient : NSObject 35 | 36 | + (instancetype)sharedInstance; 37 | 38 | /// delegate 会被 KKConnectorClient 强持有 39 | - (void)registerWithAppID:(uint32_t)appID delegate:(id)delegate; 40 | 41 | /// 搜索可以响应的 App 列表,data 为 Array,ping 失败的 app 不会出现在 data 里面 42 | /// 该方法不会 sendError 43 | - (void)searchApps:(void (^)(NSArray *))completion; 44 | 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /Shared/KKConnectorDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // LookinMessageProtocol.h 3 | // Lookin 4 | // 5 | // Created by Li Kai on 2018/8/6. 6 | // https://lookin.work 7 | // 8 | 9 | #import 10 | 11 | #pragma mark - Connection 12 | 13 | /// Server 在真机上会依次尝试监听下面这一批端口 14 | static const int KKConnectorUSBPortStart = 47185; 15 | static const int KKConnectorUSBPortEnd = 47189; 16 | 17 | /// Server 在模拟器中会依次尝试监听下面这一批端口 18 | static const int KKConnectorSimPortStart = 47190; 19 | static const int KKConnectorSimPortEnd = 47194; 20 | 21 | /// KKConnector 内部用来表示 Ping 请求,因此业务不能使用这个字符串作为 request header 22 | static NSString * const KKConnectorHeaderPing = @"KKConnectorPing"; 23 | 24 | /// 该 tag 表示这个请求属于 Push,无需回复 25 | static const int KKConnectorTagForPush = 1; 26 | 27 | #pragma mark - Error 28 | 29 | static NSString * const KKConnectorErrorDomain = @"KKConnector"; 30 | 31 | enum { 32 | /// KKConnector 内部逻辑错误 33 | KKConnectorError_Inner = -7000001, 34 | /// 连接已断开 35 | KKConnectorError_NoConnect = -7000002, 36 | /// server app 主动报告处于 inactive 状态 37 | KKConnectorError_BackgroundState = -7000003, 38 | /// 由于有 command 相同但更新的 request,因此这个旧的被废弃 39 | KKConnectorError_RepeatingCommand = -7000004, 40 | /// 超时未收到回复 41 | KKConnectorError_RequestTimeout = -7000005, 42 | /// server app 端自己的业务逻辑 43 | KKConnectorError_ServerAppLogic = -7000006, 44 | /// server 和 client 的业务 protocol version 不一致 45 | KKConnectorError_ProtocolVersionNotMatched = -7000007 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /Client/KKConnectorApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorApp.h 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/10. 6 | // 7 | 8 | #import 9 | 10 | @class PTChannel, KKConnectorApp; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @protocol KKConnectorAppDeleate 15 | 16 | - (void)kkConnectorApp:(KKConnectorApp *)app didReceivePushWithHeader:(NSString *)header body:(nullable id)body; 17 | 18 | @end 19 | 20 | @interface KKConnectorApp : NSObject 21 | 22 | @property(nonatomic, weak) id delegate; 23 | 24 | - (instancetype)initWithChannel:(PTChannel *)channel serverProtocolVersion:(NSString *)serverProtocolVersion sessionID:(NSString *)sessionID; 25 | 26 | @property(nonatomic, strong, readonly) PTChannel *channel; 27 | 28 | @property(nonatomic, copy, readonly) NSString *serverProtocolVersion; 29 | 30 | /// 每次 server app 被启动时都会随机生成一个 sessionID,这个 ID 不会变化,直到 server app 被 kill 掉 31 | @property(nonatomic, copy, readonly) NSString *sessionID; 32 | 33 | - (BOOL)isConnected; 34 | 35 | - (BOOL)isServerVersionValid; 36 | 37 | - (void)requestWithHeader:(NSString *)header body:(nullable NSObject *)body succBlock:(void (^)(NSObject * _Nullable result))succBlock completionBlock:(void (^)(void))completionBlock errorBlock:(void (^)(NSError * error))errorBlock; 38 | 39 | /// 向 Server 端发送数据,但不需要 Server 端回复,也不关心该数据是否真的发送成功 40 | - (void)pushWithHeader:(NSString *)header body:(nullable NSObject *)body; 41 | 42 | /// 不再接收已经发出去的某个请求的后续 Server 端回复,该请求的 completionBlock 会被调用 43 | /// Server 端不会得知 Client 端取消了该请求 44 | - (void)cancelRequestWithHeader:(NSString *)header; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /Server/KKConnectorServerRequestHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorServerRequestHandler.m 3 | // KKConnectorServer 4 | // 5 | // Created by 李凯 on 2020/11/10. 6 | // 7 | 8 | #import "KKConnectorServerRequestHandler.h" 9 | #import "KKConnectorServer.h" 10 | #import "KKConnectorResponse.h" 11 | 12 | @interface KKConnectorServer (Private_RequestHandler) 13 | 14 | - (void)sendData:(NSObject *)data appID:(uint32_t)appID tag:(uint32_t)tag; 15 | 16 | @end 17 | 18 | @interface KKConnectorServerRequestHandler () 19 | 20 | @property(nonatomic, assign) int totalSize; 21 | @property(nonatomic, assign) uint32_t appID; 22 | @property(nonatomic, assign) uint32_t requestTag; 23 | 24 | @end 25 | 26 | @implementation KKConnectorServerRequestHandler 27 | 28 | - (instancetype)initWithAppID:(uint32_t)appID requestTag:(uint32_t)tag { 29 | if (self = [super init]) { 30 | self.appID = appID; 31 | self.requestTag = tag; 32 | self.totalSize = 1; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)errorWithCode:(int)errorCode description:(NSString *)errorDescription { 38 | NSLog(@"[KKConnectorServer] Respond with error code: %@, desc: %@", @(errorCode), errorDescription); 39 | 40 | KKConnectorResponse *response = [KKConnectorResponse new]; 41 | response.totalSize = self.totalSize; 42 | response.thisSize = 1; 43 | response.hasError = YES; 44 | response.errorCode = errorCode; 45 | response.errorDescription = errorDescription; 46 | [[KKConnectorServer sharedInstance] sendData:response appID:self.appID tag:self.requestTag]; 47 | } 48 | 49 | - (void)just:(NSObject * _Nullable)data { 50 | NSLog(@"[KKConnectorServer] Respond with single data."); 51 | 52 | KKConnectorResponse *response = [KKConnectorResponse new]; 53 | response.totalSize = self.totalSize; 54 | response.thisSize = 1; 55 | response.body = data; 56 | [[KKConnectorServer sharedInstance] sendData:response appID:self.appID tag:self.requestTag]; 57 | } 58 | 59 | - (void)startWithTotalSize:(int)size { 60 | self.totalSize = size; 61 | } 62 | 63 | - (void)respond:(NSObject *)data thisSize:(int)size { 64 | NSLog(@"[KKConnectorServer] Respond with size: %@, total size: %@", @(size), @(self.totalSize)); 65 | 66 | KKConnectorResponse *response = [KKConnectorResponse new]; 67 | response.totalSize = self.totalSize; 68 | response.thisSize = size; 69 | response.body = data; 70 | 71 | if (response.totalSize <= 0) { 72 | NSAssert(NO, @"必须先调用 startWithTotalSize"); 73 | response.totalSize = 1; 74 | } 75 | 76 | [[KKConnectorServer sharedInstance] sendData:response appID:self.appID tag:self.requestTag]; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /Client/KKConnectorApp.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorApp.m 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/10. 6 | // 7 | 8 | #import "KKConnectorApp.h" 9 | #import "KKConnectorClient.h" 10 | #import "PTChannel.h" 11 | #import "KKConnectorError.h" 12 | 13 | @interface KKConnectorClient (KKConnectorApp) 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | - (void)requestWithChannel:(PTChannel *)channel header:(NSString *)header body:(nullable NSObject *)body timeoutInterval:(NSTimeInterval)timeoutInterval succ:(void (^)(NSObject * _Nullable data))succBlock fail:(void (^)(NSError *error))failBlock completion:(void (^)(void))completionBlock; 18 | 19 | - (void)cancelRequestInChannel:(PTChannel *)channel header:(NSString *)header; 20 | 21 | - (void)pushInChannel:(PTChannel *)channel header:(NSString *)header body:(nullable NSObject *)body; 22 | 23 | - (BOOL)isServerVersionValid:(NSString *)serverVersion; 24 | 25 | NS_ASSUME_NONNULL_END 26 | 27 | @end 28 | 29 | @interface KKConnectorApp () 30 | 31 | @property(nonatomic, strong) PTChannel *channel; 32 | 33 | @end 34 | 35 | @implementation KKConnectorApp 36 | 37 | - (instancetype)initWithChannel:(PTChannel *)channel serverProtocolVersion:(NSString *)serverProtocolVersion sessionID:(NSString *)sessionID { 38 | if (self = [super init]) { 39 | self.channel = channel; 40 | _serverProtocolVersion = serverProtocolVersion; 41 | _sessionID = sessionID; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)requestWithHeader:(NSString *)header body:(nullable NSObject *)body succBlock:(void (^)(NSObject * _Nullable result))succBlock completionBlock:(void (^)(void))completionBlock errorBlock:(void (^)(NSError * error))errorBlock { 47 | if (![self isServerVersionValid]) { 48 | NSAssert(NO, @""); 49 | if (errorBlock) { 50 | errorBlock([KKConnectorError protocolVersionNotMatched]); 51 | } 52 | if (completionBlock) { 53 | completionBlock(); 54 | } 55 | return; 56 | } 57 | 58 | [[KKConnectorClient sharedInstance] requestWithChannel:self.channel header:header body:body timeoutInterval:3 succ:^(NSObject * _Nullable data) { 59 | succBlock(data); 60 | } fail:^(NSError * _Nonnull error) { 61 | errorBlock(error); 62 | } completion:^{ 63 | completionBlock(); 64 | }]; 65 | } 66 | 67 | - (void)pushWithHeader:(NSString *)header body:(nullable NSObject *)body { 68 | [[KKConnectorClient sharedInstance] pushInChannel:self.channel header:header body:body]; 69 | } 70 | 71 | - (void)cancelRequestWithHeader:(NSString *)header { 72 | [[KKConnectorClient sharedInstance] cancelRequestInChannel:self.channel header:header]; 73 | } 74 | 75 | - (BOOL)isConnected { 76 | BOOL result = self.channel.isConnected; 77 | return result; 78 | } 79 | 80 | - (BOOL)isServerVersionValid { 81 | return [KKConnectorClient.sharedInstance isServerVersionValid:self.serverProtocolVersion]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Shared/Peertalk/PTUSBHub.h: -------------------------------------------------------------------------------- 1 | #include 2 | #import 3 | 4 | // PTUSBDeviceDidAttachNotification 5 | // Posted when a device has been attached. Also posted for each device that is 6 | // already attached when the PTUSBHub starts listening. 7 | // 8 | // .userInfo = { 9 | // DeviceID = 3; 10 | // MessageType = Attached; 11 | // Properties = { 12 | // ConnectionSpeed = 480000000; 13 | // ConnectionType = USB; 14 | // DeviceID = 3; 15 | // LocationID = 1234567890; 16 | // ProductID = 1234; 17 | // SerialNumber = 0123456789abcdef0123456789abcdef01234567; 18 | // }; 19 | // } 20 | // 21 | FOUNDATION_EXPORT NSString * const PTUSBDeviceDidAttachNotification; 22 | 23 | // PTUSBDeviceDidDetachNotification 24 | // Posted when a device has been detached. 25 | // 26 | // .userInfo = { 27 | // DeviceID = 3; 28 | // MessageType = Detached; 29 | // } 30 | // 31 | FOUNDATION_EXPORT NSString * const PTUSBDeviceDidDetachNotification; 32 | 33 | // NSError domain 34 | FOUNDATION_EXPORT NSString * const PTUSBHubErrorDomain; 35 | 36 | // Error codes returned with NSError.code for NSError domain PTUSBHubErrorDomain 37 | typedef enum { 38 | PTUSBHubErrorBadDevice = 2, 39 | PTUSBHubErrorConnectionRefused = 3, 40 | } PTUSBHubError; 41 | 42 | @interface PTUSBHub : NSObject 43 | 44 | // Shared, implicitly opened hub. 45 | + (PTUSBHub*)sharedHub; 46 | 47 | // Connect to a TCP *port* on a device, while the actual transport is over USB. 48 | // Upon success, *error* is nil and *channel* is a duplex I/O channel. 49 | // You can retrieve the underlying file descriptor using 50 | // dispatch_io_get_descriptor(channel). The dispatch_io_t channel behaves just 51 | // like any stream type dispatch_io_t, making it possible to use the same logic 52 | // for both USB bridged connections and e.g. ethernet-based connections. 53 | // 54 | // *onStart* is called either when a connection failed, in which case the error 55 | // argument is non-nil, or when the connection was successfully established (the 56 | // error argument is nil). Must not be NULL. 57 | // 58 | // *onEnd* is called when a connection was open and just did close. If the error 59 | // argument is non-nil, the channel closed because of an error. Pass NULL for no 60 | // callback. 61 | // 62 | - (void)connectToDevice:(NSNumber*)deviceID 63 | port:(int)port 64 | onStart:(void(^)(NSError *error, dispatch_io_t channel))onStart 65 | onEnd:(void(^)(NSError *error))onEnd; 66 | 67 | // Start listening for devices. You only need to invoke this method on custom 68 | // instances to start receiving notifications. The shared instance returned from 69 | // +sharedHub is always in listening mode. 70 | // 71 | // *onStart* is called either when the system failed to start listening, in 72 | // which case the error argument is non-nil, or when the receiver is listening. 73 | // Pass NULL for no callback. 74 | // 75 | // *onEnd* is called when listening stopped. If the error argument is non-nil, 76 | // listening stopped because of an error. Pass NULL for no callback. 77 | // 78 | - (void)listenOnQueue:(dispatch_queue_t)queue 79 | onStart:(void(^)(NSError*))onStart 80 | onEnd:(void(^)(NSError*))onEnd; 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Shared/Peertalk/PTProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // A universal frame-based communication protocol which can be used to exchange 3 | // arbitrary structured data. 4 | // 5 | // In short: 6 | // 7 | // - Each transmission is comprised by one fixed-size frame. 8 | // - Each frame contains a protocol version number. 9 | // - Each frame contains an application frame type. 10 | // - Each frame can contain an identifying tag. 11 | // - Each frame can have application-specific data of up to UINT32_MAX size. 12 | // - Transactions style messaging can be modeled on top using frame tags. 13 | // - Lightweight API on top of libdispatch (aka GCD) -- close to the metal. 14 | // 15 | #include 16 | #import 17 | 18 | // Special frame tag that signifies "no tag". Your implementation should never 19 | // create a reply for a frame with this tag. 20 | static const uint32_t PTFrameNoTag = 0; 21 | 22 | // Special frame type that signifies that the stream has ended. 23 | static const uint32_t PTFrameTypeEndOfStream = 0; 24 | 25 | // NSError domain 26 | FOUNDATION_EXPORT NSString * const PTProtocolErrorDomain; 27 | 28 | 29 | @interface PTProtocol : NSObject 30 | 31 | // Queue on which to run data processing blocks. 32 | @property dispatch_queue_t queue; 33 | 34 | // Get the shared protocol object for *queue* 35 | + (PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue; 36 | 37 | // Initialize a new protocol object to use a specific queue. 38 | - (id)initWithDispatchQueue:(dispatch_queue_t)queue; 39 | 40 | // Initialize a new protocol object to use the current calling queue. 41 | - (id)init; 42 | 43 | #pragma mark Sending frames 44 | 45 | // Generate a new tag that is unique within this protocol object. 46 | - (uint32_t)newTag; 47 | 48 | // Send a frame over *channel* with an optional payload and optional callback. 49 | // If *callback* is not NULL, the block is invoked when either an error occured 50 | // or when the frame (and payload, if any) has been completely sent. 51 | - (void)sendFrameOfType:(uint32_t)frameType 52 | tag:(uint32_t)tag 53 | withPayload:(dispatch_data_t)payload 54 | overChannel:(dispatch_io_t)channel 55 | callback:(void(^)(NSError *error))callback; 56 | 57 | #pragma mark Receiving frames 58 | 59 | // Read frames over *channel* as they arrive. 60 | // The onFrame handler is responsible for reading (or discarding) any payload 61 | // and call *resumeReadingFrames* afterwards to resume reading frames. 62 | // To stop reading frames, simply do not invoke *resumeReadingFrames*. 63 | // When the stream ends, a frame of type PTFrameTypeEndOfStream is received. 64 | - (void)readFramesOverChannel:(dispatch_io_t)channel 65 | onFrame:(void(^)(NSError *error, 66 | uint32_t type, 67 | uint32_t tag, 68 | uint32_t payloadSize, 69 | dispatch_block_t resumeReadingFrames))onFrame; 70 | 71 | // Read a single frame over *channel*. A frame of type PTFrameTypeEndOfStream 72 | // denotes the stream has ended. 73 | - (void)readFrameOverChannel:(dispatch_io_t)channel 74 | callback:(void(^)(NSError *error, 75 | uint32_t frameType, 76 | uint32_t frameTag, 77 | uint32_t payloadSize))callback; 78 | 79 | #pragma mark Receiving frame payloads 80 | 81 | // Read a complete payload. It's the callers responsibility to make sure 82 | // payloadSize is not too large since memory will be automatically allocated 83 | // where only payloadSize is the limit. 84 | // The returned dispatch_data_t object owns *buffer* and thus you need to call 85 | // dispatch_retain on *contiguousData* if you plan to keep *buffer* around after 86 | // returning from the callback. 87 | - (void)readPayloadOfSize:(size_t)payloadSize 88 | overChannel:(dispatch_io_t)channel 89 | callback:(void(^)(NSError *error, 90 | dispatch_data_t contiguousData, 91 | const uint8_t *buffer, 92 | size_t bufferSize))callback; 93 | 94 | // Discard data of *size* waiting on *channel*. *callback* can be NULL. 95 | - (void)readAndDiscardDataOfSize:(size_t)size 96 | overChannel:(dispatch_io_t)channel 97 | callback:(void(^)(NSError *error, BOOL endOfStream))callback; 98 | 99 | @end 100 | 101 | @interface NSData (PTProtocol) 102 | // Creates a new dispatch_data_t object which references the receiver and uses 103 | // the receivers bytes as its backing data. The returned dispatch_data_t object 104 | // holds a reference to the recevier. It's the callers responsibility to call 105 | // dispatch_release on the returned object when done. 106 | - (dispatch_data_t)createReferencingDispatchData; 107 | + (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data; 108 | @end 109 | 110 | @interface NSDictionary (PTProtocol) 111 | // See description of -[NSData(PTProtocol) createReferencingDispatchData] 112 | - (dispatch_data_t)createReferencingDispatchData; 113 | 114 | // Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure. 115 | + (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data; 116 | @end 117 | -------------------------------------------------------------------------------- /Shared/Peertalk/PTChannel.h: -------------------------------------------------------------------------------- 1 | // 2 | // Represents a communication channel between two endpoints talking the same 3 | // PTProtocol. 4 | // 5 | #import 6 | #import 7 | #import 8 | #import 9 | 10 | #import "PTProtocol.h" 11 | #import "PTUSBHub.h" 12 | 13 | @class PTData, PTAddress; 14 | @protocol PTChannelDelegate; 15 | 16 | @interface PTChannel : NSObject 17 | 18 | // Delegate 19 | @property (strong) id delegate; 20 | 21 | // Communication protocol. Must not be nil. 22 | @property PTProtocol *protocol; 23 | 24 | // YES if this channel is a listening server 25 | @property (readonly) BOOL isListening; 26 | 27 | // YES if this channel is a connected peer 28 | @property (readonly) BOOL isConnected; 29 | 30 | // Arbitrary attachment. Note that if you set this, the object will grow by 31 | // 8 bytes (64 bits). 32 | @property (strong) id userInfo; 33 | 34 | // Create a new channel using the shared PTProtocol for the current dispatch 35 | // queue, with *delegate*. 36 | + (PTChannel*)channelWithDelegate:(id)delegate; 37 | 38 | 39 | // Initialize a new frame channel, configuring it to use the calling queue's 40 | // protocol instance (as returned by [PTProtocol sharedProtocolForQueue: 41 | // dispatch_get_current_queue()]) 42 | - (id)init; 43 | 44 | // Initialize a new frame channel with a specific protocol. 45 | - (id)initWithProtocol:(PTProtocol*)protocol; 46 | 47 | // Initialize a new frame channel with a specific protocol and delegate. 48 | - (id)initWithProtocol:(PTProtocol*)protocol delegate:(id)delegate; 49 | 50 | 51 | // Connect to a TCP port on a device connected over USB 52 | - (void)connectToPort:(int)port overUSBHub:(PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback; 53 | 54 | // Connect to a TCP port at IPv4 address. Provided port must NOT be in network 55 | // byte order. Provided in_addr_t must NOT be in network byte order. A value returned 56 | // from inet_aton() will be in network byte order. You can use a value of inet_aton() 57 | // as the address parameter here, but you must flip the byte order before passing the 58 | // in_addr_t to this function. 59 | - (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, PTAddress *address))callback; 60 | 61 | // Listen for connections on port and address, effectively starting a socket 62 | // server. Provided port must NOT be in network byte order. Provided in_addr_t 63 | // must NOT be in network byte order. 64 | // For this to make sense, you should provide a onAccept block handler 65 | // or a delegate implementing ioFrameChannel:didAcceptConnection:. 66 | - (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback; 67 | 68 | // Send a frame with an optional payload and optional callback. 69 | // If *callback* is not NULL, the block is invoked when either an error occured 70 | // or when the frame (and payload, if any) has been completely sent. 71 | - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback; 72 | 73 | // Lower-level method to assign a connected dispatch IO channel to this channel 74 | - (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error; 75 | 76 | // Close the channel, preventing further reading and writing. Any ongoing and 77 | // queued reads and writes will be aborted. 78 | - (void)close; 79 | 80 | // "graceful" close -- any ongoing and queued reads and writes will complete 81 | // before the channel ends. 82 | - (void)cancel; 83 | 84 | @end 85 | 86 | 87 | // Wraps a mapped dispatch_data_t object. The memory pointed to by *data* is 88 | // valid until *dispatchData* is deallocated (normally when the receiver is 89 | // deallocated). 90 | @interface PTData : NSObject 91 | @property (readonly) dispatch_data_t dispatchData; 92 | @property (readonly) void *data; 93 | @property (readonly) size_t length; 94 | @end 95 | 96 | 97 | // Represents a peer's address 98 | @interface PTAddress : NSObject 99 | // For network addresses, this is the IP address in textual format 100 | @property (readonly) NSString *name; 101 | // For network addresses, this is the port number. Otherwise 0 (zero). 102 | @property (readonly) NSInteger port; 103 | @end 104 | 105 | 106 | // Protocol for PTChannel delegates 107 | @protocol PTChannelDelegate 108 | 109 | @required 110 | // Invoked when a new frame has arrived on a channel. 111 | - (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData*)payload; 112 | 113 | @optional 114 | // Invoked to accept an incoming frame on a channel. Reply NO ignore the 115 | // incoming frame. If not implemented by the delegate, all frames are accepted. 116 | - (BOOL)ioFrameChannel:(PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize; 117 | 118 | // Invoked when the channel closed. If it closed because of an error, *error* is 119 | // a non-nil NSError object. 120 | - (void)ioFrameChannel:(PTChannel*)channel didEndWithError:(NSError*)error; 121 | 122 | // For listening channels, this method is invoked when a new connection has been 123 | // accepted. 124 | - (void)ioFrameChannel:(PTChannel*)channel didAcceptConnection:(PTChannel*)otherChannel fromAddress:(PTAddress*)address; 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /Server/KKConnectorServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKSConnectionManager.m 3 | // KKServer 4 | // 5 | // Created by 李凯 on 2020/11/9. 6 | // 7 | 8 | #import "KKConnectorServer.h" 9 | #import "PTChannel.h" 10 | #import "KKConnectorDefines.h" 11 | #import "KKConnectorRequest.h" 12 | #import "KKConnectorServerRequestHandler.h" 13 | 14 | @interface KKConnectorServer () 15 | 16 | @property(nonatomic, weak) PTChannel *peerChannel_; 17 | 18 | @property(nonatomic, assign) BOOL applicationIsActive; 19 | @property(nonatomic, copy) NSString *sessionID; 20 | 21 | @property(nonatomic, strong) NSMutableDictionary> *delegatesMap; 22 | @property(nonatomic, strong) NSMutableDictionary *protocolVersionsMap; 23 | 24 | @end 25 | 26 | @implementation KKConnectorServer 27 | 28 | + (instancetype)sharedInstance { 29 | static KKConnectorServer *sharedInstance; 30 | static dispatch_once_t onceToken; 31 | dispatch_once(&onceToken, ^{ 32 | sharedInstance = [[KKConnectorServer alloc] init]; 33 | }); 34 | return sharedInstance; 35 | } 36 | 37 | - (instancetype)init { 38 | if (self = [super init]) { 39 | self.delegatesMap = [NSMutableDictionary dictionary]; 40 | self.protocolVersionsMap = [NSMutableDictionary dictionary]; 41 | self.sessionID = [self genRandomText]; 42 | 43 | #if TARGET_OS_IPHONE 44 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; 45 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleApplicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; 46 | #elif TARGET_OS_OSX 47 | [self handleApplicationDidBecomeActive]; 48 | #endif 49 | } 50 | return self; 51 | } 52 | 53 | - (NSString *)genRandomText { 54 | NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970]; 55 | int number = arc4random() % 10; 56 | return [NSString stringWithFormat:@"%@%.0f", @(number), timestamp]; 57 | } 58 | 59 | - (void)handleApplicationDidBecomeActive { 60 | self.applicationIsActive = YES; 61 | if (self.peerChannel_ && (self.peerChannel_.isConnected || self.peerChannel_.isListening)) { 62 | return; 63 | } 64 | #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR 65 | [self tryToListenOnPortFrom:KKConnectorUSBPortStart to:KKConnectorUSBPortEnd current:KKConnectorUSBPortStart]; 66 | #else 67 | [self tryToListenOnPortFrom:KKConnectorSimPortStart to:KKConnectorSimPortEnd current:KKConnectorSimPortStart]; 68 | #endif 69 | } 70 | 71 | - (void)handleApplicationWillResignActive { 72 | self.applicationIsActive = NO; 73 | } 74 | 75 | - (void)tryToListenOnPortFrom:(int)fromPort to:(int)toPort current:(int)currentPort { 76 | PTChannel *channel = [PTChannel channelWithDelegate:self]; 77 | [channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { 78 | if (!error) { 79 | NSLog(@"[KKConnectorServer] Connected successfully on 127.0.0.1:%d", currentPort); 80 | return;; 81 | } 82 | // errorCode 48 表示地址已被占用 83 | NSLog(@"[KKConnectorServer] 127.0.0.1:%d is unavailable(%@).", currentPort, error); 84 | if (currentPort < toPort) { 85 | // 尝试下一个端口 86 | [self tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)]; 87 | } else { 88 | // 所有端口都尝试完毕,全部失败 89 | NSLog(@"[KKConnectorServer] Connection failed."); 90 | } 91 | }]; 92 | } 93 | 94 | - (BOOL)isConnected { 95 | return self.peerChannel_ && self.peerChannel_.isConnected; 96 | } 97 | 98 | - (void)sendData:(NSObject *)data appID:(uint32_t)appID tag:(uint32_t)tag { 99 | if (!self.peerChannel_) { 100 | return; 101 | } 102 | // NSError *error; 103 | // NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data requiringSecureCoding:YES error:&error]; 104 | NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; 105 | // if (error) { 106 | // NSLog(@"[KKConnectorServer] Archived data failed: %@", error); 107 | // } 108 | 109 | dispatch_data_t payload = [archivedData createReferencingDispatchData]; 110 | 111 | [self.peerChannel_ sendFrameOfType:appID tag:tag withPayload:payload callback:^(NSError *error) { 112 | if (error) { 113 | } 114 | }]; 115 | } 116 | 117 | #pragma mark - PTChannelDelegate 118 | 119 | - (BOOL)ioFrameChannel:(PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)appID tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { 120 | if (channel == self.peerChannel_ && self.delegatesMap[@(appID)] != nil) { 121 | return YES; 122 | } else { 123 | return NO; 124 | } 125 | } 126 | 127 | - (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)appID tag:(uint32_t)requestTag payload:(PTData*)payload { 128 | id delegate = self.delegatesMap[@(appID)]; 129 | if (delegate == nil) { 130 | NSLog(@"[KKConnectorServer][E] Did receive frame but no delegate was found. AppID: %@", @(appID)); 131 | return; 132 | } 133 | if (!payload) { 134 | NSLog(@"[KKConnectorServer][E] Did receive frame but no payload. AppID: %@", @(appID)); 135 | return; 136 | } 137 | id unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfDispatchData:payload.dispatchData]]; 138 | if (![unarchived isKindOfClass:[KKConnectorRequest class]]) { 139 | NSLog(@"[KKConnectorServer][E] Did receive frame but payload was not KKConnectorRequest but %@.", unarchived); 140 | return; 141 | } 142 | KKConnectorRequest *request = (KKConnectorRequest *)unarchived; 143 | NSLog(@"[KKConnectorServer] Did receive request with header: %@", request.header); 144 | 145 | if (requestTag == 0) { 146 | // push 消息,不需要回复 147 | [delegate connectorServerDidReceiveRequestHeader:request.header body:request.body handler:nil]; 148 | return; 149 | } 150 | 151 | KKConnectorServerRequestHandler *handler = [[KKConnectorServerRequestHandler alloc] initWithAppID:appID requestTag:requestTag]; 152 | if (!self.applicationIsActive) { 153 | [handler errorWithCode:KKConnectorError_BackgroundState description:@"Server application has resigned active."]; 154 | return; 155 | } 156 | 157 | if ([request.header isEqualToString:KKConnectorHeaderPing]) { 158 | NSString *serverProtocolVersion = self.protocolVersionsMap[@(appID)]; 159 | NSDictionary *param = @{ 160 | @"version": serverProtocolVersion, 161 | @"session": self.sessionID 162 | }; 163 | [handler just:param]; 164 | } else { 165 | [delegate connectorServerDidReceiveRequestHeader:request.header body:request.body handler:handler]; 166 | } 167 | } 168 | 169 | /// 当连接过 Lookin 客户端,然后 Lookin 客户端又被关闭时,会走到这里 170 | - (void)ioFrameChannel:(PTChannel*)channel didEndWithError:(NSError*)error { 171 | } 172 | 173 | - (void)ioFrameChannel:(PTChannel*)channel didAcceptConnection:(PTChannel*)otherChannel fromAddress:(PTAddress*)address { 174 | if (self.peerChannel_) { 175 | [self.peerChannel_ cancel]; 176 | } 177 | 178 | self.peerChannel_ = otherChannel; 179 | self.peerChannel_.userInfo = address; 180 | } 181 | 182 | #pragma mark - Public 183 | 184 | - (void)registerAppID:(unsigned int)appID protocolVersion:(NSString *)protocolVersion delegate:(id)delegate { 185 | self.delegatesMap[@(appID)] = delegate; 186 | self.protocolVersionsMap[@(appID)] = protocolVersion; 187 | 188 | NSLog(@"[KKConnectorServer] Registered with appID: %@, protocolVersion: %@, delegate: %@", @(appID), protocolVersion, delegate); 189 | } 190 | 191 | - (void)pushWithAppID:(unsigned int)appID header:(NSString *)header body:(id)body { 192 | KKConnectorRequest *request = [KKConnectorRequest new]; 193 | request.header = header; 194 | request.body = body; 195 | [self sendData:request appID:appID tag:KKConnectorTagForPush]; 196 | } 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /Shared/Peertalk/PTProtocol.m: -------------------------------------------------------------------------------- 1 | #import "PTProtocol.h" 2 | #import "PTPrivate.h" 3 | #import 4 | 5 | static const uint32_t PTProtocolVersion1 = 1; 6 | 7 | NSString * const PTProtocolErrorDomain = @"PTProtocolError"; 8 | 9 | // This is what we send as the header for each frame. 10 | typedef struct _PTFrame { 11 | // The version of the frame and protocol. 12 | uint32_t version; 13 | 14 | // Type of frame 15 | uint32_t type; 16 | 17 | // Unless zero, a tag is retained in frames that are responses to previous 18 | // frames. Applications can use this to build transactions or request-response 19 | // logic. 20 | uint32_t tag; 21 | 22 | // If payloadSize is larger than zero, *payloadSize* number of bytes are 23 | // following, constituting application-specific data. 24 | uint32_t payloadSize; 25 | 26 | } PTFrame; 27 | 28 | 29 | @interface PTProtocol () { 30 | uint32_t nextFrameTag_; 31 | @public 32 | dispatch_queue_t queue_; 33 | } 34 | - (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload; 35 | @end 36 | 37 | 38 | static void _release_queue_local_protocol(void *objcobj) { 39 | if (objcobj) { 40 | PTProtocol *protocol = (__bridge_transfer id)objcobj; 41 | protocol->queue_ = NULL; 42 | } 43 | } 44 | 45 | 46 | @interface RQueueLocalIOFrameProtocol : PTProtocol 47 | @end 48 | @implementation RQueueLocalIOFrameProtocol 49 | - (void)setQueue:(dispatch_queue_t)queue { 50 | } 51 | @end 52 | 53 | 54 | @implementation PTProtocol 55 | 56 | 57 | + (PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue { 58 | static const char currentQueueFrameProtocolKey; 59 | //dispatch_queue_t queue = dispatch_get_current_queue(); 60 | PTProtocol *currentQueueFrameProtocol = (__bridge PTProtocol*)dispatch_queue_get_specific(queue, ¤tQueueFrameProtocolKey); 61 | if (!currentQueueFrameProtocol) { 62 | currentQueueFrameProtocol = [[RQueueLocalIOFrameProtocol alloc] initWithDispatchQueue:NULL]; 63 | currentQueueFrameProtocol->queue_ = queue; // reference, no retain, since we would create cyclic references 64 | dispatch_queue_set_specific(queue, ¤tQueueFrameProtocolKey, (__bridge_retained void*)currentQueueFrameProtocol, &_release_queue_local_protocol); 65 | return (__bridge PTProtocol*)dispatch_queue_get_specific(queue, ¤tQueueFrameProtocolKey); // to avoid race conds 66 | } else { 67 | return currentQueueFrameProtocol; 68 | } 69 | } 70 | 71 | 72 | - (id)initWithDispatchQueue:(dispatch_queue_t)queue { 73 | if (!(self = [super init])) return nil; 74 | queue_ = queue; 75 | #if PT_DISPATCH_RETAIN_RELEASE 76 | if (queue_) dispatch_retain(queue_); 77 | #endif 78 | return self; 79 | } 80 | 81 | - (id)init { 82 | return [self initWithDispatchQueue:dispatch_get_main_queue()]; 83 | } 84 | 85 | - (void)dealloc { 86 | if (queue_) { 87 | #if PT_DISPATCH_RETAIN_RELEASE 88 | dispatch_release(queue_); 89 | #endif 90 | } 91 | } 92 | 93 | - (dispatch_queue_t)queue { 94 | return queue_; 95 | } 96 | 97 | - (void)setQueue:(dispatch_queue_t)queue { 98 | #if PT_DISPATCH_RETAIN_RELEASE 99 | dispatch_queue_t prev_queue = queue_; 100 | queue_ = queue; 101 | if (queue_) dispatch_retain(queue_); 102 | if (prev_queue) dispatch_release(prev_queue); 103 | #else 104 | queue_ = queue; 105 | #endif 106 | } 107 | 108 | 109 | - (uint32_t)newTag { 110 | return ++nextFrameTag_; 111 | } 112 | 113 | 114 | #pragma mark - 115 | #pragma mark Creating frames 116 | 117 | 118 | - (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload { 119 | PTFrame *frame = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(PTFrame), 0); 120 | frame->version = htonl(PTProtocolVersion1); 121 | frame->type = htonl(type); 122 | frame->tag = htonl(frameTag); 123 | 124 | if (payload) { 125 | size_t payloadSize = dispatch_data_get_size(payload); 126 | assert(payloadSize <= UINT32_MAX); 127 | frame->payloadSize = htonl((uint32_t)payloadSize); 128 | } else { 129 | frame->payloadSize = 0; 130 | } 131 | 132 | dispatch_data_t frameData = dispatch_data_create((const void*)frame, sizeof(PTFrame), queue_, ^{ 133 | CFAllocatorDeallocate(kCFAllocatorDefault, (void*)frame); 134 | }); 135 | 136 | if (payload && frame->payloadSize != 0) { 137 | // chain frame + payload 138 | dispatch_data_t data = dispatch_data_create_concat(frameData, payload); 139 | #if PT_DISPATCH_RETAIN_RELEASE 140 | dispatch_release(frameData); 141 | #endif 142 | frameData = data; 143 | } 144 | 145 | return frameData; 146 | } 147 | 148 | 149 | #pragma mark - 150 | #pragma mark Sending frames 151 | 152 | 153 | - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*))callback { 154 | dispatch_data_t frame = [self createDispatchDataWithFrameOfType:frameType frameTag:tag payload:payload]; 155 | dispatch_io_write(channel, 0, frame, queue_, ^(bool done, dispatch_data_t data, int _errno) { 156 | if (done && callback) { 157 | callback(_errno == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]); 158 | } 159 | }); 160 | #if PT_DISPATCH_RETAIN_RELEASE 161 | dispatch_release(frame); 162 | #endif 163 | } 164 | 165 | 166 | #pragma mark - 167 | #pragma mark Receiving frames 168 | 169 | 170 | - (void)readFrameOverChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, uint32_t frameType, uint32_t frameTag, uint32_t payloadSize))callback { 171 | __block dispatch_data_t allData = NULL; 172 | 173 | dispatch_io_read(channel, 0, sizeof(PTFrame), queue_, ^(bool done, dispatch_data_t data, int error) { 174 | //NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error); 175 | size_t dataSize = data ? dispatch_data_get_size(data) : 0; 176 | 177 | if (dataSize) { 178 | if (!allData) { 179 | allData = data; 180 | #if PT_DISPATCH_RETAIN_RELEASE 181 | dispatch_retain(allData); 182 | #endif 183 | } else { 184 | #if PT_DISPATCH_RETAIN_RELEASE 185 | dispatch_data_t allDataPrev = allData; 186 | allData = dispatch_data_create_concat(allData, data); 187 | dispatch_release(allDataPrev); 188 | #else 189 | allData = dispatch_data_create_concat(allData, data); 190 | #endif 191 | } 192 | } 193 | 194 | if (done) { 195 | if (error != 0) { 196 | callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], 0, 0, 0); 197 | return; 198 | } 199 | 200 | if (dataSize == 0) { 201 | callback(nil, PTFrameTypeEndOfStream, 0, 0); 202 | return; 203 | } 204 | 205 | if (!allData || dispatch_data_get_size(allData) < sizeof(PTFrame)) { 206 | #if PT_DISPATCH_RETAIN_RELEASE 207 | if (allData) dispatch_release(allData); 208 | #endif 209 | callback([[NSError alloc] initWithDomain:PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0); 210 | return; 211 | } 212 | 213 | PTFrame *frame = NULL; 214 | size_t size = 0; 215 | 216 | PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(allData, (const void **)&frame, &size); // precise lifetime guarantees bytes in frame will stay valid till the end of scope 217 | #if PT_DISPATCH_RETAIN_RELEASE 218 | dispatch_release(allData); 219 | #endif 220 | if (!contiguousData) { 221 | callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], 0, 0, 0); 222 | return; 223 | } 224 | 225 | frame->version = ntohl(frame->version); 226 | if (frame->version != PTProtocolVersion1) { 227 | callback([[NSError alloc] initWithDomain:PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0); 228 | } else { 229 | frame->type = ntohl(frame->type); 230 | frame->tag = ntohl(frame->tag); 231 | frame->payloadSize = ntohl(frame->payloadSize); 232 | callback(nil, frame->type, frame->tag, frame->payloadSize); 233 | } 234 | 235 | #if PT_DISPATCH_RETAIN_RELEASE 236 | dispatch_release(contiguousData); 237 | #endif 238 | } 239 | }); 240 | } 241 | 242 | 243 | - (void)readPayloadOfSize:(size_t)payloadSize overChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize))callback { 244 | __block dispatch_data_t allData = NULL; 245 | dispatch_io_read(channel, 0, payloadSize, queue_, ^(bool done, dispatch_data_t data, int error) { 246 | //NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error); 247 | size_t dataSize = dispatch_data_get_size(data); 248 | 249 | if (dataSize) { 250 | if (!allData) { 251 | allData = data; 252 | #if PT_DISPATCH_RETAIN_RELEASE 253 | dispatch_retain(allData); 254 | #endif 255 | } else { 256 | #if PT_DISPATCH_RETAIN_RELEASE 257 | dispatch_data_t allDataPrev = allData; 258 | allData = dispatch_data_create_concat(allData, data); 259 | dispatch_release(allDataPrev); 260 | #else 261 | allData = dispatch_data_create_concat(allData, data); 262 | #endif 263 | } 264 | } 265 | 266 | if (done) { 267 | if (error != 0) { 268 | #if PT_DISPATCH_RETAIN_RELEASE 269 | if (allData) dispatch_release(allData); 270 | #endif 271 | callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], NULL, NULL, 0); 272 | return; 273 | } 274 | 275 | if (dataSize == 0) { 276 | #if PT_DISPATCH_RETAIN_RELEASE 277 | if (allData) dispatch_release(allData); 278 | #endif 279 | callback(nil, NULL, NULL, 0); 280 | return; 281 | } 282 | 283 | uint8_t *buffer = NULL; 284 | size_t bufferSize = 0; 285 | PT_PRECISE_LIFETIME dispatch_data_t contiguousData = NULL; 286 | 287 | if (allData) { 288 | contiguousData = dispatch_data_create_map(allData, (const void **)&buffer, &bufferSize); 289 | #if PT_DISPATCH_RETAIN_RELEASE 290 | dispatch_release(allData); allData = NULL; 291 | #endif 292 | if (!contiguousData) { 293 | callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], NULL, NULL, 0); 294 | return; 295 | } 296 | } 297 | 298 | callback(nil, contiguousData, buffer, bufferSize); 299 | #if PT_DISPATCH_RETAIN_RELEASE 300 | if (contiguousData) dispatch_release(contiguousData); 301 | #endif 302 | } 303 | }); 304 | } 305 | 306 | 307 | - (void)readAndDiscardDataOfSize:(size_t)size overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*, BOOL))callback { 308 | dispatch_io_read(channel, 0, size, queue_, ^(bool done, dispatch_data_t data, int error) { 309 | if (done && callback) { 310 | size_t dataSize = data ? dispatch_data_get_size(data) : 0; 311 | callback(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], dataSize == 0); 312 | } 313 | }); 314 | } 315 | 316 | 317 | - (void)readFramesOverChannel:(dispatch_io_t)channel onFrame:(void(^)(NSError*, uint32_t, uint32_t, uint32_t, dispatch_block_t))onFrame { 318 | [self readFrameOverChannel:channel callback:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize) { 319 | onFrame(error, type, tag, payloadSize, ^{ 320 | if (type != PTFrameTypeEndOfStream) { 321 | [self readFramesOverChannel:channel onFrame:onFrame]; 322 | } 323 | }); 324 | }]; 325 | } 326 | 327 | 328 | @end 329 | 330 | 331 | @interface _PTDispatchData : NSObject { 332 | dispatch_data_t dispatchData_; 333 | } 334 | @end 335 | @implementation _PTDispatchData 336 | - (id)initWithDispatchData:(dispatch_data_t)dispatchData { 337 | if (!(self = [super init])) return nil; 338 | dispatchData_ = dispatchData; 339 | #if PT_DISPATCH_RETAIN_RELEASE 340 | dispatch_retain(dispatchData_); 341 | #endif 342 | return self; 343 | } 344 | - (void)dealloc { 345 | #if PT_DISPATCH_RETAIN_RELEASE 346 | if (dispatchData_) dispatch_release(dispatchData_); 347 | #endif 348 | } 349 | @end 350 | 351 | @implementation NSData (PTProtocol) 352 | 353 | #pragma clang diagnostic push 354 | #pragma clang diagnostic ignored "-Wunused-getter-return-value" 355 | 356 | - (dispatch_data_t)createReferencingDispatchData { 357 | // Note: The queue is used to submit the destructor. Since we only perform an 358 | // atomic release of self, it doesn't really matter which queue is used, thus 359 | // we use the current calling queue. 360 | return dispatch_data_create((const void*)self.bytes, self.length, dispatch_get_main_queue(), ^{ 361 | // trick to have the block capture the data, thus retain/releasing 362 | [self length]; 363 | }); 364 | } 365 | 366 | #pragma clang diagnostic pop 367 | 368 | + (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data { 369 | if (!data) { 370 | return nil; 371 | } 372 | uint8_t *buffer = NULL; 373 | size_t bufferSize = 0; 374 | PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize); 375 | if (!contiguousData) { 376 | return nil; 377 | } 378 | 379 | _PTDispatchData *dispatchDataRef = [[_PTDispatchData alloc] initWithDispatchData:contiguousData]; 380 | NSData *newData = [NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO]; 381 | #if PT_DISPATCH_RETAIN_RELEASE 382 | dispatch_release(contiguousData); 383 | #endif 384 | static const bool kDispatchDataRefKey; 385 | objc_setAssociatedObject(newData, (const void*)kDispatchDataRefKey, dispatchDataRef, OBJC_ASSOCIATION_RETAIN); 386 | 387 | return newData; 388 | } 389 | 390 | @end 391 | 392 | 393 | @implementation NSDictionary (PTProtocol) 394 | 395 | - (dispatch_data_t)createReferencingDispatchData { 396 | NSError *error = nil; 397 | NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error]; 398 | if (!plistData) { 399 | NSLog(@"Failed to serialize property list: %@", error); 400 | return nil; 401 | } else { 402 | return [plistData createReferencingDispatchData]; 403 | } 404 | } 405 | 406 | // Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure. 407 | + (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data { 408 | if (!data) { 409 | return nil; 410 | } 411 | uint8_t *buffer = NULL; 412 | size_t bufferSize = 0; 413 | PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize); 414 | if (!contiguousData) { 415 | return nil; 416 | } 417 | NSDictionary *dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:nil]; 418 | #if PT_DISPATCH_RETAIN_RELEASE 419 | dispatch_release(contiguousData); 420 | #endif 421 | return dict; 422 | } 423 | 424 | @end 425 | -------------------------------------------------------------------------------- /Client/KKConnectorClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKConnectorClient.m 3 | // KKConnectorClient 4 | // 5 | // Created by likai123 on 2020/11/10. 6 | // 7 | 8 | #import "KKConnectorClient.h" 9 | #import "PTChannel.h" 10 | #import "KKConnectorPort.h" 11 | #import "KKConnectorDefines.h" 12 | #import "KKConnectorRequestKeeper.h" 13 | #import "PTChannel+KKConnector.h" 14 | #import "KKConnectorResponse.h" 15 | #import "KKConnectorError.h" 16 | #import "KKConnectorRequest.h" 17 | #import "KKConnectorApp.h" 18 | 19 | @interface KKConnectorClient () 20 | 21 | @property(nonatomic, assign) uint32_t appID; 22 | @property(nonatomic, strong) id delegate; 23 | 24 | @property(nonatomic, copy) NSArray *allSimulatorPorts; 25 | @property(nonatomic, strong) NSMutableArray *allUSBPorts; 26 | 27 | @property(nonatomic, strong) NSMutableArray *foundApps; 28 | 29 | @end 30 | 31 | @implementation KKConnectorClient 32 | 33 | + (instancetype)sharedInstance { 34 | static KKConnectorClient *sharedInstance; 35 | static dispatch_once_t onceToken; 36 | dispatch_once(&onceToken, ^{ 37 | sharedInstance = [[KKConnectorClient alloc] init]; 38 | }); 39 | return sharedInstance; 40 | } 41 | 42 | - (instancetype)init { 43 | if (self = [super init]) { 44 | self.allSimulatorPorts = ({ 45 | NSMutableArray *ports = [NSMutableArray array]; 46 | for (int number = KKConnectorSimPortStart; number <= KKConnectorSimPortEnd; number++) { 47 | KKConnectorSimulatorPort *port = [KKConnectorSimulatorPort new]; 48 | port.portNumber = number; 49 | [ports addObject:port]; 50 | } 51 | ports; 52 | }); 53 | self.allUSBPorts = [NSMutableArray array]; 54 | 55 | [self startUSBListening]; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)startUSBListening { 61 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 62 | 63 | [nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { 64 | NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; 65 | 66 | /// 仅一台真机 device 上的所有 app 共享同一批端口(在 Lookin 里是 47175 ~ 47179 这 5 个),不同真机互不影响。比如依次启动“真机 A 的 app1”、“真机 A 的 app2”、“真机 B 的 app3”,则它们依次会占用 47175、47176、47175(注意不是 47177)这几个端口 67 | for (int number = KKConnectorUSBPortStart; number <= KKConnectorUSBPortEnd; number++) { 68 | KKConnectorUSBPort *port = [KKConnectorUSBPort new]; 69 | port.portNumber = number; 70 | port.deviceID = deviceID; 71 | [self.allUSBPorts addObject:port]; 72 | } 73 | NSLog(@"KKConnector - USB 设备插入,DeviceID: %@", deviceID); 74 | }]; 75 | 76 | [nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { 77 | NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; 78 | [self.allUSBPorts.copy enumerateObjectsUsingBlock:^(KKConnectorUSBPort * _Nonnull port, NSUInteger idx, BOOL * _Nonnull stop) { 79 | if ([port.deviceID isEqual:deviceID]) { 80 | [self.allUSBPorts removeObject:port]; 81 | } 82 | }]; 83 | NSLog(@"KKConnector - USB 设备拔出,DeviceID: %@", deviceID); 84 | }]; 85 | } 86 | 87 | - (void)searchApps:(void (^)(NSArray *))completion { 88 | [self searchConnectedChannels:^(NSArray *channels) { 89 | NSUInteger callbackTotalCount = channels.count; 90 | if (callbackTotalCount == 0) { 91 | self.foundApps = @[]; 92 | completion(@[]); 93 | return; 94 | } 95 | 96 | __block NSUInteger callbackCount = 0; 97 | NSMutableArray *resultApps = [NSMutableArray array]; 98 | 99 | for (PTChannel *channel in channels) { 100 | [self pingChannel:channel timeout:1 completion:^(PTChannel *connectedChannel, NSString *serverProtocolVersion, NSString *sessionID, NSError *error) { 101 | if (error == nil) { 102 | KKConnectorApp *app = [[KKConnectorApp alloc] initWithChannel:connectedChannel serverProtocolVersion:serverProtocolVersion sessionID:sessionID]; 103 | [resultApps addObject:app]; 104 | } 105 | callbackCount++; 106 | if (callbackCount >= callbackTotalCount) { 107 | [resultApps sortUsingComparator:^NSComparisonResult(KKConnectorApp * _Nonnull obj1, KKConnectorApp * _Nonnull obj2) { 108 | return [obj1.sessionID compare:obj2.sessionID]; 109 | }]; 110 | self.foundApps = resultApps; 111 | completion(resultApps); 112 | } 113 | }]; 114 | } 115 | }]; 116 | } 117 | 118 | - (void)pingChannel:(PTChannel *)channel timeout:(NSTimeInterval)timeoutInterval completion:(void (^)(PTChannel *channel, NSString *serverProtocolVersion, NSString *sessionID, NSError *error))completion { 119 | [self requestWithChannel:channel header:KKConnectorHeaderPing body:nil timeoutInterval:timeoutInterval succ:^(NSObject *data) { 120 | if (![data isKindOfClass:[NSDictionary class]]) { 121 | completion(nil, nil, nil, [KKConnectorError inner]); 122 | return; 123 | } 124 | NSDictionary *dict = data; 125 | NSString *protocolVersion = dict[@"version"]; 126 | NSString *sessionID = dict[@"session"]; 127 | completion(channel, protocolVersion, sessionID, nil); 128 | } fail:^(NSError *error) { 129 | completion(nil, nil, nil, error); 130 | } completion:nil]; 131 | } 132 | 133 | /// 尝试连接所有可能的 Simulator 和 USB 端口,data 为 NSArray,即所有成功连接的 PTChannel(虽然成功连接但是 app 可能在后台之类的无法执行代码) 134 | - (void)searchConnectedChannels:(void (^)(NSArray *))completion { 135 | __block int callbackCount = 0; 136 | __block int callbackTotalCount = 2; 137 | NSMutableArray *resultChannels = [NSMutableArray array]; 138 | [self tryToConnectAllUSBDevices:^(NSArray *channels) { 139 | [resultChannels addObjectsFromArray:channels]; 140 | 141 | callbackCount++; 142 | if (callbackCount >= callbackTotalCount) { 143 | completion(resultChannels); 144 | } 145 | }]; 146 | [self tryToConnectAllSimulatorPorts:^(NSArray *channels) { 147 | [resultChannels addObjectsFromArray:channels]; 148 | 149 | callbackCount++; 150 | if (callbackCount >= callbackTotalCount) { 151 | completion(resultChannels); 152 | } 153 | }]; 154 | } 155 | 156 | /// 返回所有已成功连接的 PTChannel 数组 157 | - (void)tryToConnectAllSimulatorPorts:(void (^)(NSArray *))completion { 158 | NSMutableArray *resultChannels = [NSMutableArray array]; 159 | __block NSUInteger callbackCount = 0; 160 | __block NSUInteger callbackTotalCount = self.allSimulatorPorts.count; 161 | 162 | [self.allSimulatorPorts enumerateObjectsUsingBlock:^(KKConnectorSimulatorPort * _Nonnull port, NSUInteger idx, BOOL * _Nonnull stop) { 163 | [self connectToSimulatorPort:port completion:^(PTChannel *channel, NSError *error) { 164 | if (channel) { 165 | [resultChannels addObject:channel]; 166 | } 167 | callbackCount++; 168 | if (callbackCount >= callbackTotalCount) { 169 | completion(resultChannels); 170 | } 171 | }]; 172 | }]; 173 | } 174 | 175 | /// 返回的 x 为成功链接的 PTChannel 176 | /// 注意,如果某个 app 被退到了后台但是没有被 kill,则在这个方法里它的 channel 仍然会被成功连接 177 | - (void)connectToSimulatorPort:(KKConnectorSimulatorPort *)port completion:(void (^)(PTChannel *, NSError *))completion { 178 | if (port.connectedChannel) { 179 | // 该 port 本来就已经成功连接 180 | completion(port.connectedChannel, nil); 181 | return; 182 | } 183 | 184 | PTChannel *localChannel = [PTChannel channelWithDelegate:self]; 185 | [localChannel connectToPort:port.portNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, PTAddress *address) { 186 | if (error) { 187 | if (error.domain == NSPOSIXErrorDomain && (error.code == ECONNREFUSED || error.code == ETIMEDOUT)) { 188 | // 没有 iOS 客户端 189 | } else { 190 | // 意外 191 | } 192 | [localChannel close]; 193 | completion(nil, error); 194 | } else { 195 | port.connectedChannel = localChannel; 196 | completion(localChannel, nil); 197 | } 198 | }]; 199 | } 200 | 201 | /// callback 是所有已成功连接的 PTChannel 数组 202 | - (void)tryToConnectAllUSBDevices:(void (^)(NSArray *))completion { 203 | if (!self.allUSBPorts.count) { 204 | completion(@[]); 205 | return; 206 | } 207 | 208 | __block NSUInteger callbackCount = 0; 209 | __block NSUInteger callbackTotalCount = self.allUSBPorts.count; 210 | NSMutableArray *resultChannels = [NSMutableArray array]; 211 | [self.allUSBPorts enumerateObjectsUsingBlock:^(KKConnectorUSBPort * _Nonnull port, NSUInteger idx, BOOL * _Nonnull stop) { 212 | [self connectToUSBPort:port completion:^(PTChannel *channel, NSError *error) { 213 | if (channel) { 214 | [resultChannels addObject:channel]; 215 | } 216 | callbackCount++; 217 | if (callbackCount >= callbackTotalCount) { 218 | completion(resultChannels); 219 | } 220 | }]; 221 | }]; 222 | } 223 | 224 | /// 返回的 x 为成功链接的 PTChannel 225 | - (void)connectToUSBPort:(KKConnectorUSBPort *)port completion:(void (^)(PTChannel *, NSError *))completion { 226 | if (port.connectedChannel) { 227 | // 该 port 本来就已经成功连接 228 | completion(port.connectedChannel, nil); 229 | return; 230 | } 231 | 232 | PTChannel *channel = [PTChannel channelWithDelegate:self]; 233 | [channel connectToPort:port.portNumber overUSBHub:PTUSBHub.sharedHub deviceID:port.deviceID callback:^(NSError *error) { 234 | if (error) { 235 | if (error.domain == PTUSBHubErrorDomain && error.code == PTUSBHubErrorConnectionRefused) { 236 | // error 237 | } else { 238 | // error 239 | } 240 | [channel close]; 241 | completion(nil, error); 242 | } else { 243 | // succ 244 | port.connectedChannel = channel; 245 | completion(channel, nil); 246 | } 247 | }]; 248 | } 249 | 250 | - (void)registerWithAppID:(uint32_t)appID delegate:(id)delegate { 251 | self.appID = appID; 252 | self.delegate = delegate; 253 | } 254 | 255 | - (BOOL)isServerVersionValid:(NSString *)serverVersion { 256 | if ([self.delegate respondsToSelector:@selector(validateKKConnectorServerProtocolVersion:)]) { 257 | return [self.delegate validateKKConnectorServerProtocolVersion:serverVersion]; 258 | } else { 259 | return NO; 260 | } 261 | } 262 | 263 | - (void)requestWithChannel:(PTChannel *)channel header:(NSString *)header body:(nullable NSObject *)body timeoutInterval:(NSTimeInterval)timeoutInterval succ:(void (^)(NSObject * _Nullable data))succBlock fail:(void (^)(NSError *error))failBlock completion:(void (^)(void))completionBlock { 264 | if (!channel) { 265 | NSAssert(NO, @""); 266 | if (failBlock) { 267 | failBlock(KKConnectorError.inner); 268 | } 269 | return; 270 | } 271 | if (!channel.isConnected) { 272 | if (failBlock) { 273 | failBlock(KKConnectorError.noConnect); 274 | } 275 | return; 276 | } 277 | if (![header isEqualToString:KKConnectorHeaderPing]) { 278 | // 检查是否有相同 command 的旧请求尚在进行中,如果有则移除之前的旧请求(旧请求会被报告 error) 279 | NSMutableArray *uselessKeepers = [NSMutableArray array]; 280 | [channel.keepers enumerateObjectsUsingBlock:^(KKConnectorRequestKeeper * _Nonnull keeper, NSUInteger idx, BOOL * _Nonnull stop) { 281 | if ([keeper.header isEqualToString:header]) { 282 | [uselessKeepers addObject:keeper]; 283 | } 284 | }]; 285 | [uselessKeepers enumerateObjectsUsingBlock:^(KKConnectorRequestKeeper * _Nonnull keeper, NSUInteger idx, BOOL * _Nonnull stop) { 286 | if (keeper.failBlock) { 287 | keeper.failBlock(KKConnectorError.repeatingCommand); 288 | } 289 | [keeper endTimeoutCount]; 290 | [channel.keepers removeObject:keeper]; 291 | 292 | NSLog(@"KKConnector - will discard request, command:%@", keeper.header); 293 | }]; 294 | } 295 | 296 | KKConnectorRequestKeeper *keeper = [[KKConnectorRequestKeeper alloc] init]; 297 | keeper.header = header; 298 | keeper.tag = (uint32_t)[[NSDate date] timeIntervalSince1970]; 299 | keeper.succBlock = succBlock; 300 | keeper.failBlock = failBlock; 301 | keeper.completionBlock = completionBlock; 302 | keeper.timeoutInterval = timeoutInterval; 303 | __weak PTChannel *weakChannel = channel; 304 | keeper.timeoutBlock = ^(KKConnectorRequestKeeper *selfKeeper) { 305 | selfKeeper.failBlock(KKConnectorError.requestTimeout); 306 | [weakChannel.keepers removeObject:selfKeeper]; 307 | }; 308 | 309 | KKConnectorRequest *request = [KKConnectorRequest new]; 310 | request.header = header; 311 | request.body = body; 312 | NSError *archiveError = nil; 313 | dispatch_data_t payload = [[NSKeyedArchiver archivedDataWithRootObject:request requiringSecureCoding:YES error:&archiveError] createReferencingDispatchData]; 314 | if (archiveError) { 315 | NSAssert(NO, @""); 316 | } 317 | [channel sendFrameOfType:self.appID tag:keeper.tag withPayload:payload callback:^(NSError *error) { 318 | if (error) { 319 | if (failBlock) { 320 | failBlock(KKConnectorError.inner); 321 | } 322 | } else { 323 | // 成功发出了该 request 324 | if (!channel.keepers) { 325 | channel.keepers = [NSMutableArray array]; 326 | } 327 | [channel.keepers addObject:keeper]; 328 | [keeper resetTimeoutCount]; 329 | } 330 | }]; 331 | } 332 | 333 | - (void)pushInChannel:(PTChannel *)channel header:(NSString *)header body:(nullable NSObject *)body { 334 | if (!channel || !channel.isConnected) { 335 | return; 336 | } 337 | KKConnectorRequest *request = [KKConnectorRequest new]; 338 | request.header = header; 339 | request.body = body; 340 | NSError *archiveError = nil; 341 | dispatch_data_t payload = [[NSKeyedArchiver archivedDataWithRootObject:request requiringSecureCoding:YES error:&archiveError] createReferencingDispatchData]; 342 | if (archiveError) { 343 | NSAssert(NO, @""); 344 | } 345 | [channel sendFrameOfType:self.appID tag:0 withPayload:payload callback:^(NSError *error) { 346 | if (error) { 347 | NSLog(@"[KKConnector] Push failed. Header: %@, error: %@", header, error); 348 | } 349 | }]; 350 | } 351 | 352 | - (void)cancelRequestInChannel:(PTChannel *)channel header:(NSString *)header { 353 | NSLog(@"[KKConnector] Cancel request. Header: %@", header); 354 | 355 | if (!channel) { 356 | return; 357 | } 358 | KKConnectorRequestKeeper *keeper = [channel queryKeeperWithHeader:header]; 359 | if (!keeper) { 360 | return; 361 | } 362 | [keeper endTimeoutCount]; 363 | [channel.keepers removeObject:keeper]; 364 | if (keeper.completionBlock) { 365 | keeper.completionBlock(); 366 | } 367 | } 368 | 369 | - (KKConnectorApp *)queryAppWithChannel:(PTChannel *)channel { 370 | for (KKConnectorApp *app in self.foundApps) { 371 | if (app.channel == channel) { 372 | return app; 373 | } 374 | } 375 | return nil; 376 | } 377 | 378 | #pragma mark - 379 | 380 | - (BOOL)ioFrameChannel:(PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { 381 | uint32_t appID = type; 382 | if (appID != self.appID) { 383 | return NO; 384 | } 385 | 386 | if (tag == KKConnectorTagForPush) { 387 | return YES; 388 | } 389 | 390 | KKConnectorRequestKeeper *keeper = [channel queryKeeperWithTag:tag]; 391 | if (keeper == nil) { 392 | NSLog(@"KKConnector - will refuse, type:%@, tag:%@", @(type), @(tag)); 393 | return NO; 394 | } else { 395 | return YES; 396 | } 397 | } 398 | 399 | - (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData*)payload { 400 | if (tag == KKConnectorTagForPush) { 401 | KKConnectorApp *app = [self queryAppWithChannel:channel]; 402 | if (!app) { 403 | NSAssert(NO, @""); 404 | return; 405 | } 406 | 407 | NSData *data = [NSData dataWithContentsOfDispatchData:payload.dispatchData]; 408 | NSError *unarchiveError = nil; 409 | KKConnectorRequest *request = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSObject class] fromData:data error:&unarchiveError]; 410 | if (unarchiveError) { 411 | NSAssert(NO, @""); 412 | return; 413 | } 414 | if (![request isKindOfClass:[KKConnectorRequest class]]) { 415 | NSAssert(NO, @""); 416 | return; 417 | } 418 | [app.delegate kkConnectorApp:app didReceivePushWithHeader:request.header body:request.body]; 419 | return; 420 | } 421 | KKConnectorRequestKeeper *keeper = [channel queryKeeperWithTag:tag]; 422 | if (!keeper) { 423 | // 也许在 shouldAcceptFrameOfType 和 didReceiveFrame 两个时机之间,该 request 因为超时而被销毁了?有点玄学但确实偶尔会走到这里。 424 | return; 425 | } 426 | NSData *data = [NSData dataWithContentsOfDispatchData:payload.dispatchData]; 427 | NSError *unarchiveError = nil; 428 | KKConnectorResponse *response = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSObject class] fromData:data error:&unarchiveError]; 429 | if (unarchiveError) { 430 | [keeper endTimeoutCount]; 431 | [channel.keepers removeObject:keeper]; 432 | 433 | NSLog(@"unarchiveError:%@", unarchiveError); 434 | NSAssert(NO, @""); 435 | if (keeper.failBlock) { 436 | keeper.failBlock(KKConnectorError.inner); 437 | } 438 | return; 439 | } 440 | 441 | if (response.hasError) { 442 | [keeper endTimeoutCount]; 443 | [channel.keepers removeObject:keeper]; 444 | 445 | NSError *error = [NSError errorWithDomain:KKConnectorErrorDomain code:response.errorCode userInfo:@{NSLocalizedDescriptionKey:response.errorDescription}]; 446 | if (keeper.failBlock) { 447 | keeper.failBlock(error); 448 | } 449 | 450 | NSLog(@"KKConnector - request fail:%@", error); 451 | return; 452 | } 453 | 454 | if (keeper.succBlock) { 455 | NSObject *body = [response body]; 456 | keeper.succBlock(body); 457 | } 458 | 459 | keeper.receivedDataCount += response.thisSize; 460 | if (keeper.receivedDataCount < response.totalSize) { 461 | // 没收完,继续接收后续请求。每收到一次 response 则重置 timeout 倒计时 462 | [keeper resetTimeoutCount]; 463 | } else { 464 | // 收完了 465 | [keeper endTimeoutCount]; 466 | [channel.keepers removeObject:keeper]; 467 | if (keeper.completionBlock) { 468 | keeper.completionBlock(); 469 | } 470 | } 471 | } 472 | 473 | - (void)ioFrameChannel:(PTChannel*)channel didEndWithError:(NSError*)error { 474 | // iOS 客户端断开 475 | [self.allSimulatorPorts enumerateObjectsUsingBlock:^(KKConnectorSimulatorPort * _Nonnull port, NSUInteger idx, BOOL * _Nonnull stop) { 476 | if (port.connectedChannel == channel) { 477 | port.connectedChannel = nil; 478 | } 479 | }]; 480 | [self.allUSBPorts enumerateObjectsUsingBlock:^(KKConnectorUSBPort * _Nonnull port, NSUInteger idx, BOOL * _Nonnull stop) { 481 | if (port.connectedChannel == channel) { 482 | port.connectedChannel = nil; 483 | } 484 | }]; 485 | // [self.channelWillEnd sendNext:channel]; 486 | 487 | [channel close]; 488 | } 489 | 490 | @end 491 | -------------------------------------------------------------------------------- /Shared/Peertalk/PTChannel.m: -------------------------------------------------------------------------------- 1 | #import "PTChannel.h" 2 | #import "PTPrivate.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #import 10 | 11 | // Read member of sockaddr_in without knowing the family 12 | #define PT_SOCKADDR_ACCESS(ss, member4, member6) \ 13 | (((ss)->ss_family == AF_INET) ? ( \ 14 | ((const struct sockaddr_in *)(ss))->member4 \ 15 | ) : ( \ 16 | ((const struct sockaddr_in6 *)(ss))->member6 \ 17 | )) 18 | 19 | // Connection state (storage: uint8_t) 20 | #define kConnStateNone 0 21 | #define kConnStateConnecting 1 22 | #define kConnStateConnected 2 23 | #define kConnStateListening 3 24 | 25 | // Delegate support optimization (storage: uint8_t) 26 | #define kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize 1 27 | #define kDelegateFlagImplements_ioFrameChannel_didEndWithError 2 28 | #define kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress 4 29 | 30 | 31 | #pragma mark - 32 | // Note: We are careful about the size of this struct as each connected peer 33 | // implies one allocation of this struct. 34 | @interface PTChannel () { 35 | dispatch_io_t dispatchObj_channel_; 36 | dispatch_source_t dispatchObj_source_; 37 | NSError *endError_; // 64 bit 38 | @public // here be hacks 39 | id delegate_; // 64 bit 40 | uint8_t delegateFlags_; // 8 bit 41 | @private 42 | uint8_t connState_; // 8 bit 43 | //char padding_[6]; // 48 bit -- only if allocation speed is important 44 | } 45 | - (id)initWithProtocol:(PTProtocol*)protocol delegate:(id)delegate; 46 | - (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD; 47 | @end 48 | static const uint8_t kUserInfoKey; 49 | 50 | #pragma mark - 51 | @interface PTData () 52 | - (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length; 53 | @end 54 | 55 | #pragma mark - 56 | @interface PTAddress () { 57 | struct sockaddr_storage sockaddr_; 58 | } 59 | - (id)initWithSockaddr:(const struct sockaddr_storage*)addr; 60 | @end 61 | 62 | #pragma mark - 63 | @implementation PTChannel 64 | 65 | @synthesize protocol = protocol_; 66 | 67 | 68 | + (PTChannel*)channelWithDelegate:(id)delegate { 69 | return [[PTChannel alloc] initWithProtocol:[PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()] delegate:delegate]; 70 | } 71 | 72 | 73 | - (id)initWithProtocol:(PTProtocol*)protocol delegate:(id)delegate { 74 | if (!(self = [super init])) return nil; 75 | protocol_ = protocol; 76 | self.delegate = delegate; 77 | return self; 78 | } 79 | 80 | 81 | - (id)initWithProtocol:(PTProtocol*)protocol { 82 | if (!(self = [super init])) return nil; 83 | protocol_ = protocol; 84 | return self; 85 | } 86 | 87 | 88 | - (id)init { 89 | return [self initWithProtocol:[PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()]]; 90 | } 91 | 92 | 93 | - (void)dealloc { 94 | #if PT_DISPATCH_RETAIN_RELEASE 95 | if (dispatchObj_channel_) dispatch_release(dispatchObj_channel_); 96 | else if (dispatchObj_source_) dispatch_release(dispatchObj_source_); 97 | #endif 98 | } 99 | 100 | 101 | - (BOOL)isConnected { 102 | return connState_ == kConnStateConnecting || connState_ == kConnStateConnected; 103 | } 104 | 105 | 106 | - (BOOL)isListening { 107 | return connState_ == kConnStateListening; 108 | } 109 | 110 | 111 | - (id)userInfo { 112 | return objc_getAssociatedObject(self, (void*)&kUserInfoKey); 113 | } 114 | 115 | - (void)setUserInfo:(id)userInfo { 116 | objc_setAssociatedObject(self, (const void*)&kUserInfoKey, userInfo, OBJC_ASSOCIATION_RETAIN); 117 | } 118 | 119 | 120 | - (void)setConnState:(char)connState { 121 | connState_ = connState; 122 | } 123 | 124 | 125 | - (void)setDispatchChannel:(dispatch_io_t)channel { 126 | assert(connState_ == kConnStateConnecting || connState_ == kConnStateConnected || connState_ == kConnStateNone); 127 | dispatch_io_t prevChannel = dispatchObj_channel_; 128 | if (prevChannel != channel) { 129 | dispatchObj_channel_ = channel; 130 | #if PT_DISPATCH_RETAIN_RELEASE 131 | if (dispatchObj_channel_) dispatch_retain(dispatchObj_channel_); 132 | if (prevChannel) dispatch_release(prevChannel); 133 | #endif 134 | if (!dispatchObj_channel_ && !dispatchObj_source_) { 135 | connState_ = kConnStateNone; 136 | } 137 | } 138 | } 139 | 140 | 141 | - (void)setDispatchSource:(dispatch_source_t)source { 142 | assert(connState_ == kConnStateListening || connState_ == kConnStateNone); 143 | dispatch_source_t prevSource = dispatchObj_source_; 144 | if (prevSource != source) { 145 | dispatchObj_source_ = source; 146 | #if PT_DISPATCH_RETAIN_RELEASE 147 | if (dispatchObj_source_) dispatch_retain(dispatchObj_source_); 148 | if (prevSource) dispatch_release(prevSource); 149 | #endif 150 | if (!dispatchObj_channel_ && !dispatchObj_source_) { 151 | connState_ = kConnStateNone; 152 | } 153 | } 154 | } 155 | 156 | 157 | - (id)delegate { 158 | return delegate_; 159 | } 160 | 161 | 162 | - (void)setDelegate:(id)delegate { 163 | delegate_ = delegate; 164 | delegateFlags_ = 0; 165 | if (!delegate_) { 166 | return; 167 | } 168 | 169 | if ([delegate respondsToSelector:@selector(ioFrameChannel:shouldAcceptFrameOfType:tag:payloadSize:)]) { 170 | delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize; 171 | } 172 | 173 | if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didEndWithError:)]) { 174 | delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didEndWithError; 175 | } 176 | 177 | if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didAcceptConnection:fromAddress:)]) { 178 | delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress; 179 | } 180 | } 181 | 182 | 183 | //- (void)setFileDescriptor:(dispatch_fd_t)fd { 184 | // [self setDispatchChannel:dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) { 185 | // close(fd); 186 | // })]; 187 | //} 188 | 189 | 190 | #pragma mark - Connecting 191 | 192 | 193 | - (void)connectToPort:(int)port overUSBHub:(PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback { 194 | assert(protocol_ != NULL); 195 | if (connState_ != kConnStateNone) { 196 | if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]); 197 | return; 198 | } 199 | connState_ = kConnStateConnecting; 200 | [usbHub connectToDevice:deviceID port:port onStart:^(NSError *err, dispatch_io_t dispatchChannel) { 201 | NSError *error = err; 202 | if (!error) { 203 | [self startReadingFromConnectedChannel:dispatchChannel error:&error]; 204 | } else { 205 | self->connState_ = kConnStateNone; 206 | } 207 | if (callback) callback(error); 208 | } onEnd:^(NSError *error) { 209 | if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { 210 | [self->delegate_ ioFrameChannel:self didEndWithError:error]; 211 | } 212 | self->endError_ = nil; 213 | }]; 214 | } 215 | 216 | 217 | - (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, PTAddress *address))callback { 218 | assert(protocol_ != NULL); 219 | if (connState_ != kConnStateNone) { 220 | if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil], nil); 221 | return; 222 | } 223 | connState_ = kConnStateConnecting; 224 | 225 | int error = 0; 226 | 227 | // Create socket 228 | dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0); 229 | if (fd == -1) { 230 | perror("socket(AF_INET, SOCK_STREAM, 0) failed"); 231 | error = errno; 232 | if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil], nil); 233 | return; 234 | } 235 | 236 | // Connect socket 237 | struct sockaddr_in addr; 238 | bzero((char *)&addr, sizeof(addr)); 239 | 240 | addr.sin_len = sizeof(addr); 241 | addr.sin_family = AF_INET; 242 | addr.sin_port = htons(port); 243 | //addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 244 | //addr.sin_addr.s_addr = htonl(INADDR_ANY); 245 | addr.sin_addr.s_addr = htonl(address); 246 | 247 | // prevent SIGPIPE 248 | int on = 1; 249 | setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); 250 | 251 | // int socket, const struct sockaddr *address, socklen_t address_len 252 | if (connect(fd, (const struct sockaddr *)&addr, addr.sin_len) == -1) { 253 | //perror("connect"); 254 | error = errno; 255 | close(fd); 256 | if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil); 257 | return; 258 | } 259 | 260 | // get actual address 261 | //if (getsockname(fd, (struct sockaddr*)&addr, (socklen_t*)&addr.sin_len) == -1) { 262 | // error = errno; 263 | // close(fd); 264 | // if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil); 265 | // return; 266 | //} 267 | 268 | dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) { 269 | close(fd); 270 | if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { 271 | NSError *err = error == 0 ? self->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]; 272 | [self->delegate_ ioFrameChannel:self didEndWithError:err]; 273 | self->endError_ = nil; 274 | } 275 | }); 276 | 277 | if (!dispatchChannel) { 278 | close(fd); 279 | if (callback) callback([[NSError alloc] initWithDomain:@"PTError" code:0 userInfo:nil], nil); 280 | return; 281 | } 282 | 283 | // Success 284 | NSError *err = nil; 285 | PTAddress *ptAddr = [[PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr]; 286 | [self startReadingFromConnectedChannel:dispatchChannel error:&err]; 287 | if (callback) callback(err, ptAddr); 288 | } 289 | 290 | 291 | #pragma mark - Listening and serving 292 | 293 | 294 | - (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback { 295 | assert(dispatchObj_source_ == nil); 296 | 297 | // Create socket 298 | dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0); 299 | if (fd == -1) { 300 | if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); 301 | return; 302 | } 303 | 304 | // Connect socket 305 | struct sockaddr_in addr; 306 | bzero((char *)&addr, sizeof(addr)); 307 | 308 | addr.sin_family = AF_INET; 309 | addr.sin_port = htons(port); 310 | //addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 311 | //addr.sin_addr.s_addr = htonl(INADDR_ANY); 312 | addr.sin_addr.s_addr = htonl(address); 313 | 314 | socklen_t socklen = sizeof(addr); 315 | 316 | int on = 1; 317 | 318 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { 319 | close(fd); 320 | if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); 321 | return; 322 | } 323 | 324 | if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { 325 | close(fd); 326 | if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); 327 | return; 328 | } 329 | 330 | if (bind(fd, (struct sockaddr*)&addr, socklen) != 0) { 331 | close(fd); 332 | if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); 333 | return; 334 | } 335 | 336 | if (listen(fd, 512) != 0) { 337 | close(fd); 338 | if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); 339 | return; 340 | } 341 | 342 | [self setDispatchSource:dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, protocol_.queue)]; 343 | 344 | dispatch_source_set_event_handler(dispatchObj_source_, ^{ 345 | unsigned long nconns = dispatch_source_get_data(self->dispatchObj_source_); 346 | while ([self acceptIncomingConnection:fd] && --nconns); 347 | }); 348 | 349 | dispatch_source_set_cancel_handler(dispatchObj_source_, ^{ 350 | // Captures *self*, effectively holding a reference to *self* until cancelled. 351 | self->dispatchObj_source_ = nil; 352 | close(fd); 353 | if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { 354 | [self->delegate_ ioFrameChannel:self didEndWithError:self->endError_]; 355 | self->endError_ = nil; 356 | } 357 | }); 358 | 359 | dispatch_resume(dispatchObj_source_); 360 | //NSLog(@"%@ opened on fd #%d", self, fd); 361 | 362 | connState_ = kConnStateListening; 363 | if (callback) callback(nil); 364 | } 365 | 366 | 367 | - (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD { 368 | struct sockaddr_in addr; 369 | socklen_t addrLen = sizeof(addr); 370 | dispatch_fd_t clientSocketFD = accept(serverSocketFD, (struct sockaddr*)&addr, &addrLen); 371 | 372 | if (clientSocketFD == -1) { 373 | perror("accept()"); 374 | return NO; 375 | } 376 | 377 | // prevent SIGPIPE 378 | int on = 1; 379 | setsockopt(clientSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); 380 | 381 | if (fcntl(clientSocketFD, F_SETFL, O_NONBLOCK) == -1) { 382 | perror("fcntl(.. O_NONBLOCK)"); 383 | close(clientSocketFD); 384 | return NO; 385 | } 386 | 387 | if (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress) { 388 | PTChannel *peerChannel = [[PTChannel alloc] initWithProtocol:protocol_ delegate:delegate_]; 389 | __block PTChannel *localChannelRef = self; 390 | dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, clientSocketFD, protocol_.queue, ^(int error) { 391 | // Important note: This block captures *self*, thus a reference is held to 392 | // *self* until the fd is truly closed. 393 | localChannelRef = nil; 394 | 395 | close(clientSocketFD); 396 | 397 | if (peerChannel->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { 398 | NSError *err = error == 0 ? peerChannel->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]; 399 | [peerChannel->delegate_ ioFrameChannel:peerChannel didEndWithError:err]; 400 | peerChannel->endError_ = nil; 401 | } 402 | }); 403 | 404 | [peerChannel setConnState:kConnStateConnected]; 405 | [peerChannel setDispatchChannel:dispatchChannel]; 406 | 407 | assert(((struct sockaddr_storage*)&addr)->ss_len == addrLen); 408 | PTAddress *address = [[PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr]; 409 | [delegate_ ioFrameChannel:self didAcceptConnection:peerChannel fromAddress:address]; 410 | 411 | NSError *err = nil; 412 | if (![peerChannel startReadingFromConnectedChannel:dispatchChannel error:&err]) { 413 | NSLog(@"startReadingFromConnectedChannel failed in accept: %@", err); 414 | } 415 | } else { 416 | close(clientSocketFD); 417 | } 418 | return YES; 419 | } 420 | 421 | 422 | #pragma mark - Closing the channel 423 | 424 | 425 | - (void)close { 426 | if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) { 427 | dispatch_io_close(dispatchObj_channel_, DISPATCH_IO_STOP); 428 | [self setDispatchChannel:NULL]; 429 | } else if (connState_ == kConnStateListening && dispatchObj_source_) { 430 | dispatch_source_cancel(dispatchObj_source_); 431 | } 432 | } 433 | 434 | 435 | - (void)cancel { 436 | if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) { 437 | dispatch_io_close(dispatchObj_channel_, 0); 438 | [self setDispatchChannel:NULL]; 439 | } else if (connState_ == kConnStateListening && dispatchObj_source_) { 440 | dispatch_source_cancel(dispatchObj_source_); 441 | } 442 | } 443 | 444 | 445 | #pragma mark - Reading 446 | 447 | 448 | - (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error { 449 | if (connState_ != kConnStateNone && connState_ != kConnStateConnecting && connState_ != kConnStateConnected) { 450 | if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]; 451 | return NO; 452 | } 453 | 454 | if (dispatchObj_channel_ != channel) { 455 | [self close]; 456 | [self setDispatchChannel:channel]; 457 | } 458 | 459 | connState_ = kConnStateConnected; 460 | 461 | // helper 462 | BOOL(^handleError)(NSError*,BOOL) = ^BOOL(NSError *error, BOOL isEOS) { 463 | if (error) { 464 | //NSLog(@"Error while communicating: %@", error); 465 | self->endError_ = error; 466 | [self close]; 467 | return YES; 468 | } else if (isEOS) { 469 | [self cancel]; 470 | return YES; 471 | } 472 | return NO; 473 | }; 474 | 475 | [protocol_ readFramesOverChannel:channel onFrame:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize, dispatch_block_t resumeReadingFrames) { 476 | if (handleError(error, type == PTFrameTypeEndOfStream)) { 477 | return; 478 | } 479 | 480 | BOOL accepted = (channel == self->dispatchObj_channel_); 481 | if (accepted && (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize)) { 482 | accepted = [self->delegate_ ioFrameChannel:self shouldAcceptFrameOfType:type tag:tag payloadSize:payloadSize]; 483 | } 484 | 485 | if (payloadSize == 0) { 486 | if (accepted && self->delegate_) { 487 | [self->delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:nil]; 488 | } else { 489 | // simply ignore the frame 490 | } 491 | resumeReadingFrames(); 492 | } else { 493 | // has payload 494 | if (!accepted) { 495 | // Read and discard payload, ignoring frame 496 | [self->protocol_ readAndDiscardDataOfSize:payloadSize overChannel:channel callback:^(NSError *error, BOOL endOfStream) { 497 | if (!handleError(error, endOfStream)) { 498 | resumeReadingFrames(); 499 | } 500 | }]; 501 | } else { 502 | [self->protocol_ readPayloadOfSize:payloadSize overChannel:channel callback:^(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize) { 503 | if (handleError(error, bufferSize == 0)) { 504 | return; 505 | } 506 | 507 | if (self->delegate_) { 508 | PTData *payload = [[PTData alloc] initWithMappedDispatchData:contiguousData data:(void*)buffer length:bufferSize]; 509 | [self->delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:payload]; 510 | } 511 | 512 | resumeReadingFrames(); 513 | }]; 514 | } 515 | } 516 | }]; 517 | 518 | return YES; 519 | } 520 | 521 | 522 | #pragma mark - Sending 523 | 524 | - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback { 525 | if (connState_ == kConnStateConnecting || connState_ == kConnStateConnected) { 526 | [protocol_ sendFrameOfType:frameType tag:tag withPayload:payload overChannel:dispatchObj_channel_ callback:callback]; 527 | } else if (callback) { 528 | callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]); 529 | } 530 | } 531 | 532 | #pragma mark - NSObject 533 | 534 | - (NSString*)description { 535 | id userInfo = objc_getAssociatedObject(self, (void*)&kUserInfoKey); 536 | return [NSString stringWithFormat:@"", self, ( connState_ == kConnStateConnecting ? @"connecting" 537 | : connState_ == kConnStateConnected ? @"connected" 538 | : connState_ == kConnStateListening ? @"listening" 539 | : @"closed"), 540 | userInfo ? " " : "", userInfo ? userInfo : @""]; 541 | } 542 | 543 | 544 | @end 545 | 546 | 547 | #pragma mark - 548 | @implementation PTAddress 549 | 550 | - (id)initWithSockaddr:(const struct sockaddr_storage*)addr { 551 | if (!(self = [super init])) return nil; 552 | assert(addr); 553 | memcpy((void*)&sockaddr_, (const void*)addr, addr->ss_len); 554 | return self; 555 | } 556 | 557 | 558 | - (NSString*)name { 559 | if (sockaddr_.ss_len) { 560 | const void *sin_addr = NULL; 561 | size_t bufsize = 0; 562 | if (sockaddr_.ss_family == AF_INET6) { 563 | bufsize = INET6_ADDRSTRLEN; 564 | sin_addr = (const void *)&((const struct sockaddr_in6*)&sockaddr_)->sin6_addr; 565 | } else { 566 | bufsize = INET_ADDRSTRLEN; 567 | sin_addr = (const void *)&((const struct sockaddr_in*)&sockaddr_)->sin_addr; 568 | } 569 | char *buf = CFAllocatorAllocate(kCFAllocatorDefault, bufsize+1, 0); 570 | if (inet_ntop(sockaddr_.ss_family, sin_addr, buf, (unsigned int)bufsize-1) == NULL) { 571 | CFAllocatorDeallocate(kCFAllocatorDefault, buf); 572 | return nil; 573 | } 574 | return [[NSString alloc] initWithBytesNoCopy:(void*)buf length:strlen(buf) encoding:NSUTF8StringEncoding freeWhenDone:YES]; 575 | } else { 576 | return nil; 577 | } 578 | } 579 | 580 | 581 | - (NSInteger)port { 582 | if (sockaddr_.ss_len) { 583 | return ntohs(PT_SOCKADDR_ACCESS(&sockaddr_, sin_port, sin6_port)); 584 | } else { 585 | return 0; 586 | } 587 | } 588 | 589 | 590 | - (NSString*)description { 591 | if (sockaddr_.ss_len) { 592 | return [NSString stringWithFormat:@"%@:%u", self.name, (unsigned)self.port]; 593 | } else { 594 | return @"(?)"; 595 | } 596 | } 597 | 598 | @end 599 | 600 | 601 | #pragma mark - 602 | @implementation PTData 603 | 604 | @synthesize dispatchData = dispatchData_; 605 | @synthesize data = data_; 606 | @synthesize length = length_; 607 | 608 | - (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length { 609 | if (!(self = [super init])) return nil; 610 | dispatchData_ = mappedContiguousData; 611 | #if PT_DISPATCH_RETAIN_RELEASE 612 | if (dispatchData_) dispatch_retain(dispatchData_); 613 | #endif 614 | data_ = data; 615 | length_ = length; 616 | return self; 617 | } 618 | 619 | - (void)dealloc { 620 | #if PT_DISPATCH_RETAIN_RELEASE 621 | if (dispatchData_) dispatch_release(dispatchData_); 622 | #endif 623 | data_ = NULL; 624 | length_ = 0; 625 | } 626 | 627 | #pragma mark - NSObject 628 | 629 | - (NSString*)description { 630 | return [NSString stringWithFormat:@"", self, length_]; 631 | } 632 | 633 | @end 634 | -------------------------------------------------------------------------------- /Shared/Peertalk/PTUSBHub.m: -------------------------------------------------------------------------------- 1 | #import "PTUSBHub.h" 2 | #import "PTPrivate.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | NSString * const PTUSBHubErrorDomain = @"PTUSBHubError"; 11 | 12 | typedef uint32_t USBMuxPacketType; 13 | enum { 14 | USBMuxPacketTypeResult = 1, 15 | USBMuxPacketTypeConnect = 2, 16 | USBMuxPacketTypeListen = 3, 17 | USBMuxPacketTypeDeviceAdd = 4, 18 | USBMuxPacketTypeDeviceRemove = 5, 19 | // ? = 6, 20 | // ? = 7, 21 | USBMuxPacketTypePlistPayload = 8, 22 | }; 23 | 24 | typedef uint32_t USBMuxPacketProtocol; 25 | enum { 26 | USBMuxPacketProtocolBinary = 0, 27 | USBMuxPacketProtocolPlist = 1, 28 | }; 29 | 30 | typedef uint32_t USBMuxReplyCode; 31 | enum { 32 | USBMuxReplyCodeOK = 0, 33 | USBMuxReplyCodeBadCommand = 1, 34 | USBMuxReplyCodeBadDevice = 2, 35 | USBMuxReplyCodeConnectionRefused = 3, 36 | // ? = 4, 37 | // ? = 5, 38 | USBMuxReplyCodeBadVersion = 6, 39 | }; 40 | 41 | 42 | typedef struct usbmux_packet { 43 | uint32_t size; 44 | USBMuxPacketProtocol protocol; 45 | USBMuxPacketType type; 46 | uint32_t tag; 47 | char data[0]; 48 | } __attribute__((__packed__)) usbmux_packet_t; 49 | 50 | static const uint32_t kUsbmuxPacketMaxPayloadSize = UINT32_MAX - (uint32_t)sizeof(usbmux_packet_t); 51 | 52 | 53 | static uint32_t usbmux_packet_payload_size(usbmux_packet_t *upacket) { 54 | return upacket->size - sizeof(usbmux_packet_t); 55 | } 56 | 57 | 58 | static void *usbmux_packet_payload(usbmux_packet_t *upacket) { 59 | return (void*)upacket->data; 60 | } 61 | 62 | 63 | static void usbmux_packet_set_payload(usbmux_packet_t *upacket, 64 | const void *payload, 65 | uint32_t payloadLength) 66 | { 67 | memcpy(usbmux_packet_payload(upacket), payload, payloadLength); 68 | } 69 | 70 | 71 | static usbmux_packet_t *usbmux_packet_alloc(uint32_t payloadSize) { 72 | assert(payloadSize <= kUsbmuxPacketMaxPayloadSize); 73 | uint32_t upacketSize = sizeof(usbmux_packet_t) + payloadSize; 74 | usbmux_packet_t *upacket = CFAllocatorAllocate(kCFAllocatorDefault, upacketSize, 0); 75 | memset(upacket, 0, sizeof(usbmux_packet_t)); 76 | upacket->size = upacketSize; 77 | return upacket; 78 | } 79 | 80 | 81 | static usbmux_packet_t *usbmux_packet_create(USBMuxPacketProtocol protocol, 82 | USBMuxPacketType type, 83 | uint32_t tag, 84 | const void *payload, 85 | uint32_t payloadSize) 86 | { 87 | usbmux_packet_t *upacket = usbmux_packet_alloc(payloadSize); 88 | if (!upacket) { 89 | return NULL; 90 | } 91 | 92 | upacket->protocol = protocol; 93 | upacket->type = type; 94 | upacket->tag = tag; 95 | 96 | if (payload && payloadSize) { 97 | usbmux_packet_set_payload(upacket, payload, (uint32_t)payloadSize); 98 | } 99 | 100 | return upacket; 101 | } 102 | 103 | 104 | static void usbmux_packet_free(usbmux_packet_t *upacket) { 105 | CFAllocatorDeallocate(kCFAllocatorDefault, upacket); 106 | } 107 | 108 | 109 | NSString * const PTUSBDeviceDidAttachNotification = @"PTUSBDeviceDidAttachNotification"; 110 | NSString * const PTUSBDeviceDidDetachNotification = @"PTUSBDeviceDidDetachNotification"; 111 | 112 | static NSString *kPlistPacketTypeListen = @"Listen"; 113 | static NSString *kPlistPacketTypeConnect = @"Connect"; 114 | 115 | 116 | // Represents a channel of communication between the host process and a remote 117 | // (device) system. In practice, a PTUSBChannel is connected to a usbmuxd 118 | // endpoint which is configured to either listen for device changes (the 119 | // PTUSBHub's channel is usually configured as a device notification listener) or 120 | // configured as a TCP bridge (e.g. channels returned from PTUSBHub's 121 | // connectToDevice:port:callback:). You should not create channels yourself, but 122 | // let PTUSBHub provide you with already configured channels. 123 | @interface PTUSBChannel : NSObject { 124 | dispatch_io_t channel_; 125 | dispatch_queue_t queue_; 126 | uint32_t nextPacketTag_; 127 | NSMutableDictionary *responseQueue_; 128 | BOOL autoReadPackets_; 129 | BOOL isReadingPackets_; 130 | } 131 | 132 | // The underlying dispatch I/O channel. This is handy if you want to handle your 133 | // own I/O logic without PTUSBChannel. Remember to dispatch_retain() the channel 134 | // if you plan on using it as it might be released from the PTUSBChannel at any 135 | // point in time. 136 | @property (readonly) dispatch_io_t dispatchChannel; 137 | 138 | // The underlying file descriptor. 139 | @property (readonly) dispatch_fd_t fileDescriptor; 140 | 141 | // Send data 142 | - (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback; 143 | - (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback; 144 | 145 | // Read data 146 | - (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback; 147 | 148 | // Close the channel, preventing further reads and writes, but letting currently 149 | // queued reads and writes finish. 150 | - (void)cancel; 151 | 152 | // Close the channel, preventing further reads and writes, immediately 153 | // terminating any ongoing reads and writes. 154 | - (void)stop; 155 | 156 | @end 157 | 158 | 159 | @interface PTUSBChannel (Private) 160 | 161 | + (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload; 162 | - (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError *error))onEnd; 163 | - (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback; 164 | - (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error; 165 | - (uint32_t)nextPacketTag; 166 | - (void)sendPacketOfType:(USBMuxPacketType)type overProtocol:(USBMuxPacketProtocol)protocol tag:(uint32_t)tag payload:(NSData*)payload callback:(void(^)(NSError*))callback; 167 | - (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError *error))callback; 168 | - (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError *error, NSDictionary *responsePacket))callback; 169 | - (void)scheduleReadPacketWithCallback:(void(^)(NSError *error, NSDictionary *packet, uint32_t packetTag))callback; 170 | - (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler; 171 | - (void)setNeedsReadingPacket; 172 | @end 173 | 174 | 175 | @interface PTUSBHub () { 176 | PTUSBChannel *channel_; 177 | } 178 | - (void)handleBroadcastPacket:(NSDictionary*)packet; 179 | @end 180 | 181 | 182 | @implementation PTUSBHub 183 | 184 | 185 | + (PTUSBHub*)sharedHub { 186 | static PTUSBHub *gSharedHub; 187 | static dispatch_once_t onceToken; 188 | dispatch_once(&onceToken, ^{ 189 | gSharedHub = [PTUSBHub new]; 190 | [gSharedHub listenOnQueue:dispatch_get_main_queue() onStart:^(NSError *error) { 191 | if (error) { 192 | NSLog(@"PTUSBHub failed to initialize: %@", error); 193 | } 194 | } onEnd:nil]; 195 | }); 196 | return gSharedHub; 197 | } 198 | 199 | 200 | - (id)init { 201 | if (!(self = [super init])) return nil; 202 | 203 | return self; 204 | } 205 | 206 | 207 | - (void)listenOnQueue:(dispatch_queue_t)queue onStart:(void(^)(NSError*))onStart onEnd:(void(^)(NSError*))onEnd { 208 | if (channel_) { 209 | if (onStart) onStart(nil); 210 | return; 211 | } 212 | channel_ = [PTUSBChannel new]; 213 | NSError *error = nil; 214 | if ([channel_ openOnQueue:queue error:&error onEnd:onEnd]) { 215 | [channel_ listenWithBroadcastHandler:^(NSDictionary *packet) { [self handleBroadcastPacket:packet]; } callback:onStart]; 216 | } else if (onStart) { 217 | onStart(error); 218 | } 219 | } 220 | 221 | 222 | - (void)connectToDevice:(NSNumber*)deviceID port:(int)port onStart:(void(^)(NSError*, dispatch_io_t))onStart onEnd:(void(^)(NSError*))onEnd { 223 | PTUSBChannel *channel = [PTUSBChannel new]; 224 | NSError *error = nil; 225 | 226 | if (![channel openOnQueue:dispatch_get_main_queue() error:&error onEnd:onEnd]) { 227 | onStart(error, nil); 228 | return; 229 | } 230 | 231 | port = ((port<<8) & 0xFF00) | (port>>8); // limit 232 | 233 | NSDictionary *packet = [PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeConnect 234 | payload:[NSDictionary dictionaryWithObjectsAndKeys: 235 | deviceID, @"DeviceID", 236 | [NSNumber numberWithInt:port], @"PortNumber", 237 | nil]]; 238 | 239 | [channel sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) { 240 | NSError *error = error_; 241 | [channel errorFromPlistResponse:responsePacket error:&error]; 242 | onStart(error, (error ? nil : channel.dispatchChannel) ); 243 | }]; 244 | } 245 | 246 | 247 | - (void)handleBroadcastPacket:(NSDictionary*)packet { 248 | NSString *messageType = [packet objectForKey:@"MessageType"]; 249 | 250 | if ([@"Attached" isEqualToString:messageType]) { 251 | [[NSNotificationCenter defaultCenter] postNotificationName:PTUSBDeviceDidAttachNotification object:self userInfo:packet]; 252 | } else if ([@"Detached" isEqualToString:messageType]) { 253 | [[NSNotificationCenter defaultCenter] postNotificationName:PTUSBDeviceDidDetachNotification object:self userInfo:packet]; 254 | } else { 255 | NSLog(@"Warning: Unhandled broadcast message: %@", packet); 256 | } 257 | } 258 | 259 | 260 | @end 261 | 262 | #pragma mark - 263 | 264 | @implementation PTUSBChannel 265 | 266 | + (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload { 267 | NSDictionary *packet = nil; 268 | 269 | static NSString *bundleName = nil; 270 | static NSString *bundleVersion = nil; 271 | static dispatch_once_t onceToken; 272 | dispatch_once(&onceToken, ^{ 273 | NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary; 274 | if (infoDict) { 275 | bundleName = [infoDict objectForKey:@"CFBundleName"]; 276 | bundleVersion = [[infoDict objectForKey:@"CFBundleVersion"] description]; 277 | } 278 | }); 279 | 280 | if (bundleName) { 281 | packet = [NSDictionary dictionaryWithObjectsAndKeys: 282 | messageType, @"MessageType", 283 | bundleName, @"ProgName", 284 | bundleVersion, @"ClientVersionString", 285 | nil]; 286 | } else { 287 | packet = [NSDictionary dictionaryWithObjectsAndKeys:messageType, @"MessageType", nil]; 288 | } 289 | 290 | if (payload) { 291 | NSMutableDictionary *mpacket = [NSMutableDictionary dictionaryWithDictionary:payload]; 292 | [mpacket addEntriesFromDictionary:packet]; 293 | packet = mpacket; 294 | } 295 | 296 | return packet; 297 | } 298 | 299 | 300 | - (id)init { 301 | if (!(self = [super init])) return nil; 302 | 303 | return self; 304 | } 305 | 306 | 307 | - (void)dealloc { 308 | //NSLog(@"dealloc %@", self); 309 | if (channel_) { 310 | #if PT_DISPATCH_RETAIN_RELEASE 311 | dispatch_release(channel_); 312 | #endif 313 | channel_ = nil; 314 | } 315 | } 316 | 317 | 318 | - (BOOL)valid { 319 | return !!channel_; 320 | } 321 | 322 | 323 | - (dispatch_io_t)dispatchChannel { 324 | return channel_; 325 | } 326 | 327 | 328 | - (dispatch_fd_t)fileDescriptor { 329 | return dispatch_io_get_descriptor(channel_); 330 | } 331 | 332 | 333 | - (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError*))onEnd { 334 | assert(queue != nil); 335 | assert(channel_ == nil); 336 | queue_ = queue; 337 | 338 | // Create socket 339 | dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0); 340 | if (fd == -1) { 341 | if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; 342 | return NO; 343 | } 344 | 345 | // prevent SIGPIPE 346 | int on = 1; 347 | setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); 348 | 349 | // Connect socket 350 | struct sockaddr_un addr; 351 | addr.sun_family = AF_UNIX; 352 | strcpy(addr.sun_path, "/var/run/usbmuxd"); 353 | socklen_t socklen = sizeof(addr); 354 | if (connect(fd, (struct sockaddr*)&addr, socklen) == -1) { 355 | if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; 356 | return NO; 357 | } 358 | 359 | channel_ = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue_, ^(int error) { 360 | close(fd); 361 | if (onEnd) { 362 | onEnd(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]); 363 | } 364 | }); 365 | 366 | return YES; 367 | } 368 | 369 | 370 | - (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback { 371 | autoReadPackets_ = YES; 372 | [self scheduleReadPacketWithBroadcastHandler:broadcastHandler]; 373 | 374 | NSDictionary *packet = [PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeListen payload:nil]; 375 | 376 | [self sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) { 377 | if (!callback) 378 | return; 379 | 380 | NSError *error = error_; 381 | [self errorFromPlistResponse:responsePacket error:&error]; 382 | 383 | callback(error); 384 | }]; 385 | } 386 | 387 | 388 | - (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error { 389 | if (!*error) { 390 | NSNumber *n = [packet objectForKey:@"Number"]; 391 | 392 | if (!n) { 393 | *error = [NSError errorWithDomain:PTUSBHubErrorDomain code:(n ? n.integerValue : 0) userInfo:nil]; 394 | return NO; 395 | } 396 | 397 | USBMuxReplyCode replyCode = (USBMuxReplyCode)n.integerValue; 398 | if (replyCode != 0) { 399 | NSString *errmessage = @"Unspecified error"; 400 | switch (replyCode) { 401 | case USBMuxReplyCodeBadCommand: errmessage = @"illegal command"; break; 402 | case USBMuxReplyCodeBadDevice: errmessage = @"unknown device"; break; 403 | case USBMuxReplyCodeConnectionRefused: errmessage = @"connection refused"; break; 404 | case USBMuxReplyCodeBadVersion: errmessage = @"invalid version"; break; 405 | default: break; 406 | } 407 | *error = [NSError errorWithDomain:PTUSBHubErrorDomain code:replyCode userInfo:[NSDictionary dictionaryWithObject:errmessage forKey:NSLocalizedDescriptionKey]]; 408 | return NO; 409 | } 410 | } 411 | return YES; 412 | } 413 | 414 | 415 | - (uint32_t)nextPacketTag { 416 | return ++nextPacketTag_; 417 | } 418 | 419 | 420 | - (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError*, NSDictionary*))callback { 421 | uint32_t tag = [self nextPacketTag]; 422 | [self sendPacket:packet tag:tag callback:^(NSError *error) { 423 | if (error) { 424 | callback(error, nil); 425 | return; 426 | } 427 | // TODO: timeout un-triggered callbacks in responseQueue_ 428 | if (!self->responseQueue_) self->responseQueue_ = [NSMutableDictionary new]; 429 | [self->responseQueue_ setObject:callback forKey:[NSNumber numberWithUnsignedInt:tag]]; 430 | }]; 431 | 432 | // We are awaiting a response 433 | [self setNeedsReadingPacket]; 434 | } 435 | 436 | 437 | - (void)setNeedsReadingPacket { 438 | if (!isReadingPackets_) { 439 | [self scheduleReadPacketWithBroadcastHandler:nil]; 440 | } 441 | } 442 | 443 | 444 | - (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler { 445 | assert(isReadingPackets_ == NO); 446 | 447 | [self scheduleReadPacketWithCallback:^(NSError *error, NSDictionary *packet, uint32_t packetTag) { 448 | // Interpret the package we just received 449 | if (packetTag == 0) { 450 | // Broadcast message 451 | //NSLog(@"Received broadcast: %@", packet); 452 | if (broadcastHandler) broadcastHandler(packet); 453 | } else if (self->responseQueue_) { 454 | // Reply 455 | NSNumber *key = [NSNumber numberWithUnsignedInt:packetTag]; 456 | void(^requestCallback)(NSError*,NSDictionary*) = [self->responseQueue_ objectForKey:key]; 457 | if (requestCallback) { 458 | [self->responseQueue_ removeObjectForKey:key]; 459 | requestCallback(error, packet); 460 | } else { 461 | NSLog(@"Warning: Ignoring reply packet for which there is no registered callback. Packet => %@", packet); 462 | } 463 | } 464 | 465 | // Schedule reading another incoming package 466 | if (self->autoReadPackets_) { 467 | [self scheduleReadPacketWithBroadcastHandler:broadcastHandler]; 468 | } 469 | }]; 470 | } 471 | 472 | 473 | - (void)scheduleReadPacketWithCallback:(void(^)(NSError*, NSDictionary*, uint32_t))callback { 474 | static usbmux_packet_t ref_upacket; 475 | isReadingPackets_ = YES; 476 | 477 | // Read the first `sizeof(ref_upacket.size)` bytes off the channel_ 478 | dispatch_io_read(channel_, 0, sizeof(ref_upacket.size), queue_, ^(bool done, dispatch_data_t data, int error) { 479 | //NSLog(@"dispatch_io_read 0,4: done=%d data=%p error=%d", done, data, error); 480 | 481 | if (!done) 482 | return; 483 | 484 | if (error) { 485 | self->isReadingPackets_ = NO; 486 | callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0); 487 | return; 488 | } 489 | 490 | // Read size of incoming usbmux_packet_t 491 | uint32_t upacket_len = 0; 492 | char *buffer = NULL; 493 | size_t buffer_size = 0; 494 | 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 495 | assert(buffer_size == sizeof(ref_upacket.size)); 496 | assert(sizeof(upacket_len) == sizeof(ref_upacket.size)); 497 | memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size); 498 | #if PT_DISPATCH_RETAIN_RELEASE 499 | dispatch_release(map_data); 500 | #endif 501 | 502 | // Allocate a new usbmux_packet_t for the expected size 503 | uint32_t payloadLength = upacket_len - (uint32_t)sizeof(usbmux_packet_t); 504 | usbmux_packet_t *upacket = usbmux_packet_alloc(payloadLength); 505 | 506 | // Read rest of the incoming usbmux_packet_t 507 | off_t offset = sizeof(ref_upacket.size); 508 | dispatch_io_read(self->channel_, offset, (size_t)(upacket->size - offset), self->queue_, ^(bool done, dispatch_data_t data, int error) { 509 | //NSLog(@"dispatch_io_read X,Y: done=%d data=%p error=%d", done, data, error); 510 | 511 | if (!done) { 512 | return; 513 | } 514 | 515 | self->isReadingPackets_ = NO; 516 | 517 | if (error) { 518 | callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0); 519 | usbmux_packet_free(upacket); 520 | return; 521 | } 522 | 523 | if (upacket_len > kUsbmuxPacketMaxPayloadSize) { 524 | callback( 525 | [[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:1 userInfo:@{ 526 | NSLocalizedDescriptionKey:@"Received a packet that is too large"}], 527 | nil, 528 | 0 529 | ); 530 | usbmux_packet_free(upacket); 531 | return; 532 | } 533 | 534 | // Copy read bytes onto our usbmux_packet_t 535 | char *buffer = NULL; 536 | size_t buffer_size = 0; 537 | PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size); 538 | assert(buffer_size == upacket->size - offset); 539 | memcpy(((void *)(upacket))+offset, (const void *)buffer, buffer_size); 540 | #if PT_DISPATCH_RETAIN_RELEASE 541 | dispatch_release(map_data); 542 | #endif 543 | 544 | // We only support plist protocol 545 | if (upacket->protocol != USBMuxPacketProtocolPlist) { 546 | callback([[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package protocol" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag); 547 | usbmux_packet_free(upacket); 548 | return; 549 | } 550 | 551 | // Only one type of packet in the plist protocol 552 | if (upacket->type != USBMuxPacketTypePlistPayload) { 553 | callback([[NSError alloc] initWithDomain:PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package type" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag); 554 | usbmux_packet_free(upacket); 555 | return; 556 | } 557 | 558 | // Try to decode any payload as plist 559 | NSError *err = nil; 560 | NSDictionary *dict = nil; 561 | if (usbmux_packet_payload_size(upacket)) { 562 | dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err]; 563 | } 564 | 565 | // Invoke callback 566 | callback(err, dict, upacket->tag); 567 | usbmux_packet_free(upacket); 568 | }); 569 | }); 570 | } 571 | 572 | 573 | - (void)sendPacketOfType:(USBMuxPacketType)type 574 | overProtocol:(USBMuxPacketProtocol)protocol 575 | tag:(uint32_t)tag 576 | payload:(NSData*)payload 577 | callback:(void(^)(NSError*))callback 578 | { 579 | assert(payload.length <= kUsbmuxPacketMaxPayloadSize); 580 | usbmux_packet_t *upacket = usbmux_packet_create( 581 | protocol, 582 | type, 583 | tag, 584 | payload ? payload.bytes : nil, 585 | (uint32_t)(payload ? payload.length : 0) 586 | ); 587 | dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, queue_, ^{ 588 | // Free packet when data is freed 589 | usbmux_packet_free(upacket); 590 | }); 591 | //NSData *data1 = [NSData dataWithBytesNoCopy:(void*)upacket length:upacket->size freeWhenDone:NO]; 592 | //[data1 writeToFile:[NSString stringWithFormat:@"/Users/rsms/c-packet-%u.data", tag] atomically:NO]; 593 | [self sendDispatchData:data callback:callback]; 594 | } 595 | 596 | 597 | - (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError*))callback { 598 | NSError *error = nil; 599 | // NSPropertyListBinaryFormat_v1_0 600 | NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packet format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; 601 | if (!plistData) { 602 | callback(error); 603 | } else { 604 | [self sendPacketOfType:USBMuxPacketTypePlistPayload overProtocol:USBMuxPacketProtocolPlist tag:tag payload:plistData callback:callback]; 605 | } 606 | } 607 | 608 | 609 | - (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback { 610 | off_t offset = 0; 611 | dispatch_io_write(channel_, offset, data, queue_, ^(bool done, dispatch_data_t data, int _errno) { 612 | //NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, error); 613 | if (!done) 614 | return; 615 | if (callback) { 616 | NSError *err = nil; 617 | if (_errno) err = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]; 618 | callback(err); 619 | } 620 | }); 621 | #if PT_DISPATCH_RETAIN_RELEASE 622 | dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write 623 | #endif 624 | } 625 | 626 | #pragma clang diagnostic push 627 | #pragma clang diagnostic ignored "-Wunused-getter-return-value" 628 | 629 | - (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback { 630 | dispatch_data_t ddata = dispatch_data_create((const void*)data.bytes, data.length, queue_, ^{ 631 | // trick to have the block capture and retain the data 632 | [data length]; 633 | }); 634 | [self sendDispatchData:ddata callback:callback]; 635 | } 636 | 637 | #pragma clang diagnostic pop 638 | 639 | - (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback { 640 | dispatch_io_read(channel_, offset, length, queue_, ^(bool done, dispatch_data_t data, int _errno) { 641 | if (!done) 642 | return; 643 | 644 | NSError *error = nil; 645 | if (_errno != 0) { 646 | error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]; 647 | } 648 | 649 | callback(error, data); 650 | }); 651 | } 652 | 653 | 654 | - (void)cancel { 655 | if (channel_) { 656 | dispatch_io_close(channel_, 0); 657 | } 658 | } 659 | 660 | 661 | - (void)stop { 662 | if (channel_) { 663 | dispatch_io_close(channel_, DISPATCH_IO_STOP); 664 | } 665 | } 666 | 667 | @end 668 | --------------------------------------------------------------------------------