diff --git a/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m b/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m index 48c9c2f..1714603 100644 --- a/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m +++ b/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m @@ -16,6 +16,9 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. +#if !TARGET_OS_SIMULATOR + [NSNotificationCenter.defaultCenter postNotificationName:@"Lookin_startWirelessConnection" object:nil]; +#endif return YES; } diff --git a/LookinDemo/OC_Pod/LookinDemoOC/Info.plist b/LookinDemo/OC_Pod/LookinDemoOC/Info.plist index 81ed29b..7d1fc3e 100644 --- a/LookinDemo/OC_Pod/LookinDemoOC/Info.plist +++ b/LookinDemo/OC_Pod/LookinDemoOC/Info.plist @@ -2,6 +2,10 @@ + NSBonjourServices + + _Lookin._tcp + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/LookinServer.podspec b/LookinServer.podspec index c5ce5fc..ee38f9d 100644 --- a/LookinServer.podspec +++ b/LookinServer.podspec @@ -13,9 +13,10 @@ Pod::Spec.new do |spec| spec.source = { :git => "https://github.com/QMUI/LookinServer.git", :tag => "1.2.8"} spec.framework = "UIKit" spec.requires_arc = true - + spec.subspec 'Core' do |ss| ss.source_files = ['Src/Main/**/*', 'Src/Base/**/*'] + ss.exclude_files = 'Src/Main/Shared/Channel/**/*' ss.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER=1', 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER' @@ -37,7 +38,7 @@ Pod::Spec.new do |spec| 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_DISABLE_HOOK=1', } end - + # CocoaPods 不支持多个 subspecs 和 configurations 并列 # "pod 'LookinServer', :subspecs => ['Swift', 'NoHook'], :configurations => ['Debug']" is not supported by CocoaPods # https://github.com/QMUI/LookinServer/issues/134 @@ -50,4 +51,22 @@ Pod::Spec.new do |spec| } end + spec.subspec 'SwiftAndWireless' do |ss| + ss.dependency 'LookinShared/Wireless' + ss.dependency 'LookinServer/Core' + ss.source_files = 'Src/Swift/**/*' + ss.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_SWIFT_ENABLED=1 LOOKIN_SERVER_DISABLE_HOOK=1 LOOKIN_SERVER_WIRELESS=1', + 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) LOOKIN_SERVER_SWIFT_ENABLED', + } + end + + spec.subspec 'Wireless' do |ss| + ss.dependency 'LookinServer/Core' + ss.dependency 'LookinShared/Wireless' + ss.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_WIRELESS=1' + } + end + end diff --git a/LookinShared.podspec b/LookinShared.podspec index 8508083..05bcad9 100644 --- a/LookinShared.podspec +++ b/LookinShared.podspec @@ -17,7 +17,18 @@ Pod::Spec.new do |spec| 'Src/Main/Shared/**/*', 'Src/Base/**/*' ] + spec.exclude_files = 'Src/Main/Shared/Channel/**/*' + spec.default_subspec = :none + spec.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER=1' } + + spec.subspec 'Wireless' do |ss| + ss.source_files = 'Src/Main/Shared/Channel/**/*' + ss.dependency 'CocoaAsyncSocket' + ss.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER=1 LOOKIN_SERVER_WIRELESS=1' + } + end end diff --git a/README.md b/README.md index 548ceff..b0d275a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,23 @@ To use Lookin macOS app, you need to integrate LookinServer (iOS Framework of Lo `pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']` ### Objective-C Project `pod 'LookinServer', :configurations => ['Debug']` + +### [Optianal] Wireless Connection +`pod 'LookinServer/Wireless', :configurations => ['Debug']` + +And you need to add follow content to Info.plist. The name of `NSBonjourServices` **MUST** be `_Lookin._tcp`. + +```plist +NSLocalNetworkUsageDescription +Local Network Usage Description +NSBonjourServices + + _Lookin._tcp + +``` +Also, the wireless ability does not start automatically, you need to send a notification to turn it on. And you can also send a notification to turn it off.
+Notification Names: `Lookin_startWirelessConnection` and `Lookin_endWirelessConnection` + ## via Swift Package Manager: `https://github.com/QMUI/LookinServer/` @@ -44,7 +61,7 @@ Lookin 可以查看与修改 iOS App 里的 UI 对象,类似于 Xcode 自带 如果这是你的 iOS 项目第一次使用 Lookin,则需要先把 LookinServer 这款 iOS Framework 集成到你的 iOS 项目中。 > **Warning** -> +> > 1. 不要在 AppStore 模式下集成 LookinServer。 > 2. 不要使用早于 1.0.6 的版本,因为它包含一个严重 Bug,可能导致线上事故: https://qxh1ndiez2w.feishu.cn/wiki/Z9SpwT7zWiqvYvkBe7Lc6Disnab ## 通过 CocoaPods: @@ -53,6 +70,21 @@ Lookin 可以查看与修改 iOS App 里的 UI 对象,类似于 Xcode 自带 `pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']` ### Objective-C 项目 `pod 'LookinServer', :configurations => ['Debug']` +### [可选] 无线连接功能 +`pod 'LookinServer/Wireless', :configurations => ['Debug']` + +你需要将下面的内容添加进你的`Info.plist`。 `NSBonjourServices`的值**必须是**`_Lookin._tcp`。 + +```plist +NSLocalNetworkUsageDescription +Local Network Usage Description +NSBonjourServices + + _Lookin._tcp + +``` +并且,无线功能不会自动启动,需要你发送通知来开启该功能,也支持发送通知来关闭它。
+通知名称`Lookin_startWirelessConnection`和`Lookin_endWirelessConnection` ## 通过 Swift Package Manager: `https://github.com/QMUI/LookinServer/` diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.h b/Src/Main/Server/Connection/LKS_ConnectionManager.h index 684127b..2937484 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.h +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.h @@ -20,9 +20,21 @@ extern NSString *const LKS_ConnectionDidEndNotificationName; @property(nonatomic, assign) BOOL applicationIsActive; -- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag; +#if LOOKIN_SERVER_WIRELESS +- (void)startWirelessConnection; -- (void)pushData:(NSObject *)data type:(uint32_t)type; +- (void)endWirelessConnection; +#endif + +- (BOOL)isConnected; + +#if LOOKIN_SERVER_WIRELESS +- (BOOL)isWirelessConnnect; +#endif + +- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag isWireless:(BOOL)isWireless; + +- (void)pushData:(NSObject *)data type:(uint32_t)type isWireless:(BOOL)isWireless; @end diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.m b/Src/Main/Server/Connection/LKS_ConnectionManager.m index c1ec8fc..2bc15ad 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.m +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.m @@ -16,6 +16,11 @@ #import "LookinServerDefines.h" #import "LKS_TraceManager.h" #import "LKS_MultiplatformAdapter.h" +#import "ECOChannelManager.h" + +#if LOOKIN_SERVER_WIRELESS +@import CocoaAsyncSocket; +#endif NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName"; @@ -24,6 +29,12 @@ @interface LKS_ConnectionManager () @property(nonatomic, weak) Lookin_PTChannel *peerChannel_; @property(nonatomic, strong) LKS_RequestHandler *requestHandler; +@property(nonatomic, strong) LKS_RequestHandler *wirelessRequestHandler; + +@property(nonatomic, strong) ECOChannelManager *wirelessChannel; +@property(nonatomic, strong) ECOChannelDeviceInfo *wirelessDevice; + +@property BOOL hasStartWirelessConnnection; @end @@ -52,6 +63,13 @@ - (instancetype)init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_2D" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_3D" object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn2D:) name:@"Lookin_2D" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn3D:) name:@"Lookin_3D" object:nil]; +#if LOOKIN_SERVER_WIRELESS + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startWirelessConnection) name:@"Lookin_startWirelessConnection" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endWirelessConnection) name:@"Lookin_endWirelessConnection" object:nil]; +#endif [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { [[LKS_ExportManager sharedInstance] exportAndShare]; }]; @@ -61,10 +79,95 @@ - (instancetype)init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGetLookinInfo:) name:@"GetLookinInfo" object:nil]; self.requestHandler = [LKS_RequestHandler new]; + self.wirelessRequestHandler = LKS_RequestHandler.wireless; } return self; } +#if LOOKIN_SERVER_WIRELESS +- (void)startWirelessConnection { +#if TARGET_OS_SIMULATOR + NSLog(@"LookinServer - warning: you should not start Wireless Connection on Simulator. We wouldn't start it."); + return; +#endif + self.hasStartWirelessConnnection = YES; + if (!self.wirelessChannel) { +#if TARGET_OS_IPHONE + self.wirelessChannel = ECOChannelManager.new; + __weak __typeof(self) weakSelf = self; + // 接收到数据回调 + self.wirelessChannel.receivedBlock = ^(ECOChannelDeviceInfo *device, NSData *data, NSDictionary *extraInfo) { + NSLog(@"🚀 Lookin receivedBlock device:%@", device); + NSNumber *type = extraInfo[@"type"]; + NSNumber *tag = extraInfo[@"tag"]; + id object = nil; + id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) { + LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject; + object = attachment.data; + } else { + object = unarchivedObject; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf.wirelessRequestHandler handleRequestType:type.intValue tag:tag.intValue object:object]; + }); + }; + // 设备连接变更 + self.wirelessChannel.deviceBlock = ^(ECOChannelDeviceInfo *device, BOOL isConnected) { + NSLog(@"🚀 Lookin deviceBlock device:%@", device); + if ([device isEqual:weakSelf.wirelessDevice] && !isConnected) { + weakSelf.wirelessDevice = nil; + } + }; + // 授权状态变更回调 + self.wirelessChannel.authStateChangedBlock = ^(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState) { + NSLog(@"🚀 Lookin authStateChangedBlock device:%@ authState:%ld", device, authState); + if (authState == ECOAuthorizeResponseType_AllowAlways) { + weakSelf.wirelessDevice = device; + } + }; + // 请求授权状态认证回调 + self.wirelessChannel.requestAuthBlock = ^(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState) { + NSLog(@"🚀 Lookin requestAuthBlock device:%@ authState:%ld", device, authState); + NSString *title = @"Lookin 连接请求"; + NSString *message = [NSString stringWithFormat:@"%@ 的Lookin想要连接你的设备,如果你想启用调试功能,请选择允许", device.hostName ?: device.ipAddress]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *denyAction = [UIAlertAction actionWithTitle:@"拒绝" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [weakSelf.wirelessChannel sendAuthorizationMessageToDevice:device state:ECOAuthorizeResponseType_Deny showAuthAlert:NO]; + }]; + UIAlertAction *allowOnceAction = [UIAlertAction actionWithTitle:@"允许一次" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [weakSelf.wirelessChannel sendAuthorizationMessageToDevice:device state:ECOAuthorizeResponseType_AllowOnce showAuthAlert:NO]; + weakSelf.wirelessDevice = device; + }]; + UIAlertAction *allowAlwaysAction = [UIAlertAction actionWithTitle:@"始终允许" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [weakSelf.wirelessChannel sendAuthorizationMessageToDevice:device state:ECOAuthorizeResponseType_AllowAlways showAuthAlert:NO]; + weakSelf.wirelessDevice = device; + }]; + [alertController addAction:denyAction]; + [alertController addAction:allowOnceAction]; + [alertController addAction:allowAlwaysAction]; + + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; + [rootVC presentViewController:alertController animated:YES completion:nil]; + }); + }; +#endif + } +} + +- (void)endWirelessConnection { + self.hasStartWirelessConnnection = NO; + GCDAsyncSocket *asyncSocket = [self.wirelessChannel valueForKeyPath:@"socketChannel.cSocket"]; + if (asyncSocket) { + [asyncSocket setDelegate:nil]; + [asyncSocket disconnect]; + [self.wirelessChannel setValue:nil forKeyPath:@"socketChannel.cSocket"]; + } + self.wirelessChannel = nil; +} +#endif + - (void)_handleWillResignActiveNotification { self.applicationIsActive = NO; @@ -153,23 +256,43 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { - [self _sendData:data frameOfType:requestType tag:tag]; +- (BOOL)isConnected { +#if LOOKIN_SERVER_WIRELESS + return self.isWirelessConnnect || (self.peerChannel_ && self.peerChannel_.isConnected); +#else + return self.peerChannel_ && self.peerChannel_.isConnected; +#endif } -- (void)pushData:(NSObject *)data type:(uint32_t)type { - [self _sendData:data frameOfType:type tag:0]; +#if LOOKIN_SERVER_WIRELESS +- (BOOL)isWirelessConnnect { + return self.wirelessChannel.isConnected; } +#endif -- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag { - if (self.peerChannel_) { - NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; - dispatch_data_t payload = [archivedData createReferencingDispatchData]; - - [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) { - if (error) { - } - }]; +- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag isWireless:(BOOL)isWireless { + [self _sendData:data frameOfType:requestType tag:tag isWireless:isWireless]; +} + +- (void)pushData:(NSObject *)data type:(uint32_t)type isWireless:(BOOL)isWireless { + [self _sendData:data frameOfType:type tag:0 isWireless:isWireless]; +} + +- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag isWireless:(BOOL)isWireless { + NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; + if (isWireless) { + if (self.wirelessDevice.isConnected) { + [self.wirelessChannel sendPacket:archivedData extraInfo:@{@"tag": @(tag), @"type": @(frameOfType)} toDevice:self.wirelessDevice]; + } + } else { + if (self.peerChannel_) { + dispatch_data_t payload = [archivedData createReferencingDispatchData]; + + [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) { + if (error) { + } + }]; + } } } diff --git a/Src/Main/Server/Connection/LKS_RequestHandler.h b/Src/Main/Server/Connection/LKS_RequestHandler.h index 52ebe71..e05345b 100644 --- a/Src/Main/Server/Connection/LKS_RequestHandler.h +++ b/Src/Main/Server/Connection/LKS_RequestHandler.h @@ -12,6 +12,8 @@ @interface LKS_RequestHandler : NSObject ++ (instancetype)wireless; + - (BOOL)canHandleRequestType:(uint32_t)requestType; - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object; diff --git a/Src/Main/Server/Connection/LKS_RequestHandler.m b/Src/Main/Server/Connection/LKS_RequestHandler.m index c9d1572..f1be19c 100644 --- a/Src/Main/Server/Connection/LKS_RequestHandler.m +++ b/Src/Main/Server/Connection/LKS_RequestHandler.m @@ -31,6 +31,8 @@ @interface LKS_RequestHandler () @property(nonatomic, strong) NSMutableSet *activeDetailHandlers; +@property(nonatomic, assign) BOOL isWireless; + @end @implementation LKS_RequestHandler { @@ -60,6 +62,12 @@ - (instancetype)init { return self; } ++ (instancetype)wireless { + LKS_RequestHandler *handler = self.new; + handler.isWireless = YES; + return handler; +} + - (BOOL)canHandleRequestType:(uint32_t)requestType { if ([_validRequestTypes containsObject:@(requestType)]) { return YES; @@ -74,7 +82,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj if (![LKS_ConnectionManager sharedInstance].applicationIsActive) { responseAttachment.appIsInBackground = YES; } - [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeApp) { // 请求可用设备信息 @@ -90,7 +98,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new]; responseAttachment.data = appInfo; - [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeHierarchy) { // 从 LookinClient 1.0.4 开始有这个参数,之前是 nil @@ -105,7 +113,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new]; responseAttachment.data = [LookinHierarchyInfo staticInfoWithLookinVersion:clientVersion]; - [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeInbuiltAttrModification) { // 请求修改某个属性 @@ -116,7 +124,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj } else { attachment.data = data; } - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag isWireless:self.isWireless]; }]; } else if (requestType == LookinRequestTypeCustomAttrModification) { @@ -135,7 +143,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj attrAttachment.data = data; attrAttachment.dataTotalCount = dataTotalCount; attrAttachment.currentDataCount = 1; - [[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag isWireless:self.isWireless]; }]; } else if (requestType == LookinRequestTypeHierarchyDetails) { @@ -153,7 +161,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj attachment.data = details; attachment.dataTotalCount = responsesDataTotalCount; attachment.currentDataCount = details.count; - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag isWireless:self.isWireless]; } finishedBlock:^{ [self.activeDetailHandlers removeObject:handler]; @@ -166,7 +174,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj LookinConnectionResponseAttachment *attach = [LookinConnectionResponseAttachment new]; attach.data = lookinObj; - [[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeAllAttrGroups) { unsigned long oid = ((NSNumber *)object).unsignedLongValue; @@ -544,13 +552,13 @@ - (void)_handleInvokeWithObject:(NSObject *)obj selector:(SEL)selector resultDes - (void)_submitResponseWithError:(NSError *)error requestType:(uint32_t)requestType tag:(uint32_t)tag { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.error = error; - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag isWireless:self.isWireless]; } - (void)_submitResponseWithData:(NSObject *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.data = data; - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag isWireless:self.isWireless]; } @end diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.h b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.h new file mode 100644 index 0000000..fab2b28 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.h @@ -0,0 +1,19 @@ +// +// ECONetServiceBrowser.h +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import + +typedef void(^ECONetServiceBrowserResolvedAddressesBlock)(NSArray *addresses, NSString *hostName); + +@interface ECONetServiceBrowser : NSObject + +@property (nonatomic, copy) ECONetServiceBrowserResolvedAddressesBlock addressesBlock; + +- (void)startBrowsing; + +@end diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.m b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.m new file mode 100644 index 0000000..98166d4 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.m @@ -0,0 +1,126 @@ +// +// ECONetServiceBrowser.m +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import "ECONetServiceBrowser.h" +#import "LookinDefines.h" +#if TARGET_OS_IPHONE +#import +#endif + +@interface ECONetServiceBrowser() + + +@property (nonatomic, strong) NSMutableArray *services; +@property (nonatomic, strong) NSNetServiceBrowser *serviceBrowser; + +@end + +@implementation ECONetServiceBrowser + +- (instancetype)init { + self = [super init]; + if (self) { + + } + return self; +} +#pragma mark - Browser Services +//启动Bonjour服务搜索 +- (void)startBrowsing { + [self.services removeAllObjects]; + //创建Browser对象 + self.serviceBrowser = [[NSNetServiceBrowser alloc] init]; + self.serviceBrowser.includesPeerToPeer = YES; + [self.serviceBrowser setDelegate:self]; + [self.serviceBrowser searchForServicesOfType:LookinNetServiceType inDomain:LookinNetServiceDomain]; +} +//重置查找服务 +- (void)resetBrowserService { + [self.serviceBrowser stop]; + self.serviceBrowser = nil; + //重启查找 + [self startBrowsing]; +} +#pragma mark - NSNetServiceBrowserDelegate methods + +/* Sent to the NSNetServiceBrowser instance's delegate for each service discovered. If there are more services, moreComing will be YES. If for some reason handling discovered services requires significant processing, accumulating services until moreComing is NO and then doing the processing in bulk fashion may be desirable. + */ +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing { + NSLog(@"%s",__func__); + //解析服务 + [self.services addObject:service]; + service.delegate = self; + [service resolveWithTimeout:LookinNetServiceResolveAddressTimeout]; +} + +/* Sent to the NSNetServiceBrowser instance's delegate when a previously discovered service is no longer published. + */ +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing { + NSLog(@"%s",__func__); + //移除服务 + [self.services removeObject:service]; + service.delegate = nil; +} + +/* Sent to the NSNetServiceBrowser instance's delegate when an error in searching for domains or services has occurred. The error dictionary will contain two key/value pairs representing the error domain and code (see the NSNetServicesError enumeration above for error code constants). It is possible for an error to occur after a search has been started successfully. + */ +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary *)errorDict { + NSLog(@"%s",__func__); + //重试 +#if TARGET_OS_IPHONE + if (@available(iOS 14.0, *)) { + NSNetServicesError errorCode = [errorDict[@"NSNetServicesErrorCode"] integerValue]; + if (errorCode == -72008) { + //iOS14新增本地网络隐私权限,提示用户如何设置并忽略 + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + NSString *title = @"Echo 连接提示"; + NSString *message = @"由于iOS14本地网络权限限制,请在Info.plist中设置NSLocalNetworkUsageDescription和NSBonjourServices,详细内容见:https://github.com/didi/echo"; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + + }]; + [alertController addAction:confirmAction]; + UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; + [rootVC presentViewController:alertController animated:YES completion:nil]; + }); + NSLog(@">>Echo Warning:Bonjour服务错误,由于iOS14本地网络权限限制,请在Info.plist中设置NSLocalNetworkUsageDescription和NSBonjourServices,详细内容见:https://github.com/didi/echo"); + return; + } + } +#endif + [self resetBrowserService]; +} +#pragma mark - NSNetServiceDelegate methods + +/* Sent to the NSNetService instance's delegate when one or more addresses have been resolved for an NSNetService instance. Some NSNetService methods will return different results before and after a successful resolution. An NSNetService instance may get resolved more than once; truly robust clients may wish to resolve again after an error, or to resolve more than once. + */ +- (void)netServiceDidResolveAddress:(NSNetService *)sender { + NSLog(@"%s",__func__); + //解析address地址 + NSString *name = [sender name]; + NSString *hostName = name ?: [sender hostName]; + NSArray *addresses = [[sender addresses] copy]; + !self.addressesBlock ?: self.addressesBlock(addresses, hostName ?: @""); +} + +/* Sent to the NSNetService instance's delegate when the instance's previously running publication or resolution request has stopped. + */ +- (void)netServiceDidStop:(NSNetService *)sender { + NSLog(@"%s",__func__); +} + +#pragma mark - getters +- (NSMutableArray *)services { + if (!_services) { + _services = [[NSMutableArray alloc] initWithCapacity:0]; + } + return _services; +} + +@end diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.h b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.h new file mode 100644 index 0000000..d6fe588 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.h @@ -0,0 +1,15 @@ +// +// ECONetServicePublisher.h +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import + +@interface ECONetServicePublisher : NSObject + +- (void)startPublish; + +@end diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.m b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.m new file mode 100644 index 0000000..fca7c16 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.m @@ -0,0 +1,50 @@ +// +// ECONetServicePublisher.m +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import "ECONetServicePublisher.h" +#import "LookinDefines.h" + +@interface ECONetServicePublisher() + + +@property (nonatomic, strong) NSNetService *netService; +@end + +@implementation ECONetServicePublisher + +- (instancetype)init { + self = [super init]; + if (self) { + + } + return self; +} +#pragma mark - Publish Service +- (void)startPublish { + if (self.netService) { + [self.netService stop]; + self.netService.delegate = nil; + self.netService = nil; + } + self.netService = [[NSNetService alloc] initWithDomain:LookinNetServiceDomain type:LookinNetServiceType name:LookinNetServiceName port:LookinNetServicePortNumber]; + self.netService.delegate = self; + [self.netService publish]; +} +#pragma mark - NSNetServiceDelegate methods +/* Sent to the NSNetService instance's delegate when the publication of the instance is complete and successful. + */ +- (void)netServiceDidPublish:(NSNetService *)sender { + +} +/* Sent to the NSNetService instance's delegate when an error in publishing the instance occurs. The error dictionary will contain two key/value pairs representing the error domain and code (see the NSNetServicesError enumeration above for error code constants). It is possible for an error to occur after a successful publication. + */ +- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict { + +} + +@end diff --git a/Src/Main/Shared/Channel/ECOBaseChannel.h b/Src/Main/Shared/Channel/ECOBaseChannel.h new file mode 100644 index 0000000..74738b7 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOBaseChannel.h @@ -0,0 +1,57 @@ +// +// ECOBaseChannel.h +// Echo +// +// Created by 陈爱彬 on 2019/4/18. Maintain by 陈爱彬 +// Description +// + +#import +#import "ECOChannelDeviceInfo.h" + +// 通道优先级,usb > socket +typedef NS_ENUM(NSInteger, ECOChannelPriority) { + ECOChannelPriority_Socket = 0, + ECOChannelPriority_USB, +}; + +typedef NS_ENUM(NSInteger, ECOChannelConnectType) { + ECOChannelConnectType_Authorization = 0, //授权连接 + ECOChannelConnectType_Auto, //自动连接 +}; + +@class ECOBaseChannel; +@protocol ECOChannelConnectedDeviceProtocol + +@optional +//新的设备连接 +- (void)channel:(ECOBaseChannel *)channel didConnectedToDevice:(ECOChannelDeviceInfo *)device; +//设备已断开 +- (void)channel:(ECOBaseChannel *)channel didDisconnectWithDevice:(ECOChannelDeviceInfo *)device; +//接收到数据 +- (void)channel:(ECOBaseChannel *)channel didReceivedDevice:(ECOChannelDeviceInfo *)device andData:(NSData *)data extraInfo:(NSDictionary *)extraInfo; + +//设备变更了授权状态 +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device didChangedAuthState:(ECOAuthorizeResponseType)authorizedType; + +//设备要请求授权状态,弹窗展示 +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device willRequestAuthState:(ECOAuthorizeResponseType)authorizedType; + +@end + +@interface ECOBaseChannel : NSObject + +@property (nonatomic, assign) ECOChannelPriority priority; +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign) ECOChannelConnectType connectType; + +- (instancetype)initWithDelegate:(id)delegate; +- (instancetype)init NS_UNAVAILABLE; + +//初始化Channel,供子类调用,该方法会在初始化后自动被调用 +- (void)setupChannel; + +// 是否已连接到Mac客户端 +- (BOOL)isConnected; + +@end diff --git a/Src/Main/Shared/Channel/ECOBaseChannel.m b/Src/Main/Shared/Channel/ECOBaseChannel.m new file mode 100644 index 0000000..caefdcf --- /dev/null +++ b/Src/Main/Shared/Channel/ECOBaseChannel.m @@ -0,0 +1,33 @@ +// +// ECOBaseChannel.m +// Echo +// +// Created by 陈爱彬 on 2019/4/18. Maintain by 陈爱彬 +// Description +// + +#import "ECOBaseChannel.h" + +@implementation ECOBaseChannel + +- (instancetype)initWithDelegate:(id)delegate { + self = [super init]; + if (self) { + self.delegate = delegate; + //目前使用授权连接机制,如果想自动连接已发现的设备,将该值改为ECOChannelConnectType_Auto + self.connectType = ECOChannelConnectType_Authorization; + [self setupChannel]; + } + return self; +} + +//初始化Channel,供子类调用,该方法会在初始化后自动被调用 +- (void)setupChannel { + +} + +// 是否已连接到Mac客户端 +- (BOOL)isConnected { + return NO; +} +@end diff --git a/Src/Main/Shared/Channel/ECOChannelAppInfo.h b/Src/Main/Shared/Channel/ECOChannelAppInfo.h new file mode 100644 index 0000000..ddaf933 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelAppInfo.h @@ -0,0 +1,27 @@ +// +// ECOChannelAppInfo.h +// EchoSDK +// +// Created by 陈爱彬 on 2019/10/30. Maintain by 陈爱彬 +// Description +// + +#import + +@interface ECOChannelAppInfo : NSObject + +@property (nonatomic, copy) NSString *appId; +@property (nonatomic, copy) NSString *appName; +@property (nonatomic, copy) NSString *appShortVersion; +@property (nonatomic, copy) NSString *appVersion; +@property (nonatomic, copy) NSString *appIcon; + +- (instancetype)initWithDictionary:(NSDictionary *)dict; + +- (NSDictionary *)toDictionary; + +//由外部设置通用的appid和appname ++ (void)setUniqueAppId:(NSString *)appId + appName:(NSString *)appName; + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelAppInfo.m b/Src/Main/Shared/Channel/ECOChannelAppInfo.m new file mode 100644 index 0000000..2cd0c98 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelAppInfo.m @@ -0,0 +1,85 @@ +// +// ECOChannelAppInfo.m +// EchoSDK +// +// Created by 陈爱彬 on 2019/10/30. Maintain by 陈爱彬 +// Description +// + +#import "ECOChannelAppInfo.h" + +static NSString *_ecoUniqueAppId = nil; +static NSString *_ecoUniqueAppName = nil; + +@implementation ECOChannelAppInfo + +- (instancetype)init { + self = [super init]; + if (self) { + if (_ecoUniqueAppId) { + self.appId = _ecoUniqueAppId; + }else{ + self.appId = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleIdentifierKey]; + } + if (_ecoUniqueAppName) { + self.appName = _ecoUniqueAppName; + }else{ + NSString *displayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; + NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey]; + self.appName = displayName ?: bundleName; + } + self.appShortVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: @""; + self.appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] ?: @""; +#if TARGET_OS_IPHONE + NSString *icon = [[[[NSBundle mainBundle] infoDictionary] valueForKeyPath:@"CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles"] lastObject]; + UIImage *appIcon = [UIImage imageNamed:icon]; + if (appIcon) { + NSData *iconData = UIImageJPEGRepresentation(appIcon, 1.0f); + NSString *encodedImageStr = [iconData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; + self.appIcon = encodedImageStr; + } +#endif + } + return self; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dict { + self = [super init]; + if (self) { + self.appId = dict[@"appId"] ?: @""; + self.appName = dict[@"appName"] ?: @""; + self.appShortVersion = dict[@"sVer"] ?: @""; + self.appVersion = dict[@"ver"] ?: @""; + self.appIcon = dict[@"appIcon"] ?: @""; + } + return self; +} + +- (NSDictionary *)toDictionary { + return @{@"appId": self.appId ?: @"", + @"appName": self.appName ?: @"", + @"sVer": self.appShortVersion ?: @"", + @"ver": self.appVersion ?: @"", + @"appIcon": self.appIcon ?: @"" + }; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ECOChannelAppInfo class]]) { + return NO; + } + ECOChannelAppInfo *project = (ECOChannelAppInfo *)object; + if (![project.appId isEqual:self.appId]) { + return NO; + } + return YES; +} + +//由外部设置通用的appid和appname ++ (void)setUniqueAppId:(NSString *)appId + appName:(NSString *)appName { + _ecoUniqueAppId = appId; + _ecoUniqueAppName = appName; +} + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h new file mode 100644 index 0000000..8d21f4c --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h @@ -0,0 +1,49 @@ +// +// ECOChannelDeviceInfo.h +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description 设备信息 +// + +#import +#import "ECOChannelAppInfo.h" + +typedef NS_ENUM(NSInteger, ECODeviceType) { + ECODeviceType_Simulator = 0, //模拟器 + ECODeviceType_Device, //真机 + ECODeviceType_iPad_Device, //iPad真机 + ECODeviceType_MacApp, //Mac客户端 +}; + +typedef NS_ENUM(NSInteger, ECOAuthorizeResponseType) { + ECOAuthorizeResponseType_Deny = 0, //拒绝连接 + ECOAuthorizeResponseType_AllowOnce, //允许本次 + ECOAuthorizeResponseType_AllowAlways, //始终允许 +}; + +@interface ECOChannelDeviceInfo : NSObject + +@property (nonatomic, copy) NSString *hostName; +@property (nonatomic, copy, readonly) NSString *ipAddress; +@property (nonatomic, copy, readonly) NSString *uuid; +@property (nonatomic, copy, readonly) NSString *deviceName; +@property (nonatomic, copy, readonly) NSString *systemVersion; +@property (nonatomic, copy, readonly) NSString *deviceModel; +@property (nonatomic, assign, readonly) ECODeviceType deviceType; +@property (nonatomic, assign) ECOAuthorizeResponseType authorizedType; +@property (nonatomic, assign) BOOL showAuthAlert; +@property (nonatomic, assign) BOOL isConnected; +@property (nonatomic, strong, readonly) ECOChannelAppInfo *appInfo; + +/** + 透传数据 + */ +@property (nonatomic, copy) NSDictionary *extraData; + +//解析网络信息传输过来的设备信息 +- (instancetype)initWithData:(NSData *)data; + +- (NSDictionary *)toJSONObject; + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m new file mode 100644 index 0000000..f503ef1 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m @@ -0,0 +1,209 @@ +// +// ECOChannelDeviceInfo.m +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import "ECOChannelDeviceInfo.h" +#include +#include +#include +#include + +static NSInteger const ECOINET_ADDRSTRLEN = 16; +static NSInteger const ECOINET6_ADDRSTRLEN = 46; +static NSString *_macUUIDString = nil; + +@interface ECOChannelDeviceInfo() + +@property (nonatomic, readwrite) NSString *ipAddress; +@property (nonatomic, readwrite) NSString *uuid; +@property (nonatomic, readwrite) NSString *deviceName; +@property (nonatomic, readwrite) NSString *systemVersion; +@property (nonatomic, readwrite) NSString *deviceModel; +@property (nonatomic, readwrite) ECODeviceType deviceType; +@property (nonatomic, readwrite) ECOChannelAppInfo *appInfo; + +@end + +@implementation ECOChannelDeviceInfo + +//解析网络信息传输过来的设备信息 +- (instancetype)initWithData:(NSData *)data { + self = [super init]; + if (self) { + NSError *error = nil; + NSDictionary *deviceDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if (deviceDict) { + self.uuid = deviceDict[@"uuid"]; + self.ipAddress = deviceDict[@"ipAddress"]; + self.deviceName = deviceDict[@"deviceName"]; + self.deviceType = [deviceDict[@"deviceType"] integerValue]; + self.systemVersion = deviceDict[@"systemVersion"]; + self.deviceModel = deviceDict[@"model"]; + self.authorizedType = [deviceDict[@"authType"] integerValue]; + self.showAuthAlert = [deviceDict[@"showAuth"] boolValue]; + self.hostName = deviceDict[@"hostName"]; + + ECOChannelAppInfo *appInfo = [[ECOChannelAppInfo alloc] initWithDictionary:deviceDict[@"appInfo"]]; + self.appInfo = appInfo; + } + } + return self; +} + +- (id)copy { + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + deviceInfo.hostName = self.hostName; + deviceInfo.ipAddress = self.ipAddress; + deviceInfo.uuid = self.uuid; + deviceInfo.deviceName = self.deviceName; + deviceInfo.systemVersion = self.systemVersion; + deviceInfo.deviceModel = self.deviceModel; + deviceInfo.deviceType = self.deviceType; + deviceInfo.authorizedType = self.authorizedType; + deviceInfo.showAuthAlert = self.showAuthAlert; + deviceInfo.appInfo = self.appInfo; + + return deviceInfo; +} + +- (instancetype)init { + self = [super init]; + if (self) { +#if TARGET_OS_OSX + [self setupMacDeviceInfo]; +#else + [self setupIOSDeviceInfo]; +#endif + //ipAddress + NSDictionary *addresses = [self getIPAddresses]; + //这里只取局域网的en0/ipv4地址,ipv6和pdp_ip0/ipv4暂时先忽略 + NSString *address = addresses[@"en0/ipv4"]; + self.ipAddress = address ?: @"0.0.0.0"; + } + return self; +} +- (void)setupMacDeviceInfo { + //uuid + if (!_macUUIDString) { + _macUUIDString = [[NSUUID UUID] UUIDString]; + } + self.uuid = _macUUIDString; + //设备 + self.deviceName = @"MacApp"; + self.deviceType = ECODeviceType_MacApp; +} +#if TARGET_OS_IPHONE +- (void)setupIOSDeviceInfo { + //设备名称 + UIDevice *device = [UIDevice currentDevice]; + self.deviceName = device.name; + self.deviceModel = device.model; + self.systemVersion = device.systemVersion; + //是否为模拟器 +#if TARGET_IPHONE_SIMULATOR + self.deviceType = ECODeviceType_Simulator; +#else + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.deviceType = ECODeviceType_iPad_Device; + } else { + self.deviceType = ECODeviceType_Device; + } +#endif + //uuid + // self.uuid = [[NSUUID UUID] UUIDString]; + self.uuid = [[device identifierForVendor] UUIDString]; + + ECOChannelAppInfo *appInfo = [ECOChannelAppInfo new]; + self.appInfo = appInfo; +} +#endif +#pragma mark - IPAddress +//获取本机的ip地址表 +- (NSDictionary *)getIPAddresses { + NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; + + // retrieve the current interfaces - returns 0 on success + struct ifaddrs *interfaces; + if(!getifaddrs(&interfaces)) { + // Loop through linked list of interfaces + struct ifaddrs *interface; + for(interface=interfaces; interface; interface=interface->ifa_next) { + if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { + continue; // deeply nested code harder to read + } + const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; + char addrBuf[ MAX(ECOINET_ADDRSTRLEN, ECOINET6_ADDRSTRLEN) ]; + if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { + NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; + NSString *type; + if(addr->sin_family == AF_INET) { + if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, ECOINET_ADDRSTRLEN)) { + type = @"ipv4"; + } + } else { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; + if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, ECOINET6_ADDRSTRLEN)) { + type = @"ipv6"; + } + } + if(type) { + NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; + addresses[key] = [NSString stringWithUTF8String:addrBuf]; + } + } + } + // Free memory + freeifaddrs(interfaces); + } + return [addresses count] ? addresses : nil; +} + +#pragma mark - Helper +- (NSDictionary *)toJSONObject { + NSMutableDictionary *json = [NSMutableDictionary dictionary]; + [json setValue:self.uuid ?: @"" forKey:@"uuid"]; + [json setValue:self.ipAddress ?: @"0.0.0.0" forKey:@"ipAddress"]; + [json setValue:self.deviceName ?: @"" forKey:@"deviceName"]; + [json setValue:self.deviceModel ?: @"" forKey:@"model"]; + [json setValue:@(self.deviceType) forKey:@"deviceType"]; + [json setValue:self.systemVersion ?: @"" forKey:@"systemVersion"]; + [json setValue:@(self.authorizedType) forKey:@"authType"]; + [json setValue:@(self.showAuthAlert) forKey:@"showAuth"]; + [json setValue:self.hostName ?: @"" forKey:@"hostName"]; + [json setValue:[self.appInfo toDictionary] forKey:@"appInfo"]; + + return [json copy]; +} +// 返回调试信息 +- (NSString *)description { + return [NSString stringWithFormat:@"%@", @{@"uuid": self.uuid ?: @"", + @"ipAddress": self.ipAddress ?: @"0.0.0.0", + @"deviceName": self.deviceName ?: @"", + @"deviceModel": self.deviceModel ?: @"", + @"deviceType": @(self.deviceType), + @"systemVersion": self.systemVersion ?: @"", + @"authType": @(self.authorizedType), + @"showAuth": @(self.showAuthAlert), + @"appId": self.appInfo.appId ?: @"", + @"appName": self.appInfo.appName ?: @"", + @"hostName": self.hostName ?: @"" + }]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ECOChannelDeviceInfo class]]) { + return NO; + } + ECOChannelDeviceInfo *device = (ECOChannelDeviceInfo *)object; + if ([device.uuid isEqualToString:self.uuid] && + [device.appInfo.appId isEqualToString:self.appInfo.appId]) { + return YES; + } + return NO; +} + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelManager.h b/Src/Main/Shared/Channel/ECOChannelManager.h new file mode 100644 index 0000000..f801d4a --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelManager.h @@ -0,0 +1,69 @@ +// +// ECOChannelManager.h +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import +#import "ECOChannelDeviceInfo.h" +#import "ECOUSBChannel.h" +#import "ECOSocketChannel.h" + +typedef void(^ECOChannelReceivedPacket)(ECOChannelDeviceInfo *device, NSData *data, NSDictionary *extraInfo); +typedef void(^ECOChannelDeviceConnectedStatusChanged)(ECOChannelDeviceInfo *device, BOOL isConnected); + +typedef void(^ECOChannelAuthStateChangedBlock)(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState); +typedef void(^ECOChannelRequestAuthStateBlock)(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState); + +@interface ECOChannelManager : NSObject + +/** + 接收到数据回调 + */ +@property (nonatomic, copy) ECOChannelReceivedPacket receivedBlock; + +/** + 设备连接变更 + */ +@property (nonatomic, copy) ECOChannelDeviceConnectedStatusChanged deviceBlock; + +/// 授权状态变更回调 +@property (nonatomic, copy) ECOChannelAuthStateChangedBlock authStateChangedBlock; + +/// 请求授权状态认证回调 +@property (nonatomic, copy) ECOChannelRequestAuthStateBlock requestAuthBlock; + +/// 设备白名单列表,记录始终允许的设备 +@property (nonatomic, strong, readonly) NSMutableArray *whitelistDevices; + +/** + 发送数据包 + + @param packet 数据包 + @param type 数据包类型,json或者普通数据包 + @param extraInfo 透传信息 + @param device 要接收消息的设备,如果传入nil,则对所有已授权连接的设备发送消息 + */ +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device; + +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert; + +/** + 是否已连接到Mac客户端 + + @return 连接状态 + */ +- (BOOL)isConnected; + +//链接IP地址的主机 +- (void)connectToClientIPAddress:(NSString *)ipAddress; + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelManager.m b/Src/Main/Shared/Channel/ECOChannelManager.m new file mode 100644 index 0000000..e11325d --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelManager.m @@ -0,0 +1,116 @@ +// +// ECOChannelManager.m +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOChannelManager.h" + +@interface ECOChannelManager() + + +@property (nonatomic, strong) ECOUSBChannel *ptChannel; +@property (nonatomic, strong) ECOSocketChannel *socketChannel; + +@end + +@implementation ECOChannelManager + +- (instancetype)init +{ + self = [super init]; + if (self) { + _socketChannel = [[ECOSocketChannel alloc] initWithDelegate:self]; + _ptChannel = [[ECOUSBChannel alloc] initWithDelegate:self]; +#if TARGET_OS_IPHONE + __weak typeof(self) weakSelf = self; + _ptChannel.attachBlock = ^(NSString *ipAddress) { + [weakSelf.socketChannel connectToIPAddress:ipAddress]; + }; +#endif + + } + return self; +} + +#pragma mark - 优先级管理 + +#pragma mark - 通道连接 +- (void)connectToClientIPAddress:(NSString *)ipAddress { + [self.socketChannel autoConnectToClientIPAddress:ipAddress]; +} +#pragma mark - 数据传输 +//发送数据 +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device { + if (!packet || + ![packet isKindOfClass:[NSData class]]) { + return; + } + [self.socketChannel sendPacket:packet extraInfo:extraInfo toDevice:device]; +} +//接收数据 +- (void)device:(ECOChannelDeviceInfo *)device receivePacket:(NSData *)packet extraInfo:(NSDictionary *)extraInfo { + if (!packet) { + return; + } + !self.receivedBlock ?: self.receivedBlock(device, packet, extraInfo); +} +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert { + if (!device || + ![device isKindOfClass:[ECOChannelDeviceInfo class]]) { + return; + } + [self.socketChannel sendAuthorizationMessageToDevice:device state:responseType showAuthAlert:showAuthAlert]; +} +#pragma mark - 连接状态 +// 是否已连接到Mac客户端 +- (BOOL)isConnected { + BOOL isSocketConnected = [_socketChannel isConnected]; + return isSocketConnected; +} +#pragma mark - ECOChannelConnectedDeviceProtocol methods +- (void)channel:(ECOBaseChannel *)channel didConnectedToDevice:(ECOChannelDeviceInfo *)device { + NSLog(@">> [ECOChannelManager] did Connected to device:%@", [device description]); +// //状态回调 +// if (device.authorizedType != ECOAuthorizeResponseType_Deny) { +// !self.connectBlock ?: self.connectBlock([self isConnected]); +// } + //连接设备状态 + !self.deviceBlock ?: self.deviceBlock(device, YES); +} +- (void)channel:(ECOBaseChannel *)channel didDisconnectWithDevice:(ECOChannelDeviceInfo *)device { + NSLog(@">> [ECOChannelManager] did Disconnect to device:%@", [device description]); +// //状态回调 +// if (device.authorizedType != ECOAuthorizeResponseType_Deny) { +// !self.connectBlock ?: self.connectBlock([self isConnected]); +// } + //连接设备状态 + !self.deviceBlock ?: self.deviceBlock(device, NO); +} +- (void)channel:(ECOBaseChannel *)channel didReceivedDevice:(ECOChannelDeviceInfo *)device andData:(NSData *)data extraInfo:(NSDictionary *)extraInfo{ +// NSLog(@">> [ECOChannelManager] did Received data from device:%@", [device description]); + [self device:device receivePacket:data extraInfo:extraInfo]; +} + +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device didChangedAuthState:(ECOAuthorizeResponseType)authorizedType { + NSLog(@">> [ECOChannelManager] device did changed authState:%@", @(authorizedType)); + !self.authStateChangedBlock ?: self.authStateChangedBlock(device, authorizedType); +} +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device willRequestAuthState:(ECOAuthorizeResponseType)authorizedType { + NSLog(@">> [ECOChannelManager] device will request authState:%@", @(authorizedType)); + !self.requestAuthBlock ?: self.requestAuthBlock(device, authorizedType); +} + +- (NSMutableArray *)whitelistDevices { + return self.socketChannel.whitelistDevices; +} + +@end diff --git a/Src/Main/Shared/Channel/ECOSocketChannel.h b/Src/Main/Shared/Channel/ECOSocketChannel.h new file mode 100644 index 0000000..5811e21 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOSocketChannel.h @@ -0,0 +1,60 @@ +// +// ECOSocketChannel.h +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOBaseChannel.h" +//#import "ECOCoreDefines.h" + +typedef NS_ENUM(NSInteger, ECOSocketHeadTag) { + ECOSocketHeadTag_Data = 0, //普通数据 + ECOSocketHeadTag_Device = 1, //设备信息 + ECOSocketHeadTag_Authorization = 2, //授权连接 + ECOSocketHeadTag_ClientListen = 3, //Client监听Mac端的主动申请 +}; + +typedef NS_ENUM(NSInteger, ECOSocketDataTag) { + ECOSocketDataTag_Data = 10, //普通数据 + ECOSocketDataTag_Device = 11, //设备信息 + ECOSocketDataTag_Authorization = 12, //授权连接 + ECOSocketDataTag_ClientListen = 13, //Client监听Mac端的主动申请 +}; + +//版本号,版本不一致忽略该数据 +static const int ECOSocketProtocolVersion = 1; + +//包体主协议,1表示授权连接,2表示发送数据,3表示Client侧接收主动申请 +static const int ECOHeadMainType_Authorization = 1; +static const int ECOHeadMainType_Data = 2; +static const int ECOHeadMainType_ClientListen = 3; + +//包体子协议,0表示JSON数据,1表示普通NSData数据 +static const int ECOHeadSubType_JSON = 0; +static const int ECOHeadSubType_Data = 1; + +@interface ECOSocketChannel : ECOBaseChannel + +/// 设备白名单列表,记录始终允许的设备 +@property (nonatomic, strong) NSMutableArray *whitelistDevices; + +//连接到ip地址 +- (void)autoConnectToClientIPAddress:(NSString *)ipAddress; + +//Client侧连接Mac侧:连接到ip地址 +- (void)connectToIPAddress:(NSString *)ip; + +//发送数据 +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device; + +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert; + +@end diff --git a/Src/Main/Shared/Channel/ECOSocketChannel.m b/Src/Main/Shared/Channel/ECOSocketChannel.m new file mode 100644 index 0000000..1980712 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOSocketChannel.m @@ -0,0 +1,546 @@ +// +// ECOSocketChannel.m +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOSocketChannel.h" +#import +#import +#include + +#import "ECONetServicePublisher.h" +#import "ECONetServiceBrowser.h" + +#import "LookinDefines.h" + +//static uint16_t const ECOClientSockeListenPortNumber = 23235; +//static uint16_t const ECOSocketAcceptPortNumber = 23234; +static CGFloat const ECOSocketRetryListenDelay = 1.f; +static NSInteger const ECOSocketHeadOffsetValue = 10; +static NSString *const ECHOAuthorizedDevicesKey = @"echoAuthorizedDevicesKey"; + +@interface ECOSocketChannel() + { + NSRecursiveLock *_socketLock; +} +@property (nonatomic, strong) NSMutableArray *sockets; +@property (nonatomic, strong) GCDAsyncSocket *mSocket; +@property (nonatomic, strong) ECONetServicePublisher *publisher; +@property (nonatomic, strong) ECONetServiceBrowser *browser; +//Client端主动连接的监听socket +@property (nonatomic, strong) GCDAsyncSocket *cSocket; +@property (nonatomic, strong) NSMutableArray *clientSockets; + +@end + +@implementation ECOSocketChannel + +- (void)dealloc { + NSLog(@"%s",__func__); +} +//初始化 +- (void)setupChannel { + [super setupChannel]; + _socketLock = [[NSRecursiveLock alloc] init]; + self.priority = ECOChannelPriority_Socket; + + //开启服务监听 +#if TARGET_OS_OSX + [self startListening]; +#else + [self setupClientListenSocket]; + [self.browser startBrowsing]; +#endif +} +//是否有socket连接 +- (BOOL)isConnected { + BOOL v = NO; + [self p_lock]; + if (self.connectType == ECOChannelConnectType_Authorization) { + for (GCDAsyncSocket *socket in self.sockets) { + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if (deviceInfo.authorizedType != ECOAuthorizeResponseType_Deny) { + v = YES; + break; + } + } + }else{ + NSInteger count = [self.sockets count]; + v = count != 0; + } + [self p_unlock]; + return v; +} +//判断某个设备是否已连接 +- (BOOL)isEchoConnectedOfDevice:(ECOChannelDeviceInfo *)device { + BOOL v = NO; + [self p_lock]; + for (GCDAsyncSocket *socket in self.sockets) { + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if ([device.ipAddress isEqualToString:deviceInfo.ipAddress]) { + v = YES; + break; + } + } + [self p_unlock]; + return v; +} +- (void)setupClientListenSocket { + self.cSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + [self.cSocket acceptOnPort:LookinClientSockeListenPortNumber error:&error]; + if (error) { + NSLog(@"Socket Listen error:%@", error); + self.cSocket.delegate = nil; + self.cSocket = nil; + } +} +- (void)startListening { + [self p_lock]; + [self.sockets removeAllObjects]; + [self p_unlock]; + /* + https://developer.apple.com/library/archive/documentation/Networking/Conceptual/NSNetServiceProgGuide/Articles/PublishingServices.html + 创建listening socket for communication + */ + self.mSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + BOOL isAccept = [self.mSocket acceptOnPort:LookinSocketAcceptPortNumber error:&error]; + if (error) { + NSLog(@"Socket Listen error:%@", error); + self.mSocket.delegate = nil; + self.mSocket = nil; + [self restartListening]; + return; + } + NSLog(@"Socket Listen:%@", isAccept ? @"Success" : @"Failure"); + //启动NetService + [self.publisher startPublish]; +} + +//重试监听 +- (void)restartListening { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ECOSocketRetryListenDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self startListening]; + }); +} +#pragma mark - Socket连接 +//Client侧连接Mac侧 +- (void)connectToAddresses:(NSArray *)addresses + hostName:(NSString *)hostName { + if (!addresses || ![addresses count]) { + return; + } + GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSData *address = [addresses objectAtIndex:0]; + NSError *error = nil; + BOOL connected = [socket connectToAddress:address error:&error]; + if (connected) { + [self p_lock]; + [self.sockets addObject:socket]; + [self p_unlock]; + //发送设备信息 + [self sendDeviceInfo:socket hostName:hostName]; + } + if (error) { + NSLog(@">> [ECOSocketChannel] connect to address failed:%@", error); + } +} +//Client侧连接Mac侧:连接到ip地址 +- (void)connectToIPAddress:(NSString *)ip { + if (!ip || ![ip length]) { + return; + } + GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + BOOL connected = [socket connectToHost:ip onPort:LookinSocketAcceptPortNumber error:&error]; + if (connected) { + [self p_lock]; + [self.sockets addObject:socket]; + [self p_unlock]; + //发送设备信息 + [self sendDeviceInfo:socket hostName:nil]; + } + if (error) { + NSLog(@">> [ECOSocketChannel] connect to address failed:%@", error); + } +} +//Mac侧连接Client侧 +- (void)autoConnectToClientIPAddress:(NSString *)ipAddress { + if (!ipAddress || ![ipAddress length]) { + return; + } + GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + BOOL connected = [socket connectToHost:ipAddress onPort:LookinClientSockeListenPortNumber error:&error]; + if (connected) { + [self.clientSockets addObject:socket]; + //发送本Mac侧的信息 + [self sendMacAutoConnectInfoInfo:socket]; + } + if (error) { + NSLog(@">> [ECOSocketChannel] autoConnectToClientIPAddress failed:%@", error); + } +} +#pragma mark - Socket通信 +//发送数据 +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device { + if (!packet) { + return; + } + + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_Data) forKey:@"mType"]; +// [header setValue:@(type) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; +// if (type == ECOPacketDataType_Data) { + [header setValue:extraInfo forKey:@"extra"]; +// } + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + + [self p_lock]; + for (GCDAsyncSocket *socket in self.sockets) { + if (self.connectType == ECOChannelConnectType_Authorization) { + //授权机制 + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if (deviceInfo.authorizedType != ECOAuthorizeResponseType_Deny) { + if (device != nil) { + //给单个设备发送消息 + if ([device isEqual:deviceInfo]) { + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + }else{ + //给所有设备发送消息 + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + } + }else{ + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + } + [self p_unlock]; +} +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert { + //修改数据 + ECOChannelDeviceInfo *authDevice = device; + device.showAuthAlert = showAuthAlert; +#if TARGET_OS_OSX + if (responseType != ECOAuthorizeResponseType_Deny) { + //允许需要等待对方确认,稍后修改device的内容 + authDevice = [device copy]; + } +#endif + authDevice.authorizedType = responseType; + NSError *error = nil; + NSData *packet = [NSJSONSerialization dataWithJSONObject:[authDevice toJSONObject] options:0 error:&error]; + if (error) { + NSLog(@">>[ECOCoreManager] send AuthorizationResponse failed:%@", error); + } + [self p_lock]; + for (GCDAsyncSocket *socket in self.sockets) { + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if ([deviceInfo isEqual:authDevice]) { + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_Authorization) forKey:@"mType"]; + [header setValue:@(ECOHeadSubType_JSON) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + } + [self p_unlock]; + +#if TARGET_OS_OSX + //授权连接回调,手动断开时主动回调,主动连接时等收到对方的消息再回调 + if (responseType == ECOAuthorizeResponseType_Deny) { + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:device didChangedAuthState:NO]; + } + //修改授权白名单 + if (device.uuid.length > 0) { + NSString *uniId = [NSString stringWithFormat:@"%@_%@",device.uuid, device.appInfo.appId]; + if ([self.whitelistDevices containsObject:uniId]) { + [self.whitelistDevices removeObject:uniId]; + [self saveWhiteListDevices]; + } + } + } +#else + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:device didChangedAuthState:responseType]; + } +#endif + +} +//发送设备信息 +- (void)sendDeviceInfo:(GCDAsyncSocket *)socket + hostName:(NSString *)hostName { + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + deviceInfo.hostName = hostName; + NSData *packet = [NSJSONSerialization dataWithJSONObject:[deviceInfo toJSONObject] options:0 error:nil]; + if (!packet) { + return; + } + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_Data) forKey:@"mType"]; + [header setValue:@(ECOHeadSubType_JSON) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Device]; +} +//发送给Client侧消息 +- (void)sendMacAutoConnectInfoInfo:(GCDAsyncSocket *)socket { + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + NSData *packet = [NSJSONSerialization dataWithJSONObject:[deviceInfo toJSONObject] options:0 error:nil]; + if (!packet) { + return; + } + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_ClientListen) forKey:@"mType"]; + [header setValue:@(ECOHeadSubType_JSON) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_ClientListen]; +} +#pragma mark - GCDAsyncSocketDelegate methods +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { + // NSLog(@"%s",__func__); +#if TARGET_OS_IPHONE + //读取数据 + [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_Device]; +#endif +} + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { + // NSLog(@"%s",__func__); +#if TARGET_OS_OSX + [self p_lock]; + [self.sockets addObject:newSocket]; + newSocket.delegate = self; + [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_Device]; + [self p_unlock]; + //发送设备信息 + [self sendDeviceInfo:newSocket hostName:nil]; +#else + [self.clientSockets addObject:newSocket]; + newSocket.delegate = self; + [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_ClientListen]; +#endif +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { +// NSLog(@"%s",__func__); + if (tag == ECOSocketHeadTag_Data || + tag == ECOSocketHeadTag_Device || + tag == ECOSocketHeadTag_ClientListen) { + NSData *headerData = [data subdataWithRange:NSMakeRange(0, data.length - [GCDAsyncSocket CRLFData].length)]; + NSDictionary *header = [NSJSONSerialization JSONObjectWithData:headerData options:NSJSONReadingAllowFragments error:nil]; + NSInteger version = [header[@"version"] integerValue]; + if (version < ECOSocketProtocolVersion) { + return; + } + NSInteger length = [header[@"len"] integerValue]; + NSInteger mainType = [header[@"mType"] integerValue]; + if (mainType == ECOHeadMainType_Authorization) { + [sock readDataToLength:length withTimeout:-1 tag:ECOSocketDataTag_Authorization]; + }else if(mainType == ECOHeadMainType_Data) { + ECOChannelDeviceInfo *deviceInfo = sock.userData; + NSDictionary *extraInfo = header[@"extra"]; + deviceInfo.extraData = extraInfo; + [sock readDataToLength:length withTimeout:-1 tag:tag + ECOSocketHeadOffsetValue]; + }else if (mainType == ECOHeadMainType_ClientListen) { + [sock readDataToLength:length withTimeout:-1 tag:tag + ECOSocketHeadOffsetValue]; + } + return; + } + if (tag == ECOSocketDataTag_Device) { + ECOChannelDeviceInfo *deviceInfo = [[ECOChannelDeviceInfo alloc] initWithData:data]; + sock.userData = deviceInfo; +#if TARGET_OS_OSX + NSString *uniId = [NSString stringWithFormat:@"%@_%@",deviceInfo.uuid, deviceInfo.appInfo.appId]; + if ([self.whitelistDevices containsObject:uniId]) { + //设置为已授权 + deviceInfo.authorizedType = ECOAuthorizeResponseType_AllowAlways; + [self sendAuthorizationMessageToDevice:deviceInfo state:ECOAuthorizeResponseType_AllowAlways showAuthAlert:NO]; + } +#endif + deviceInfo.isConnected = YES; + //连接到新设备 + if ([self.delegate respondsToSelector:@selector(channel:didConnectedToDevice:)]) { + [self.delegate channel:self didConnectedToDevice:deviceInfo]; + } + } + if (tag == ECOSocketDataTag_ClientListen) { + ECOChannelDeviceInfo *deviceInfo = [[ECOChannelDeviceInfo alloc] initWithData:data]; + BOOL isConnected = [self isEchoConnectedOfDevice:deviceInfo]; + if (!isConnected) { + [self connectToIPAddress:deviceInfo.ipAddress]; + }else{ + NSLog(@"当前Echo主机已连接:%@",deviceInfo.ipAddress); + } + [sock setDelegate:nil]; + [self.clientSockets removeObject:sock]; + } + if (tag == ECOSocketDataTag_Authorization) { + //授权信息 + ECOChannelDeviceInfo *deviceInfo = sock.userData; + ECOChannelDeviceInfo *tempDevice = [[ECOChannelDeviceInfo alloc] initWithData:data]; + BOOL isAlwaysAllow = tempDevice.authorizedType == ECOAuthorizeResponseType_AllowAlways; +#if TARGET_OS_IPHONE + deviceInfo.hostName = tempDevice.hostName; + if (tempDevice.showAuthAlert) { + //弹窗提示用户 + if ([self.delegate respondsToSelector:@selector(channel:device:willRequestAuthState:)]) { + [self.delegate channel:self device:deviceInfo willRequestAuthState:tempDevice.authorizedType]; + } + }else{ + deviceInfo.authorizedType = tempDevice.authorizedType; + //连接到新设备 + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:deviceInfo didChangedAuthState:tempDevice.authorizedType]; + } + } +#endif +#if TARGET_OS_OSX + //修改设备的授权状态 + deviceInfo.authorizedType = tempDevice.authorizedType; + //重置标记位 + deviceInfo.showAuthAlert = NO; + //始终允许,加入白名单 + if (deviceInfo.uuid.length > 0) { + NSString *uniId = [NSString stringWithFormat:@"%@_%@",deviceInfo.uuid, deviceInfo.appInfo.appId]; + if (isAlwaysAllow && ![self.whitelistDevices containsObject:uniId]) { + [self.whitelistDevices addObject:uniId]; + [self saveWhiteListDevices]; + }else if (!isAlwaysAllow && [self.whitelistDevices containsObject:uniId]){ + [self.whitelistDevices removeObject:uniId]; + [self saveWhiteListDevices]; + } + } + //回调 + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:deviceInfo didChangedAuthState:tempDevice.authorizedType]; + } +#endif + } + //传递数据给上层 + if (tag == ECOSocketDataTag_Data) { + if ([self.delegate respondsToSelector:@selector(channel:didReceivedDevice:andData:extraInfo:)]) { + ECOChannelDeviceInfo *deviceInfo = sock.userData; + if (deviceInfo.authorizedType != ECOAuthorizeResponseType_Deny) { + //未授权的通信忽略 + [self.delegate channel:self didReceivedDevice:deviceInfo andData:data extraInfo:deviceInfo.extraData]; + } + } + } + //读取下次发送的数据 + [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_Data]; +} +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err { +// NSLog(@"%s",__func__); + [self p_lock]; + [sock setDelegate:nil]; + if ([self.sockets containsObject:sock]) { + if ([self.delegate respondsToSelector:@selector(channel:didDisconnectWithDevice:)]) { + ECOChannelDeviceInfo *deviceInfo = sock.userData; + deviceInfo.isConnected = NO; + [self.delegate channel:self didDisconnectWithDevice:deviceInfo]; + } + [self.sockets removeObject:sock]; + } + if ([self.clientSockets containsObject:sock]) { + [self.clientSockets removeObject:sock]; + } + [self p_unlock]; + //重启监听 + if (sock == self.mSocket) { + [self restartListening]; + } +} +#pragma mark - helper +- (void)p_lock { + [_socketLock lock]; +} +- (void)p_unlock { + [_socketLock unlock]; +} +- (void)saveWhiteListDevices { + NSArray *list = [self.whitelistDevices copy]; + [[NSUserDefaults standardUserDefaults] setObject:list forKey:ECHOAuthorizedDevicesKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} +#pragma mark - getters +- (NSMutableArray *)sockets { + if (!_sockets) { + _sockets = [[NSMutableArray alloc] initWithCapacity:0]; + } + return _sockets; +} +- (NSMutableArray *)clientSockets { + if (!_clientSockets) { + _clientSockets = [[NSMutableArray alloc] initWithCapacity:0]; + } + return _clientSockets; +} + +#if TARGET_OS_OSX +- (ECONetServicePublisher *)publisher { + if (!_publisher) { + _publisher = [[ECONetServicePublisher alloc] init]; + } + return _publisher; +} +#endif + + +#if TARGET_OS_IPHONE +- (ECONetServiceBrowser *)browser { + if (!_browser) { + _browser = [[ECONetServiceBrowser alloc] init]; + __weak typeof(self) weakSelf = self; + _browser.addressesBlock = ^(NSArray *addresses, NSString *hostName) { + __strong typeof(weakSelf) strongSelf = weakSelf; + [strongSelf connectToAddresses:addresses hostName:hostName]; + }; + } + return _browser; +} +#endif + +- (NSMutableArray *)whitelistDevices { + if (!_whitelistDevices) { + NSArray *list = [[NSUserDefaults standardUserDefaults] objectForKey:ECHOAuthorizedDevicesKey]; + _whitelistDevices = [NSMutableArray arrayWithArray:list ?: @[]]; + } + return _whitelistDevices; +} +@end diff --git a/Src/Main/Shared/Channel/ECOUSBChannel.h b/Src/Main/Shared/Channel/ECOUSBChannel.h new file mode 100644 index 0000000..ca79458 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOUSBChannel.h @@ -0,0 +1,29 @@ +// +// ECOUSBChannel.h +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOBaseChannel.h" + +typedef NS_ENUM(NSInteger, ECOUSBChannelType) { + ECOUSBChannelType_Command = 100, + ECOUSBChannelType_TextMessage = 101, + ECOUSBChannelType_Ping = 102, + ECOUSBChannelType_Pong = 103, +}; + +typedef struct _ECOUSBChannelTextFrame { + uint32_t length; + uint8_t utf8text[0]; +} ECOUSBChannelTextFrame; + +typedef void(^ECOUSBChannelDidAttachBlock)(NSString *ipAddress); + +@interface ECOUSBChannel : ECOBaseChannel + +@property (nonatomic, copy) ECOUSBChannelDidAttachBlock attachBlock; + +@end diff --git a/Src/Main/Shared/Channel/ECOUSBChannel.m b/Src/Main/Shared/Channel/ECOUSBChannel.m new file mode 100644 index 0000000..9e59b75 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOUSBChannel.m @@ -0,0 +1,325 @@ +// +// ECOUSBChannel.m +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOUSBChannel.h" +#import "Lookin_PTChannel.h" +#import "ECOChannelDeviceInfo.h" + +#if TARGET_OS_OSX +#import +#endif + +static const int ECOUSBChannelIPv4PortNumber = 2333; +static const NSTimeInterval ECOUSBChannelReconnectDelay = 1.0; + +@interface ECOUSBChannel() + { + dispatch_queue_t _notConnectedQueue; +} + +@property (nonatomic, weak) Lookin_PTChannel *serverChannel; +@property (nonatomic, weak) Lookin_PTChannel *peerChannel; + +@property (nonatomic, strong) NSNumber *connectingToDeviceID; +@property (nonatomic, strong) NSNumber *connectedDeviceID; +@property (nonatomic, strong) NSDictionary *connectedDeviceProperties; +@property (nonatomic, strong) Lookin_PTChannel *connectedChannel; + +@end + +@implementation ECOUSBChannel + +#pragma mark - LifeCycle methods +- (void)dealloc { + NSLog(@"%s",__func__); + if (self.serverChannel) { + [self.serverChannel close]; + } + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} +//初始化 +- (void)setupChannel { + [super setupChannel]; + self.priority = ECOChannelPriority_USB; + +#if TARGET_OS_OSX + _notConnectedQueue = dispatch_queue_create("com.echo.notConnectedQueue", DISPATCH_QUEUE_SERIAL); + // Start listening for device attached/detached notifications + [self startListeningForDevices]; + // Start trying to connect to local IPv4 port (defined in PTExampleProtocol.h) + [self enqueueConnectToLocalIPv4Port]; + + [self ping]; +#else + [self setupPTChannel]; +#endif +} +//是否有usb连接 +- (BOOL)isConnected { +#if TARGET_OS_OSX + return self.connectedDeviceID != nil; +#else + return self.peerChannel != nil; +#endif +} +//创建channel +- (void)setupPTChannel { + Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; + [channel listenOnPort:ECOUSBChannelIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { + if (error) { + NSLog(@">> [ECOUSBChannel] Failed to listen on 127.0.0.1:%d: %@", ECOUSBChannelIPv4PortNumber, error); + }else{ + NSLog(@">> [ECOUSBChannel] Listening on 127.0.0.1:%d", ECOUSBChannelIPv4PortNumber); + self.serverChannel = channel; + } + }]; +} + +#pragma mark - Wired device connections +- (void)startListeningForDevices { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(onUSBDeviceDidAttach:) name:Lookin_PTUSBDeviceDidAttachNotification object:Lookin_PTUSBHub.sharedHub]; + [center addObserver:self selector:@selector(onUSBDeviceDidDetach:) name:Lookin_PTUSBDeviceDidDetachNotification object:Lookin_PTUSBHub.sharedHub]; +} +- (void)onUSBDeviceDidAttach:(NSNotification *)note { + NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + NSLog(@"<< [ECOUSBChannel] PTUSBDeviceDidAttachNotification:%@", deviceID); + // [self showAlertWithMessage:[NSString stringWithFormat:@"usb设备连接:%@",deviceID]]; + + dispatch_async(_notConnectedQueue, ^{ + if (!self.connectingToDeviceID || + ![deviceID isEqualToNumber:self.connectingToDeviceID]) { + [self disconnectFromCurrentChannel]; + self.connectingToDeviceID = deviceID; + self.connectedDeviceProperties = [note.userInfo objectForKey:@"Properties"]; + [self enqueueConnectToUSBDevice]; + } + }); +} +- (void)onUSBDeviceDidDetach:(NSNotification *)note { + NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + NSLog(@"<< [ECOUSBChannel] PTUSBDeviceDidDetachNotification:%@", deviceID); + // [self showAlertWithMessage:[NSString stringWithFormat:@"usb设备断开:%@",deviceID]]; + + if ([self.connectingToDeviceID isEqualToNumber:deviceID]) { + self.connectedDeviceProperties = nil; + self.connectingToDeviceID = nil; + if (self.connectedChannel) { + [self.connectedChannel close]; + } + } +} + +- (void)enqueueConnectToLocalIPv4Port { + dispatch_async(_notConnectedQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self connectToLocalIPv4Port]; + }); + }); +} + +- (void)connectToLocalIPv4Port { + Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; + channel.userInfo = [NSString stringWithFormat:@"127.0.0.1:%d", ECOUSBChannelIPv4PortNumber]; + [channel connectToPort:ECOUSBChannelIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, Lookin_PTAddress *address) { + if (error) { + if (error.domain == NSPOSIXErrorDomain && (error.code == ECONNREFUSED || error.code == ETIMEDOUT)) { + // this is an expected state + }else{ + NSLog(@"<< [ECOUSBChannel] Failed to connect to 127.0.0.1:%d: %@", ECOUSBChannelIPv4PortNumber, error); + } + [self performSelector:@selector(enqueueConnectToLocalIPv4Port) withObject:nil afterDelay:ECOUSBChannelReconnectDelay]; + }else{ + [self disconnectFromCurrentChannel]; + self.connectedChannel = channel; + channel.userInfo = address; + NSLog(@"<< [ECOUSBChannel] Connected to %@", address); + } + }]; +} + +- (void)enqueueConnectToUSBDevice { + dispatch_async(_notConnectedQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self connectToUSBDevice]; + }); + }); +} + +- (void)connectToUSBDevice { + Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; + channel.userInfo = self.connectingToDeviceID; + channel.delegate = self; + [channel connectToPort:ECOUSBChannelIPv4PortNumber overUSBHub:Lookin_PTUSBHub.sharedHub deviceID:self.connectingToDeviceID callback:^(NSError *error) { + if (error) { + if (error.domain == Lookin_PTUSBHubErrorDomain && error.code == PTUSBHubErrorConnectionRefused) { + // NSLog(@"<< [ECOUSBChannel] Failed to connect to device #%@: %@", channel.userInfo, error); + }else{ + // NSLog(@"<< [ECOUSBChannel] Failed to connect to device #%@: %@", channel.userInfo, error); + } + if (channel.userInfo == self.connectingToDeviceID) { + //重试连接 + [self performSelector:@selector(enqueueConnectToUSBDevice) withObject:nil afterDelay:ECOUSBChannelReconnectDelay]; + } + }else{ + self.connectedDeviceID = self.connectingToDeviceID; + self.connectedChannel = channel; + NSLog(@"<< [ECOUSBChannel] Connect to device #%@\n%@", channel.userInfo, self.connectedDeviceProperties); + //发送ping信息 + [self ping]; + } + }]; +} + +- (void)disconnectFromCurrentChannel { + if (self.connectedDeviceID && self.connectedChannel) { + [self.connectedChannel close]; + self.connectedChannel = nil; + } +} + +- (void)didDisconnectFromDevice:(NSNumber*)deviceID { + NSLog(@"<< [ECOUSBChannel] Disconnected from device:%@", deviceID); + if ([self.connectedDeviceID isEqualToNumber:deviceID]) { + [self willChangeValueForKey:@"connectedDeviceID"]; + self.connectedDeviceID = nil; + [self didChangeValueForKey:@"connectedDeviceID"]; + } +} + +#pragma mark - Send Messages +- (void)ping { + if (!self.connectedChannel) { + [self performSelector:@selector(ping) withObject:nil afterDelay:1.f]; + return; + } + uint32_t tagno = [self.connectedChannel.protocol newTag]; + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + dispatch_data_t payload = ECOUSBChannelDispatchDataWithPayload(deviceInfo.ipAddress); + [self.connectedChannel sendFrameOfType:ECOUSBChannelType_Ping tag:tagno withPayload:payload callback:^(NSError *error) { + // [self performSelector:@selector(ping) withObject:nil afterDelay:1.f]; + }]; +} +- (void)sendMessage:(NSString *)message { +#if TARGET_OS_OSX + if (self.connectedChannel) { + [self.connectedChannel sendFrameOfType:ECOUSBChannelType_TextMessage tag:PTFrameNoTag withPayload:ECOUSBChannelDispatchDataWithPayload(message) callback:^(NSError *error) { + if (error) { + NSLog(@">> [ECOUSBChannel] Failed to send message: %@", error); + } + NSLog(@">> [ECOUSBChannel] you: %@", message); + }]; + } +#else + if (self.peerChannel) { + [self.peerChannel sendFrameOfType:ECOUSBChannelType_TextMessage tag:PTFrameNoTag withPayload:ECOUSBChannelDispatchDataWithPayload(message) callback:^(NSError *error) { + if (error) { + NSLog(@">> [ECOUSBChannel] Failed to send message: %@", error); + } + NSLog(@">> [ECOUSBChannel] you: %@", message); + }]; + } +#endif +} +#pragma mark - dispatch_data_t +static dispatch_data_t ECOUSBChannelDispatchDataWithPayload(id payload) { + if ([payload isKindOfClass:[NSString class]]) { + //字符串 + NSString *message = (NSString *)payload; + const char *utf8text = [message cStringUsingEncoding:NSUTF8StringEncoding]; + size_t length = strlen(utf8text); + ECOUSBChannelTextFrame *textFrame = CFAllocatorAllocate(nil, sizeof(ECOUSBChannelTextFrame) + length, 0); + memcpy(textFrame->utf8text, utf8text, length); + textFrame->length = htonl(length); + + return dispatch_data_create((const void*)textFrame, sizeof(ECOUSBChannelTextFrame) + length, nil, ^{ + CFAllocatorDeallocate(nil, textFrame); + }); + }else if ([payload isKindOfClass:[NSDictionary class]]) { + //字典 + NSDictionary *info = (NSDictionary *)payload; + return [info createReferencingDispatchData]; + } + return nil; +} +#pragma mark - PTChannelDelegate methods +// Invoked to accept an incoming frame on a channel. Reply NO ignore the +// incoming frame. If not implemented by the delegate, all frames are accepted. +- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { +// NSLog(@"%s",__func__); +#if TARGET_OS_OSX + if (channel != self.peerChannel) { + // A previous channel that has been canceled but not yet ended. Ignore. + return NO; + } +#endif + return YES; +} + +// Invoked when a new frame has arrived on a channel. +- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload { +// NSLog(@"%s",__func__); + if (type == ECOUSBChannelType_TextMessage) { + ECOUSBChannelTextFrame *textFrame = (ECOUSBChannelTextFrame *)payload.data; + textFrame->length = ntohl(textFrame->length); + NSString *message = [[NSString alloc] initWithBytes:textFrame->utf8text length:textFrame->length encoding:NSUTF8StringEncoding]; +// NSLog(@">> [ECOUSBChannel] [%@]: %@", channel.userInfo, message); + // 测试:自动会话 + [self sendMessage:message]; + }else if (type == ECOUSBChannelType_Ping) { +// NSLog(@">> [ECOUSBChannel] received ping: %@", channel.userInfo); + ECOUSBChannelTextFrame *textFrame = (ECOUSBChannelTextFrame *)payload.data; + textFrame->length = ntohl(textFrame->length); + NSString *ipAddress = [[NSString alloc] initWithBytes:textFrame->utf8text length:textFrame->length encoding:NSUTF8StringEncoding]; +// NSLog(@">> [ECOUSBChannel] [%@]: %@", channel.userInfo, ipAddress); + !self.attachBlock ?: self.attachBlock(ipAddress); + +// if (self.peerChannel) { +// [self.peerChannel sendFrameOfType:ECOUSBChannelType_Pong tag:tag withPayload:nil callback:nil]; +// } + }else if (type == ECOUSBChannelType_Pong) { +// NSLog(@">> [ECOUSBChannel] receive pong: %@", channel.userInfo); + } +} + +// Invoked when the channel closed. If it closed because of an error, *error* is +// a non-nil NSError object. +- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error { +// NSLog(@"%s",__func__); + if (error) { +// NSLog(@">> [ECOUSBChannel] %@ end with error:%@", channel, error); + }else{ +// NSLog(@">> [ECOUSBChannel] Disconnected from %@", channel.userInfo); + } +#if TARGET_OS_OSX + if (self.connectedDeviceID && [self.connectedDeviceID isEqualToNumber:channel.userInfo]) { + [self didDisconnectFromDevice:self.connectedDeviceID]; + } + if (self.connectedChannel == channel) { +// NSLog(@">> [ECOUSBChannel] Disconnected from: %@", channel.userInfo); + self.connectedChannel = nil; + } +#endif +} + +// For listening channels, this method is invoked when a new connection has been +// accepted. +- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address { +// NSLog(@"%s",__func__); + if (self.peerChannel) { + [self.peerChannel cancel]; + } + + self.peerChannel = otherChannel; + self.peerChannel.userInfo = address; +// NSLog(@">> [ECOUSBChannel] Connected to %@", address); + //测试,回复消息 +// [self sendMessage:@"你好"]; +} + +@end diff --git a/Src/Main/Shared/LookinAppInfo.h b/Src/Main/Shared/LookinAppInfo.h index 1082d12..caa351b 100644 --- a/Src/Main/Shared/LookinAppInfo.h +++ b/Src/Main/Shared/LookinAppInfo.h @@ -54,6 +54,10 @@ typedef NS_ENUM(NSInteger, LookinAppInfoDevice) { @property(nonatomic, assign) double screenHeight; /// 是几倍的屏幕 @property(nonatomic, assign) double screenScale; +#if LOOKIN_SERVER_WIRELESS +/// 是否为无线连接 +@property(nonatomic, assign) BOOL isWireless; +#endif - (BOOL)isEqualToAppInfo:(LookinAppInfo *)info; diff --git a/Src/Main/Shared/LookinAppInfo.m b/Src/Main/Shared/LookinAppInfo.m index 1e61d7a..895598c 100644 --- a/Src/Main/Shared/LookinAppInfo.m +++ b/Src/Main/Shared/LookinAppInfo.m @@ -11,7 +11,10 @@ #import "LookinAppInfo.h" +#if TARGET_OS_IPHONE #import "LKS_MultiplatformAdapter.h" +#import "LKS_ConnectionManager.h" +#endif static NSString * const CodingKey_AppIcon = @"1"; static NSString * const CodingKey_Screenshot = @"2"; @@ -36,6 +39,9 @@ - (id)copyWithZone:(NSZone *)zone { newAppInfo.screenHeight = self.screenHeight; newAppInfo.screenScale = self.screenScale; newAppInfo.appInfoIdentifier = self.appInfoIdentifier; +#if LOOKIN_SERVER_WIRELESS + newAppInfo.isWireless = self.isWireless; +#endif return newAppInfo; } @@ -62,6 +68,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { self.screenScale = [aDecoder decodeDoubleForKey:@"screenScale"]; self.appInfoIdentifier = [aDecoder decodeIntegerForKey:@"appInfoIdentifier"]; self.shouldUseCache = [aDecoder decodeBoolForKey:@"shouldUseCache"]; +#if LOOKIN_SERVER_WIRELESS + self.isWireless = [aDecoder decodeBoolForKey:@"isWireless"]; +#endif } return self; } @@ -96,6 +105,9 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeDouble:self.screenScale forKey:@"screenScale"]; [aCoder encodeInteger:self.appInfoIdentifier forKey:@"appInfoIdentifier"]; [aCoder encodeBool:self.shouldUseCache forKey:@"shouldUseCache"]; +#if LOOKIN_SERVER_WIRELESS + [aCoder encodeBool:self.isWireless forKey:@"isWireless"]; +#endif } + (BOOL)supportsSecureCoding { @@ -175,6 +187,12 @@ + (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasI if (hasIcon) { info.appIcon = [self appIcon]; } +#if LOOKIN_SERVER_WIRELESS + info.isWireless = LKS_ConnectionManager.sharedInstance.isWirelessConnnect; + if (info.isWireless) { + info.deviceDescription = [NSString stringWithFormat:@"ᯤ %@", info.deviceDescription]; + } +#endif return info; } diff --git a/Src/Main/Shared/LookinDefines.h b/Src/Main/Shared/LookinDefines.h index f00540d..5c9d9b9 100644 --- a/Src/Main/Shared/LookinDefines.h +++ b/Src/Main/Shared/LookinDefines.h @@ -43,6 +43,17 @@ static const int LookinUSBDeviceIPv4PortNumberEnd = 47179; static const int LookinSimulatorIPv4PortNumberStart = 47164; static const int LookinSimulatorIPv4PortNumberEnd = 47169; +#if LOOKIN_SERVER_WIRELESS +static const int LookinClientSockeListenPortNumber = 47180; +static const int LookinSocketAcceptPortNumber = 47181; +static const int LookinNetServicePortNumber = LookinSocketAcceptPortNumber; + +static NSString *const LookinNetServiceDomain = @""; +static NSString *const LookinNetServiceType = @"_Lookin._tcp"; +static NSString *const LookinNetServiceName = @""; +static NSTimeInterval const LookinNetServiceResolveAddressTimeout = 30; +#endif + enum { /// 确认两端是否可以响应通讯 LookinRequestTypePing = 200,