From 389bb46c8ed6b47cc29def56a394a88e2d13c2fd Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 22 Aug 2026 07:40:03 +0200 Subject: [PATCH] feat: unify HTTP server across iOS/tvOS/watchOS on Network.framework Extends the Network.framework-based HTTP server built for watchOS (which can't use BSD sockets) to iOS and tvOS as well, replacing the vendored CocoaAsyncSocket/CocoaHTTPServer/RoutingHTTPServer stack (~22k lines) with the ~600-line implementation already proven in production on watchOS. FBWatchHTTPServer is renamed to FBHTTPServer and moved out of the watchOS-specific directory since it's now the only implementation; custom bind-IP support (previously iOS/tvOS-only via setInterface:) is added to FBTCPSocket via Network.framework so it works on all three platforms. Co-Authored-By: Claude Sonnet 5 --- WebDriverAgent.xcodeproj/project.pbxproj | 348 +- .../FBWatchHTTPServer.h => FBHTTPServer.h} | 18 +- .../FBWatchHTTPServer.m => FBHTTPServer.m} | 39 +- WebDriverAgentLib/Routing/FBTCPSocket.h | 55 +- WebDriverAgentLib/Routing/FBTCPSocket.m | 106 +- WebDriverAgentLib/Routing/FBWebServer.h | 2 +- WebDriverAgentLib/Routing/FBWebServer.m | 53 +- .../Routing/{WatchOS => }/RouteRequest.h | 6 +- .../Routing/{WatchOS => }/RouteRequest.m | 0 .../Routing/{WatchOS => }/RouteResponse.h | 4 +- .../Routing/{WatchOS => }/RouteResponse.m | 0 WebDriverAgentLib/Utilities/FBMjpegServer.h | 8 +- WebDriverAgentLib/Utilities/FBMjpegServer.m | 67 +- .../Vendor/CocoaAsyncSocket/GCDAsyncSocket.h | 1220 --- .../Vendor/CocoaAsyncSocket/GCDAsyncSocket.m | 8786 ----------------- .../CocoaAsyncSocket/GCDAsyncUdpSocket.h | 1036 -- .../CocoaAsyncSocket/GCDAsyncUdpSocket.m | 5868 ----------- .../CocoaHTTPServer/Categories/DDNumber.h | 12 - .../CocoaHTTPServer/Categories/DDNumber.m | 88 - .../CocoaHTTPServer/Categories/DDRange.h | 56 - .../CocoaHTTPServer/Categories/DDRange.m | 100 - .../Vendor/CocoaHTTPServer/HTTPConnection.h | 109 - .../Vendor/CocoaHTTPServer/HTTPConnection.m | 2281 ----- .../Vendor/CocoaHTTPServer/HTTPLogging.h | 122 - .../Vendor/CocoaHTTPServer/HTTPMessage.h | 53 - .../Vendor/CocoaHTTPServer/HTTPMessage.m | 357 - .../Vendor/CocoaHTTPServer/HTTPResponse.h | 149 - .../Vendor/CocoaHTTPServer/HTTPServer.h | 126 - .../Vendor/CocoaHTTPServer/HTTPServer.m | 372 - .../Vendor/CocoaHTTPServer/LICENSE | 18 - .../Responses/HTTPDataResponse.h | 13 - .../Responses/HTTPDataResponse.m | 83 - .../Responses/HTTPErrorResponse.h | 9 - .../Responses/HTTPErrorResponse.m | 38 - .../RoutingHTTPServer/HTTPResponseProxy.h | 13 - .../RoutingHTTPServer/HTTPResponseProxy.m | 84 - .../Vendor/RoutingHTTPServer/LICENSE | 19 - .../Vendor/RoutingHTTPServer/Route.h | 18 - .../Vendor/RoutingHTTPServer/Route.m | 11 - .../Vendor/RoutingHTTPServer/RouteRequest.h | 16 - .../Vendor/RoutingHTTPServer/RouteRequest.m | 50 - .../Vendor/RoutingHTTPServer/RouteResponse.h | 20 - .../Vendor/RoutingHTTPServer/RouteResponse.m | 66 - .../RoutingHTTPServer/RoutingConnection.h | 5 - .../RoutingHTTPServer/RoutingConnection.m | 142 - .../RoutingHTTPServer/RoutingHTTPServer.h | 55 - .../RoutingHTTPServer/RoutingHTTPServer.m | 303 - 47 files changed, 117 insertions(+), 22287 deletions(-) rename WebDriverAgentLib/Routing/{WatchOS/FBWatchHTTPServer.h => FBHTTPServer.h} (75%) rename WebDriverAgentLib/Routing/{WatchOS/FBWatchHTTPServer.m => FBHTTPServer.m} (92%) rename WebDriverAgentLib/Routing/{WatchOS => }/RouteRequest.h (68%) rename WebDriverAgentLib/Routing/{WatchOS => }/RouteRequest.m (100%) rename WebDriverAgentLib/Routing/{WatchOS => }/RouteResponse.h (82%) rename WebDriverAgentLib/Routing/{WatchOS => }/RouteResponse.m (100%) delete mode 100644 WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h delete mode 100755 WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h delete mode 100755 WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/LICENSE delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h delete mode 100644 WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h delete mode 100644 WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m diff --git a/WebDriverAgent.xcodeproj/project.pbxproj b/WebDriverAgent.xcodeproj/project.pbxproj index 01266a0c61..cbb9c438b1 100644 --- a/WebDriverAgent.xcodeproj/project.pbxproj +++ b/WebDriverAgent.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 005B327C702EF47820887884 /* FBErrorBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = EE3A18601CDE618F00DE4205 /* FBErrorBuilder.h */; }; 00ABDDC324B060006EA182F7 /* FBScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 715AFAC01FFA29180053896D /* FBScreen.m */; }; + 0161F45997A981E47729DB25 /* RouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 01E7CFEBAD717FCF4BCDD383 /* RouteResponse.h */; }; 01A6F9758F11F6EF5B495D97 /* XCTPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CA02992F03AE1E134F2CAF5 /* XCTPromise.h */; }; 01AC2C121519821EC6D2ED6B /* XCTMacCatalystStatusProviding-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 42D2B5A0C490D9698C2A87A9 /* XCTMacCatalystStatusProviding-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 02032DB6E25DC5D0DEDD6184 /* XCUIElement+FBUID.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B49EC61ED1A58100D51AD6 /* XCUIElement+FBUID.m */; }; @@ -32,6 +33,7 @@ 09C9A6DCF11987546931BE57 /* XCUIResetAuthorizationStatusOfProtectedResourcesInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B17F41E4461BBD09A2962 /* XCUIResetAuthorizationStatusOfProtectedResourcesInterface-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 09D97BD46764FD8149747723 /* XCUIElement+FBResolve.h in Headers */ = {isa = PBXBuildFile; fileRef = 71D3B3D3267FC7260076473D /* XCUIElement+FBResolve.h */; }; 09E27E0455701764DDB8C747 /* XCUIAccessibilityInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D47DD8BF27FE639742EA2E3E /* XCUIAccessibilityInterface-Protocol.h */; }; + 0A4413521ECE45EA182E8403 /* FBHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = AADFEA2ED9E61A8C1A99B2D7 /* FBHTTPServer.m */; }; 0B2D769FCCF7C5EEB3BA2404 /* XCUIApplicationPlatformServicesProviderDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 63FB001C1E84011C0096E5D8 /* XCUIApplicationPlatformServicesProviderDelegate-Protocol.h */; }; 0B51B2F12BB356C47986D6F9 /* XCTTagSelection.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E2092D85A7C691F157B1971 /* XCTTagSelection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0C120FA673A94DB66CB0E6B0 /* XCUIElement+FBCustomActions.m in Sources */ = {isa = PBXBuildFile; fileRef = F59CD6D32EF16E5E00F91287 /* XCUIElement+FBCustomActions.m */; }; @@ -46,7 +48,6 @@ 0E04133C2DF1E15900AF007C /* XCUIElement+FBMinMax.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E04133A2DF1E15900AF007C /* XCUIElement+FBMinMax.h */; }; 0E7CAA2FA6570D0C1EADFE4A /* XCTMessagingRole_MemoryTesting-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 13D863EAE7F6F8B7E42D99B9 /* XCTMessagingRole_MemoryTesting-Protocol.h */; }; 0EFF39CC188B42E85D258F5A /* XCUIIssueDiagnosticsProviding-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 55B3452AB887CADB7AD757A4 /* XCUIIssueDiagnosticsProviding-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0F282CAB2A025ECA9EAB18B3 /* HTTPResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8F249131D40060D7EB /* HTTPResponse.h */; }; 10878F7DCE410B73C3F8CCF1 /* FBRouteRequest-Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9AB7861CAEDF0C008C271F /* FBRouteRequest-Private.h */; }; 109F8F2F96E674EB1464EF3B /* FBTVNavigationTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 641EE70A2240CE2D00173FCB /* FBTVNavigationTracker.h */; }; 10EC41A2ECCD2E865EEE7AB0 /* XCUIElement+FBClassChain.h in Headers */ = {isa = PBXBuildFile; fileRef = 71A7EAF31E20516B001DA4F2 /* XCUIElement+FBClassChain.h */; }; @@ -116,6 +117,7 @@ 203561B6C2B8456509B60AD7 /* XCTScreenCapturePolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = FDB15E393EA0850C004D26B2 /* XCTScreenCapturePolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205E75731851D363B53A61DE /* UITestingUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7FD1CAEE048008C271F /* UITestingUITests.m */; }; 208F3FB46E41B0A1C87B8800 /* FBScreenshot.m in Sources */ = {isa = PBXBuildFile; fileRef = 71C9EAAB25E8415A00470CD8 /* FBScreenshot.m */; }; + 2112EC67BDFFA4A0B2CF24EB /* RouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = D585660F7A04651223F29B07 /* RouteRequest.h */; }; 2238B4FC5C7DCD4B4215444D /* XCTMessagingRole_ProtectedResourceAuthorization-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCBEAE654102DBB1C8C22CD /* XCTMessagingRole_ProtectedResourceAuthorization-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 227E3F36FAA2C3881F89FD81 /* WDAClickIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C878996F07A9B26E66FC4EAC /* WDAClickIntegrationTests.swift */; }; 229F9F5B85C1255447495847 /* XCUIApplicationAutomationSessionProviding-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 603D97F0F9A5B9D4E6442BF2 /* XCUIApplicationAutomationSessionProviding-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -137,14 +139,12 @@ 28104BC8093DBFAB035820FA /* XCTMetricDiagnosticHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = E46239748EC4A6BFBC13F28B /* XCTMetricDiagnosticHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2892ACD1F2CC2B0AF4FC22EA /* XCUIIssueDiagnosticsProviding-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 55B3452AB887CADB7AD757A4 /* XCUIIssueDiagnosticsProviding-Protocol.h */; }; 28B292C9A3654A81B765EB37 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD1ABD1093739852B52C472B /* Foundation.framework */; }; - 293BD2162964EEC2A3BA6B57 /* HTTPResponseProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA224913C210060D7EB /* HTTPResponseProxy.h */; }; 2B39D11DD29FD3816F8AAC7C /* XCUIKnobControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 6C78201A2212BEA77979F4FF /* XCUIKnobControl.h */; }; 2B9D1292546083ACA2E34DB4 /* XCTCapabilitiesProviding-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B069904A4086E12556F1FAB /* XCTCapabilitiesProviding-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2BBEC31A5B1401C28C14899E /* XCTExpectedFailureContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AF0584AD9B6D0A57012C978 /* XCTExpectedFailureContextManager.h */; }; 2C0D6BA4BD20262B836CEDA3 /* XCTRunnerAutomationSession-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 01AF8E73DD47455B4854E470 /* XCTRunnerAutomationSession-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2C3893641ACA0AAFCC8DEB98 /* XCTPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CA02992F03AE1E134F2CAF5 /* XCTPromise.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2C6A45AFEC6B04782EE8E0B0 /* XCUIElement+FBForceTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8DDD7C20C5733B004D4925 /* XCUIElement+FBForceTouch.m */; }; - 2C6A876B29ECA17D6A5BCEED /* HTTPConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC89249131D30060D7EB /* HTTPConnection.h */; }; 2D6B818DC921A4631A496432 /* FBCommandStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9AB7761CAEDF0C008C271F /* FBCommandStatus.h */; }; 2EB7B0D78CEB3388711C7A8B /* FBErrorBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = EE3A18611CDE618F00DE4205 /* FBErrorBuilder.m */; }; 2F56E0614B3A533F388B6B51 /* XCTRemoteSignpostListenerProxy-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E47AA1A44A4A3C6EDCB6804 /* XCTRemoteSignpostListenerProxy-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -162,11 +162,10 @@ 328BB10FEA4029E1464BA7B6 /* XCTMessagingRole_TelemetrySending-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BA6647ADE7B44F13513065A /* XCTMessagingRole_TelemetrySending-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 33114F7BD00543F50F3BA4C8 /* XCTMessagingRole_SystemConfiguration-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = EA24F4BE52B2520874E09BEF /* XCTMessagingRole_SystemConfiguration-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3400BE6CBA163DC9A58D60E6 /* XCUIApplicationProcessTracker-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3238E68F292452D8234153F1 /* XCUIApplicationProcessTracker-Protocol.h */; }; - 3414F451472B235F637F46BC /* DDNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC7D249131B00060D7EB /* DDNumber.h */; }; 348B7FFB742C26599E44DB67 /* XCTMessagingRole_ProcessMonitoring-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B43D656732067585371ADD31 /* XCTMessagingRole_ProcessMonitoring-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34A32D91F0C8B6A31A9E8F9A /* FBSettingsHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F3E7D725417FF400E0C22C /* FBSettingsHandler.m */; }; - 34AB13EFF1F673084C910195 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 718226C72587443600661B83 /* GCDAsyncSocket.h */; }; 34E3403E0FE0E93C74E8FB2D /* XCTScreenCapturePolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = FDB15E393EA0850C004D26B2 /* XCTScreenCapturePolicy.h */; }; + 35924251B4B5D0A486A6A0BB /* FBHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = AADFEA2ED9E61A8C1A99B2D7 /* FBHTTPServer.m */; }; 35C087E005C82F9642F8A76C /* XCUIDeviceDelayedAttachmentTransferSupportInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C19CEEEC9858A106061D1F8 /* XCUIDeviceDelayedAttachmentTransferSupportInterface-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 36635A59A5AEDDEFC2FC01E3 /* XCUIXcodeApplicationManaging-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 30ABCB5051B826025F77E360 /* XCUIXcodeApplicationManaging-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3663B9177361DA1B4255D8F1 /* NSString+FBXMLSafeString.m in Sources */ = {isa = PBXBuildFile; fileRef = 716E0BCD1E917E810087A825 /* NSString+FBXMLSafeString.m */; }; @@ -186,7 +185,6 @@ 3F213A7FAB64730F66D3F0A6 /* XCTReportingSession.h in Headers */ = {isa = PBXBuildFile; fileRef = CB69A2606052D5979C5A436F /* XCTReportingSession.h */; }; 3FB977871FD8786CA90EB3E2 /* FBSessionCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7611CAEDF0C008C271F /* FBSessionCommands.m */; }; 3FBA39A4D511311DB7C779F4 /* FBProtocolHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B155DE23080CA600646AFB /* FBProtocolHelpers.m */; }; - 3FEF512914A962C5E30579FC /* RouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = ED271671803AAF7188E828CF /* RouteResponse.m */; }; 40955058B413422AABBB21E7 /* XCUIAlertMonitoring-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = DB5797DE5A0B4E7EE3D166F7 /* XCUIAlertMonitoring-Protocol.h */; }; 409BDFE1D64246E9248CD392 /* XCTMessagingChannel_RunnerToDaemon-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = A2793FE9DE63C687BB6E2D37 /* XCTMessagingChannel_RunnerToDaemon-Protocol.h */; }; 40CF9CA5CE21FC96F1AFF761 /* XCUIApplicationOpenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AEAD1CF473F6AD60333E9FF /* XCUIApplicationOpenRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -197,9 +195,11 @@ 431476F175B158C8E009AF69 /* XCTMessagingRole_AttachmentFutureResultStatusUpdating-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 10FBCE8B3A419B970DB7A5CE /* XCTMessagingRole_AttachmentFutureResultStatusUpdating-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 43AFED121DDF428411F72F83 /* FBXMLGenerationOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 714D88CB2733FB970074A925 /* FBXMLGenerationOptions.m */; }; 43BC9FE4BF5C19F6F6569177 /* FBRouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9AB7871CAEDF0C008C271F /* FBRouteRequest.h */; }; + 43DE58587952717F4DEE228E /* FBHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = AADFEA2ED9E61A8C1A99B2D7 /* FBHTTPServer.m */; }; 43EBA0D0D54EB99815CE63B6 /* XCUIElement+FBWebDriverAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = EEE376471D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.h */; }; 444F211126EAF77FB2B1DB42 /* FBXCElementSnapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = 13DE7A4D287C46BB003243C6 /* FBXCElementSnapshot.h */; }; 445523B6024EAE485C37C931 /* XCTRuntimeIssueDetectionPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = B98A9F937EF98D6359FCCC7A /* XCTRuntimeIssueDetectionPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 44A6F8DBFDCDD735D000458D /* RouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 01E7CFEBAD717FCF4BCDD383 /* RouteResponse.h */; }; 44A9308980F99E72907DD327 /* NSPredicate+FBFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 71A224E41DE2F56600844D55 /* NSPredicate+FBFormat.m */; }; 44D4667EC1743A6368395EF2 /* XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = E29888B75A756D1CCD21604C /* XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h */; }; 45033CF6BD35C16E34BF76CC /* FBScreenRecordingPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58E02B9631F100CB9BFE /* FBScreenRecordingPromise.m */; }; @@ -246,13 +246,13 @@ 583AD004FF71CAA305E6DEAF /* XCUIDevice+FBHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = AD6C26971CF2481700F8B5FF /* XCUIDevice+FBHelpers.m */; }; 587AA46196BC2C46555C8E44 /* XCUIButtonConsole.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E09842154A44874C4E9CA01 /* XCUIButtonConsole.h */; settings = {ATTRIBUTES = (Public, ); }; }; 588F51093FD2A5597366C282 /* XCUIElementEventTarget-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DC62BF635704E9C72AF533E /* XCUIElementEventTarget-Protocol.h */; }; - 5917EF5F1372B2098168EA0D /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 718226C62587443600661B83 /* GCDAsyncUdpSocket.h */; }; 5967912A40E807CDBE87E4B2 /* XCTReportingSessionIssueReporter-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 525D62A58488A52AA1BFE94A /* XCTReportingSessionIssueReporter-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 59689A4C5FC03AF28D15FAA2 /* XCUIAccessibilityInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D47DD8BF27FE639742EA2E3E /* XCUIAccessibilityInterface-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 59892BBAB84DFD927C94593F /* _XCTestObservationPrivate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = C840A8703A7C8D48897E158A /* _XCTestObservationPrivate-Protocol.h */; }; 5A1B8098AE35B82BD379B421 /* XCUIElement+FBForceTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */; }; 5AF49CE43E731B433375C5B7 /* ViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F87CD156E93338642EEFD54 /* ViewController.h */; }; 5B736BB83DEA69D3C5EEC7E1 /* XCTElementSetTransformer-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BA7DD8C206D694B007C7C26 /* XCTElementSetTransformer-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5B9C00B488A31A95F1460727 /* RouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B52E4A09ADEA252A1B8190F /* RouteResponse.m */; }; 5C1E821041979B5FA859B6D0 /* XCUIElement+FBPickerWheel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7136A4781E8918E60024FC3D /* XCUIElement+FBPickerWheel.m */; }; 5D1959FC0B6EBB249DD86062 /* XCApplicationQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACB71E3B77D600A02D78 /* XCApplicationQuery.h */; }; 5D2A81B261FEC67033F41101 /* XCUIElementQuery+FBHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 71E75E6B254824230099FC87 /* XCUIElementQuery+FBHelpers.h */; }; @@ -310,7 +310,6 @@ 641EE5F62240C5CA00173FCB /* FBRouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7881CAEDF0C008C271F /* FBRouteRequest.m */; }; 641EE5F72240C5CA00173FCB /* FBResponseJSONPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7811CAEDF0C008C271F /* FBResponseJSONPayload.m */; }; 641EE5F92240C5CA00173FCB /* FBMjpegServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7155D702211DCEF400166C20 /* FBMjpegServer.m */; }; - AA11BB22CC33DD44EE55FF02 /* FBMjpegServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7155D702211DCEF400166C20 /* FBMjpegServer.m */; }; 641EE5FA2240C5CA00173FCB /* XCUIDevice+FBHealthCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = EEDFE1201D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.m */; }; 641EE5FD2240C5CA00173FCB /* FBBaseActionsSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7140974D1FAE20EE008FB2C5 /* FBBaseActionsSynthesizer.m */; }; 641EE5FE2240C5CA00173FCB /* XCUIElement+FBWebDriverAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE376481D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m */; }; @@ -480,6 +479,7 @@ 66030D36126676CD334588B3 /* XCUIApplicationProcessTracker-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3238E68F292452D8234153F1 /* XCUIApplicationProcessTracker-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6627D7B40D3D915B115D7B1A /* FBSession-Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9AB7891CAEDF0C008C271F /* FBSession-Private.h */; }; 664471C47B921A09884F5C1F /* XCTMessagingRole_EventSynthesis-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E2F8B9A21359AC98424DDE0 /* XCTMessagingRole_EventSynthesis-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 667705A8195BF7B0D48180B3 /* RouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 374BB11AE4E4243BCA444CF8 /* RouteRequest.m */; }; 668B1D9AFF85F0538E738675 /* XCUIElementAttributesPrivate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EF5BE504321321FB9320C43 /* XCUIElementAttributesPrivate-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 67402F46AAB3DCAB3E40ED27 /* FBUnknownCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9AB7641CAEDF0C008C271F /* FBUnknownCommands.h */; }; 6786D4B257269918E591AC66 /* FBElementUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 713C6DCD1DDC772A00285B92 /* FBElementUtils.h */; }; @@ -510,6 +510,7 @@ 6EC82BD561787BE315AD369A /* XCTMessagingRole_SiriAutomation-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FCD1815F2BF21CA0936B04E1 /* XCTMessagingRole_SiriAutomation-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6EDF83DA0A6C7A7E67C30AF6 /* WDADeviceIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646D714BC17FE9258CFBDF55 /* WDADeviceIntegrationTests.swift */; }; 6FC8E41707F65D5A432206CC /* FBXCAccessibilityElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 13DE7A41287C2A8D003243C6 /* FBXCAccessibilityElement.h */; }; + 7072174F17BA109C6AB2859F /* RouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 374BB11AE4E4243BCA444CF8 /* RouteRequest.m */; }; 70E21F76098198E19A6E5A5E /* FBXCAXClientProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 7157B28F221DADD2001C348C /* FBXCAXClientProxy.h */; }; 711084441DA3AA7500F913D6 /* FBXPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 711084421DA3AA7500F913D6 /* FBXPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 711084451DA3AA7500F913D6 /* FBXPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 711084431DA3AA7500F913D6 /* FBXPath.m */; }; @@ -603,29 +604,6 @@ 716F0DA12A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 716F0D9F2A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h */; }; 716F0DA32A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 716F0DA02A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m */; }; 716F0DA62A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 716F0DA52A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m */; }; - 718226CA2587443700661B83 /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 718226C62587443600661B83 /* GCDAsyncUdpSocket.h */; }; - 718226CB2587443700661B83 /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 718226C62587443600661B83 /* GCDAsyncUdpSocket.h */; }; - 718226CC2587443700661B83 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 718226C72587443600661B83 /* GCDAsyncSocket.h */; }; - 718226CD2587443700661B83 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 718226C72587443600661B83 /* GCDAsyncSocket.h */; }; - 718226CE2587443700661B83 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 718226C82587443600661B83 /* GCDAsyncSocket.m */; }; - 718226CF2587443700661B83 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 718226C82587443600661B83 /* GCDAsyncSocket.m */; }; - 718226D02587443700661B83 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 718226C92587443600661B83 /* GCDAsyncUdpSocket.m */; }; - 718226D12587443700661B83 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 718226C92587443600661B83 /* GCDAsyncUdpSocket.m */; }; - 71822702258744A400661B83 /* HTTPResponseProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA224913C210060D7EB /* HTTPResponseProxy.h */; }; - 7182270B258744A700661B83 /* Route.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA324913C210060D7EB /* Route.h */; }; - 71822714258744A900661B83 /* RouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCAA24913C220060D7EB /* RouteRequest.h */; }; - 7182271D258744AB00661B83 /* RouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA124913C210060D7EB /* RouteResponse.h */; }; - 71822726258744AE00661B83 /* RoutingConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA524913C210060D7EB /* RoutingConnection.h */; }; - 7182272F258744B000661B83 /* RoutingHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA724913C210060D7EB /* RoutingHTTPServer.h */; }; - 71822738258744B800661B83 /* HTTPConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC89249131D30060D7EB /* HTTPConnection.h */; }; - 71822741258744BB00661B83 /* HTTPLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8D249131D30060D7EB /* HTTPLogging.h */; }; - 7182274A258744BE00661B83 /* HTTPMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC87249131D30060D7EB /* HTTPMessage.h */; }; - 71822753258744C100661B83 /* HTTPResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8F249131D40060D7EB /* HTTPResponse.h */; }; - 7182275C258744C300661B83 /* HTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8B249131D30060D7EB /* HTTPServer.h */; }; - 71822765258744C700661B83 /* HTTPDataResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC60249131890060D7EB /* HTTPDataResponse.h */; }; - 7182276E258744C900661B83 /* HTTPErrorResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC59249131880060D7EB /* HTTPErrorResponse.h */; }; - 71822777258744CE00661B83 /* DDNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC7D249131B00060D7EB /* DDNumber.h */; }; - 71822780258744D000661B83 /* DDRange.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC7B249131B00060D7EB /* DDRange.h */; }; 7182A87F3CAA27F71B624AD2 /* XCTRunnerAutomationSession-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 01AF8E73DD47455B4854E470 /* XCTRunnerAutomationSession-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 718F49C8230844330045FE8B /* FBProtocolHelpersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 718F49C7230844330045FE8B /* FBProtocolHelpersTests.m */; }; 718F49C923087ACF0045FE8B /* FBProtocolHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B155DD23080CA600646AFB /* FBProtocolHelpers.h */; }; @@ -806,7 +784,6 @@ 88EE4275BFBD9207CBD84959 /* XCTIssue.h in Headers */ = {isa = PBXBuildFile; fileRef = ACB055BCA9CCEAB3DECD1A74 /* XCTIssue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8934D96BE720E53106DCFA6C /* XCUIElement+FBResolve.m in Sources */ = {isa = PBXBuildFile; fileRef = 71D3B3D4267FC7260076473D /* XCUIElement+FBResolve.m */; }; 8935209ECEC079F126FBDFAC /* XCTSkippedTestContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C32C75CC17179B347DF6F78 /* XCTSkippedTestContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 894AE4397B5992EF738248AE /* RouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCAA24913C220060D7EB /* RouteRequest.h */; }; 8A8D9B0342BC43BEB65749B7 /* XCUILocation.h in Headers */ = {isa = PBXBuildFile; fileRef = A8230FDD93639CE2E9EFE311 /* XCUILocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8AAA33A5B1943B667B0DB05E /* FBMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9B76A51CF7A43900275851 /* FBMacros.h */; }; 8CB293E6451EB1A6D1240BAA /* XCTElementSetTransformer-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BA7DD8C206D694B007C7C26 /* XCTElementSetTransformer-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -817,11 +794,10 @@ 8EA51935E09A89896FB1D463 /* FBW3CActionsSynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 714097491FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.h */; }; 8F36546FCCCBE918C9088111 /* XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = E29888B75A756D1CCD21604C /* XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8FC4331C2C06A3E8B8F490E2 /* FBDebugCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7551CAEDF0C008C271F /* FBDebugCommands.m */; }; - 90107B3BBFBF3B073807D51B /* HTTPLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8D249131D30060D7EB /* HTTPLogging.h */; }; 908767A5CCB6552156CDAFB3 /* XCTAttachmentManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D80202DC5D679EF37896431 /* XCTAttachmentManager.h */; }; + 90C6C1E34B9B850CC79BABCB /* RouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B52E4A09ADEA252A1B8190F /* RouteResponse.m */; }; 914D7116A5BF672ACC7F1CE6 /* XCUIInterruptionMonitoring-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 44019CB45CF491B5FDAB8213 /* XCUIInterruptionMonitoring-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 918A48693FF49E932FDFE3AE /* FBElementHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 715A84CD2DD92AD3007134CC /* FBElementHelpers.h */; }; - 91FF05D51401D1F1A88FB0B0 /* FBWatchHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CFF432CCF46627AB7315F9 /* FBWatchHTTPServer.m */; }; 923B2C779F413A9EB7023AC5 /* XCUIElement+FBVisibleFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 71AE3CF62D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m */; }; 92A632860C8133ADDD5DE5F4 /* LRUCacheNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 71414ED12670A1ED003A8C5D /* LRUCacheNode.h */; }; 92DC81D2F6E58386AC592482 /* FBXCTestDaemonsProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = EE35AD7A1E3B80C000A02D78 /* FBXCTestDaemonsProxy.m */; }; @@ -888,16 +864,18 @@ A72A8752161D64BDB5290703 /* XCUISystem.h in Headers */ = {isa = PBXBuildFile; fileRef = DE39D4384E531299FAA143F7 /* XCUISystem.h */; settings = {ATTRIBUTES = (Public, ); }; }; A781B56952349EB47FBF0DAD /* XCTMessagingRole_SystemConfiguration-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = EA24F4BE52B2520874E09BEF /* XCTMessagingRole_SystemConfiguration-Protocol.h */; }; A7F0DE38C7CB24D31857C7AE /* XCTSourceCodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 530D925D1492C4DF826B46BB /* XCTSourceCodeContext.h */; }; - A8635BD557F97E6C29A0790E /* RoutingHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA724913C210060D7EB /* RoutingHTTPServer.h */; }; A893F1BC22A6066353FCD515 /* XCUIApplicationProcess+FBQuiescence.m in Sources */ = {isa = PBXBuildFile; fileRef = 71D475C12538F5A8008D9401 /* XCUIApplicationProcess+FBQuiescence.m */; }; A8CEAEFC8CC63F94DA0176A9 /* XCUIDevice+FBVoiceOver.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B2C3D41F001A00A1B0001 /* XCUIDevice+FBVoiceOver.h */; }; A95AE6DC8C54B7E2C3CB2570 /* XCTMessagingRole_SelfDiagnosisIssueReporting-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 74AA57E9A81B1B0515CB587F /* XCTMessagingRole_SelfDiagnosisIssueReporting-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; A967F44C38E10C0295AD08FF /* XCTMessagingRole_CapabilityExchange-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 62A682C55077710D1D90ABC3 /* XCTMessagingRole_CapabilityExchange-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; A970B1FBC690CBE536636228 /* WebDriverAgentLib_watchOS.framework in Copy frameworks */ = {isa = PBXBuildFile; fileRef = D1171249FE5D91AC5D797E5C /* WebDriverAgentLib_watchOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A9BCCC677A46E48D4D2B05A9 /* FBActiveAppDetectionPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 13815F6E2328D20400CDAB61 /* FBActiveAppDetectionPoint.m */; }; + AA11BB22CC33DD44EE55FF02 /* FBMjpegServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7155D702211DCEF400166C20 /* FBMjpegServer.m */; }; + AA11BB22CC33DD44EE55FF04 /* WDAMjpegStreamingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11BB22CC33DD44EE55FF03 /* WDAMjpegStreamingIntegrationTests.swift */; }; AAA213E600F40AB7F43D1015 /* WebDriverAgentLib_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 641EE6F82240C5CA00173FCB /* WebDriverAgentLib_tvOS.framework */; }; AABBCCDDEEFF001122334457 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AABBCCDDEEFF001122334456 /* SceneDelegate.m */; }; AB126E96C642B235EE02B4F1 /* _XCTestObservationPrivate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = C840A8703A7C8D48897E158A /* _XCTestObservationPrivate-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AB531917FF460EB87E4AD5A2 /* RouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B52E4A09ADEA252A1B8190F /* RouteResponse.m */; }; AB8D3BCC12F3A47869D31341 /* XCTMessagingRole_TestExecution-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 209C65575782ECAA09892D2B /* XCTMessagingRole_TestExecution-Protocol.h */; }; AC3529AFA966E7202CB3B3B1 /* FBRoute.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9AB7841CAEDF0C008C271F /* FBRoute.h */; }; AC8CA9AA80C6ECF4D1405F25 /* XCTMemoryCheckerDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EEB78C32D7E6DD6E4EBCD26 /* XCTMemoryCheckerDelegate-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -922,7 +900,6 @@ ADEF63AF1D09DEBE0070A7E3 /* FBRuntimeUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ADEF63AE1D09DEBE0070A7E3 /* FBRuntimeUtilsTests.m */; }; AEA08D6963D0F969992A56C3 /* XCTMemoryChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = AFD7929A5F0B45397F0D64CB /* XCTMemoryChecker.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF13E087AA2FF233A495CD0A /* XCUIResetAuthorizationStatusOfProtectedResourcesInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B17F41E4461BBD09A2962 /* XCUIResetAuthorizationStatusOfProtectedResourcesInterface-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AF4223DDBCC9EC79D4F9DC0D /* DDRange.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC7B249131B00060D7EB /* DDRange.h */; }; AF4B652A13BCF1C08AD6ECA0 /* XCTSignpostListener-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F9351F7A43851CA5DFF47CD /* XCTSignpostListener-Protocol.h */; }; AF718FF9F916429C65A2FD11 /* XCTRuntimeIssueDetectionPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = B98A9F937EF98D6359FCCC7A /* XCTRuntimeIssueDetectionPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; B1A18407C6600D9E8ABABD5A /* XCUIApplicationProcessDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 43FCB89739438814F43BCA24 /* XCUIApplicationProcessDelegate-Protocol.h */; }; @@ -946,7 +923,6 @@ BC91C1F00C54FE2115DE983F /* FBRunLoopSpinner.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE9B4711CD02B88009D2030 /* FBRunLoopSpinner.m */; }; BC91E1DAF79A56CFC4A77D6E /* XCTReportingSessionTestReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D6B33F48F089C945641DB3F /* XCTReportingSessionTestReporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCC63D234BBBA7F8BF8E953D /* XCTRepetitionPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = A1291FF806FE75EDE239FE44 /* XCTRepetitionPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BCC9E02491560712221045CC /* HTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8B249131D30060D7EB /* HTTPServer.h */; }; BD2A5D43881B449E533D5800 /* UIKeyboardImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 648C10AA22AAAD9C00B81B9A /* UIKeyboardImpl.h */; }; BD8002B8812AF2CDD76BDF4D /* FBImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 7150348621A6DAD600A0F4BA /* FBImageUtils.m */; }; BE63FC311D9DD0A98874568B /* XCUIApplicationManaging-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 00834B3220005AD5A5ABEF7C /* XCUIApplicationManaging-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -958,11 +934,9 @@ C07F140AF96143A0A5CAA2B0 /* XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = E29888B75A756D1CCD21604C /* XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; C1414C29C836902466C4D6DB /* XCTestCastMethodNamesUIAutomationDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = A80A1C12FA9899367D316E8C /* XCTestCastMethodNamesUIAutomationDelegate-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; C14FD6933C5E978E7E54F44D /* XCTMessagingRole_BundleRequesting-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = C928E94CBC13E262D818C28E /* XCTMessagingRole_BundleRequesting-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C154B8CD167DBE068EA74719 /* HTTPMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC87249131D30060D7EB /* HTTPMessage.h */; }; C158CE0AD6EEBEA854454AA2 /* XCUIApplicationProcessManaging-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C710FE3AB9E9C22BD2A9E84 /* XCUIApplicationProcessManaging-Protocol.h */; }; C1ACCF2EAF1C402FDF4FEFFD /* XCTMessagingRole_PerformanceMeasurementReporting-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B650ADFEEA2369A10F6C1F1 /* XCTMessagingRole_PerformanceMeasurementReporting-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; C22CA4AEE1C7395AE82B3BD3 /* XCTMessagingRole_PerformanceMeasurementReporting-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B650ADFEEA2369A10F6C1F1 /* XCTMessagingRole_PerformanceMeasurementReporting-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C2D22426DB9FCE6AD84FA16A /* RouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 97FFAEF000CE312DEBEA9385 /* RouteRequest.m */; }; C2F6BB3D8A49F762E5172768 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7155B419224D5B460042A993 /* libxml2.tbd */; }; C309FAE89D050C90496FB87B /* XCTRemoteSignpostListenerProxy-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E47AA1A44A4A3C6EDCB6804 /* XCTRemoteSignpostListenerProxy-Protocol.h */; }; C3A3578B56BF260A77EB1ECF /* XCUIAlertMonitoring-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = DB5797DE5A0B4E7EE3D166F7 /* XCUIAlertMonitoring-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -988,6 +962,7 @@ C8FB547922D4C1FC00B69954 /* FBUnattachedAppLauncher.h in Headers */ = {isa = PBXBuildFile; fileRef = C8FB547722D4C1FC00B69954 /* FBUnattachedAppLauncher.h */; }; C8FB547A22D4C1FC00B69954 /* FBUnattachedAppLauncher.m in Sources */ = {isa = PBXBuildFile; fileRef = C8FB547822D4C1FC00B69954 /* FBUnattachedAppLauncher.m */; }; C931666B44D9F20B3A1026B0 /* XCAXClient_iOS+FBSnapshotReqParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 714E14B629805CAE00375DD7 /* XCAXClient_iOS+FBSnapshotReqParams.h */; }; + CA077D3ED0D0BA2590CB85DE /* RouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = D585660F7A04651223F29B07 /* RouteRequest.h */; }; CA1E428789B0D9CB9C874133 /* XCUIKnobControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 6C78201A2212BEA77979F4FF /* XCUIKnobControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; CAA29EF94D29713540C57528 /* XCUIInterruptionMonitoring-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 44019CB45CF491B5FDAB8213 /* XCUIInterruptionMonitoring-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; CB1790B793BB813AF80F65DF /* XCUIElement+FBTVFocuse.h in Headers */ = {isa = PBXBuildFile; fileRef = 641EE7042240CDCF00173FCB /* XCUIElement+FBTVFocuse.h */; }; @@ -1005,6 +980,7 @@ D02E19924A93582C7F4F80AF /* _TtC10XCTestCore19XCTReportingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DFEF3D0F3F006AC53F9E525 /* _TtC10XCTestCore19XCTReportingContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; D062CA5914608761FE002799 /* FBXCAXClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7157B290221DADD2001C348C /* FBXCAXClientProxy.m */; }; D101B194EE49F32110C394FD /* NSFastEnumeration-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 58156929D12ADE7DFE10116F /* NSFastEnumeration-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D1172A7F53F89B78D8324A13 /* RouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 374BB11AE4E4243BCA444CF8 /* RouteRequest.m */; }; D1212325FDE77754EE503755 /* FBRouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7881CAEDF0C008C271F /* FBRouteRequest.m */; }; D142183578036F5FD5B9880B /* XCUIDevice+FBHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C26961CF2481700F8B5FF /* XCUIDevice+FBHelpers.h */; }; D14718959F7D46949859EEE6 /* FBDebugLogDelegateDecorator.m in Sources */ = {isa = PBXBuildFile; fileRef = EE7E27191D06C69F001BEC7B /* FBDebugLogDelegateDecorator.m */; }; @@ -1032,6 +1008,7 @@ D87509BF0784FDFFD57A2EF5 /* FBNotificationsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 719DCF142601EAFB000E765F /* FBNotificationsHelper.m */; }; D9D6F0D054AF1BDAAFD0E667 /* XCUIXcodeApplicationManaging-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 30ABCB5051B826025F77E360 /* XCUIXcodeApplicationManaging-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA26A56D4FE01CA1FAF6F1E7 /* FBOrientationCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB75D1CAEDF0C008C271F /* FBOrientationCommands.m */; }; + DAB4F2FB4C0AB107E461BBA4 /* RouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 01E7CFEBAD717FCF4BCDD383 /* RouteResponse.h */; }; DADC5E6FD3E80449EE5B5841 /* NSString+FBVisualLength.h in Headers */ = {isa = PBXBuildFile; fileRef = EE0D1F5F1EBCDCF7006A3123 /* NSString+FBVisualLength.h */; }; DAFD9ED6842CE53DBAF9F8EB /* XCTMessagingRole_DebugLogging-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 471DF7EE3C63C3069FB9D40D /* XCTMessagingRole_DebugLogging-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; DBD3777FD8EAD997F04F52A2 /* XCTMessagingRole_ProcessMonitoring-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B43D656732067585371ADD31 /* XCTMessagingRole_ProcessMonitoring-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1052,49 +1029,9 @@ E2EEFB9B3D049FB31AF1CFF4 /* XCUISiriService.h in Headers */ = {isa = PBXBuildFile; fileRef = 7076779B6AB29D5AAB5E4D33 /* XCUISiriService.h */; settings = {ATTRIBUTES = (Public, ); }; }; E30D52357A950271BD8EF346 /* XCTReportingSessionTestContainer-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D5D58975822CD58C8FEF7FEB /* XCTReportingSessionTestContainer-Protocol.h */; }; E3653A8F329E8AB4061B0310 /* FBConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9B76A11CF7A43900275851 /* FBConfiguration.h */; }; - E444DC65249131890060D7EB /* HTTPErrorResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC59249131880060D7EB /* HTTPErrorResponse.h */; }; - E444DC67249131890060D7EB /* HTTPDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC5B249131880060D7EB /* HTTPDataResponse.m */; }; - E444DC6C249131890060D7EB /* HTTPDataResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC60249131890060D7EB /* HTTPDataResponse.h */; }; - E444DC6D249131890060D7EB /* HTTPErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC61249131890060D7EB /* HTTPErrorResponse.m */; }; - E444DC81249131B10060D7EB /* DDRange.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC7B249131B00060D7EB /* DDRange.h */; }; - E444DC83249131B10060D7EB /* DDNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC7D249131B00060D7EB /* DDNumber.h */; }; - E444DC84249131B10060D7EB /* DDRange.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC7E249131B00060D7EB /* DDRange.m */; }; - E444DC85249131B10060D7EB /* DDNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC7F249131B00060D7EB /* DDNumber.m */; }; - E444DC93249131D40060D7EB /* HTTPMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC87249131D30060D7EB /* HTTPMessage.h */; }; - E444DC95249131D40060D7EB /* HTTPConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC89249131D30060D7EB /* HTTPConnection.h */; }; - E444DC97249131D40060D7EB /* HTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8B249131D30060D7EB /* HTTPServer.h */; }; - E444DC98249131D40060D7EB /* HTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC8C249131D30060D7EB /* HTTPConnection.m */; }; - E444DC99249131D40060D7EB /* HTTPLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8D249131D30060D7EB /* HTTPLogging.h */; }; - E444DC9B249131D40060D7EB /* HTTPResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC8F249131D40060D7EB /* HTTPResponse.h */; }; - E444DC9C249131D40060D7EB /* HTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC90249131D40060D7EB /* HTTPServer.m */; }; - E444DC9D249131D40060D7EB /* HTTPMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC91249131D40060D7EB /* HTTPMessage.m */; }; - E444DCAB24913C220060D7EB /* HTTPResponseProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC9F24913C210060D7EB /* HTTPResponseProxy.m */; }; - E444DCAC24913C220060D7EB /* Route.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA024913C210060D7EB /* Route.m */; }; - E444DCAD24913C220060D7EB /* RouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA124913C210060D7EB /* RouteResponse.h */; }; - E444DCAE24913C220060D7EB /* HTTPResponseProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA224913C210060D7EB /* HTTPResponseProxy.h */; }; - E444DCAF24913C220060D7EB /* Route.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA324913C210060D7EB /* Route.h */; }; - E444DCB024913C220060D7EB /* RouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA424913C210060D7EB /* RouteResponse.m */; }; - E444DCB124913C220060D7EB /* RoutingConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA524913C210060D7EB /* RoutingConnection.h */; }; - E444DCB224913C220060D7EB /* RoutingConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA624913C210060D7EB /* RoutingConnection.m */; }; - E444DCB324913C220060D7EB /* RoutingHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA724913C210060D7EB /* RoutingHTTPServer.h */; }; - E444DCB424913C220060D7EB /* RoutingHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA824913C220060D7EB /* RoutingHTTPServer.m */; }; - E444DCB524913C220060D7EB /* RouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA924913C220060D7EB /* RouteRequest.m */; }; - E444DCB624913C220060D7EB /* RouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCAA24913C220060D7EB /* RouteRequest.h */; }; - E444DCBC24917A5E0060D7EB /* HTTPResponseProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC9F24913C210060D7EB /* HTTPResponseProxy.m */; }; - E444DCBE24917A5E0060D7EB /* Route.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA024913C210060D7EB /* Route.m */; }; - E444DCC024917A5E0060D7EB /* RouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA924913C220060D7EB /* RouteRequest.m */; }; - E444DCC224917A5E0060D7EB /* RouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA424913C210060D7EB /* RouteResponse.m */; }; - E444DCC424917A5E0060D7EB /* RoutingConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA624913C210060D7EB /* RoutingConnection.m */; }; - E444DCC624917A5E0060D7EB /* RoutingHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DCA824913C220060D7EB /* RoutingHTTPServer.m */; }; - E444DCC824917A5E0060D7EB /* HTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC8C249131D30060D7EB /* HTTPConnection.m */; }; - E444DCCB24917A5E0060D7EB /* HTTPMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC91249131D40060D7EB /* HTTPMessage.m */; }; - E444DCCE24917A5E0060D7EB /* HTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC90249131D40060D7EB /* HTTPServer.m */; }; - E444DCD024917A5E0060D7EB /* HTTPDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC5B249131880060D7EB /* HTTPDataResponse.m */; }; - E444DCD224917A5E0060D7EB /* HTTPErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC61249131890060D7EB /* HTTPErrorResponse.m */; }; - E444DCD424917A5E0060D7EB /* DDNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC7F249131B00060D7EB /* DDNumber.m */; }; - E444DCD624917A5E0060D7EB /* DDRange.m in Sources */ = {isa = PBXBuildFile; fileRef = E444DC7E249131B00060D7EB /* DDRange.m */; }; E47C0E7ED1200567EC7DA4FC /* XCUIApplicationProcess+FBQuiescence.h in Headers */ = {isa = PBXBuildFile; fileRef = 71D475C02538F5A8008D9401 /* XCUIApplicationProcess+FBQuiescence.h */; }; E4F38E2031A5FB938468C536 /* XCTIssueHandling-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = CD4DA0D55FD20614EDA82368 /* XCTIssueHandling-Protocol.h */; }; + E4FA72A388B36E0B6C41C421 /* RouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = D585660F7A04651223F29B07 /* RouteRequest.h */; }; E571286CBEA891946EED7C70 /* XCTSourceCodeLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 81BB54ECBFB5B7B680BB5D4F /* XCTSourceCodeLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; E608A983E2E6A6A1A46A6E91 /* XCUIDeviceAutomationModeInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CF2F53B94CC13C628FC7760 /* XCUIDeviceAutomationModeInterface-Protocol.h */; }; E62AED48CAE64088C1E7498B /* XCUIElement+FBPickerWheel.h in Headers */ = {isa = PBXBuildFile; fileRef = 7136A4771E8918E60024FC3D /* XCUIElement+FBPickerWheel.h */; }; @@ -1117,7 +1054,6 @@ ECC4ABB5477FBA3D442559B1 /* AppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 242FCA9DD0E30D748E0A1969 /* AppDelegate.h */; }; ED055540E72DDD419F88EEA4 /* XCUIApplicationProcessDelay.m in Sources */ = {isa = PBXBuildFile; fileRef = 6385F4A5220A40760095BBDB /* XCUIApplicationProcessDelay.m */; }; ED342D194B20A206864675B2 /* XCUIApplication+FBUIInterruptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 716C9DFE27315EFF005AD475 /* XCUIApplication+FBUIInterruptions.h */; }; - EDB13FDB5D8220A65560629B /* HTTPErrorResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC59249131880060D7EB /* HTTPErrorResponse.h */; }; EDC993E0858F89F73D8E9DF8 /* FBCustomCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7531CAEDF0C008C271F /* FBCustomCommands.m */; }; EDF7C92DC3B9860FC08BE3DC /* XCTFuture.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F044AD574A9837C04F833CB /* XCTFuture.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE006EAD1EB99B15006900A4 /* FBElementVisibilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE006EAC1EB99B15006900A4 /* FBElementVisibilityTests.m */; }; @@ -1185,7 +1121,6 @@ EE2202131ECC612200A29571 /* FBIntegrationTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EE1E06D91D1808C2007CF043 /* FBIntegrationTestCase.m */; }; EE2202171ECC612200A29571 /* WebDriverAgentLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE158A991CBD452B00A3E3F0 /* WebDriverAgentLib.framework */; }; EE22021E1ECC618900A29571 /* FBTapTest.m in Sources */ = {isa = PBXBuildFile; fileRef = EE26409A1D0EB5E8009BE6B0 /* FBTapTest.m */; }; - EE22974A8F641FA94DCBCB3E /* HTTPDataResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DC60249131890060D7EB /* HTTPDataResponse.h */; }; EE26409D1D0EBA25009BE6B0 /* FBElementAttributeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE26409C1D0EBA25009BE6B0 /* FBElementAttributeTests.m */; }; EE35AD151E3B77D600A02D78 /* CDStructures.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACA41E3B77D600A02D78 /* CDStructures.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE35AD281E3B77D600A02D78 /* XCApplicationQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACB71E3B77D600A02D78 /* XCApplicationQuery.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1287,7 +1222,6 @@ F036DAC96136D88F0427B9CB /* XCUIElementTypeQueryProvider_Private-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF208CBAC82CDFF8B6F88867 /* XCUIElementTypeQueryProvider_Private-Protocol.h */; }; F03F1D8CFEA1D26423192BF0 /* XCTExpectedFailureContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AF0584AD9B6D0A57012C978 /* XCTExpectedFailureContextManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; F044875736461509BDFC72B9 /* XCTMessagingRole_HIDEventRecording-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 94D7F0C1E30FBDB5D2583908 /* XCTMessagingRole_HIDEventRecording-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F0F7B8DFB14C9DFC86DFFBFD /* RouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA124913C210060D7EB /* RouteResponse.h */; }; F12C29FCEE471095C1FA8A7A /* XCUIDeviceEventAndStateInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 003AAAB9CB5FB38E45E05F6F /* XCUIDeviceEventAndStateInterface-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; F12D854E1692CE11249A9762 /* XCTReportingSession.h in Headers */ = {isa = PBXBuildFile; fileRef = CB69A2606052D5979C5A436F /* XCTReportingSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; F13426E5482242AFB787BE4A /* FBActiveAppDetectionPoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 13815F6D2328D20400CDAB61 /* FBActiveAppDetectionPoint.h */; }; @@ -1318,10 +1252,8 @@ FAC85D261CAD26DB5F9D804C /* XCUIPlatformApplicationServicesProviding-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3BA71BCF1FDA482419CA8596 /* XCUIPlatformApplicationServicesProviding-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; FBAFC553152CE132D6CB98E5 /* XCUIDeviceDelayedAttachmentTransferSupportInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C19CEEEC9858A106061D1F8 /* XCUIDeviceDelayedAttachmentTransferSupportInterface-Protocol.h */; }; FBB82323D9D57F069440BF94 /* XCTMessagingRole_SystemConfiguration-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = EA24F4BE52B2520874E09BEF /* XCTMessagingRole_SystemConfiguration-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FBDF04E5916B91FCEC0190CB /* Route.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA324913C210060D7EB /* Route.h */; }; FBF064E4D96CFCD08E1F4EF7 /* XCUIDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACFD1E3B77D600A02D78 /* XCUIDevice.h */; }; FBFEC05ED2C01D1EAE34BE9A /* WDAScreenshotAndSourceIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F677B5B7737E7E1F321C20 /* WDAScreenshotAndSourceIntegrationTests.swift */; }; - AA11BB22CC33DD44EE55FF04 /* WDAMjpegStreamingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11BB22CC33DD44EE55FF03 /* WDAMjpegStreamingIntegrationTests.swift */; }; FC607DB5132FEF425237267B /* XCTMessagingRole_MemoryTesting-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 13D863EAE7F6F8B7E42D99B9 /* XCTMessagingRole_MemoryTesting-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; FC73EE781D24A598B44C3337 /* XCUIElement+FBScrolling.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB74A1CAEDF0C008C271F /* XCUIElement+FBScrolling.m */; }; FD89236D119E129E8CEBDBCD /* XCTMessagingRole_HIDEventRecording-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 94D7F0C1E30FBDB5D2583908 /* XCTMessagingRole_HIDEventRecording-Protocol.h */; }; @@ -1330,7 +1262,6 @@ FDEB571007C83EC56F365EB3 /* XCTMessagingRole_EventSynthesis-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E2F8B9A21359AC98424DDE0 /* XCTMessagingRole_EventSynthesis-Protocol.h */; }; FEB143DBE613795D4F8693B4 /* FBSession.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9AB78A1CAEDF0C008C271F /* FBSession.h */; }; FEC3A97A4929115A192F947A /* XCUIDeviceEventAndStateInterface-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 003AAAB9CB5FB38E45E05F6F /* XCUIDeviceEventAndStateInterface-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FEC52AE4F35AD38D3AC2659F /* RoutingConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA524913C210060D7EB /* RoutingConnection.h */; }; FFA7672B731B57AB9283DD40 /* FBXCElementSnapshotWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 13DE7A53287CA1EC003243C6 /* FBXCElementSnapshotWrapper.h */; }; FFD70914E6D8CE5D4FED4B69 /* XCUIAXNotificationHandling-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = EA96C2FEDE73CB5148BA4949 /* XCUIAXNotificationHandling-Protocol.h */; }; /* End PBXBuildFile section */ @@ -1482,7 +1413,9 @@ /* Begin PBXFileReference section */ 003AAAB9CB5FB38E45E05F6F /* XCUIDeviceEventAndStateInterface-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIDeviceEventAndStateInterface-Protocol.h"; sourceTree = ""; }; 00834B3220005AD5A5ABEF7C /* XCUIApplicationManaging-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIApplicationManaging-Protocol.h"; sourceTree = ""; }; + 00B1F89716AFE04C8509B916 /* FBHTTPServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = FBHTTPServer.h; sourceTree = ""; }; 01AF8E73DD47455B4854E470 /* XCTRunnerAutomationSession-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTRunnerAutomationSession-Protocol.h"; sourceTree = ""; }; + 01E7CFEBAD717FCF4BCDD383 /* RouteResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = RouteResponse.h; sourceTree = ""; }; 036CD60C5DFA1644E3D51289 /* XCTMessagingRole_UserPresence-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_UserPresence-Protocol.h"; sourceTree = ""; }; 059F291E7D13B17580B4AD43 /* _XCTMessaging_VoidProtocol-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "_XCTMessaging_VoidProtocol-Protocol.h"; sourceTree = ""; }; 0DC62BF635704E9C72AF533E /* XCUIElementEventTarget-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIElementEventTarget-Protocol.h"; sourceTree = ""; }; @@ -1534,7 +1467,7 @@ 315A15082518D6F400A3A064 /* TouchViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchViewController.h; sourceTree = ""; }; 315A15092518D6F400A3A064 /* TouchViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TouchViewController.m; sourceTree = ""; }; 3238E68F292452D8234153F1 /* XCUIApplicationProcessTracker-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIApplicationProcessTracker-Protocol.h"; sourceTree = ""; }; - 341B5F150DED296FF38FB5F7 /* FBWatchHTTPServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = FBWatchHTTPServer.h; sourceTree = ""; }; + 374BB11AE4E4243BCA444CF8 /* RouteRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = RouteRequest.m; sourceTree = ""; }; 38E3FC945B8BF6F6AFD73EE7 /* XCTElementSnapshotAttributeDataSource-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTElementSnapshotAttributeDataSource-Protocol.h"; sourceTree = ""; }; 3A53731B41356D9B4E723A4D /* FBTVFocusIntegrationTests.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = FBTVFocusIntegrationTests.m; sourceTree = ""; }; 3B3F8E1F3F489A3A94A61ADB /* XCTMessagingChannel_IDEToRunner-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingChannel_IDEToRunner-Protocol.h"; sourceTree = ""; }; @@ -1550,6 +1483,7 @@ 4951F55904679A17CDFEC186 /* XCTMessagingRole_UIAutomationRunnerEventReporting-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_UIAutomationRunnerEventReporting-Protocol.h"; sourceTree = ""; }; 49D8AC825D239A4A1D834F62 /* XCTestCaseUIAutomationDelegate-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTestCaseUIAutomationDelegate-Protocol.h"; sourceTree = ""; }; 4AEAD1CF473F6AD60333E9FF /* XCUIApplicationOpenRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCUIApplicationOpenRequest.h; sourceTree = ""; }; + 4B52E4A09ADEA252A1B8190F /* RouteResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = RouteResponse.m; sourceTree = ""; }; 4C19CEEEC9858A106061D1F8 /* XCUIDeviceDelayedAttachmentTransferSupportInterface-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIDeviceDelayedAttachmentTransferSupportInterface-Protocol.h"; sourceTree = ""; }; 4E2F683D8A6C6EAAEC8B080A /* XCTMessagingRole_SignpostRequesting-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_SignpostRequesting-Protocol.h"; sourceTree = ""; }; 525D62A58488A52AA1BFE94A /* XCTReportingSessionIssueReporter-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTReportingSessionIssueReporter-Protocol.h"; sourceTree = ""; }; @@ -1663,10 +1597,6 @@ 716F0DA52A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSDictionaryFBUtf8SafeTests.m; sourceTree = ""; }; 717C0D702518ED2800CAA6EC /* TVOSSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TVOSSettings.xcconfig; sourceTree = ""; }; 717C0D862518ED7000CAA6EC /* TVOSTestSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TVOSTestSettings.xcconfig; sourceTree = ""; }; - 718226C62587443600661B83 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncUdpSocket.h; path = WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h; sourceTree = SOURCE_ROOT; }; - 718226C72587443600661B83 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncSocket.h; path = WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h; sourceTree = SOURCE_ROOT; }; - 718226C82587443600661B83 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncSocket.m; path = WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m; sourceTree = SOURCE_ROOT; }; - 718226C92587443600661B83 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncUdpSocket.m; path = WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m; sourceTree = SOURCE_ROOT; }; 7183E8C2B556594311CB8898 /* XCUIRemoteSiriInterface-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIRemoteSiriInterface-Protocol.h"; sourceTree = ""; }; 718F49C7230844330045FE8B /* FBProtocolHelpersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBProtocolHelpersTests.m; sourceTree = ""; }; 71930C4020662E1F00D3AFEC /* FBPasteboard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBPasteboard.h; sourceTree = ""; }; @@ -1741,7 +1671,6 @@ 75438FC693C39052C82C27DB /* XCUIApplicationRegistry.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCUIApplicationRegistry.h; sourceTree = ""; }; 758FC0D745C185A2C28BEDA1 /* IntegrationApp_watchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntegrationApp_watchOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 75F677B5B7737E7E1F321C20 /* WDAScreenshotAndSourceIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAScreenshotAndSourceIntegrationTests.swift; sourceTree = ""; }; - AA11BB22CC33DD44EE55FF03 /* WDAMjpegStreamingIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAMjpegStreamingIntegrationTests.swift; sourceTree = ""; }; 7AA21CEBA6E92AAC73FB6A48 /* XCTAggregateSuiteRunStatistics.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTAggregateSuiteRunStatistics.h; sourceTree = ""; }; 7CBAA574F7786985E01D0B6F /* XCTHarnessEventReporting-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTHarnessEventReporting-Protocol.h"; sourceTree = ""; }; 7E079E00FE148476F94BC42F /* XCTTestIdentifierSet.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTTestIdentifierSet.h; sourceTree = ""; }; @@ -1762,7 +1691,6 @@ 912F32C353FE7C3E7C841405 /* WDATypingIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDATypingIntegrationTests.swift; sourceTree = ""; }; 92182E4007B37665AB8CD88E /* XCTMessagingRole_UIAutomation-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_UIAutomation-Protocol.h"; sourceTree = ""; }; 94D7F0C1E30FBDB5D2583908 /* XCTMessagingRole_HIDEventRecording-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_HIDEventRecording-Protocol.h"; sourceTree = ""; }; - 97FFAEF000CE312DEBEA9385 /* RouteRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = RouteRequest.m; sourceTree = ""; }; 9A0B17F41E4461BBD09A2962 /* XCUIResetAuthorizationStatusOfProtectedResourcesInterface-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIResetAuthorizationStatusOfProtectedResourcesInterface-Protocol.h"; sourceTree = ""; }; 9AF0584AD9B6D0A57012C978 /* XCTExpectedFailureContextManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTExpectedFailureContextManager.h; sourceTree = ""; }; 9C280328DE3379F9EF701A20 /* WDAWatchInProcessTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAWatchInProcessTestCase.swift; sourceTree = ""; }; @@ -1776,9 +1704,11 @@ A80A1C12FA9899367D316E8C /* XCTestCastMethodNamesUIAutomationDelegate-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTestCastMethodNamesUIAutomationDelegate-Protocol.h"; sourceTree = ""; }; A8230FDD93639CE2E9EFE311 /* XCUILocation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCUILocation.h; sourceTree = ""; }; A87AE5544E9A5CA7C9168DDF /* SceneDelegate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; + AA11BB22CC33DD44EE55FF03 /* WDAMjpegStreamingIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAMjpegStreamingIntegrationTests.swift; sourceTree = ""; }; AA2351C66A4616534FB81AE4 /* XCTMessagingRole_ForcePressureSupportQuerying-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_ForcePressureSupportQuerying-Protocol.h"; sourceTree = ""; }; AABBCCDDEEFF001122334455 /* SceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; AABBCCDDEEFF001122334456 /* SceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; + AADFEA2ED9E61A8C1A99B2D7 /* FBHTTPServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = FBHTTPServer.m; sourceTree = ""; }; AAE921136A147FD01D630869 /* WatchSpikeApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WatchSpikeApp.swift; sourceTree = ""; }; ACA330765D30E21E3EEB163D /* IntegrationTests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ACB055BCA9CCEAB3DECD1A74 /* XCTIssue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTIssue.h; sourceTree = ""; }; @@ -1806,12 +1736,9 @@ B38AC76FAF9275974F272DE9 /* XCUIEventRecording-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIEventRecording-Protocol.h"; sourceTree = ""; }; B3FDA51EB36F03592BF48762 /* XCTMeasureOptions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTMeasureOptions.h; sourceTree = ""; }; B43D656732067585371ADD31 /* XCTMessagingRole_ProcessMonitoring-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_ProcessMonitoring-Protocol.h"; sourceTree = ""; }; - B6F479A809ED48C9D0604659 /* RouteRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = RouteRequest.h; sourceTree = ""; }; B8A163261EFA440E42CA6AC1 /* XCTRunnerDaemonSessionUIAutomationDelegate-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTRunnerDaemonSessionUIAutomationDelegate-Protocol.h"; sourceTree = ""; }; B98A9F937EF98D6359FCCC7A /* XCTRuntimeIssueDetectionPolicy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTRuntimeIssueDetectionPolicy.h; sourceTree = ""; }; BCED63DFD03326F6351165FE /* WDAFindIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAFindIntegrationTests.swift; sourceTree = ""; }; - C1CFF432CCF46627AB7315F9 /* FBWatchHTTPServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = FBWatchHTTPServer.m; sourceTree = ""; }; - C31A91BD7553670CBF19500F /* RouteResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = RouteResponse.h; sourceTree = ""; }; C840A8703A7C8D48897E158A /* _XCTestObservationPrivate-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "_XCTestObservationPrivate-Protocol.h"; sourceTree = ""; }; C878996F07A9B26E66FC4EAC /* WDAClickIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAClickIntegrationTests.swift; sourceTree = ""; }; C8FB547322D3949C00B69954 /* LSApplicationWorkspace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LSApplicationWorkspace.h; sourceTree = ""; }; @@ -1825,6 +1752,7 @@ CFE8F33794194FA3EB790C46 /* WebDriverAgentRunner_watchOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebDriverAgentRunner_watchOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D1171249FE5D91AC5D797E5C /* WebDriverAgentLib_watchOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WebDriverAgentLib_watchOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D47DD8BF27FE639742EA2E3E /* XCUIAccessibilityInterface-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIAccessibilityInterface-Protocol.h"; sourceTree = ""; }; + D585660F7A04651223F29B07 /* RouteRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = RouteRequest.h; sourceTree = ""; }; D5D58975822CD58C8FEF7FEB /* XCTReportingSessionTestContainer-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTReportingSessionTestContainer-Protocol.h"; sourceTree = ""; }; DB5400B170F08C71779CCD0E /* XCTCapabilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTCapabilities.h; sourceTree = ""; }; DB5797DE5A0B4E7EE3D166F7 /* XCUIAlertMonitoring-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIAlertMonitoring-Protocol.h"; sourceTree = ""; }; @@ -1833,41 +1761,12 @@ E005FEDAF49DCFA9FB77BEF3 /* IntegrationApp_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntegrationApp_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; E29888B75A756D1CCD21604C /* XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTestCaseDiscoveryUIAutomationDelegate-Protocol.h"; sourceTree = ""; }; E2F99C1A19D7B6D5B872D084 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E444DC59249131880060D7EB /* HTTPErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPErrorResponse.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h; sourceTree = SOURCE_ROOT; }; - E444DC5B249131880060D7EB /* HTTPDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPDataResponse.m; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m; sourceTree = SOURCE_ROOT; }; - E444DC60249131890060D7EB /* HTTPDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPDataResponse.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h; sourceTree = SOURCE_ROOT; }; - E444DC61249131890060D7EB /* HTTPErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPErrorResponse.m; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m; sourceTree = SOURCE_ROOT; }; - E444DC7B249131B00060D7EB /* DDRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DDRange.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h; sourceTree = SOURCE_ROOT; }; - E444DC7D249131B00060D7EB /* DDNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DDNumber.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h; sourceTree = SOURCE_ROOT; }; - E444DC7E249131B00060D7EB /* DDRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DDRange.m; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m; sourceTree = SOURCE_ROOT; }; - E444DC7F249131B00060D7EB /* DDNumber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DDNumber.m; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m; sourceTree = SOURCE_ROOT; }; - E444DC87249131D30060D7EB /* HTTPMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPMessage.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h; sourceTree = SOURCE_ROOT; }; - E444DC89249131D30060D7EB /* HTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPConnection.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h; sourceTree = SOURCE_ROOT; }; - E444DC8B249131D30060D7EB /* HTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPServer.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h; sourceTree = SOURCE_ROOT; }; - E444DC8C249131D30060D7EB /* HTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPConnection.m; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m; sourceTree = SOURCE_ROOT; }; - E444DC8D249131D30060D7EB /* HTTPLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPLogging.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h; sourceTree = SOURCE_ROOT; }; - E444DC8F249131D40060D7EB /* HTTPResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPResponse.h; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h; sourceTree = SOURCE_ROOT; }; - E444DC90249131D40060D7EB /* HTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPServer.m; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m; sourceTree = SOURCE_ROOT; }; - E444DC91249131D40060D7EB /* HTTPMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPMessage.m; path = WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m; sourceTree = SOURCE_ROOT; }; - E444DC9F24913C210060D7EB /* HTTPResponseProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPResponseProxy.m; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m; sourceTree = SOURCE_ROOT; }; - E444DCA024913C210060D7EB /* Route.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Route.m; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m; sourceTree = SOURCE_ROOT; }; - E444DCA124913C210060D7EB /* RouteResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RouteResponse.h; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h; sourceTree = SOURCE_ROOT; }; - E444DCA224913C210060D7EB /* HTTPResponseProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPResponseProxy.h; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h; sourceTree = SOURCE_ROOT; }; - E444DCA324913C210060D7EB /* Route.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Route.h; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h; sourceTree = SOURCE_ROOT; }; - E444DCA424913C210060D7EB /* RouteResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RouteResponse.m; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m; sourceTree = SOURCE_ROOT; }; - E444DCA524913C210060D7EB /* RoutingConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RoutingConnection.h; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h; sourceTree = SOURCE_ROOT; }; - E444DCA624913C210060D7EB /* RoutingConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RoutingConnection.m; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m; sourceTree = SOURCE_ROOT; }; - E444DCA724913C210060D7EB /* RoutingHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RoutingHTTPServer.h; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h; sourceTree = SOURCE_ROOT; }; - E444DCA824913C220060D7EB /* RoutingHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RoutingHTTPServer.m; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m; sourceTree = SOURCE_ROOT; }; - E444DCA924913C220060D7EB /* RouteRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RouteRequest.m; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m; sourceTree = SOURCE_ROOT; }; - E444DCAA24913C220060D7EB /* RouteRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RouteRequest.h; path = WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h; sourceTree = SOURCE_ROOT; }; E46239748EC4A6BFBC13F28B /* XCTMetricDiagnosticHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTMetricDiagnosticHelper.h; sourceTree = ""; }; E859E58C7C717CB6CD2A80BE /* XCTReportingSessionConfiguration-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTReportingSessionConfiguration-Protocol.h"; sourceTree = ""; }; EA24F4BE52B2520874E09BEF /* XCTMessagingRole_SystemConfiguration-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTMessagingRole_SystemConfiguration-Protocol.h"; sourceTree = ""; }; EA96C2FEDE73CB5148BA4949 /* XCUIAXNotificationHandling-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIAXNotificationHandling-Protocol.h"; sourceTree = ""; }; EB1B9A793C9EFB7853C1AA13 /* WDAAppLifecycleIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAAppLifecycleIntegrationTests.swift; sourceTree = ""; }; EC8B17452E38AA1AE03AE251 /* XCTElementSnapshotProvider-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTElementSnapshotProvider-Protocol.h"; sourceTree = ""; }; - ED271671803AAF7188E828CF /* RouteResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = RouteResponse.m; sourceTree = ""; }; EE006EAC1EB99B15006900A4 /* FBElementVisibilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBElementVisibilityTests.m; sourceTree = ""; }; EE006EB21EBA1C7B006900A4 /* XCElementSnapshotHitPointTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCElementSnapshotHitPointTests.m; sourceTree = ""; }; EE05BAF91D13003C00A3EB00 /* FBKeyboardTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBKeyboardTests.m; sourceTree = ""; }; @@ -2196,19 +2095,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 12A83FB6068CC1266BBDBE2C /* WatchOS */ = { - isa = PBXGroup; - children = ( - 97FFAEF000CE312DEBEA9385 /* RouteRequest.m */, - ED271671803AAF7188E828CF /* RouteResponse.m */, - C1CFF432CCF46627AB7315F9 /* FBWatchHTTPServer.m */, - B6F479A809ED48C9D0604659 /* RouteRequest.h */, - C31A91BD7553670CBF19500F /* RouteResponse.h */, - 341B5F150DED296FF38FB5F7 /* FBWatchHTTPServer.h */, - ); - path = WatchOS; - sourceTree = ""; - }; 498495C81BB2E6FA009CC848 /* Resources */ = { isa = PBXGroup; children = ( @@ -2306,17 +2192,6 @@ name = iOS; sourceTree = ""; }; - 7182268F2587432E00661B83 /* CocoaAsyncSocket */ = { - isa = PBXGroup; - children = ( - 718226C72587443600661B83 /* GCDAsyncSocket.h */, - 718226C82587443600661B83 /* GCDAsyncSocket.m */, - 718226C62587443600661B83 /* GCDAsyncUdpSocket.h */, - 718226C92587443600661B83 /* GCDAsyncUdpSocket.m */, - ); - name = CocoaAsyncSocket; - sourceTree = ""; - }; 89DE54DD1FA69115F5538E11 /* IntegrationTests_tvOS */ = { isa = PBXGroup; children = ( @@ -2430,74 +2305,6 @@ path = IntegrationApp_watchOS; sourceTree = ""; }; - E444DC4A24912EC40060D7EB /* Vendor */ = { - isa = PBXGroup; - children = ( - 7182268F2587432E00661B83 /* CocoaAsyncSocket */, - E444DC9E24913C080060D7EB /* RoutingHTTPServer */, - E444DC52249131050060D7EB /* CocoaHTTPServer */, - ); - name = Vendor; - sourceTree = ""; - }; - E444DC52249131050060D7EB /* CocoaHTTPServer */ = { - isa = PBXGroup; - children = ( - E444DC89249131D30060D7EB /* HTTPConnection.h */, - E444DC8C249131D30060D7EB /* HTTPConnection.m */, - E444DC8D249131D30060D7EB /* HTTPLogging.h */, - E444DC87249131D30060D7EB /* HTTPMessage.h */, - E444DC91249131D40060D7EB /* HTTPMessage.m */, - E444DC8F249131D40060D7EB /* HTTPResponse.h */, - E444DC8B249131D30060D7EB /* HTTPServer.h */, - E444DC90249131D40060D7EB /* HTTPServer.m */, - E444DC55249131740060D7EB /* Responses */, - E444DC53249131640060D7EB /* Categories */, - ); - name = CocoaHTTPServer; - sourceTree = ""; - }; - E444DC53249131640060D7EB /* Categories */ = { - isa = PBXGroup; - children = ( - E444DC7D249131B00060D7EB /* DDNumber.h */, - E444DC7F249131B00060D7EB /* DDNumber.m */, - E444DC7B249131B00060D7EB /* DDRange.h */, - E444DC7E249131B00060D7EB /* DDRange.m */, - ); - name = Categories; - sourceTree = ""; - }; - E444DC55249131740060D7EB /* Responses */ = { - isa = PBXGroup; - children = ( - E444DC60249131890060D7EB /* HTTPDataResponse.h */, - E444DC5B249131880060D7EB /* HTTPDataResponse.m */, - E444DC59249131880060D7EB /* HTTPErrorResponse.h */, - E444DC61249131890060D7EB /* HTTPErrorResponse.m */, - ); - name = Responses; - sourceTree = ""; - }; - E444DC9E24913C080060D7EB /* RoutingHTTPServer */ = { - isa = PBXGroup; - children = ( - E444DCA224913C210060D7EB /* HTTPResponseProxy.h */, - E444DC9F24913C210060D7EB /* HTTPResponseProxy.m */, - E444DCA324913C210060D7EB /* Route.h */, - E444DCA024913C210060D7EB /* Route.m */, - E444DCAA24913C220060D7EB /* RouteRequest.h */, - E444DCA924913C220060D7EB /* RouteRequest.m */, - E444DCA124913C210060D7EB /* RouteResponse.h */, - E444DCA424913C210060D7EB /* RouteResponse.m */, - E444DCA524913C210060D7EB /* RoutingConnection.h */, - E444DCA624913C210060D7EB /* RoutingConnection.m */, - E444DCA724913C210060D7EB /* RoutingHTTPServer.h */, - E444DCA824913C220060D7EB /* RoutingHTTPServer.m */, - ); - name = RoutingHTTPServer; - sourceTree = ""; - }; E94EE979A7D7C50FDC1EFE7D /* IntegrationTests_watchOS */ = { isa = PBXGroup; children = ( @@ -2685,7 +2492,12 @@ 13DE7A4E287C46BB003243C6 /* FBXCElementSnapshot.m */, 13DE7A53287CA1EC003243C6 /* FBXCElementSnapshotWrapper.h */, 13DE7A54287CA1EC003243C6 /* FBXCElementSnapshotWrapper.m */, - 12A83FB6068CC1266BBDBE2C /* WatchOS */, + 00B1F89716AFE04C8509B916 /* FBHTTPServer.h */, + AADFEA2ED9E61A8C1A99B2D7 /* FBHTTPServer.m */, + D585660F7A04651223F29B07 /* RouteRequest.h */, + 374BB11AE4E4243BCA444CF8 /* RouteRequest.m */, + 01E7CFEBAD717FCF4BCDD383 /* RouteResponse.h */, + 4B52E4A09ADEA252A1B8190F /* RouteResponse.m */, ); name = Routing; path = WebDriverAgentLib/Routing; @@ -2926,7 +2738,6 @@ EEC288F81BF0ED2500B4DC79 /* WebDriverAgentLib */ = { isa = PBXGroup; children = ( - E444DC4A24912EC40060D7EB /* Vendor */, EE9AB73E1CAEDF0C008C271F /* Categories */, EE9AB74F1CAEDF0C008C271F /* Commands */, EE9AB7721CAEDF0C008C271F /* Resources */, @@ -3162,11 +2973,9 @@ buildActionMask = 2147483647; files = ( 641EE6312240C5CA00173FCB /* XCUIElement+FBWebDriverAttributes.h in Headers */, - 7182274A258744BE00661B83 /* HTTPMessage.h in Headers */, 641EE6322240C5CA00173FCB /* FBScreen.h in Headers */, 641EE6332240C5CA00173FCB /* XCTestPrivateSymbols.h in Headers */, 641EE6342240C5CA00173FCB /* XCUIElement+FBTyping.h in Headers */, - 7182270B258744A700661B83 /* Route.h in Headers */, 641EE6352240C5CA00173FCB /* XCUIElement+FBUtilities.h in Headers */, 641EE6362240C5CA00173FCB /* XCUIElement+FBScrolling.h in Headers */, 1357E297233D05240054BDB8 /* XCUIHitPointResult.h in Headers */, @@ -3174,7 +2983,6 @@ 641EE6382240C5CA00173FCB /* XCPointerEventPath.h in Headers */, 641EE6392240C5CA00173FCB /* FBRouteRequest.h in Headers */, 648C10AC22AAAD9C00B81B9A /* UIKeyboardImpl.h in Headers */, - 718226CD2587443700661B83 /* GCDAsyncSocket.h in Headers */, 13DE7A50287C46BB003243C6 /* FBXCElementSnapshot.h in Headers */, 13DE7A56287CA1EC003243C6 /* FBXCElementSnapshotWrapper.h in Headers */, 71F3E7D525417FF400E0C22B /* FBSettings.h in Headers */, @@ -3211,7 +3019,6 @@ 71BB58E22B9631F100CB9BFE /* FBScreenRecordingPromise.h in Headers */, 641EE6632240C5CA00173FCB /* FBUnknownCommands.h in Headers */, 641EE7062240CDCF00173FCB /* XCUIElement+FBTVFocuse.h in Headers */, - 71822738258744B800661B83 /* HTTPConnection.h in Headers */, 641EE6642240C5CA00173FCB /* NSPredicate+FBFormat.h in Headers */, 641EE6662240C5CA00173FCB /* XCTestCase.h in Headers */, 641EE6682240C5CA00173FCB /* XCUIApplicationImpl.h in Headers */, @@ -3220,7 +3027,6 @@ 641EE66A2240C5CA00173FCB /* NSExpression+FBFormat.h in Headers */, 641EE66E2240C5CA00173FCB /* XCUIApplication+FBAlert.h in Headers */, 716C9E0127315EFF005AD475 /* XCUIApplication+FBUIInterruptions.h in Headers */, - 7182275C258744C300661B83 /* HTTPServer.h in Headers */, 641EE6702240C5CA00173FCB /* FBMathUtils.h in Headers */, 641EE6722240C5CA00173FCB /* FBElementUtils.h in Headers */, 641EE6732240C5CA00173FCB /* FBDebugCommands.h in Headers */, @@ -3237,18 +3043,14 @@ 641EE6802240C5CA00173FCB /* FBElementTypeTransformer.h in Headers */, 641EE6812240C5CA00173FCB /* FBXCAXClientProxy.h in Headers */, 641EE6822240C5CA00173FCB /* FBElementCache.h in Headers */, - 7182271D258744AB00661B83 /* RouteResponse.h in Headers */, 641EE6852240C5CA00173FCB /* XCUIElement+FBClassChain.h in Headers */, 13DE7A44287C2A8D003243C6 /* FBXCAccessibilityElement.h in Headers */, 641EE6862240C5CA00173FCB /* FBResponseJSONPayload.h in Headers */, - 71822714258744A900661B83 /* RouteRequest.h in Headers */, 641EE6882240C5CA00173FCB /* FBElement.h in Headers */, 641EE68B2240C5CA00173FCB /* FBExceptionHandler.h in Headers */, - 71822726258744AE00661B83 /* RoutingConnection.h in Headers */, 641EE68C2240C5CA00173FCB /* FBRoute.h in Headers */, 641EE68D2240C5CA00173FCB /* XCTestDriver.h in Headers */, 641EE68F2240C5CA00173FCB /* XCSynthesizedEventRecord.h in Headers */, - 71822753258744C100661B83 /* HTTPResponse.h in Headers */, 641EE6942240C5CA00173FCB /* FBXPath.h in Headers */, 641EE6972240C5CA00173FCB /* XCUIElement+FBForceTouch.h in Headers */, 641EE6982240C5CA00173FCB /* FBRuntimeUtils.h in Headers */, @@ -3258,7 +3060,6 @@ 641EE69F2240C5CA00173FCB /* FBTCPSocket.h in Headers */, 641EE6A02240C5CA00173FCB /* XCUIElement+FBUID.h in Headers */, 641EE6A22240C5CA00173FCB /* XCUIDevice.h in Headers */, - 7182272F258744B000661B83 /* RoutingHTTPServer.h in Headers */, 641EE6A32240C5CA00173FCB /* XCUIApplication+FBTouchAction.h in Headers */, 641EE6A42240C5CA00173FCB /* FBCommandHandler.h in Headers */, 641EE6A52240C5CA00173FCB /* FBSessionCommands.h in Headers */, @@ -3270,8 +3071,6 @@ B316351F2DDF0D0B007D9317 /* FBAccessibilityTraits.h in Headers */, 64E3502F2AC0B6FE005F3ACB /* NSDictionary+FBUtf8SafeDictionary.h in Headers */, 641EE6A92240C5CA00173FCB /* FBCommandStatus.h in Headers */, - 71822702258744A400661B83 /* HTTPResponseProxy.h in Headers */, - 71822741258744BB00661B83 /* HTTPLogging.h in Headers */, 641EE6AB2240C5CA00173FCB /* FBAlertViewCommands.h in Headers */, 641EE6AC2240C5CA00173FCB /* XCTWaiter.h in Headers */, 641EE6AD2240C5CA00173FCB /* XCTWaiterManagement-Protocol.h in Headers */, @@ -3280,7 +3079,6 @@ 648C10B022AAAE4000B81B9A /* TIPreferencesController.h in Headers */, 71F5BE24252E576C00EE9EBA /* XCUIElement+FBSwiping.h in Headers */, 641EE6B72240C5CA00173FCB /* FBBaseActionsSynthesizer.h in Headers */, - 7182276E258744C900661B83 /* HTTPErrorResponse.h in Headers */, 641EE6B82240C5CA00173FCB /* FBAlert.h in Headers */, 641EE6B92240C5CA00173FCB /* XCUIElementQuery.h in Headers */, 71BB58F02B96511800CB9BFE /* FBVideoCommands.h in Headers */, @@ -3298,7 +3096,6 @@ 641EE6C32240C5CA00173FCB /* FBClassChainQueryParser.h in Headers */, 641EE6C42240C5CA00173FCB /* FBMacros.h in Headers */, 641EE6C52240C5CA00173FCB /* XCTestExpectationDelegate-Protocol.h in Headers */, - 71822777258744CE00661B83 /* DDNumber.h in Headers */, 641EE6C92240C5CA00173FCB /* XCUIDevice+FBRotation.h in Headers */, A1B2C3D41F001A00A1B0004 /* XCUIDevice+FBVoiceOver.h in Headers */, 719DCF162601EAFB000E765F /* FBNotificationsHelper.h in Headers */, @@ -3310,7 +3107,6 @@ 641EE6D62240C5CA00173FCB /* FBLogger.h in Headers */, 71BB58F72B96531900CB9BFE /* FBScreenRecordingContainer.h in Headers */, 641EE6D82240C5CA00173FCB /* XCUIElement.h in Headers */, - 718226CB2587443700661B83 /* GCDAsyncUdpSocket.h in Headers */, 641EE6DB2240C5CA00173FCB /* FBPasteboard.h in Headers */, 711CD03525ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h in Headers */, 641EE6DD2240C5CA00173FCB /* FBDebugLogDelegateDecorator.h in Headers */, @@ -3319,9 +3115,7 @@ 641EE6E12240C5CA00173FCB /* XCUIApplicationProcess.h in Headers */, 641EE6E22240C5CA00173FCB /* FBW3CActionsSynthesizer.h in Headers */, 641EE6E32240C5CA00173FCB /* CDStructures.h in Headers */, - 71822780258744D000661B83 /* DDRange.h in Headers */, F59CD6D62EF16E5E00F91287 /* XCUIElement+FBCustomActions.h in Headers */, - 71822765258744C700661B83 /* HTTPDataResponse.h in Headers */, 641EE6E72240C5CA00173FCB /* XCUIElement+FBFind.h in Headers */, 641EE6E92240C5CA00173FCB /* FBFailureProofTestCase.h in Headers */, 641EE6ED2240C5CA00173FCB /* FBXPath-Private.h in Headers */, @@ -3435,6 +3229,8 @@ 1DFC6477940D11BBAD68D59F /* _XCTestObservationInternal-Protocol.h in Headers */, BF9B191D841681A571BAFED1 /* _XCTestObservationPrivate-Protocol.h in Headers */, 5B736BB83DEA69D3C5EEC7E1 /* XCTElementSetTransformer-Protocol.h in Headers */, + 2112EC67BDFFA4A0B2CF24EB /* RouteRequest.h in Headers */, + 0161F45997A981E47729DB25 /* RouteResponse.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3488,11 +3284,9 @@ buildActionMask = 2147483647; files = ( 43EBA0D0D54EB99815CE63B6 /* XCUIElement+FBWebDriverAttributes.h in Headers */, - C154B8CD167DBE068EA74719 /* HTTPMessage.h in Headers */, 93D5C4C8442EFE91D70780CC /* FBScreen.h in Headers */, B9FAD6E3F3AB201B59D6F8BD /* XCTestPrivateSymbols.h in Headers */, CDDEBD9F5F6A380C8D32D671 /* XCUIElement+FBTyping.h in Headers */, - FBDF04E5916B91FCEC0190CB /* Route.h in Headers */, 488C4CBD86EBFA2AB5396917 /* XCUIElement+FBUtilities.h in Headers */, CE3D815D09AD8E137CC18AA5 /* XCUIElement+FBScrolling.h in Headers */, 767CB761C99AA6275E08FDB9 /* XCUIHitPointResult.h in Headers */, @@ -3500,7 +3294,6 @@ 852D69D1623D81697D700B3C /* XCPointerEventPath.h in Headers */, 43BC9FE4BF5C19F6F6569177 /* FBRouteRequest.h in Headers */, BD2A5D43881B449E533D5800 /* UIKeyboardImpl.h in Headers */, - 34AB13EFF1F673084C910195 /* GCDAsyncSocket.h in Headers */, 444F211126EAF77FB2B1DB42 /* FBXCElementSnapshot.h in Headers */, FFA7672B731B57AB9283DD40 /* FBXCElementSnapshotWrapper.h in Headers */, D43AF1E150975634F3AFF329 /* FBSettings.h in Headers */, @@ -3537,7 +3330,6 @@ F876324AFD3617FFCFC479C3 /* FBScreenRecordingPromise.h in Headers */, 67402F46AAB3DCAB3E40ED27 /* FBUnknownCommands.h in Headers */, CB1790B793BB813AF80F65DF /* XCUIElement+FBTVFocuse.h in Headers */, - 2C6A876B29ECA17D6A5BCEED /* HTTPConnection.h in Headers */, 617AE9B41FB23CCE66769441 /* NSPredicate+FBFormat.h in Headers */, 60CF6CA8AC7961DCCE402764 /* XCTestCase.h in Headers */, 995DF7C1928C60D7AE02840D /* XCUIApplicationImpl.h in Headers */, @@ -3546,7 +3338,6 @@ 4D80F932D0CD80C99950DA4F /* NSExpression+FBFormat.h in Headers */, 95A597B8CC694006DDE49999 /* XCUIApplication+FBAlert.h in Headers */, ED342D194B20A206864675B2 /* XCUIApplication+FBUIInterruptions.h in Headers */, - BCC9E02491560712221045CC /* HTTPServer.h in Headers */, E6B214145FE46BBFD824FAE7 /* FBMathUtils.h in Headers */, 6786D4B257269918E591AC66 /* FBElementUtils.h in Headers */, 51FFE79AF3A5FADFFAB287BC /* FBDebugCommands.h in Headers */, @@ -3563,18 +3354,14 @@ D40D635A4ABBC79829A0D590 /* FBElementTypeTransformer.h in Headers */, 70E21F76098198E19A6E5A5E /* FBXCAXClientProxy.h in Headers */, 97AB752A8043A962100DB4EB /* FBElementCache.h in Headers */, - F0F7B8DFB14C9DFC86DFFBFD /* RouteResponse.h in Headers */, 10EC41A2ECCD2E865EEE7AB0 /* XCUIElement+FBClassChain.h in Headers */, 6FC8E41707F65D5A432206CC /* FBXCAccessibilityElement.h in Headers */, 4E8E83C42F2E2A2A5A62F3E3 /* FBResponseJSONPayload.h in Headers */, - 894AE4397B5992EF738248AE /* RouteRequest.h in Headers */, D2B83B27DAB80FE235285D2F /* FBElement.h in Headers */, 8597F35CEB1617767B63D770 /* FBExceptionHandler.h in Headers */, - FEC52AE4F35AD38D3AC2659F /* RoutingConnection.h in Headers */, AC3529AFA966E7202CB3B3B1 /* FBRoute.h in Headers */, A47F8777C5C8D021B2AC910C /* XCTestDriver.h in Headers */, 1D0C64A6600E2D1BBAFC2D62 /* XCSynthesizedEventRecord.h in Headers */, - 0F282CAB2A025ECA9EAB18B3 /* HTTPResponse.h in Headers */, 4C472F2027329ACA65C7D721 /* FBXPath.h in Headers */, 5A1B8098AE35B82BD379B421 /* XCUIElement+FBForceTouch.h in Headers */, 9DFDA1423A4651A4DB9FE341 /* FBRuntimeUtils.h in Headers */, @@ -3584,7 +3371,6 @@ BC1470BF3E54F478DAEDF05D /* FBTCPSocket.h in Headers */, 476E44716A4A3A22F95EC51A /* XCUIElement+FBUID.h in Headers */, FBF064E4D96CFCD08E1F4EF7 /* XCUIDevice.h in Headers */, - A8635BD557F97E6C29A0790E /* RoutingHTTPServer.h in Headers */, 46AEB32485B508AF1D9B8CE2 /* XCUIApplication+FBTouchAction.h in Headers */, 3EC5404A04E5A61B8144E834 /* FBCommandHandler.h in Headers */, 3E199AC580C5DA4B070DD01A /* FBSessionCommands.h in Headers */, @@ -3596,8 +3382,6 @@ 9452D57FE97CB2ECED093B9F /* FBAccessibilityTraits.h in Headers */, DE2D708340ED70EBF9244D0F /* NSDictionary+FBUtf8SafeDictionary.h in Headers */, 2D6B818DC921A4631A496432 /* FBCommandStatus.h in Headers */, - 293BD2162964EEC2A3BA6B57 /* HTTPResponseProxy.h in Headers */, - 90107B3BBFBF3B073807D51B /* HTTPLogging.h in Headers */, CFB0AFBC9F429B279C95F5BC /* FBAlertViewCommands.h in Headers */, 6A1D9B851D58D0A36D6822B7 /* XCTWaiter.h in Headers */, F7010B261A861C5C63D59273 /* XCTWaiterManagement-Protocol.h in Headers */, @@ -3606,7 +3390,6 @@ 16E809B6BFDF7CBD862A7B48 /* TIPreferencesController.h in Headers */, 42A92793513CA29B39B72721 /* XCUIElement+FBSwiping.h in Headers */, 268CBDE31376AF96DDDD4BD0 /* FBBaseActionsSynthesizer.h in Headers */, - EDB13FDB5D8220A65560629B /* HTTPErrorResponse.h in Headers */, 462DA317CCA9DBE4F249516B /* FBAlert.h in Headers */, 79FD4FC269A91F525A4E1413 /* XCUIElementQuery.h in Headers */, 71BB64FB648CB9BBBD25F3E3 /* FBVideoCommands.h in Headers */, @@ -3624,7 +3407,6 @@ 6BF3F57247F59B81B8BCEE7B /* FBClassChainQueryParser.h in Headers */, 8AAA33A5B1943B667B0DB05E /* FBMacros.h in Headers */, 1CA8A47F9D6B967D99FC0896 /* XCTestExpectationDelegate-Protocol.h in Headers */, - 3414F451472B235F637F46BC /* DDNumber.h in Headers */, 74CF72DC7209A5B04763D0D2 /* XCUIDevice+FBRotation.h in Headers */, A8CEAEFC8CC63F94DA0176A9 /* XCUIDevice+FBVoiceOver.h in Headers */, 150276DDADD9963F29465D57 /* FBNotificationsHelper.h in Headers */, @@ -3636,7 +3418,6 @@ 1F545FFB67878CBAF4D6E6A0 /* FBLogger.h in Headers */, 05F8A3B33BCFAFFA75CD81CA /* FBScreenRecordingContainer.h in Headers */, 5787FD65011B3AF632694EEA /* XCUIElement.h in Headers */, - 5917EF5F1372B2098168EA0D /* GCDAsyncUdpSocket.h in Headers */, 6B3AB44BFFDDF042E4B712C1 /* FBPasteboard.h in Headers */, 36C5DA6C013EAAFC575B4A2B /* XCUIScreenDataSource-Protocol.h in Headers */, D1DE06DF54156F744E364B5F /* FBDebugLogDelegateDecorator.h in Headers */, @@ -3645,9 +3426,7 @@ CFDBE0DF5D99CB310C7AFB01 /* XCUIApplicationProcess.h in Headers */, 8EA51935E09A89896FB1D463 /* FBW3CActionsSynthesizer.h in Headers */, 79A47D7F50179802052ED774 /* CDStructures.h in Headers */, - AF4223DDBCC9EC79D4F9DC0D /* DDRange.h in Headers */, B70EB4BDC07CE9EC58505E85 /* XCUIElement+FBCustomActions.h in Headers */, - EE22974A8F641FA94DCBCB3E /* HTTPDataResponse.h in Headers */, 760885DAFDB4A126DBD66649 /* XCUIElement+FBFind.h in Headers */, B6B73CE709728984EF03361D /* FBFailureProofTestCase.h in Headers */, 1C159E8E35E0823278141DC2 /* FBXPath-Private.h in Headers */, @@ -3761,6 +3540,8 @@ F4E8FB5A2EB57854EB6A00E9 /* _XCTestObservationInternal-Protocol.h in Headers */, 59892BBAB84DFD927C94593F /* _XCTestObservationPrivate-Protocol.h in Headers */, A47A7F9E098C88328DF38A4C /* XCTElementSetTransformer-Protocol.h in Headers */, + E4FA72A388B36E0B6C41C421 /* RouteRequest.h in Headers */, + DAB4F2FB4C0AB107E461BBA4 /* RouteResponse.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3795,7 +3576,6 @@ EE35AD611E3B77D600A02D78 /* XCTRunnerIDESession.h in Headers */, EE158AE01CBD456F00A3E3F0 /* FBRouteRequest-Private.h in Headers */, EE35AD281E3B77D600A02D78 /* XCApplicationQuery.h in Headers */, - E444DCB124913C220060D7EB /* RoutingConnection.h in Headers */, EE35AD601E3B77D600A02D78 /* XCTRunnerDaemonSession.h in Headers */, 71414ED62670A1EE003A8C5D /* LRUCacheNode.h in Headers */, 64B2650A228CE4FF002A5025 /* FBTVNavigationTracker-Private.h in Headers */, @@ -3834,15 +3614,12 @@ 714EAA0D2673FDFE005C5B47 /* FBCapabilities.h in Headers */, EE35AD521E3B77D600A02D78 /* XCTestObservationCenter.h in Headers */, 71AE3CF92D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h in Headers */, - E444DC97249131D40060D7EB /* HTTPServer.h in Headers */, - E444DCAE24913C220060D7EB /* HTTPResponseProxy.h in Headers */, 1357E296233D05240054BDB8 /* XCUIHitPointResult.h in Headers */, 711CD03425ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h in Headers */, EE158AAE1CBD456F00A3E3F0 /* XCUIElement+FBAccessibility.h in Headers */, EE35AD421E3B77D600A02D78 /* XCTestCaseRun.h in Headers */, EE35AD441E3B77D600A02D78 /* XCTestConfiguration.h in Headers */, 715A84D02DD92AD3007134CC /* FBElementHelpers.h in Headers */, - 718226CA2587443700661B83 /* GCDAsyncUdpSocket.h in Headers */, EE35AD491E3B77D600A02D78 /* XCTestExpectation.h in Headers */, EE158AE81CBD456F00A3E3F0 /* FBElementTypeTransformer.h in Headers */, 7157B291221DADD2001C348C /* FBXCAXClientProxy.h in Headers */, @@ -3853,20 +3630,15 @@ EE158AD01CBD456F00A3E3F0 /* FBElement.h in Headers */, EE158AD41CBD456F00A3E3F0 /* FBExceptionHandler.h in Headers */, EE158ADE1CBD456F00A3E3F0 /* FBRoute.h in Headers */, - E444DC81249131B10060D7EB /* DDRange.h in Headers */, EE35AD471E3B77D600A02D78 /* XCTestDriver.h in Headers */, - E444DC93249131D40060D7EB /* HTTPMessage.h in Headers */, EE35AD3A1E3B77D600A02D78 /* XCSynthesizedEventRecord.h in Headers */, - E444DCAD24913C220060D7EB /* RouteResponse.h in Headers */, 13DE7A5B287CA444003243C6 /* FBXCElementSnapshotWrapper+Helpers.h in Headers */, 711084441DA3AA7500F913D6 /* FBXPath.h in Headers */, - E444DC83249131B10060D7EB /* DDNumber.h in Headers */, EE8DDD7F20C5733C004D4925 /* XCUIElement+FBForceTouch.h in Headers */, 71A5C67329A4F39600421C37 /* XCTIssue+FBPatcher.h in Headers */, 716F0DA12A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h in Headers */, EE158AEA1CBD456F00A3E3F0 /* FBRuntimeUtils.h in Headers */, 7136A4791E8918E60024FC3D /* XCUIElement+FBPickerWheel.h in Headers */, - E444DCB324913C220060D7EB /* RoutingHTTPServer.h in Headers */, EE158ABE1CBD456F00A3E3F0 /* FBElementCommands.h in Headers */, 715557D3211DBCE700613B26 /* FBTCPSocket.h in Headers */, 71B49EC71ED1A58100D51AD6 /* XCUIElement+FBUID.h in Headers */, @@ -3885,16 +3657,12 @@ EE35AD681E3B77D600A02D78 /* XCTWaiterManagement-Protocol.h in Headers */, EE35AD291E3B77D600A02D78 /* XCAXClient_iOS.h in Headers */, 648C10AF22AAAE4000B81B9A /* TIPreferencesController.h in Headers */, - E444DC6C249131890060D7EB /* HTTPDataResponse.h in Headers */, - E444DC65249131890060D7EB /* HTTPErrorResponse.h in Headers */, 714097431FAE1B0B008FB2C5 /* FBBaseActionsSynthesizer.h in Headers */, AD6C26941CF2379700F8B5FF /* FBAlert.h in Headers */, EE35AD731E3B77D600A02D78 /* XCUIElementQuery.h in Headers */, EE35AD331E3B77D600A02D78 /* XCPointerEvent.h in Headers */, 71D04DC825356C43008A052C /* XCUIElement+FBCaching.h in Headers */, 71BB58E12B9631F100CB9BFE /* FBScreenRecordingPromise.h in Headers */, - E444DC99249131D40060D7EB /* HTTPLogging.h in Headers */, - E444DC9B249131D40060D7EB /* HTTPResponse.h in Headers */, EEE9B4721CD02B88009D2030 /* FBRunLoopSpinner.h in Headers */, EE3A18621CDE618F00DE4205 /* FBErrorBuilder.h in Headers */, 0E04133B2DF1E15900AF007C /* XCUIElement+FBMinMax.h in Headers */, @@ -3918,7 +3686,6 @@ EE35AD2A1E3B77D600A02D78 /* XCDebugLogDelegate-Protocol.h in Headers */, 7150348721A6DAD600A0F4BA /* FBImageUtils.h in Headers */, C8FB547422D3949C00B69954 /* LSApplicationWorkspace.h in Headers */, - E444DCAF24913C220060D7EB /* Route.h in Headers */, EE9B76A81CF7A43900275851 /* FBLogger.h in Headers */, EE35AD6F1E3B77D600A02D78 /* XCUIElement.h in Headers */, 71930C4220662E1F00D3AFEC /* FBPasteboard.h in Headers */, @@ -3930,12 +3697,9 @@ EE35AD151E3B77D600A02D78 /* CDStructures.h in Headers */, 71E75E6D254824230099FC87 /* XCUIElementQuery+FBHelpers.h in Headers */, 716C9DFA27315D21005AD475 /* FBReflectionUtils.h in Headers */, - E444DCB624913C220060D7EB /* RouteRequest.h in Headers */, 71F5BE23252E576C00EE9EBA /* XCUIElement+FBSwiping.h in Headers */, - 718226CC2587443700661B83 /* GCDAsyncSocket.h in Headers */, EEBBD48B1D47746D00656A81 /* XCUIElement+FBFind.h in Headers */, EE6A893A1D0B38640083E92B /* FBFailureProofTestCase.h in Headers */, - E444DC95249131D40060D7EB /* HTTPConnection.h in Headers */, 712A0C871DA3E55D007D02E5 /* FBXPath-Private.h in Headers */, 1D0120AD346A99834385FBAB /* NSFastEnumeration-Protocol.h in Headers */, 7738D42AE84269A6963A5610 /* XCTAggregateSuiteRunStatistics.h in Headers */, @@ -4045,6 +3809,8 @@ 604A7EDA9D8A9BFCF3B62E5C /* _XCTestObservationInternal-Protocol.h in Headers */, AB126E96C642B235EE02B4F1 /* _XCTestObservationPrivate-Protocol.h in Headers */, 8CB293E6451EB1A6D1240BAA /* XCTElementSetTransformer-Protocol.h in Headers */, + CA077D3ED0D0BA2590CB85DE /* RouteRequest.h in Headers */, + 44A6F8DBFDCDD735D000458D /* RouteResponse.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4588,23 +4354,9 @@ files = ( F59CD6D72EF16E5E00F91287 /* XCUIElement+FBCustomActions.m in Sources */, 64E3502E2AC0B6EB005F3ACB /* NSDictionary+FBUtf8SafeDictionary.m in Sources */, - 718226CF2587443700661B83 /* GCDAsyncSocket.m in Sources */, - E444DCBC24917A5E0060D7EB /* HTTPResponseProxy.m in Sources */, 71D3B3D8267FC7260076473D /* XCUIElement+FBResolve.m in Sources */, - E444DCBE24917A5E0060D7EB /* Route.m in Sources */, - E444DCC024917A5E0060D7EB /* RouteRequest.m in Sources */, 13DE7A52287C46BB003243C6 /* FBXCElementSnapshot.m in Sources */, - E444DCC224917A5E0060D7EB /* RouteResponse.m in Sources */, - E444DCC424917A5E0060D7EB /* RoutingConnection.m in Sources */, - E444DCC624917A5E0060D7EB /* RoutingHTTPServer.m in Sources */, - E444DCC824917A5E0060D7EB /* HTTPConnection.m in Sources */, - E444DCCB24917A5E0060D7EB /* HTTPMessage.m in Sources */, - E444DCCE24917A5E0060D7EB /* HTTPServer.m in Sources */, - E444DCD024917A5E0060D7EB /* HTTPDataResponse.m in Sources */, - E444DCD224917A5E0060D7EB /* HTTPErrorResponse.m in Sources */, 71414ED92670A1EE003A8C5D /* LRUCache.m in Sources */, - E444DCD424917A5E0060D7EB /* DDNumber.m in Sources */, - E444DCD624917A5E0060D7EB /* DDRange.m in Sources */, 641EE5D72240C5CA00173FCB /* FBScreenshotCommands.m in Sources */, 71F3E7D725417FF400E0C22B /* FBSettings.m in Sources */, 71F3E7DA25417FF400E0C22C /* FBSettingsHandler.m in Sources */, @@ -4650,7 +4402,6 @@ 641EE5F52240C5CA00173FCB /* XCUIElement+FBUID.m in Sources */, 641EE5F62240C5CA00173FCB /* FBRouteRequest.m in Sources */, 641EE5F72240C5CA00173FCB /* FBResponseJSONPayload.m in Sources */, - 718226D12587443700661B83 /* GCDAsyncUdpSocket.m in Sources */, 641EE5F92240C5CA00173FCB /* FBMjpegServer.m in Sources */, 641EE5FA2240C5CA00173FCB /* XCUIDevice+FBHealthCheck.m in Sources */, 641EE5FD2240C5CA00173FCB /* FBBaseActionsSynthesizer.m in Sources */, @@ -4711,6 +4462,9 @@ 13DE7A58287CA1EC003243C6 /* FBXCElementSnapshotWrapper.m in Sources */, 641EE6262240C5CA00173FCB /* FBMathUtils.m in Sources */, 641EE6272240C5CA00173FCB /* FBXCAXClientProxy.m in Sources */, + 43DE58587952717F4DEE228E /* FBHTTPServer.m in Sources */, + 667705A8195BF7B0D48180B3 /* RouteRequest.m in Sources */, + 90C6C1E34B9B850CC79BABCB /* RouteResponse.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4864,10 +4618,10 @@ C4E500AC8CD81B4FE3A75EAE /* FBMathUtils.m in Sources */, D062CA5914608761FE002799 /* FBXCAXClientProxy.m in Sources */, 46C54343FB83AD3AFC6CFCD4 /* FBTCPSocket.m in Sources */, - C2D22426DB9FCE6AD84FA16A /* RouteRequest.m in Sources */, - 3FEF512914A962C5E30579FC /* RouteResponse.m in Sources */, - 91FF05D51401D1F1A88FB0B0 /* FBWatchHTTPServer.m in Sources */, AA11BB22CC33DD44EE55FF02 /* FBMjpegServer.m in Sources */, + 0A4413521ECE45EA182E8403 /* FBHTTPServer.m in Sources */, + D1172A7F53F89B78D8324A13 /* RouteRequest.m in Sources */, + AB531917FF460EB87E4AD5A2 /* RouteResponse.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4876,9 +4630,7 @@ buildActionMask = 2147483647; files = ( EE158AC71CBD456F00A3E3F0 /* FBScreenshotCommands.m in Sources */, - E444DC98249131D40060D7EB /* HTTPConnection.m in Sources */, 7136A47A1E8918E60024FC3D /* XCUIElement+FBPickerWheel.m in Sources */, - E444DC84249131B10060D7EB /* DDRange.m in Sources */, 6385F4A7220A40760095BBDB /* XCUIApplicationProcessDelay.m in Sources */, 71A5C67529A4F39600421C37 /* XCTIssue+FBPatcher.m in Sources */, 711084451DA3AA7500F913D6 /* FBXPath.m in Sources */, @@ -4896,7 +4648,6 @@ AD6C269D1CF2494200F8B5FF /* XCUIApplication+FBHelpers.m in Sources */, EE3A18671CDE734B00DE4205 /* FBKeyboard.m in Sources */, 719DCF172601EAFB000E765F /* FBNotificationsHelper.m in Sources */, - E444DCAC24913C220060D7EB /* Route.m in Sources */, 713C6DD01DDC772A00285B92 /* FBElementUtils.m in Sources */, 71BB58E32B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */, 7140974C1FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.m in Sources */, @@ -4908,17 +4659,14 @@ EEBBD48C1D47746D00656A81 /* XCUIElement+FBFind.m in Sources */, EE158ADD1CBD456F00A3E3F0 /* FBResponsePayload.m in Sources */, B316351C2DDF0CF5007D9317 /* FBAccessibilityTraits.m in Sources */, - E444DCB524913C220060D7EB /* RouteRequest.m in Sources */, C8FB547A22D4C1FC00B69954 /* FBUnattachedAppLauncher.m in Sources */, EE158ADF1CBD456F00A3E3F0 /* FBRoute.m in Sources */, EE0D1F621EBCDCF7006A3123 /* NSString+FBVisualLength.m in Sources */, EEE9B4731CD02B88009D2030 /* FBRunLoopSpinner.m in Sources */, 719CD8F92126C78F00C7D0C2 /* FBAlertsMonitor.m in Sources */, 71A7EAFA1E224648001DA4F2 /* FBClassChainQueryParser.m in Sources */, - 718226D02587443700661B83 /* GCDAsyncUdpSocket.m in Sources */, 13DE7A51287C46BB003243C6 /* FBXCElementSnapshot.m in Sources */, 71A224E61DE2F56600844D55 /* NSPredicate+FBFormat.m in Sources */, - E444DC85249131B10060D7EB /* DDNumber.m in Sources */, EEE376441D59F81400ED88DD /* XCUIDevice+FBRotation.m in Sources */, A1B2C3D41F001A00A1B0007 /* XCUIDevice+FBVoiceOver.m in Sources */, 13815F712328D20400CDAB61 /* FBActiveAppDetectionPoint.m in Sources */, @@ -4929,7 +4677,6 @@ 7155D704211DCEF400166C20 /* FBMjpegServer.m in Sources */, EEDFE1221D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.m in Sources */, 714D88CE2733FB970074A925 /* FBXMLGenerationOptions.m in Sources */, - E444DCB424913C220060D7EB /* RoutingHTTPServer.m in Sources */, 7140974E1FAE20EE008FB2C5 /* FBBaseActionsSynthesizer.m in Sources */, EEE3764A1D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m in Sources */, EE8DDD7E20C5733C004D4925 /* XCUIElement+FBForceTouch.m in Sources */, @@ -4959,12 +4706,9 @@ EE158AAF1CBD456F00A3E3F0 /* XCUIElement+FBAccessibility.m in Sources */, 714E14BA29805CAE00375DD7 /* XCAXClient_iOS+FBSnapshotReqParams.m in Sources */, 7150348821A6DAD600A0F4BA /* FBImageUtils.m in Sources */, - E444DCAB24913C220060D7EB /* HTTPResponseProxy.m in Sources */, - E444DC6D249131890060D7EB /* HTTPErrorResponse.m in Sources */, 71F5BE25252E576C00EE9EBA /* XCUIElement+FBSwiping.m in Sources */, EE158AE51CBD456F00A3E3F0 /* FBSession.m in Sources */, 71C9EAAE25E8415A00470CD8 /* FBScreenshot.m in Sources */, - E444DCB224913C220060D7EB /* RoutingConnection.m in Sources */, EE158AC11CBD456F00A3E3F0 /* FBFindElementCommands.m in Sources */, EE7E271D1D06C69F001BEC7B /* FBDebugLogDelegateDecorator.m in Sources */, 716C9DFC27315D21005AD475 /* FBReflectionUtils.m in Sources */, @@ -4977,13 +4721,10 @@ 13DE7A57287CA1EC003243C6 /* FBXCElementSnapshotWrapper.m in Sources */, 71BB58F82B96531900CB9BFE /* FBScreenRecordingContainer.m in Sources */, EE158AB31CBD456F00A3E3F0 /* XCUIElement+FBScrolling.m in Sources */, - 718226CE2587443700661B83 /* GCDAsyncSocket.m in Sources */, EE158AC91CBD456F00A3E3F0 /* FBSessionCommands.m in Sources */, 715A84CF2DD92AD3007134CC /* FBElementHelpers.m in Sources */, EE9B76A71CF7A43900275851 /* FBConfiguration.m in Sources */, - E444DC9C249131D40060D7EB /* HTTPServer.m in Sources */, 71414ED82670A1EE003A8C5D /* LRUCache.m in Sources */, - E444DC67249131890060D7EB /* HTTPDataResponse.m in Sources */, EE158AD31CBD456F00A3E3F0 /* FBElementCache.m in Sources */, 71930C4320662E1F00D3AFEC /* FBPasteboard.m in Sources */, AD6C26951CF2379700F8B5FF /* FBAlert.m in Sources */, @@ -4993,14 +4734,15 @@ EE158AD51CBD456F00A3E3F0 /* FBExceptionHandler.m in Sources */, EE5A24421F136D360078B1D9 /* FBXCodeCompatibility.m in Sources */, EE158AE91CBD456F00A3E3F0 /* FBElementTypeTransformer.m in Sources */, - E444DC9D249131D40060D7EB /* HTTPMessage.m in Sources */, - E444DCB024913C220060D7EB /* RouteResponse.m in Sources */, 71D3B3D7267FC7260076473D /* XCUIElement+FBResolve.m in Sources */, 715AFAC21FFA29180053896D /* FBScreen.m in Sources */, 71B155DC230711E900646AFB /* FBCommandStatus.m in Sources */, EE35AD7C1E3B80C000A02D78 /* FBXCTestDaemonsProxy.m in Sources */, EE18883B1DA661C400307AA8 /* FBMathUtils.m in Sources */, 7157B292221DADD2001C348C /* FBXCAXClientProxy.m in Sources */, + 35924251B4B5D0A486A6A0BB /* FBHTTPServer.m in Sources */, + 7072174F17BA109C6AB2859F /* RouteRequest.m in Sources */, + 5B9C00B488A31A95F1460727 /* RouteResponse.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h b/WebDriverAgentLib/Routing/FBHTTPServer.h similarity index 75% rename from WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h rename to WebDriverAgentLib/Routing/FBHTTPServer.h index a570642c2f..96c59d46bd 100644 --- a/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h +++ b/WebDriverAgentLib/Routing/FBHTTPServer.h @@ -6,13 +6,11 @@ * LICENSE file in the root directory of this source tree. */ -// RoutingHTTPServer/CocoaHTTPServer need BSD sockets (via GCDAsyncSocket), which watchOS -// forbids - see FBTCPSocket.h/.m. This is a minimal HTTP/1.1 server on top of the watchOS -// FBTCPSocket, mirroring just enough of RoutingHTTPServer's API for FBWebServer.m to swap -// servers with a single #if TARGET_OS_WATCH. +// A minimal HTTP/1.1 server on top of FBTCPSocket (Network.framework-backed on every platform, +// since watchOS forbids BSD sockets outright - see FBTCPSocket.h/.m). // // No chunked encoding, range requests, or pipelining - just request line + headers + -// Content-Length body, and ":param" path matching like RoutingHTTPServer.m. +// Content-Length body, and ":param" path matching. @import Foundation; @@ -21,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface FBWatchHTTPServer : NSObject +@interface FBHTTPServer : NSObject /*! The port the server is (or will be) listening on */ @property (nonatomic) uint16_t port; @@ -40,9 +38,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setDefaultHeader:(NSString *)field value:(NSString *)value; +/** + Sets the local IP address to bind the listener to. Must be called before -start:. Pass nil (the + default) to listen on all interfaces. + */ +- (void)setInterface:(nullable NSString *)interface; + /** Registers a route handler for the given HTTP method and path pattern (":param" segments are - captured into the request's `params`, matching RoutingHTTPServer's convention). + captured into the request's `params`). */ - (void)handleMethod:(NSString *)method withPath:(NSString *)path diff --git a/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m b/WebDriverAgentLib/Routing/FBHTTPServer.m similarity index 92% rename from WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m rename to WebDriverAgentLib/Routing/FBHTTPServer.m index b5b9eed85c..a31f52a3b0 100644 --- a/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m +++ b/WebDriverAgentLib/Routing/FBHTTPServer.m @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -#import "FBWatchHTTPServer.h" +#import "FBHTTPServer.h" #import "FBConfiguration.h" #import "FBTCPSocket.h" @@ -28,29 +28,30 @@ return (NSData * _Nonnull)[string dataUsingEncoding:NSUTF8StringEncoding]; } -@interface FBWatchHTTPRoute : NSObject +@interface FBHTTPRoute : NSObject @property (nonatomic, copy) NSString *verb; @property (nonatomic, strong) NSRegularExpression *regex; @property (nonatomic, copy, nullable) NSArray *keys; @property (nonatomic, copy) void (^block)(RouteRequest *request, RouteResponse *response); @end -@implementation FBWatchHTTPRoute +@implementation FBHTTPRoute @end -@interface FBWatchHTTPServer () +@interface FBHTTPServer () @property (nonatomic, nullable, strong) FBTCPSocket *socket; -@property (nonatomic, strong) NSMutableArray *routes; +@property (nonatomic, strong) NSMutableArray *routes; @property (nonatomic, strong) NSMutableDictionary *defaultHeaders; @property (nonatomic, nullable) dispatch_queue_t routeQueue; +@property (nonatomic, copy, nullable) NSString *interface; // nw_connection_t isn't NSCopying, so it can't be an NSDictionary key - use NSMapTable instead. @property (nonatomic, strong) NSMapTable *connectionBuffers; @end -@implementation FBWatchHTTPServer +@implementation FBHTTPServer - (instancetype)init { @@ -73,15 +74,20 @@ - (void)setDefaultHeader:(NSString *)field value:(NSString *)value self.defaultHeaders[field] = value; } +- (void)setInterface:(nullable NSString *)interface +{ + _interface = interface.copy; +} + #pragma mark - Route registration -- (FBWatchHTTPRoute *)compiledRouteWithPath:(NSString *)path +- (FBHTTPRoute *)compiledRouteWithPath:(NSString *)path { - FBWatchHTTPRoute *route = [FBWatchHTTPRoute new]; + FBHTTPRoute *route = [FBHTTPRoute new]; NSMutableArray *keys = [NSMutableArray array]; // Escape regex-significant characters before substituting :param placeholders, like - // RoutingHTTPServer.m does. + // RoutingHTTPServer.m used to. NSRegularExpression *escapeRegex = [NSRegularExpression regularExpressionWithPattern:@"[.+()]" options:(NSRegularExpressionOptions)0 error:nil]; @@ -126,7 +132,7 @@ - (void)handleMethod:(NSString *)method withPath:(NSString *)path block:(void (^)(RouteRequest *request, RouteResponse *response))block { - FBWatchHTTPRoute *route = [self compiledRouteWithPath:path]; + FBHTTPRoute *route = [self compiledRouteWithPath:path]; route.verb = method.uppercaseString; route.block = block; [self.routes addObject:route]; @@ -142,6 +148,7 @@ - (void)get:(NSString *)path withBlock:(void (^)(RouteRequest *request, RouteRes - (BOOL)start:(NSError **)error { FBTCPSocket *socket = [[FBTCPSocket alloc] initWithPort:self.port]; + socket.interface = self.interface; socket.delegate = self; if (![socket startWithError:error]) { return NO; @@ -239,8 +246,8 @@ - (void)processBufferForClient:(nw_connection_t)client NSUInteger contentLength = (NSUInteger)requestHeaders[@"content-length"].integerValue; if (contentLength > FBConfiguration.sharedInstance.httpRequestBodySizeLimit) { - // Mirrors FBHTTPConnection's maxRequestBodySize enforcement on iOS/tvOS. Closes the - // connection after responding, since the rest of the oversized body is still incoming. + // Mirrors CocoaHTTPServer's maxRequestBodySize enforcement. Closes the connection after + // responding, since the rest of the oversized body is still incoming. RouteResponse *tooLarge = [RouteResponse new]; tooLarge.statusCode = kHTTPStatusCodeRequestEntityTooLarge; [tooLarge respondWithString:@"Request Entity Too Large"]; @@ -269,7 +276,7 @@ - (void)dispatchMethod:(NSString *)method pathAndQuery:(NSString *)pathAndQuery NSURLComponents *requestTarget = [NSURLComponents componentsWithString:pathAndQuery]; NSString *path = requestTarget.path ?: pathAndQuery; - for (FBWatchHTTPRoute *route in self.routes) { + for (FBHTTPRoute *route in self.routes) { if (![route.verb isEqualToString:method]) { continue; } @@ -367,6 +374,12 @@ - (NSString *)reasonPhraseForStatusCode:(HTTPStatusCode)statusCode return @"Bad Request"; } else if (kHTTPStatusCodeNotFound == statusCode) { return @"Not Found"; + } else if (kHTTPStatusCodeMethodNotAllowed == statusCode) { + return @"Method Not Allowed"; + } else if (kHTTPStatusCodeRequestTimeout == statusCode) { + return @"Request Timeout"; + } else if (kHTTPStatusCodeRequestEntityTooLarge == statusCode) { + return @"Request Entity Too Large"; } else if (kHTTPStatusCodeInternalServerError == statusCode) { return @"Internal Server Error"; } diff --git a/WebDriverAgentLib/Routing/FBTCPSocket.h b/WebDriverAgentLib/Routing/FBTCPSocket.h index d8896f0821..9613330e58 100644 --- a/WebDriverAgentLib/Routing/FBTCPSocket.h +++ b/WebDriverAgentLib/Routing/FBTCPSocket.h @@ -6,28 +6,18 @@ * LICENSE file in the root directory of this source tree. */ -// TARGET_OS_WATCH must be defined before the #if below runs, or (on some older Xcode/SDK -// toolchains) it silently evaluates as undefined/false here despite being true for the rest of -// the translation unit - which desyncs this file's declarations from FBTCPSocket.m's own -// #if TARGET_OS_WATCH branch. -#import - -#if TARGET_OS_WATCH @import Foundation; // A textual import, not `@import Network;` - older Xcode/watchOS SDK combinations (verified: // Xcode 15.4/watchOS 10.5) fail to expose nw_listener_t/nw_connection_t and friends through the // Network module map on watchOS, even though the underlying API has existed since watchOS 5.0. +// Kept unconditional (rather than gated to watchOS) since it also builds cleanly on iOS/tvOS. #import -#else -#import "GCDAsyncSocket.h" -#endif NS_ASSUME_NONNULL_BEGIN -#if TARGET_OS_WATCH - -// watchOS forbids BSD sockets, so this is backed by Network.framework instead of GCDAsyncSocket - -// hence a differently-shaped, push-style delegate protocol. +// Backed by Network.framework rather than BSD sockets on every platform, since watchOS forbids +// BSD sockets outright and there is no reason to keep a second, socket-based implementation +// around just for iOS/tvOS. @protocol FBTCPSocketDelegate /** @@ -54,35 +44,6 @@ NS_ASSUME_NONNULL_BEGIN @end -#else - -@protocol FBTCPSocketDelegate - -/** - The callback which is fired on new TCP client connection - - @param newClient The newly connected socket - */ -- (void)didClientConnect:(GCDAsyncSocket *)newClient; - -/** - The callback which is fired when the TCP server receives a data from a connected client - - @param client The client, which sent the data -*/ -- (void)didClientSendData:(GCDAsyncSocket *)client; - -/** - The callback which is fired when TCP client disconnects - - @param client The actual diconnected client - */ -- (void)didClientDisconnect:(GCDAsyncSocket *)client; - -@end - -#endif - @interface FBTCPSocket : NSObject @@ -99,6 +60,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) uint16_t port; +/** + The local IP address to bind the listener to, or nil to listen on all interfaces. Must be set + before -startWithError: is called. + */ +@property (nonatomic, copy, nullable) NSString *interface; + /** Creates TCP socket isntance which is going to be started on the specified port @@ -120,7 +87,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)stop; -#if TARGET_OS_WATCH /** Writes data to the given connected client @@ -139,7 +105,6 @@ NS_ASSUME_NONNULL_BEGIN @param completion Called once the send attempt finishes */ - (void)writeData:(NSData *)data toClient:(nw_connection_t)client completion:(nullable void (^)(void))completion; -#endif @end diff --git a/WebDriverAgentLib/Routing/FBTCPSocket.m b/WebDriverAgentLib/Routing/FBTCPSocket.m index 2a884dec1c..d69a10bfcc 100644 --- a/WebDriverAgentLib/Routing/FBTCPSocket.m +++ b/WebDriverAgentLib/Routing/FBTCPSocket.m @@ -8,8 +8,6 @@ #import "FBTCPSocket.h" -#if TARGET_OS_WATCH - @interface FBTCPSocket() @property (readonly, nonatomic) dispatch_queue_t socketQueue; @property (nullable, nonatomic) nw_listener_t listener; @@ -37,7 +35,18 @@ - (BOOL)startWithError:(NSError **)error NSString *portString = [NSString stringWithFormat:@"%u", (unsigned int)self.port]; // portString is always valid UTF8; -UTF8String is just declared nullable in general. const char * _Nonnull portCString = (const char * _Nonnull)portString.UTF8String; - nw_listener_t listener = nw_listener_create_with_port(portCString, parameters); + + nw_listener_t listener; + if (nil != self.interface) { + const char * _Nonnull interfaceCString = (const char * _Nonnull)((NSString * _Nonnull)self.interface).UTF8String; + nw_endpoint_t localEndpoint = nw_endpoint_create_host(interfaceCString, portCString); + nw_parameters_set_local_endpoint(parameters, localEndpoint); + // The port is already encoded in localEndpoint above - do not also pass it to + // nw_listener_create_with_port, which would be ambiguous. + listener = nw_listener_create(parameters); + } else { + listener = nw_listener_create_with_port(portCString, parameters); + } if (nil == listener) { if (error) { *error = [NSError errorWithDomain:@"FBTCPSocket" @@ -206,94 +215,3 @@ - (void)stop } @end - -#else - -@interface FBTCPSocket() -@property (readonly, nonatomic) dispatch_queue_t socketQueue; -@property (readonly, nonatomic) GCDAsyncSocket *listeningSocket; -@property (readonly, nonatomic) NSMutableArray *connectedClients; -@end - - -@interface FBTCPSocket(AsyncSocket) - -@end - - -@implementation FBTCPSocket - -- (instancetype)initWithPort:(uint16_t)port -{ - if ((self = [super init])) { - _socketQueue = dispatch_queue_create("socketQueue", NULL); - _listeningSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue]; - _connectedClients = [[NSMutableArray alloc] initWithCapacity:1]; - _port = port; - _delegate = nil; - } - return self; -} - -- (BOOL)startWithError:(NSError **)error -{ - if (![self.listeningSocket acceptOnPort:self.port error:error]) { - return NO; - } - - _port = self.listeningSocket.localPort; - return YES; -} - -- (void)stop -{ - @synchronized(self.connectedClients) { - NSArray *clients = self.connectedClients.copy; - [self.connectedClients removeAllObjects]; - for (GCDAsyncSocket *client in clients) { - [client disconnect]; - } - } - - self.delegate = nil; - [self.listeningSocket disconnect]; -} - -@end - - -@implementation FBTCPSocket(AsyncSocket) - -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket -{ - @synchronized(self.connectedClients) { - [self.connectedClients addObject:newSocket]; - } - id delegate = self.delegate; - if (nil != delegate) { - [delegate didClientConnect:newSocket]; - } -} - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag -{ - id delegate = self.delegate; - if (nil != delegate) { - [delegate didClientSendData:sock]; - } -} - -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err -{ - @synchronized(self.connectedClients) { - [self.connectedClients removeObject:sock]; - } - id delegate = self.delegate; - if (nil != delegate) { - [delegate didClientDisconnect:sock]; - } -} - -@end - -#endif diff --git a/WebDriverAgentLib/Routing/FBWebServer.h b/WebDriverAgentLib/Routing/FBWebServer.h index 7ab0b8809d..ed590fd203 100644 --- a/WebDriverAgentLib/Routing/FBWebServer.h +++ b/WebDriverAgentLib/Routing/FBWebServer.h @@ -8,7 +8,7 @@ #import -@class RouteResponse, RoutingHTTPServer, FBExceptionHandler; +@class RouteResponse, FBExceptionHandler; @protocol FBWebServerDelegate; NS_ASSUME_NONNULL_BEGIN diff --git a/WebDriverAgentLib/Routing/FBWebServer.m b/WebDriverAgentLib/Routing/FBWebServer.m index 1434e4ccc5..10a1ec4c15 100644 --- a/WebDriverAgentLib/Routing/FBWebServer.m +++ b/WebDriverAgentLib/Routing/FBWebServer.m @@ -8,12 +8,7 @@ #import "FBWebServer.h" -#if TARGET_OS_WATCH -#import "FBWatchHTTPServer.h" -#else -#import "RoutingConnection.h" -#import "RoutingHTTPServer.h" -#endif +#import "FBHTTPServer.h" #import "FBMjpegServer.h" #import "FBTCPSocket.h" @@ -32,34 +27,9 @@ static NSString *const FBServerURLBeginMarker = @"ServerURLHere->"; static NSString *const FBServerURLEndMarker = @"<-ServerURLHere"; -#if !TARGET_OS_WATCH -@interface FBHTTPConnection : RoutingConnection -@end - -@implementation FBHTTPConnection - -- (void)handleResourceNotFound -{ - [FBLogger logFmt:@"Received request for %@ which we do not handle", self.requestURI]; - [super handleResourceNotFound]; -} - -- (UInt64)maxRequestBodySize -{ - return FBConfiguration.sharedInstance.httpRequestBodySizeLimit; -} - -@end -#endif - - @interface FBWebServer () @property (nonatomic, strong) FBExceptionHandler *exceptionHandler; -#if TARGET_OS_WATCH -@property (nonatomic, strong) FBWatchHTTPServer *server; -#else -@property (nonatomic, strong) RoutingHTTPServer *server; -#endif +@property (nonatomic, strong) FBHTTPServer *server; @property (nonatomic, nullable) FBTCPSocket *screenshotsBroadcaster; @property (nonatomic, nullable, strong) FBMjpegServer *mjpegServer; @property (atomic, assign) BOOL keepAlive; @@ -104,30 +74,21 @@ - (void)startServing - (BOOL)startHTTPServer { -#if TARGET_OS_WATCH - self.server = [[FBWatchHTTPServer alloc] init]; -#else - self.server = [[RoutingHTTPServer alloc] init]; -#endif + self.server = [[FBHTTPServer alloc] init]; [self.server setRouteQueue:dispatch_get_main_queue()]; [self.server setDefaultHeader:@"Server" value:@"WebDriverAgent/1.0"]; [self.server setDefaultHeader:@"Access-Control-Allow-Origin" value:@"*"]; [self.server setDefaultHeader:@"Access-Control-Allow-Headers" value:@"Content-Type, X-Requested-With"]; -#if !TARGET_OS_WATCH - [self.server setConnectionClass:[FBHTTPConnection self]]; -#endif [self registerRouteHandlers:[self.class collectCommandHandlerClasses]]; [self registerServerKeyRouteHandlers]; NSRange serverPortRange = FBConfiguration.sharedInstance.bindingPortRange; NSString *bindingIP = FBConfiguration.sharedInstance.bindingIPAddress; -#if !TARGET_OS_WATCH if (bindingIP != nil) { [self.server setInterface:bindingIP]; [FBLogger logFmt:@"Using custom binding IP address: %@", bindingIP]; } -#endif NSError *error; BOOL serverStarted = NO; @@ -165,9 +126,7 @@ - (void)initScreenshotsBroadcaster self.mjpegServer = [[FBMjpegServer alloc] init]; self.screenshotsBroadcaster = [[FBTCPSocket alloc] initWithPort:(uint16_t)FBConfiguration.sharedInstance.mjpegServerPort]; -#if TARGET_OS_WATCH self.mjpegServer.socket = self.screenshotsBroadcaster; -#endif self.screenshotsBroadcaster.delegate = self.mjpegServer; NSError *error; if (![self.screenshotsBroadcaster startWithError:&error]) { @@ -220,11 +179,7 @@ - (void)stopServing self.keepAlive = NO; } -#if TARGET_OS_WATCH -- (BOOL)attemptToStartServer:(FBWatchHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error -#else -- (BOOL)attemptToStartServer:(RoutingHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error -#endif +- (BOOL)attemptToStartServer:(FBHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error { server.port = (UInt16)port; NSError *innerError = nil; diff --git a/WebDriverAgentLib/Routing/WatchOS/RouteRequest.h b/WebDriverAgentLib/Routing/RouteRequest.h similarity index 68% rename from WebDriverAgentLib/Routing/WatchOS/RouteRequest.h rename to WebDriverAgentLib/Routing/RouteRequest.h index d81830eda8..1a5c1b458c 100644 --- a/WebDriverAgentLib/Routing/WatchOS/RouteRequest.h +++ b/WebDriverAgentLib/Routing/RouteRequest.h @@ -6,10 +6,8 @@ * LICENSE file in the root directory of this source tree. */ -// Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteRequest.h, which is not -// available on watchOS because RoutingHTTPServer/CocoaHTTPServer/CocoaAsyncSocket cannot be -// built there (see FBWatchHTTPServer.h). Exposes just the surface FBWebServer's route blocks -// and FBRoute.decorateRequest: read. +// A minimal request value type, exposing just the surface FBWebServer's route blocks and +// FBRoute.decorateRequest: read. @import Foundation; diff --git a/WebDriverAgentLib/Routing/WatchOS/RouteRequest.m b/WebDriverAgentLib/Routing/RouteRequest.m similarity index 100% rename from WebDriverAgentLib/Routing/WatchOS/RouteRequest.m rename to WebDriverAgentLib/Routing/RouteRequest.m diff --git a/WebDriverAgentLib/Routing/WatchOS/RouteResponse.h b/WebDriverAgentLib/Routing/RouteResponse.h similarity index 82% rename from WebDriverAgentLib/Routing/WatchOS/RouteResponse.h rename to WebDriverAgentLib/Routing/RouteResponse.h index c0af255aba..da71e57d7a 100644 --- a/WebDriverAgentLib/Routing/WatchOS/RouteResponse.h +++ b/WebDriverAgentLib/Routing/RouteResponse.h @@ -6,8 +6,8 @@ * LICENSE file in the root directory of this source tree. */ -// Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteResponse.h, reproducing just -// what FBRoute.m/FBResponseJSONPayload.m call on it. See FBWatchHTTPServer.h. +// A minimal response value type, exposing just the surface FBRoute.m/FBResponseJSONPayload.m +// call on it. @import Foundation; #import diff --git a/WebDriverAgentLib/Routing/WatchOS/RouteResponse.m b/WebDriverAgentLib/Routing/RouteResponse.m similarity index 100% rename from WebDriverAgentLib/Routing/WatchOS/RouteResponse.m rename to WebDriverAgentLib/Routing/RouteResponse.m diff --git a/WebDriverAgentLib/Utilities/FBMjpegServer.h b/WebDriverAgentLib/Utilities/FBMjpegServer.h index 5957c40833..03cd717541 100644 --- a/WebDriverAgentLib/Utilities/FBMjpegServer.h +++ b/WebDriverAgentLib/Utilities/FBMjpegServer.h @@ -19,14 +19,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)init; -#if TARGET_OS_WATCH /** - The socket that owns this instance as its delegate. watchOS clients are bare nw_connection_t - values with no write method of their own (unlike GCDAsyncSocket on other platforms), so frame - writes are routed through -[FBTCPSocket writeData:toClient:]. Must be set before streaming starts. + The socket that owns this instance as its delegate. Clients are bare nw_connection_t values + with no write method of their own, so frame writes are routed through + -[FBTCPSocket writeData:toClient:]. Must be set before streaming starts. */ @property (nonatomic, weak, nullable) FBTCPSocket *socket; -#endif /** Stops screenshot broadcasting and prevents future scheduling. diff --git a/WebDriverAgentLib/Utilities/FBMjpegServer.m b/WebDriverAgentLib/Utilities/FBMjpegServer.m index 0b5dcca7ae..8bb2b958c8 100644 --- a/WebDriverAgentLib/Utilities/FBMjpegServer.m +++ b/WebDriverAgentLib/Utilities/FBMjpegServer.m @@ -11,12 +11,8 @@ #import @import UniformTypeIdentifiers; -#if TARGET_OS_WATCH // Textual import, not `@import Network;` - see the comment in FBTCPSocket.h. #import -#else -#import "GCDAsyncSocket.h" -#endif #import "FBConfiguration.h" #import "FBLogger.h" #import "FBScreenshot.h" @@ -41,11 +37,7 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate) @interface FBMjpegServer() @property (nonatomic, readonly) dispatch_queue_t backgroundQueue; -#if TARGET_OS_WATCH @property (nonatomic, readonly) NSMutableArray *listeningClients; -#else -@property (nonatomic, readonly) NSMutableArray *listeningClients; -#endif @property (nonatomic, readonly) FBImageProcessor *imageProcessor; @property (nonatomic, readonly) long long mainScreenID; @property (nonatomic, assign) NSUInteger consecutiveScreenshotFailures; @@ -157,16 +149,9 @@ - (void)sendScreenshot:(NSData *)screenshotData { return; } NSUInteger clientCount = self.listeningClients.count; -#if TARGET_OS_WATCH for (nw_connection_t client in self.listeningClients) { [self.socket writeData:chunk toClient:client]; } -#else - for (GCDAsyncSocket *client in self.listeningClients) { - // Slow clients should fail/close instead of buffering indefinitely. - [client writeData:chunk withTimeout:FRAME_TIMEOUT tag:0]; - } -#endif self.sentFramesCount++; self.sentBytesCount += chunk.length * clientCount; NSUInteger framerate = FBNormalizedMjpegFramerate(FBConfiguration.sharedInstance.mjpegServerFramerate); @@ -179,13 +164,10 @@ - (void)sendScreenshot:(NSData *)screenshotData { } } -#if TARGET_OS_WATCH - - (void)didClientConnect:(nw_connection_t)newClient { [FBLogger log:@"Got screenshots broadcast client connection"]; - // FBTCPSocket already schedules the receive that -client:didReceiveData: relies on below; - // unlike GCDAsyncSocket, there is nothing to arm here. + // FBTCPSocket already schedules the receive that -client:didReceiveData: relies on below. } - (void)client:(nw_connection_t)client didReceiveData:(NSData *)data @@ -224,53 +206,6 @@ - (void)stopStreaming } } -#else - -- (void)didClientConnect:(GCDAsyncSocket *)newClient -{ - [FBLogger logFmt:@"Got screenshots broadcast client connection at %@:%d", newClient.connectedHost, newClient.connectedPort]; - // Start broadcast only after there is any data from the client - [newClient readDataWithTimeout:-1 tag:0]; -} - -- (void)didClientSendData:(GCDAsyncSocket *)client -{ - @synchronized (self.listeningClients) { - if ([self.listeningClients containsObject:client]) { - return; - } - } - - [FBLogger logFmt:@"Starting screenshots broadcast for the client at %@:%d", client.connectedHost, client.connectedPort]; - NSString *streamHeader = [NSString stringWithFormat:@"HTTP/1.0 200 OK\r\nServer: %@\r\nConnection: close\r\nMax-Age: 0\r\nExpires: 0\r\nCache-Control: no-cache, private\r\nPragma: no-cache\r\nContent-Type: multipart/x-mixed-replace; boundary=--BoundaryString\r\n\r\n", SERVER_NAME]; - [client writeData:(id)[streamHeader dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; - @synchronized (self.listeningClients) { - [self.listeningClients addObject:client]; - } -} - -- (void)didClientDisconnect:(GCDAsyncSocket *)client -{ - @synchronized (self.listeningClients) { - [self.listeningClients removeObject:client]; - } - [FBLogger log:@"Disconnected a client from screenshots broadcast"]; -} - -- (void)stopStreaming -{ - self.isStreaming = NO; - @synchronized (self.listeningClients) { - NSArray *clients = self.listeningClients.copy; - [self.listeningClients removeAllObjects]; - for (GCDAsyncSocket *client in clients) { - [client disconnect]; - } - } -} - -#endif - - (void)dealloc { [self stopStreaming]; diff --git a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h deleted file mode 100644 index 92d53a450f..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h +++ /dev/null @@ -1,1220 +0,0 @@ -// -// GCDAsyncSocket.h -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q3 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import -#import -#import -#import -#import - -#include // AF_INET, AF_INET6 - -@class GCDAsyncReadPacket; -@class GCDAsyncWritePacket; -@class GCDAsyncSocketPreBuffer; -@protocol GCDAsyncSocketDelegate; - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const GCDAsyncSocketException; -extern NSString *const GCDAsyncSocketErrorDomain; - -extern NSString *const GCDAsyncSocketQueueName; -extern NSString *const GCDAsyncSocketThreadName; - -extern NSString *const GCDAsyncSocketManuallyEvaluateTrust; -#if TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketUseCFStreamForTLS; -#endif -#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName -#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates -#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer -extern NSString *const GCDAsyncSocketSSLPeerID; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMin; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMax; -extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart; -extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord; -extern NSString *const GCDAsyncSocketSSLCipherSuites; -extern NSString *const GCDAsyncSocketSSLALPN; -#if !TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; -#endif - -#define GCDAsyncSocketLoggingContext 65535 - - -typedef NS_ERROR_ENUM(GCDAsyncSocketErrorDomain, GCDAsyncSocketError) { - GCDAsyncSocketNoError = 0, // Never used - GCDAsyncSocketBadConfigError, // Invalid configuration - GCDAsyncSocketBadParamError, // Invalid parameter was passed - GCDAsyncSocketConnectTimeoutError, // A connect operation timed out - GCDAsyncSocketReadTimeoutError, // A read operation timed out - GCDAsyncSocketWriteTimeoutError, // A write operation timed out - GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing - GCDAsyncSocketClosedError, // The remote peer closed the connection - GCDAsyncSocketOtherError, // Description provided in userInfo -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - -@interface GCDAsyncSocket : NSObject - -/** - * GCDAsyncSocket uses the standard delegate paradigm, - * but executes all delegate callbacks on a given delegate dispatch queue. - * This allows for maximum concurrency, while at the same time providing easy thread safety. - * - * You MUST set a delegate AND delegate dispatch queue before attempting to - * use the socket, or you will get an error. - * - * The socket queue is optional. - * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue. - * If you choose to provide a socket queue, the socket queue must not be a concurrent queue. - * If you choose to provide a socket queue, and the socket queue has a configured target queue, - * then please see the discussion for the method markSocketQueueTargetQueue. - * - * The delegate queue and socket queue can optionally be the same. -**/ -- (instancetype)init; -- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; -- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq; -- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER; - -/** - * Create GCDAsyncSocket from already connect BSD socket file descriptor -**/ -+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error; - -+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error; - -+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError **)error; - -#pragma mark Configuration - -@property (atomic, weak, readwrite, nullable) id delegate; -#if OS_OBJECT_USE_OBJC -@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue; -#else -@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue; -#endif - -- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; -- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; - -/** - * If you are setting the delegate to nil within the delegate's dealloc method, - * you may need to use the synchronous versions below. -**/ -- (void)synchronouslySetDelegate:(nullable id)delegate; -- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; - -/** - * By default, both IPv4 and IPv6 are enabled. - * - * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols, - * and can simulataneously accept incoming connections on either protocol. - * - * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol. - * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4. - * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6. - * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen. - * By default, the preferred protocol is IPv4, but may be configured as desired. -**/ - -@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled; -@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled; - -@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6; - -/** - * When connecting to both IPv4 and IPv6 using Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555 - * this is the delay between connecting to the preferred protocol and the fallback protocol. - * - * Defaults to 300ms. -**/ -@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay; - -/** - * User data allows you to associate arbitrary information with the socket. - * This data is not used internally by socket in any way. -**/ -@property (atomic, strong, readwrite, nullable) id userData; - -#pragma mark Accepting - -/** - * Tells the socket to begin listening and accepting connections on the given port. - * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, - * and the socket:didAcceptNewSocket: delegate method will be invoked. - * - * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) -**/ -- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * This method is the same as acceptOnPort:error: with the - * additional option of specifying which interface to listen on. - * - * For example, you could specify that the socket should only accept connections over ethernet, - * and not other interfaces such as wifi. - * - * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept connections from the local machine. - * - * You can see the list of interfaces via the command line utility "ifconfig", - * or programmatically via the getifaddrs() function. - * - * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. -**/ -- (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; - -/** - * Tells the socket to begin listening and accepting connections on the unix domain at the given url. - * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, - * and the socket:didAcceptNewSocket: delegate method will be invoked. - * - * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) - **/ -- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; - -#pragma mark Connecting - -/** - * Connects to the given host and port. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: - * and uses the default interface, and no timeout. -**/ -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * Connects to the given host and port with an optional timeout. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given host & port, via the optional interface, with an optional timeout. - * - * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * The host may also be the special strings "localhost" or "loopback" to specify connecting - * to a service on the local machine. - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - viaInterface:(nullable NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * This method invokes connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -/** - * This method is the same as connectToAddress:error: with an additional timeout option. - * To not time out use a negative time interval, or simply use the connectToAddress:error: method. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; - -/** - * Connects to the given address, using the specified interface and timeout. - * - * The address is specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * The timeout is optional. To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr - viaInterface:(nullable NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; -/** - * Connects to the unix domain socket at the given url, using the specified timeout. - */ -- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; - -#pragma mark Disconnecting - -/** - * Disconnects immediately (synchronously). Any pending reads or writes are dropped. - * - * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method - * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods). - * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns. - * - * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method) - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket release]; - * - * If you plan on disconnecting the socket, and then immediately asking it to connect again, - * you'll likely want to do so like this: - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket setDelegate:self]; - * [asyncSocket connect...]; -**/ -- (void)disconnect; - -/** - * Disconnects after all pending reads have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending writes. -**/ -- (void)disconnectAfterReading; - -/** - * Disconnects after all pending writes have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending reads. -**/ -- (void)disconnectAfterWriting; - -/** - * Disconnects after all pending reads and writes have completed. - * After calling this, the read and write methods will do nothing. -**/ -- (void)disconnectAfterReadingAndWriting; - -#pragma mark Diagnostics - -/** - * Returns whether the socket is disconnected or connected. - * - * A disconnected socket may be recycled. - * That is, it can be used again for connecting or listening. - * - * If a socket is in the process of connecting, it may be neither disconnected nor connected. -**/ -@property (atomic, readonly) BOOL isDisconnected; -@property (atomic, readonly) BOOL isConnected; - -/** - * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. - * The host will be an IP address. -**/ -@property (atomic, readonly, nullable) NSString *connectedHost; -@property (atomic, readonly) uint16_t connectedPort; -@property (atomic, readonly, nullable) NSURL *connectedUrl; - -@property (atomic, readonly, nullable) NSString *localHost; -@property (atomic, readonly) uint16_t localPort; - -/** - * Returns the local or remote address to which this socket is connected, - * specified as a sockaddr structure wrapped in a NSData object. - * - * @seealso connectedHost - * @seealso connectedPort - * @seealso localHost - * @seealso localPort -**/ -@property (atomic, readonly, nullable) NSData *connectedAddress; -@property (atomic, readonly, nullable) NSData *localAddress; - -/** - * Returns whether the socket is IPv4 or IPv6. - * An accepting socket may be both. -**/ -@property (atomic, readonly) BOOL isIPv4; -@property (atomic, readonly) BOOL isIPv6; - -/** - * Returns whether or not the socket has been secured via SSL/TLS. - * - * See also the startTLS method. -**/ -@property (atomic, readonly) BOOL isSecure; - -#pragma mark Reading - -// The readData and writeData methods won't block (they are asynchronous). -// -// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue. -// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue. -// -// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) -// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method -// is called to optionally allow you to extend the timeout. -// Upon a timeout, the "socket:didDisconnectWithError:" method is called -// -// The tag is for your convenience. -// You can use it as an array index, step number, state id, pointer, etc. - -/** - * Reads the first available bytes that become available on the socket. - * - * If the timeout value is negative, the read operation will not use a timeout. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer is nil, the socket will create a buffer for you. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(nullable NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * A maximum of length bytes will be read. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer is nil, a buffer will automatically be created for you. - * If maxLength is zero, no length restriction is enforced. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(nullable NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Reads the given number of bytes. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If the length is 0, this method does nothing and the delegate is not called. -**/ -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the given number of bytes. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer is nil, a buffer will automatically be created for you. - * - * If the length is 0, this method does nothing and the delegate is not called. - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(nullable NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer is nil, a buffer will automatically be created for you. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(nullable NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * If you pass a maxLength parameter that is less than the length of the data parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer is nil, a buffer will automatically be created for you. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass a maxLength parameter that is less than the length of the data (separator) parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(nullable NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check). - * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. -**/ -- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr; - -#pragma mark Writing - -/** - * Writes data to the socket, and calls the delegate when finished. - * - * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. - * If the timeout value is negative, the write operation will not use a timeout. - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method - * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed. - * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it. - * This is for performance reasons. Often times, if NSMutableData is passed, it is because - * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time - * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. -**/ -- (void)writeData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check). - * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. -**/ -- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr; - -#pragma mark Security - -/** - * Secures the connection using SSL/TLS. - * - * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes - * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing - * the upgrade to TLS at the same time, without having to wait for the write to finish. - * Any reads or writes scheduled after this method is called will occur over the secured connection. - * - * ==== The available TOP-LEVEL KEYS are: - * - * - GCDAsyncSocketManuallyEvaluateTrust - * The value must be of type NSNumber, encapsulating a BOOL value. - * If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer. - * Instead it will pause at the moment evaulation would typically occur, - * and allow us to handle the security evaluation however we see fit. - * So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef. - * - * Note that if you set this option, then all other configuration keys are ignored. - * Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method. - * - * For more information on trust evaluation see: - * Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation - * https://developer.apple.com/library/ios/technotes/tn2232/_index.html - * - * If unspecified, the default value is NO. - * - * - GCDAsyncSocketUseCFStreamForTLS (iOS only) - * The value must be of type NSNumber, encapsulating a BOOL value. - * By default GCDAsyncSocket will use the SecureTransport layer to perform encryption. - * This gives us more control over the security protocol (many more configuration options), - * plus it allows us to optimize things like sys calls and buffer allocation. - * - * However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption - * technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket - * will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property - * (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method. - * - * Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket, - * and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty. - * For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings. - * - * If unspecified, the default value is NO. - * - * ==== The available CONFIGURATION KEYS are: - * - * - kCFStreamSSLPeerName - * The value must be of type NSString. - * It should match the name in the X.509 certificate given by the remote party. - * See Apple's documentation for SSLSetPeerDomainName. - * - * - kCFStreamSSLCertificates - * The value must be of type NSArray. - * See Apple's documentation for SSLSetCertificate. - * - * - kCFStreamSSLIsServer - * The value must be of type NSNumber, encapsulationg a BOOL value. - * See Apple's documentation for SSLCreateContext for iOS. - * This is optional for iOS. If not supplied, a NO value is the default. - * This is not needed for Mac OS X, and the value is ignored. - * - * - GCDAsyncSocketSSLPeerID - * The value must be of type NSData. - * You must set this value if you want to use TLS session resumption. - * See Apple's documentation for SSLSetPeerID. - * - * - GCDAsyncSocketSSLProtocolVersionMin - * - GCDAsyncSocketSSLProtocolVersionMax - * The value(s) must be of type NSNumber, encapsulting a SSLProtocol value. - * See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax. - * See also the SSLProtocol typedef. - * - * - GCDAsyncSocketSSLSessionOptionFalseStart - * The value must be of type NSNumber, encapsulating a BOOL value. - * See Apple's documentation for kSSLSessionOptionFalseStart. - * - * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord - * The value must be of type NSNumber, encapsulating a BOOL value. - * See Apple's documentation for kSSLSessionOptionSendOneByteRecord. - * - * - GCDAsyncSocketSSLCipherSuites - * The values must be of type NSArray. - * Each item within the array must be a NSNumber, encapsulating an SSLCipherSuite. - * See Apple's documentation for SSLSetEnabledCiphers. - * See also the SSLCipherSuite typedef. - * - * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only) - * The value must be of type NSData. - * See Apple's documentation for SSLSetDiffieHellmanParams. - * - * ==== The following UNAVAILABLE KEYS are: (with throw an exception) - * - * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsAnyRoot - * - * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsExpiredRoots - * - * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsExpiredCerts - * - * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetEnableCertVerify - * - * - kCFStreamSSLLevel (UNAVAILABLE) - * You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead. - * Corresponding deprecated method: SSLSetProtocolVersionEnabled - * - * - * Please refer to Apple's documentation for corresponding SSLFunctions. - * - * If you pass in nil or an empty dictionary, the default settings will be used. - * - * IMPORTANT SECURITY NOTE: - * The default settings will check to make sure the remote party's certificate is signed by a - * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. - * However it will not verify the name on the certificate unless you - * give it a name to verify against via the kCFStreamSSLPeerName key. - * The security implications of this are important to understand. - * Imagine you are attempting to create a secure connection to MySecureServer.com, - * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. - * If you simply use the default settings, and MaliciousServer.com has a valid certificate, - * the default settings will not detect any problems since the certificate is valid. - * To properly secure your connection in this particular scenario you - * should set the kCFStreamSSLPeerName property to "MySecureServer.com". - * - * You can also perform additional validation in socketDidSecure. -**/ -- (void)startTLS:(nullable NSDictionary *)tlsSettings; - -#pragma mark Advanced - -/** - * Traditionally sockets are not closed until the conversation is over. - * However, it is technically possible for the remote enpoint to close its write stream. - * Our socket would then be notified that there is no more data to be read, - * but our socket would still be writeable and the remote endpoint could continue to receive our data. - * - * The argument for this confusing functionality stems from the idea that a client could shut down its - * write stream after sending a request to the server, thus notifying the server there are to be no further requests. - * In practice, however, this technique did little to help server developers. - * - * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close - * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell - * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work. - * Otherwise an error will be occur shortly (when the remote end sends us a RST packet). - * - * In addition to the technical challenges and confusion, many high level socket/stream API's provide - * no support for dealing with the problem. If the read stream is closed, the API immediately declares the - * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does. - * It might sound like poor design at first, but in fact it simplifies development. - * - * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket. - * Thus it actually makes sense to close the socket at this point. - * And in fact this is what most networking developers want and expect to happen. - * However, if you are writing a server that interacts with a plethora of clients, - * you might encounter a client that uses the discouraged technique of shutting down its write stream. - * If this is the case, you can set this property to NO, - * and make use of the socketDidCloseReadStream delegate method. - * - * The default value is YES. -**/ -@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream; - -/** - * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. - * In most cases, the instance creates this queue itself. - * However, to allow for maximum flexibility, the internal queue may be passed in the init method. - * This allows for some advanced options such as controlling socket priority via target queues. - * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. - * - * For example, imagine there are 2 queues: - * dispatch_queue_t socketQueue; - * dispatch_queue_t socketTargetQueue; - * - * If you do this (pseudo-code): - * socketQueue.targetQueue = socketTargetQueue; - * - * Then all socketQueue operations will actually get run on the given socketTargetQueue. - * This is fine and works great in most situations. - * But if you run code directly from within the socketTargetQueue that accesses the socket, - * you could potentially get deadlock. Imagine the following code: - * - * - (BOOL)socketHasSomething - * { - * __block BOOL result = NO; - * dispatch_block_t block = ^{ - * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; - * } - * if (is_executing_on_queue(socketQueue)) - * block(); - * else - * dispatch_sync(socketQueue, block); - * - * return result; - * } - * - * What happens if you call this method from the socketTargetQueue? The result is deadlock. - * This is because the GCD API offers no mechanism to discover a queue's targetQueue. - * Thus we have no idea if our socketQueue is configured with a targetQueue. - * If we had this information, we could easily avoid deadlock. - * But, since these API's are missing or unfeasible, you'll have to explicitly set it. - * - * IF you pass a socketQueue via the init method, - * AND you've configured the passed socketQueue with a targetQueue, - * THEN you should pass the end queue in the target hierarchy. - * - * For example, consider the following queue hierarchy: - * socketQueue -> ipQueue -> moduleQueue - * - * This example demonstrates priority shaping within some server. - * All incoming client connections from the same IP address are executed on the same target queue. - * And all connections for a particular module are executed on the same target queue. - * Thus, the priority of all networking for the entire module can be changed on the fly. - * Additionally, networking traffic from a single IP cannot monopolize the module. - * - * Here's how you would accomplish something like that: - * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock - * { - * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); - * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; - * - * dispatch_set_target_queue(socketQueue, ipQueue); - * dispatch_set_target_queue(iqQueue, moduleQueue); - * - * return socketQueue; - * } - * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket - * { - * [clientConnections addObject:newSocket]; - * [newSocket markSocketQueueTargetQueue:moduleQueue]; - * } - * - * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. - * This is often NOT the case, as such queues are used solely for execution shaping. -**/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; - -/** - * It's not thread-safe to access certain variables from outside the socket's internal queue. - * - * For example, the socket file descriptor. - * File descriptors are simply integers which reference an index in the per-process file table. - * However, when one requests a new file descriptor (by opening a file or socket), - * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. - * So if we're not careful, the following could be possible: - * - * - Thread A invokes a method which returns the socket's file descriptor. - * - The socket is closed via the socket's internal queue on thread B. - * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. - * - Thread A is now accessing/altering the file instead of the socket. - * - * In addition to this, other variables are not actually objects, - * and thus cannot be retained/released or even autoreleased. - * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. - * - * Although there are internal variables that make it difficult to maintain thread-safety, - * it is important to provide access to these variables - * to ensure this class can be used in a wide array of environments. - * This method helps to accomplish this by invoking the current block on the socket's internal queue. - * The methods below can be invoked from within the block to access - * those generally thread-unsafe internal variables in a thread-safe manner. - * The given block will be invoked synchronously on the socket's internal queue. - * - * If you save references to any protected variables and use them outside the block, you do so at your own peril. -**/ -- (void)performBlock:(dispatch_block_t)block; - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's file descriptor(s). - * If the socket is a server socket (is accepting incoming connections), - * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. -**/ -- (int)socketFD; -- (int)socket4FD; -- (int)socket6FD; - -#if TARGET_OS_IPHONE - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's internal CFReadStream/CFWriteStream. - * - * These streams are only used as workarounds for specific iOS shortcomings: - * - * - Apple has decided to keep the SecureTransport framework private is iOS. - * This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it. - * Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream, - * instead of the preferred and faster and more powerful SecureTransport. - * - * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded, - * Apple only bothers to notify us via the CFStream API. - * The faster and more powerful GCD API isn't notified properly in this case. - * - * See also: (BOOL)enableBackgroundingOnSocket -**/ -- (nullable CFReadStreamRef)readStream; -- (nullable CFWriteStreamRef)writeStream; - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Configures the socket to allow it to operate when the iOS application has been backgrounded. - * In other words, this method creates a read & write stream, and invokes: - * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * - * Returns YES if successful, NO otherwise. - * - * Note: Apple does not officially support backgrounding server sockets. - * That is, if your socket is accepting incoming connections, Apple does not officially support - * allowing iOS applications to accept incoming connections while an app is backgrounded. - * - * Example usage: - * - * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port - * { - * [asyncSocket performBlock:^{ - * [asyncSocket enableBackgroundingOnSocket]; - * }]; - * } -**/ -- (BOOL)enableBackgroundingOnSocket; - -#endif - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket. -**/ -- (nullable SSLContextRef)sslContext; - -#pragma mark Utilities - -/** - * The address lookup utility used by the class. - * This method is synchronous, so it's recommended you use it on a background thread/queue. - * - * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6. - * - * @returns - * A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo. - * The addresses are specifically for TCP connections. - * You can filter the addresses, if needed, using the other utility methods provided by the class. -**/ -+ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr; - -/** - * Extracting host and port information from raw address data. -**/ - -+ (nullable NSString *)hostFromAddress:(NSData *)address; -+ (uint16_t)portFromAddress:(NSData *)address; - -+ (BOOL)isIPv4Address:(NSData *)address; -+ (BOOL)isIPv6Address:(NSData *)address; - -+ (BOOL)getHost:( NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr fromAddress:(NSData *)address; - -+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address; - -/** - * A few common line separators, for use with the readDataToData:... methods. -**/ -+ (NSData *)CRLFData; // 0x0D0A -+ (NSData *)CRData; // 0x0D -+ (NSData *)LFData; // 0x0A -+ (NSData *)ZeroData; // 0x00 - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol GCDAsyncSocketDelegate -@optional - -/** - * This method is called immediately prior to socket:didAcceptNewSocket:. - * It optionally allows a listening socket to specify the socketQueue for a new accepted socket. - * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue. - * - * Since you cannot autorelease a dispatch_queue, - * this method uses the "new" prefix in its name to specify that the returned queue has been retained. - * - * Thus you could do something like this in the implementation: - * return dispatch_queue_create("MyQueue", NULL); - * - * If you are placing multiple sockets on the same queue, - * then care should be taken to increment the retain count each time this method is invoked. - * - * For example, your implementation might look something like this: - * dispatch_retain(myExistingQueue); - * return myExistingQueue; -**/ -- (nullable dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock; - -/** - * Called when a socket accepts a connection. - * Another socket is automatically spawned to handle it. - * - * You must retain the newSocket if you wish to handle the connection. - * Otherwise the newSocket instance will be released and the spawned connection will be closed. - * - * By default the new socket will have the same delegate and delegateQueue. - * You may, of course, change this at any time. -**/ -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket; - -/** - * Called when a socket connects and is ready for reading and writing. - * The host parameter will be an IP address, not a DNS name. -**/ -- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; - -/** - * Called when a socket connects and is ready for reading and writing. - * The host parameter will be an IP address, not a DNS name. - **/ -- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url; - -/** - * Called when a socket has completed reading the requested data into memory. - * Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; - -/** - * Called when a socket has read in data, but has not yet completed the read. - * This would occur if using readToData: or readToLength: methods. - * It may be used for things such as updating progress bars. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called when a socket has completed writing the requested data. Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag; - -/** - * Called when a socket has written some data, but has not yet completed the entire write. - * It may be used for things such as updating progress bars. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called if a read operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been read so far for the read operation. - * - * Note that this method may be called multiple times for a single read if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Called if a write operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been written so far for the write operation. - * - * Note that this method may be called multiple times for a single write if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Conditionally called if the read stream closes, but the write stream may still be writeable. - * - * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO. - * See the discussion on the autoDisconnectOnClosedReadStream method for more information. -**/ -- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock; - -/** - * Called when a socket disconnects with or without error. - * - * If you call the disconnect method, and the socket wasn't already disconnected, - * then an invocation of this delegate method will be enqueued on the delegateQueue - * before the disconnect method returns. - * - * Note: If the GCDAsyncSocket instance is deallocated while it is still connected, - * and the delegate is not also deallocated, then this method will be invoked, - * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.) - * This is a generally rare, but is possible if one writes code like this: - * - * asyncSocket = nil; // I'm implicitly disconnecting the socket - * - * In this case it may preferrable to nil the delegate beforehand, like this: - * - * asyncSocket.delegate = nil; // Don't invoke my delegate method - * asyncSocket = nil; // I'm implicitly disconnecting the socket - * - * Of course, this depends on how your state machine is configured. -**/ -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err; - -/** - * Called after the socket has successfully completed SSL/TLS negotiation. - * This method is not called unless you use the provided startTLS method. - * - * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, - * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code. -**/ -- (void)socketDidSecure:(GCDAsyncSocket *)sock; - -/** - * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to. - * - * This is only called if startTLS is invoked with options that include: - * - GCDAsyncSocketManuallyEvaluateTrust == YES - * - * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer. - * - * Note from Apple's documentation: - * Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain, - * [it] might block while attempting network access. You should never call it from your main thread; - * call it only from within a function running on a dispatch queue or on a separate thread. - * - * Thus this method uses a completionHandler block rather than a normal return value. - * The completionHandler block is thread-safe, and may be invoked from a background queue/thread. - * It is safe to invoke the completionHandler block even if the socket has been closed. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust - completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler; - -@end -NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m deleted file mode 100755 index a30d261b42..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +++ /dev/null @@ -1,8786 +0,0 @@ -// -// GCDAsyncSocket.m -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q4 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import "GCDAsyncSocket.h" - -#if TARGET_OS_IPHONE -#import -#import -// Note: CFStream APIs are still used for TLS support and are part of CoreFoundation -// CFStream SSL constants are needed only for the legacy CFStream TLS path (when GCDAsyncSocketUseCFStreamForTLS is set) -// The default path uses SecureTransport which doesn't require these constants -// Declare SSL constants as extern to avoid importing deprecated CFNetwork framework -// These symbols are linked at runtime from CFNetwork framework -extern const CFStringRef kCFStreamPropertySSLSettings; -extern const CFStringRef kCFStreamSSLPeerName; -extern const CFStringRef kCFStreamSSLCertificates; -extern const CFStringRef kCFStreamSSLIsServer; -#endif - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC -#endif - - -#ifndef GCDAsyncSocketLoggingEnabled -#define GCDAsyncSocketLoggingEnabled 0 -#endif - -#if GCDAsyncSocketLoggingEnabled - -// Logging Enabled - See log level below - -// Logging uses the CocoaLumberjack framework (which is also GCD based). -// https://github.com/robbiehanson/CocoaLumberjack -// -// It allows us to do a lot of logging without significantly slowing down the code. -#import "DDLog.h" - -#define LogAsync YES -#define LogContext GCDAsyncSocketLoggingContext - -#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) -#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) - -#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) - -#ifndef GCDAsyncSocketLogLevel -#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE -#endif - -// Log levels : off, error, warn, info, verbose -static const int logLevel = GCDAsyncSocketLogLevel; - -#else - -// Logging Disabled - -#define LogError(frmt, ...) do {} while (0) -#define LogWarn(frmt, ...) do {} while (0) -#define LogInfo(frmt, ...) do {} while (0) -#define LogVerbose(frmt, ...) do {} while (0) - -#define LogCError(frmt, ...) do {} while (0) -#define LogCWarn(frmt, ...) do {} while (0) -#define LogCInfo(frmt, ...) do {} while (0) -#define LogCVerbose(frmt, ...) do {} while (0) - -#define LogTrace() do {} while (0) -#define LogCTrace(frmt, ...) do {} while (0) - -#endif - -/** - * Seeing a return statements within an inner block - * can sometimes be mistaken for a return point of the enclosing method. - * This makes inline blocks a bit easier to read. - **/ -#define return_from_block return - -/** - * A socket file descriptor is really just an integer. - * It represents the index of the socket within the kernel. - * This makes invalid file descriptor comparisons easier to read. - **/ -#define SOCKET_NULL -1 - - -NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException"; -NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain"; - -NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket"; -NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream"; - -NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust"; -#if TARGET_OS_IPHONE -NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS"; -#endif -NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID"; -NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin"; -NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax"; -NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart"; -NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord"; -NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; -NSString *const GCDAsyncSocketSSLALPN = @"GCDAsyncSocketSSLALPN"; -#if !TARGET_OS_IPHONE -NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; -#endif - -enum GCDAsyncSocketFlags -{ - kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) - kConnected = 1 << 1, // If set, the socket is connected - kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed - kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout - kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout - kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued - kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued - kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown. - kReadSourceSuspended = 1 << 8, // If set, the read source is suspended - kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended - kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS - kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete - kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete - kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS - kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket - kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained - kDealloc = 1 << 16, // If set, the socket is being deallocated -#if TARGET_OS_IPHONE - kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread - kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport - kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available -#endif -}; - -enum GCDAsyncSocketConfig -{ - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4 - kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes -}; - -#if TARGET_OS_IPHONE -static NSThread *cfstreamThread; // Used for CFStreams - - -static uint64_t cfstreamThreadRetainCount; // setup & teardown -static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * A PreBuffer is used when there is more data available on the socket - * than is being requested by current read request. - * In this case we slurp up all data from the socket (to minimize sys calls), - * and store additional yet unread data in a "prebuffer". - * - * The prebuffer is entirely drained before we read from the socket again. - * In other words, a large chunk of data is written is written to the prebuffer. - * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)). - * - * A ring buffer was once used for this purpose. - * But a ring buffer takes up twice as much memory as needed (double the size for mirroring). - * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size. - * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed. - * - * The current design is very simple and straight-forward, while also keeping memory requirements lower. - **/ - -@interface GCDAsyncSocketPreBuffer : NSObject -{ - uint8_t *preBuffer; - size_t preBufferSize; - - uint8_t *readPointer; - uint8_t *writePointer; -} - -- (instancetype)initWithCapacity:(size_t)numBytes NS_DESIGNATED_INITIALIZER; - -- (void)ensureCapacityForWrite:(size_t)numBytes; - -- (size_t)availableBytes; -- (uint8_t *)readBuffer; - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr; - -- (size_t)availableSpace; -- (uint8_t *)writeBuffer; - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr; - -- (void)didRead:(size_t)bytesRead; -- (void)didWrite:(size_t)bytesWritten; - -- (void)reset; - -@end - -@implementation GCDAsyncSocketPreBuffer - -// Cover the superclass' designated initializer -- (instancetype)init NS_UNAVAILABLE -{ - NSAssert(0, @"Use the designated initializer"); - return nil; -} - -- (instancetype)initWithCapacity:(size_t)numBytes -{ - if ((self = [super init])) - { - preBufferSize = numBytes; - preBuffer = (uint8_t *)malloc(preBufferSize); - - readPointer = preBuffer; - writePointer = preBuffer; - } - return self; -} - -- (void)dealloc -{ - if (preBuffer) - free(preBuffer); -} - -- (void)ensureCapacityForWrite:(size_t)numBytes -{ - size_t availableSpace = [self availableSpace]; - - if (numBytes > availableSpace) - { - size_t additionalBytes = numBytes - availableSpace; - - size_t newPreBufferSize = preBufferSize + additionalBytes; - uint8_t *newPreBuffer = (uint8_t *)realloc(preBuffer, newPreBufferSize); - - size_t readPointerOffset = readPointer - preBuffer; - size_t writePointerOffset = writePointer - preBuffer; - - preBuffer = newPreBuffer; - preBufferSize = newPreBufferSize; - - readPointer = preBuffer + readPointerOffset; - writePointer = preBuffer + writePointerOffset; - } -} - -- (size_t)availableBytes -{ - return writePointer - readPointer; -} - -- (uint8_t *)readBuffer -{ - return readPointer; -} - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr -{ - if (bufferPtr) *bufferPtr = readPointer; - if (availableBytesPtr) *availableBytesPtr = [self availableBytes]; -} - -- (void)didRead:(size_t)bytesRead -{ - readPointer += bytesRead; - - if (readPointer == writePointer) - { - // The prebuffer has been drained. Reset pointers. - readPointer = preBuffer; - writePointer = preBuffer; - } -} - -- (size_t)availableSpace -{ - return preBufferSize - (writePointer - preBuffer); -} - -- (uint8_t *)writeBuffer -{ - return writePointer; -} - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr -{ - if (bufferPtr) *bufferPtr = writePointer; - if (availableSpacePtr) *availableSpacePtr = [self availableSpace]; -} - -- (void)didWrite:(size_t)bytesWritten -{ - writePointer += bytesWritten; -} - -- (void)reset -{ - readPointer = preBuffer; - writePointer = preBuffer; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncReadPacket encompasses the instructions for any given read. - * The content of a read packet allows the code to determine if we're: - * - reading to a certain length - * - reading to a certain separator - * - or simply reading the first chunk of available data - **/ -@interface GCDAsyncReadPacket : NSObject -{ -@public - NSMutableData *buffer; - NSUInteger startOffset; - NSUInteger bytesDone; - NSUInteger maxLength; - NSTimeInterval timeout; - NSUInteger readLength; - NSData *term; - BOOL bufferOwner; - NSUInteger originalBufferLength; - long tag; -} -- (instancetype)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i NS_DESIGNATED_INITIALIZER; - -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead; - -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr; - -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable; -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr; -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr; - -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes; - -@end - -@implementation GCDAsyncReadPacket - -// Cover the superclass' designated initializer -- (instancetype)init NS_UNAVAILABLE -{ - NSAssert(0, @"Use the designated initializer"); - return nil; -} - -- (instancetype)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i -{ - if((self = [super init])) - { - bytesDone = 0; - maxLength = m; - timeout = t; - readLength = l; - term = [e copy]; - tag = i; - - if (d) - { - buffer = d; - startOffset = s; - bufferOwner = NO; - originalBufferLength = [d length]; - } - else - { - if (readLength > 0) - buffer = [[NSMutableData alloc] initWithLength:readLength]; - else - buffer = [[NSMutableData alloc] initWithLength:0]; - - startOffset = 0; - bufferOwner = YES; - originalBufferLength = 0; - } - } - return self; -} - -/** - * Increases the length of the buffer (if needed) to ensure a read of the given size will fit. - **/ -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead -{ - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (bytesToRead > buffSpace) - { - NSUInteger buffInc = bytesToRead - buffSpace; - - [buffer increaseLengthBy:buffInc]; - } -} - -/** - * This method is used when we do NOT know how much data is available to be read from the socket. - * This method returns the default value unless it exceeds the specified readLength or maxLength. - * - * Furthermore, the shouldPreBuffer decision is based upon the packet type, - * and whether the returned value would fit in the current buffer without requiring a resize of the buffer. - **/ -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSUInteger result; - - if (readLength > 0) - { - // Read a specific length of data - result = readLength - bytesDone; - - // There is no need to prebuffer since we know exactly how much data we need to read. - // Even if the buffer isn't currently big enough to fit this amount of data, - // it would have to be resized eventually anyway. - - if (shouldPreBufferPtr) - *shouldPreBufferPtr = NO; - } - else - { - // Either reading until we find a specified terminator, - // or we're simply reading all available data. - // - // In other words, one of: - // - // - readDataToData packet - // - readDataWithTimeout packet - - if (maxLength > 0) - result = MIN(defaultValue, (maxLength - bytesDone)); - else - result = defaultValue; - - // Since we don't know the size of the read in advance, - // the shouldPreBuffer decision is based upon whether the returned value would fit - // in the current buffer without requiring a resize of the buffer. - // - // This is because, in all likelyhood, the amount read from the socket will be less than the default value. - // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (buffSpace >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - } - - return result; -} - -/** - * For read packets without a set terminator, returns the amount of data - * that can be read without exceeding the readLength or maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * The given hint MUST be greater than zero. - **/ -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable -{ - NSAssert(term == nil, @"This method does not apply to term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - if (readLength > 0) - { - // Read a specific length of data - - return MIN(bytesAvailable, (readLength - bytesDone)); - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read a certain length of data that exceeds the size of the buffer, - // then it is clear that our code will resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - } - else - { - // Read all available data - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read all available data without giving us a maxLength, - // then it is clear that our code might resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - - return result; - } -} - -/** - * For read packets with a set terminator, returns the amount of data - * that can be read without exceeding the maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * To optimize memory allocations, mem copies, and mem moves - * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first, - * or if the data can be read directly into the read packet's buffer. - **/ -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // Should the data be read into the read packet's buffer, or into a pre-buffer first? - // - // One would imagine the preferred option is the faster one. - // So which one is faster? - // - // Reading directly into the packet's buffer requires: - // 1. Possibly resizing packet buffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy) - // - // Reading into prebuffer first: - // 1. Possibly resizing prebuffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Copying underflow into packet buffer (malloc/realloc, memcpy) - // 5. Removing underflow from prebuffer (memmove) - // - // Comparing the performance of the two we can see that reading - // data into the prebuffer first is slower due to the extra memove. - // - // However: - // The implementation of NSMutableData is open source via core foundation's CFMutableData. - // Decreasing the length of a mutable data object doesn't cause a realloc. - // In other words, the capacity of a mutable data object can grow, but doesn't shrink. - // - // This means the prebuffer will rarely need a realloc. - // The packet buffer, on the other hand, may often need a realloc. - // This is especially true if we are the buffer owner. - // Furthermore, if we are constantly realloc'ing the packet buffer, - // and then moving the overflow into the prebuffer, - // then we're consistently over-allocating memory for each term read. - // And now we get into a bit of a tradeoff between speed and memory utilization. - // - // The end result is that the two perform very similarly. - // And we can answer the original question very simply by another means. - // - // If we can read all the data directly into the packet's buffer without resizing it first, - // then we do so. Otherwise we use the prebuffer. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - if ((buffSize - buffUsed) >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - - return result; -} - -/** - * For read packets with a set terminator, - * returns the amount of data that can be read from the given preBuffer, - * without going over a terminator or the maxLength. - * - * It is assumed the terminator has not already been read. - **/ -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!"); - - // We know that the terminator, as a whole, doesn't exist in our own buffer. - // But it is possible that a _portion_ of it exists in our buffer. - // So we're going to look for the terminator starting with a portion of our own buffer. - // - // Example: - // - // term length = 3 bytes - // bytesDone = 5 bytes - // preBuffer length = 5 bytes - // - // If we append the preBuffer to our buffer, - // it would look like this: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // --------------------- - // - // So we start our search here: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // -------^-^-^--------- - // - // And move forwards... - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------^-^-^------- - // - // Until we find the terminator or reach the end. - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------------^-^-^- - - BOOL found = NO; - - NSUInteger termLength = [term length]; - NSUInteger preBufferLength = [preBuffer availableBytes]; - - if ((bytesDone + preBufferLength) < termLength) - { - // Not enough data for a full term sequence yet - return preBufferLength; - } - - NSUInteger maxPreBufferLength; - if (maxLength > 0) { - maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); - - // Note: maxLength >= termLength - } - else { - maxPreBufferLength = preBufferLength; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wvla" - uint8_t seq[termLength]; -#pragma clang diagnostic pop - const void *termBuf = [term bytes]; - - NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); - uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen; - - NSUInteger preLen = termLength - bufLen; - const uint8_t *pre = [preBuffer readBuffer]; - - NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. - - NSUInteger result = maxPreBufferLength; - - NSUInteger i; - for (i = 0; i < loopCount; i++) - { - if (bufLen > 0) - { - // Combining bytes from buffer and preBuffer - - memcpy(seq, buf, bufLen); - memcpy(seq + bufLen, pre, preLen); - - if (memcmp(seq, termBuf, termLength) == 0) - { - result = preLen; - found = YES; - break; - } - - buf++; - bufLen--; - preLen++; - } - else - { - // Comparing directly from preBuffer - - if (memcmp(pre, termBuf, termLength) == 0) - { - NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic - - result = preOffset + termLength; - found = YES; - break; - } - - pre++; - } - } - - // There is no need to avoid resizing the buffer in this particular situation. - - if (foundPtr) *foundPtr = found; - return result; -} - -/** - * For read packets with a set terminator, scans the packet buffer for the term. - * It is assumed the terminator had not been fully read prior to the new bytes. - * - * If the term is found, the number of excess bytes after the term are returned. - * If the term is not found, this method will return -1. - * - * Note: A return value of zero means the term was found at the very end. - * - * Prerequisites: - * The given number of bytes have been added to the end of our buffer. - * Our bytesDone variable has NOT been changed due to the prebuffered bytes. - **/ -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - - // The implementation of this method is very similar to the above method. - // See the above method for a discussion of the algorithm used here. - - uint8_t *buff = (uint8_t *)[buffer mutableBytes]; - NSUInteger buffLength = bytesDone + numBytes; - - const void *termBuff = [term bytes]; - NSUInteger termLength = [term length]; - - // Note: We are dealing with unsigned integers, - // so make sure the math doesn't go below zero. - - NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0; - - while (i + termLength <= buffLength) - { - uint8_t *subBuffer = buff + startOffset + i; - - if (memcmp(subBuffer, termBuff, termLength) == 0) - { - return buffLength - (i + termLength); - } - - i++; - } - - return -1; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncWritePacket encompasses the instructions for any given write. - **/ -@interface GCDAsyncWritePacket : NSObject -{ -@public - NSData *buffer; - NSUInteger bytesDone; - long tag; - NSTimeInterval timeout; -} -- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; -@end - -@implementation GCDAsyncWritePacket - -// Cover the superclass' designated initializer -- (instancetype)init NS_UNAVAILABLE -{ - NSAssert(0, @"Use the designated initializer"); - return nil; -} - -- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i -{ - if((self = [super init])) - { - buffer = d; // Retain not copy. For performance as documented in header file. - bytesDone = 0; - timeout = t; - tag = i; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. - * This class my be altered to support more than just TLS in the future. - **/ -@interface GCDAsyncSpecialPacket : NSObject -{ -@public - NSDictionary *tlsSettings; -} -- (instancetype)initWithTLSSettings:(NSDictionary *)settings NS_DESIGNATED_INITIALIZER; -@end - -@implementation GCDAsyncSpecialPacket - -// Cover the superclass' designated initializer -- (instancetype)init NS_UNAVAILABLE -{ - NSAssert(0, @"Use the designated initializer"); - return nil; -} - -- (instancetype)initWithTLSSettings:(NSDictionary *)settings -{ - if((self = [super init])) - { - tlsSettings = [settings copy]; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation GCDAsyncSocket -{ - uint32_t flags; - uint16_t config; - - __weak id delegate; - dispatch_queue_t delegateQueue; - - int socket4FD; - int socket6FD; - int socketUN; - NSURL *socketUrl; - int stateIndex; - NSData * connectInterface4; - NSData * connectInterface6; - NSData * connectInterfaceUN; - - dispatch_queue_t socketQueue; - - dispatch_source_t accept4Source; - dispatch_source_t accept6Source; - dispatch_source_t acceptUNSource; - dispatch_source_t connectTimer; - dispatch_source_t readSource; - dispatch_source_t writeSource; - dispatch_source_t readTimer; - dispatch_source_t writeTimer; - - NSMutableArray *readQueue; - NSMutableArray *writeQueue; - - GCDAsyncReadPacket *currentRead; - GCDAsyncWritePacket *currentWrite; - - unsigned long socketFDBytesAvailable; - - GCDAsyncSocketPreBuffer *preBuffer; - -#if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream; - CFWriteStreamRef writeStream; -#endif - SSLContextRef sslContext; - GCDAsyncSocketPreBuffer *sslPreBuffer; - size_t sslWriteCachedLength; - OSStatus sslErrCode; - OSStatus lastSSLHandshakeError; - - void *IsOnSocketQueueOrTargetQueueKey; - - id userData; - NSTimeInterval alternateAddressDelay; -} - -- (instancetype)init -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; -} - -- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; -} - -- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq -{ - return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; -} - -- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq -{ - if((self = [super init])) - { - delegate = aDelegate; - delegateQueue = dq; - -#if !OS_OBJECT_USE_OBJC - if (dq) dispatch_retain(dq); -#endif - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - socketUN = SOCKET_NULL; - socketUrl = nil; - stateIndex = 0; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(sq); -#endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], - NULL); - } - - // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. - // From the documentation: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // - // We're just going to use the memory address of an ivar. - // Specifically an ivar that is explicitly named for our purpose to make the code more readable. - // - // However, it feels tedious (and less readable) to include the "&" all the time: - // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) - // - // So we're going to make it so it doesn't matter if we use the '&' or not, - // by assigning the value of the ivar to the address of the ivar. - // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; - - IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketQueue, - IsOnSocketQueueOrTargetQueueKey, - nonNullUnusedPointer, - NULL); - - readQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentRead = nil; - - writeQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentWrite = nil; - - preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - alternateAddressDelay = 0.3; - } - return self; -} - -- (void)dealloc -{ - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); - - // Set dealloc flag. - // This is used by closeWithError to ensure we don't accidentally retain ourself. - flags |= kDealloc; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - -#if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); -#endif - delegateQueue = NULL; - -#if !OS_OBJECT_USE_OBJC - if (socketQueue) dispatch_release(socketQueue); -#endif - socketQueue = NULL; - - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); -} - -#pragma mark - - -+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error { - return [self socketFromConnectedSocketFD:socketFD delegate:nil delegateQueue:NULL socketQueue:sq error:error]; -} - -+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error { - return [self socketFromConnectedSocketFD:socketFD delegate:aDelegate delegateQueue:dq socketQueue:NULL error:error]; -} - -+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError* __autoreleasing *)error -{ - GCDAsyncSocket *socket = [[[self class] alloc] initWithDelegate:aDelegate delegateQueue:dq socketQueue:sq]; - - __block NSError *innerError = nil; - dispatch_sync(socket->socketQueue, - ^{ @autoreleasepool { - struct sockaddr addr; - socklen_t addr_size = sizeof(struct sockaddr); - int retVal = getpeername(socketFD, (struct sockaddr *)&addr, &addr_size); - if (retVal) - { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError", - @"GCDAsyncSocket", - [NSBundle mainBundle], - @"Attempt to create socket from socket FD failed. getpeername() failed", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - innerError = [NSError errorWithDomain:GCDAsyncSocketErrorDomain - code:GCDAsyncSocketOtherError - userInfo:userInfo]; - return; - } - - if (addr.sa_family == AF_INET) - { - socket->socket4FD = socketFD; - } - else if (addr.sa_family == AF_INET6) - { - socket->socket6FD = socketFD; - } - else - { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError", - @"GCDAsyncSocket", - [NSBundle mainBundle], - @"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - innerError = [NSError errorWithDomain:GCDAsyncSocketErrorDomain - code:GCDAsyncSocketOtherError - userInfo:userInfo]; - return; - } - - socket->flags = kSocketStarted; - [socket didConnect:socket->stateIndex]; - }}); - - if (nil != innerError && nil != error) { - *error = innerError; - } - return nil == innerError ? socket : nil; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (id)delegate -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegate; - } - else - { - __block id result; - - dispatch_sync(socketQueue, ^{ - result = self->delegate; - }); - - return result; - } -} - -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - self->delegate = newDelegate; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:YES]; -} - -- (dispatch_queue_t)delegateQueue -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result; - - dispatch_sync(socketQueue, ^{ - result = self->delegateQueue; - }); - - return result; - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - -#if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); -#endif - - self->delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:YES]; -} - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; - - dispatch_sync(socketQueue, ^{ - dPtr = self->delegate; - dqPtr = self->delegateQueue; - }); - - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - self->delegate = newDelegate; - -#if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); -#endif - - self->delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; -} - -- (BOOL)isIPv4Enabled -{ - // Note: YES means kIPv4Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv4Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((self->config & kIPv4Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv4Enabled:(BOOL)flag -{ - // Note: YES means kIPv4Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - self->config &= ~kIPv4Disabled; - else - self->config |= kIPv4Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv6Enabled -{ - // Note: YES means kIPv6Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv6Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((self->config & kIPv6Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv6Enabled:(BOOL)flag -{ - // Note: YES means kIPv6Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - self->config &= ~kIPv6Disabled; - else - self->config |= kIPv6Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv4PreferredOverIPv6 -{ - // Note: YES means kPreferIPv6 is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kPreferIPv6) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((self->config & kPreferIPv6) == 0); - }); - - return result; - } -} - -- (void)setIPv4PreferredOverIPv6:(BOOL)flag -{ - // Note: YES means kPreferIPv6 is OFF - - dispatch_block_t block = ^{ - - if (flag) - self->config &= ~kPreferIPv6; - else - self->config |= kPreferIPv6; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (NSTimeInterval) alternateAddressDelay { - __block NSTimeInterval delay; - dispatch_block_t block = ^{ - delay = self->alternateAddressDelay; - }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - return delay; -} - -- (void) setAlternateAddressDelay:(NSTimeInterval)delay { - dispatch_block_t block = ^{ - self->alternateAddressDelay = delay; - }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (id)userData -{ - __block id result = nil; - - dispatch_block_t block = ^{ - - result = self->userData; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setUserData:(id)arbitraryUserData -{ - dispatch_block_t block = ^{ - - if (self->userData != arbitraryUserData) - { - self->userData = arbitraryUserData; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Accepting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self acceptOnInterface:nil port:port error:errPtr]; -} - -- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr -{ - LogTrace(); - - // Just in-case interface parameter is immutable. - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - // CreateSocket Block - // This block will be invoked within the dispatch block below. - - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - - int socketFD = socket(domain, SOCK_STREAM, 0); - - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self errorWithErrno:errno reason:reason]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - int reuseOn = 1; - status = setsockopt(socketFD, - SOL_SOCKET, - SO_REUSEADDR, - &reuseOn, - sizeof(reuseOn)); - if (status == -1) - { - NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Bind socket - - status = bind(socketFD, - (const struct sockaddr *)[interfaceAddr bytes], - (socklen_t)[interfaceAddr length]); - if (status == -1) - { - NSString *reason = @"Error in bind() function"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Listen - - status = listen(socketFD, 1024); - if (status == -1) - { - NSString *reason = @"Error in listen() function"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - return socketFD; - }; - - // Create dispatch block and run on socketQueue - - dispatch_block_t block = ^{ @autoreleasepool { - - if (self->delegate == nil) // Must have delegate set - { - NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (self->delegateQueue == NULL) // Must have delegate queue set - { - NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (![self isDisconnected]) // Must be disconnected - { - NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - // Clear queues (spurious read/write requests post disconnect) - [self->readQueue removeAllObjects]; - [self->writeQueue removeAllObjects]; - - // Resolve interface from description - - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port]; - - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil); - - // Create sockets, configure, bind, and listen - - if (enableIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - self->socket4FD = createSocket(AF_INET, interface4); - - if (self->socket4FD == SOCKET_NULL) - { - return_from_block; - } - } - - if (enableIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - if (enableIPv4 && (port == 0)) - { - // No specific port was specified, so we allowed the OS to pick an available port for us. - // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket. - - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes]; - addr6->sin6_port = htons([self localPort4]); - } - - self->socket6FD = createSocket(AF_INET6, interface6); - - if (self->socket6FD == SOCKET_NULL) - { - if (self->socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(self->socket4FD); - self->socket4FD = SOCKET_NULL; - } - - return_from_block; - } - } - - // Create accept sources - - if (enableIPv4) - { - self->accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, - self->socket4FD, - 0, - self->socketQueue); - - int socketFD = self->socket4FD; - dispatch_source_t acceptSource = self->accept4Source; - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(self->accept4Source, - ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"event4Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - -#pragma clang diagnostic pop - }}); - - - dispatch_source_set_cancel_handler(self->accept4Source, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); -#endif - - LogVerbose(@"close(socket4FD)"); - close(socketFD); - -#pragma clang diagnostic pop - }); - - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(self->accept4Source); - } - - if (enableIPv6) - { - self->accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, - self->socket6FD, - 0, - self->socketQueue); - - int socketFD = self->socket6FD; - dispatch_source_t acceptSource = self->accept6Source; - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(self->accept6Source, - ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"event6Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - -#pragma clang diagnostic pop - }}); - - dispatch_source_set_cancel_handler(self->accept6Source, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept6Source)"); - dispatch_release(acceptSource); -#endif - - LogVerbose(@"close(socket6FD)"); - close(socketFD); - -#pragma clang diagnostic pop - }); - - LogVerbose(@"dispatch_resume(accept6Source)"); - dispatch_resume(self->accept6Source); - } - - self->flags |= kSocketStarted; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - LogInfo(@"Error in accept: %@", err); - - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - // CreateSocket Block - // This block will be invoked within the dispatch block below. - - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - - int socketFD = socket(domain, SOCK_STREAM, 0); - - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self errorWithErrno:errno reason:reason]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - int reuseOn = 1; - status = setsockopt(socketFD, - SOL_SOCKET, - SO_REUSEADDR, - &reuseOn, - sizeof(reuseOn)); - if (status == -1) - { - NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Bind socket - - status = bind(socketFD, - (const struct sockaddr *)[interfaceAddr bytes], - (socklen_t)[interfaceAddr length]); - if (status == -1) - { - NSString *reason = @"Error in bind() function"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Listen - - status = listen(socketFD, 1024); - if (status == -1) - { - NSString *reason = @"Error in listen() function"; - err = [self errorWithErrno:errno reason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - return socketFD; - }; - - // Create dispatch block and run on socketQueue - - dispatch_block_t block = ^{ @autoreleasepool { - - if (self->delegate == nil) // Must have delegate set - { - NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (self->delegateQueue == NULL) // Must have delegate queue set - { - NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (![self isDisconnected]) // Must be disconnected - { - NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - // Clear queues (spurious read/write requests post disconnect) - [self->readQueue removeAllObjects]; - [self->writeQueue removeAllObjects]; - - // Remove a previous socket - - NSError *error = nil; - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *urlPath = url.path; - if (urlPath && [fileManager fileExistsAtPath:urlPath]) { - if (![fileManager removeItemAtURL:url error:&error]) { - NSString *msg = @"Could not remove previous unix domain socket at given url."; - err = [self otherError:msg]; - - return_from_block; - } - } - - // Resolve interface from description - - NSData *interface = [self getInterfaceAddressFromUrl:url]; - - if (interface == nil) - { - NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")"; - err = [self badParamError:msg]; - - return_from_block; - } - - // Create sockets, configure, bind, and listen - - LogVerbose(@"Creating unix domain socket"); - self->socketUN = createSocket(AF_UNIX, interface); - - if (self->socketUN == SOCKET_NULL) - { - return_from_block; - } - - self->socketUrl = url; - - // Create accept sources - - self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, - self->socketUN, - 0, - self->socketQueue); - - int socketFD = self->socketUN; - dispatch_source_t acceptSource = self->acceptUNSource; - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(self->acceptUNSource, - ^{ @autoreleasepool { - - __strong GCDAsyncSocket *strongSelf = weakSelf; - - LogVerbose(@"eventUNBlock"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - }}); - - dispatch_source_set_cancel_handler(self->acceptUNSource, ^{ - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(acceptUNSource)"); - dispatch_release(acceptSource); -#endif - - LogVerbose(@"close(socketUN)"); - close(socketFD); - }); - - LogVerbose(@"dispatch_resume(acceptUNSource)"); - dispatch_resume(self->acceptUNSource); - - self->flags |= kSocketStarted; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - LogInfo(@"Error in accept: %@", err); - - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)doAccept:(int)parentSocketFD -{ - LogTrace(); - - int socketType; - int childSocketFD; - NSData *childSocketAddress; - - if (parentSocketFD == socket4FD) - { - socketType = 0; - - struct sockaddr_in addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - else if (parentSocketFD == socket6FD) - { - socketType = 1; - - struct sockaddr_in6 addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - else // if (parentSocketFD == socketUN) - { - socketType = 2; - - struct sockaddr_un addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - - // Enable non-blocking IO on the socket - - int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); - LogVerbose(@"close(childSocketFD)"); - close(childSocketFD); - return NO; - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(childSocketFD, - SOL_SOCKET, - SO_NOSIGPIPE, - &nosigpipe, - sizeof(nosigpipe)); - - // Notify delegate - - if (delegateQueue) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, - ^{ @autoreleasepool { - - // Query delegate for custom socket queue - - dispatch_queue_t childSocketQueue = NULL; - - if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)]) - { - childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress - onSocket:self]; - } - - // Create GCDAsyncSocket instance for accepted socket - - GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate - delegateQueue:self->delegateQueue - socketQueue:childSocketQueue]; - - if (socketType == 0) - acceptedSocket->socket4FD = childSocketFD; - else if (socketType == 1) - acceptedSocket->socket6FD = childSocketFD; - else - acceptedSocket->socketUN = childSocketFD; - - acceptedSocket->flags = (kSocketStarted | kConnected); - - // Setup read and write sources for accepted socket - - dispatch_async(acceptedSocket->socketQueue, - ^{ @autoreleasepool { - - [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD]; - }}); - - // Notify delegate - - if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) - { - [theDelegate socket:self didAcceptNewSocket:acceptedSocket]; - } - - // Release the socket queue returned from the delegate (it was retained by acceptedSocket) -#if !OS_OBJECT_USE_OBJC - if (childSocketQueue) dispatch_release(childSocketQueue); -#endif - - // The accepted socket should have been retained by the delegate. - // Otherwise it gets properly released when exiting the block. - }}); - } - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Connecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a connection attempt. - * It is shared between the connectToHost and connectToAddress methods. - * - **/ -- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (interface) - { - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0]; - - if ((interface4 == nil) && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterface4 = interface4; - connectInterface6 = interface6; - } - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} - -- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - NSData *interface = [self getInterfaceAddressFromUrl:url]; - - if (interface == nil) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterfaceUN = interface; - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} - -- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; -} - -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToHost:(NSString *)inHost - onPort:(uint16_t)port - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSString *host = [inHost copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *preConnectErr = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with host parameter - - if ([host length] == 0) - { - NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; - preConnectErr = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&preConnectErr]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - self->flags |= kSocketStarted; - - LogVerbose(@"Dispatching DNS lookup..."); - - // It's possible that the given host parameter is actually a NSMutableString. - // So we want to copy it now, within this block that will be executed synchronously. - // This way the asynchronous lookup block below doesn't have to worry about it changing. - - NSString *hostCpy = [host copy]; - - int aStateIndex = self->stateIndex; - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0); - dispatch_async(globalConcurrentQueue, - ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - NSError *lookupErr = nil; - NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr]; - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - if (lookupErr) - { - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf lookup:aStateIndex didFail:lookupErr]; - }}); - } - else - { - NSData *address4 = nil; - NSData *address6 = nil; - - for (NSData *address in addresses) - { - if (!address4 && [[self class] isIPv4Address:address]) - { - address4 = address; - } - else if (!address6 && [[self class] isIPv6Address:address]) - { - address6 = address; - } - } - - dispatch_async(strongSelf->socketQueue, - ^{ @autoreleasepool { - - [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6]; - }}); - } - -#pragma clang diagnostic pop - }}); - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - - if (errPtr) *errPtr = preConnectErr; - return result; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)inRemoteAddr - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSData *remoteAddr = [inRemoteAddr copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with remoteAddr parameter - - NSData *address4 = nil; - NSData *address6 = nil; - - if ([remoteAddr length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes]; - - if (sockaddr->sa_family == AF_INET) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in)) - { - address4 = remoteAddr; - } - } - else if (sockaddr->sa_family == AF_INET6) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in6)) - { - address6 = remoteAddr; - } - } - } - - if ((address4 == nil) && (address6 == nil)) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address4 != nil)) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (address6 != nil)) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - return_from_block; - } - - self->flags |= kSocketStarted; - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with host parameter - - if ([url.path length] == 0) - { - NSString *msg = @"Invalid unix domain socket url."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithUrl:url error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - self->flags |= kSocketStarted; - - // Start the normal connection process - - NSError *connectError = nil; - if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError]) - { - [self closeWithError:connectError]; - - return_from_block; - } - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert(address4 || address6, @"Expected at least one valid address"); - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - // Check for problems - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - if (isIPv6Disabled && (address4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - // Start the normal connection process - - NSError *err = nil; - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - [self closeWithError:err]; - } -} - -/** - * This method is called if the DNS lookup fails. - * This method is executed on the socketQueue. - * - * Since the DNS lookup executed synchronously on a global concurrent queue, - * the original connection request may have already been cancelled or timed-out by the time this method is invoked. - * The lookupIndex tells us whether the lookup is still valid or not. - **/ -- (void)lookup:(int)aStateIndex didFail:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring lookup:didFail: - already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self endConnectTimeout]; - [self closeWithError:error]; -} - -- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr -{ - // Bind the socket to the desired interface (if needed) - - if (connectInterface) - { - LogVerbose(@"Binding socket..."); - - if ([[self class] portFromAddress:connectInterface] > 0) - { - // Since we're going to be binding to a specific port, - // we should turn on reuseaddr to allow us to override sockets in time_wait. - - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - } - - const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; - - int result = bind(socketFD, - interfaceAddr, - (socklen_t)[connectInterface length]); - if (result != 0) - { - if (errPtr) - *errPtr = [self errorWithErrno:errno reason:@"Error in bind() function"]; - - return NO; - } - } - - return YES; -} - -- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr -{ - int socketFD = socket(family, SOCK_STREAM, 0); - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; - - return socketFD; - } - - if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr]) - { - [self closeSocket:socketFD]; - - return SOCKET_NULL; - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - return socketFD; -} - -- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex -{ - // If there already is a socket connected, we close socketFD and return - if (self.isConnected) - { - [self closeSocket:socketFD]; - return; - } - - // Start the connection process in a background queue - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0); - dispatch_async(globalConcurrentQueue, - ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - int result = connect(socketFD, - (const struct sockaddr *)[address bytes], - (socklen_t)[address length]); - int err = errno; - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - dispatch_async(strongSelf->socketQueue, - ^{ @autoreleasepool { - - if (strongSelf.isConnected) - { - [strongSelf closeSocket:socketFD]; - return_from_block; - } - - if (result == 0) - { - [self closeUnusedSocket:socketFD]; - - [strongSelf didConnect:aStateIndex]; - } - else - { - [strongSelf closeSocket:socketFD]; - - // If there are no more sockets trying to connect, we inform the error to the delegate - if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL) - { - NSError *error = [strongSelf errorWithErrno:err reason:@"Error in connect() function"]; - [strongSelf didNotConnect:aStateIndex error:error]; - } - } - }}); - -#pragma clang diagnostic pop - }); - - LogVerbose(@"Connecting..."); -} - -- (void)closeSocket:(int)socketFD -{ - if (socketFD != SOCKET_NULL && - (socketFD == socket6FD || socketFD == socket4FD)) - { - close(socketFD); - - if (socketFD == socket4FD) - { - LogVerbose(@"close(socket4FD)"); - socket4FD = SOCKET_NULL; - } - else if (socketFD == socket6FD) - { - LogVerbose(@"close(socket6FD)"); - socket6FD = SOCKET_NULL; - } - } -} - -- (void)closeUnusedSocket:(int)usedSocketFD -{ - if (usedSocketFD != socket4FD) - { - [self closeSocket:socket4FD]; - } - else if (usedSocketFD != socket6FD) - { - [self closeSocket:socket6FD]; - } -} - -- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - LogVerbose(@"IPv4: %@:%hu", - [[self class] hostFromAddress:address4], - [[self class] portFromAddress:address4]); - LogVerbose(@"IPv6: %@:%hu", - [[self class] hostFromAddress:address6], - [[self class] portFromAddress:address6]); - - // Determine socket type - - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - - // Create and bind the sockets - - if (address4) - { - LogVerbose(@"Creating IPv4 socket"); - - socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr]; - } - - if (address6) - { - LogVerbose(@"Creating IPv6 socket"); - - socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr]; - } - - if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) - { - return NO; - } - - int socketFD, alternateSocketFD; - NSData *address, *alternateAddress; - - if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL) - { - socketFD = socket6FD; - alternateSocketFD = socket4FD; - address = address6; - alternateAddress = address4; - } - else - { - socketFD = socket4FD; - alternateSocketFD = socket6FD; - address = address4; - alternateAddress = address6; - } - - int aStateIndex = stateIndex; - - [self connectSocket:socketFD address:address stateIndex:aStateIndex]; - - if (alternateAddress) - { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), - socketQueue, - ^{ - [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex]; - }); - } - - return YES; -} - -- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - // Create the socket - - int socketFD; - - LogVerbose(@"Creating unix domain socket"); - - socketUN = socket(AF_UNIX, SOCK_STREAM, 0); - - socketFD = socketUN; - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; - - return NO; - } - - // Bind the socket to the desired interface (if needed) - - LogVerbose(@"Binding socket..."); - - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - - // const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes]; - // - // int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]); - // if (result != 0) - // { - // if (errPtr) - // *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; - // - // return NO; - // } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Start the connection process in a background queue - - int aStateIndex = stateIndex; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0); - dispatch_async(globalConcurrentQueue, - ^{ - - const struct sockaddr *addr = (const struct sockaddr *)[address bytes]; - int result = connect(socketFD, addr, addr->sa_len); - if (result == 0) - { - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - [self didConnect:aStateIndex]; - }}); - } - else - { - // TODO: Bad file descriptor - perror("connect"); - NSError *error = [self errorWithErrno:errno reason:@"Error in connect() function"]; - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - [self didNotConnect:aStateIndex error:error]; - }}); - } - }); - - LogVerbose(@"Connecting..."); - - return YES; -} - -- (void)didConnect:(int)aStateIndex -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring didConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - flags |= kConnected; - - [self endConnectTimeout]; - -#if TARGET_OS_IPHONE - // The endConnectTimeout method executed above incremented the stateIndex. - aStateIndex = stateIndex; -#endif - - // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) - // - // Note: - // There may be configuration options that must be set by the delegate before opening the streams. - // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. - // - // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. - // This gives the delegate time to properly configure the streams if needed. - - dispatch_block_t SetupStreamsPart1 = ^{ -#if TARGET_OS_IPHONE - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:NO]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - -#endif - }; - dispatch_block_t SetupStreamsPart2 = ^{ -#if TARGET_OS_IPHONE - - if (aStateIndex != self->stateIndex) - { - // The socket has been disconnected. - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - -#endif - }; - - // Notify delegate - - NSString *host = [self connectedHost]; - uint16_t port = [self connectedPort]; - NSURL *url = [self connectedUrl]; - - __strong id theDelegate = delegate; - - if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) - { - SetupStreamsPart1(); - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didConnectToHost:host port:port]; - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - SetupStreamsPart2(); - }}); - }}); - } - else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)]) - { - SetupStreamsPart1(); - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didConnectToUrl:url]; - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - SetupStreamsPart2(); - }}); - }}); - } - else - { - SetupStreamsPart1(); - SetupStreamsPart2(); - } - - // Get the connected socket - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - // Enable non-blocking IO on the socket - - int result = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)"; - [self closeWithError:[self otherError:errMsg]]; - - return; - } - - // Setup our read/write sources - - [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD]; - - // Dequeue any pending read/write requests - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; -} - -- (void)didNotConnect:(int)aStateIndex error:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring didNotConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self closeWithError:error]; -} - -- (void)startConnectTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, - 0, - 0, - socketQueue); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doConnectTimeout]; - -#pragma clang diagnostic pop - }}); - -#if !OS_OBJECT_USE_OBJC - dispatch_source_t theConnectTimer = connectTimer; - dispatch_source_set_cancel_handler(connectTimer, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(connectTimer)"); - dispatch_release(theConnectTimer); - -#pragma clang diagnostic pop - }); -#endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(timeout * NSEC_PER_SEC)); - dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); - - dispatch_resume(connectTimer); - } -} - -- (void)endConnectTimeout -{ - LogTrace(); - - if (connectTimer) - { - dispatch_source_cancel(connectTimer); - connectTimer = NULL; - } - - // Increment stateIndex. - // This will prevent us from processing results from any related background asynchronous operations. - // - // Note: This should be called from close method even if connectTimer is NULL. - // This is because one might disconnect a socket prior to a successful connection which had no timeout. - - stateIndex++; - - if (connectInterface4) - { - connectInterface4 = nil; - } - if (connectInterface6) - { - connectInterface6 = nil; - } -} - -- (void)doConnectTimeout -{ - LogTrace(); - - [self endConnectTimeout]; - [self closeWithError:[self connectTimeoutError]]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Disconnecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)closeWithError:(NSError *)error -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - [self endConnectTimeout]; - - if (currentRead != nil) [self endCurrentRead]; - if (currentWrite != nil) [self endCurrentWrite]; - - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - [preBuffer reset]; - -#if TARGET_OS_IPHONE - { - if (readStream || writeStream) - { - [self removeStreamsFromRunLoop]; - - if (readStream) - { - CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - } - } -#endif - - [sslPreBuffer reset]; - sslErrCode = lastSSLHandshakeError = noErr; - - if (sslContext) - { - // Getting a linker error here about the SSLx() functions? - // You need to add the Security Framework to your application. - - SSLClose(sslContext); - -#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - CFRelease(sslContext); -#else - SSLDisposeContext(sslContext); -#endif - - sslContext = NULL; - } - - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - - if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource) - { - LogVerbose(@"manually closing close"); - - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"close(socket6FD)"); - close(socket6FD); - socket6FD = SOCKET_NULL; - } - - if (socketUN != SOCKET_NULL) - { - LogVerbose(@"close(socketUN)"); - close(socketUN); - socketUN = SOCKET_NULL; - unlink(socketUrl.path.fileSystemRepresentation); - socketUrl = nil; - } - } - else - { - if (accept4Source) - { - LogVerbose(@"dispatch_source_cancel(accept4Source)"); - dispatch_source_cancel(accept4Source); - - // We never suspend accept4Source - - accept4Source = NULL; - } - - if (accept6Source) - { - LogVerbose(@"dispatch_source_cancel(accept6Source)"); - dispatch_source_cancel(accept6Source); - - // We never suspend accept6Source - - accept6Source = NULL; - } - - if (acceptUNSource) - { - LogVerbose(@"dispatch_source_cancel(acceptUNSource)"); - dispatch_source_cancel(acceptUNSource); - - // We never suspend acceptUNSource - - acceptUNSource = NULL; - } - - if (readSource) - { - LogVerbose(@"dispatch_source_cancel(readSource)"); - dispatch_source_cancel(readSource); - - [self resumeReadSource]; - - readSource = NULL; - } - - if (writeSource) - { - LogVerbose(@"dispatch_source_cancel(writeSource)"); - dispatch_source_cancel(writeSource); - - [self resumeWriteSource]; - - writeSource = NULL; - } - - // The sockets will be closed by the cancel handlers of the corresponding source - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - socketUN = SOCKET_NULL; - } - - // If the client has passed the connect/accept method, then the connection has at least begun. - // Notify delegate that it is now ending. - BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO; - BOOL isDeallocating = (flags & kDealloc) ? YES : NO; - - // Clear stored socket info and all flags (config remains as is) - socketFDBytesAvailable = 0; - flags = 0; - sslWriteCachedLength = 0; - - if (shouldCallDelegate) - { - __strong id theDelegate = delegate; - __strong id theSelf = isDeallocating ? nil : self; - - if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidDisconnect:theSelf withError:error]; - }}); - } - } -} - -- (void)disconnect -{ - dispatch_block_t block = ^{ @autoreleasepool { - - if (self->flags & kSocketStarted) - { - [self closeWithError:nil]; - } - }}; - - // Synchronous disconnection, as documented in the header file - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (void)disconnectAfterReading -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (self->flags & kSocketStarted) - { - self->flags |= (kForbidReadsWrites | kDisconnectAfterReads); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (self->flags & kSocketStarted) - { - self->flags |= (kForbidReadsWrites | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterReadingAndWriting -{ - dispatch_async(socketQueue, - ^{ @autoreleasepool { - - if (self->flags & kSocketStarted) - { - self->flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -/** - * Closes the socket if possible. - * That is, if all writes have completed, and we're set to disconnect after writing, - * or if all reads have completed, and we're set to disconnect after reading. - **/ -- (void)maybeClose -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - BOOL shouldClose = NO; - - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - else - { - shouldClose = YES; - } - } - } - else if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - - if (shouldClose) - { - [self closeWithError:nil]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSError *)badConfigError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; -} - -- (NSError *)badParamError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; -} - -+ (NSError *)gaiError:(int)gai_error -{ - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; -} - -- (NSError *)errorWithErrno:(int)err reason:(NSString *)reason -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(err)]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg, - NSLocalizedFailureReasonErrorKey : reason}; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo]; -} - -- (NSError *)errnoError -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)sslError:(OSStatus)ssl_error -{ - NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h"; - NSDictionary *userInfo = @{NSLocalizedRecoverySuggestionErrorKey : msg}; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; -} - -- (NSError *)connectTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError", - @"GCDAsyncSocket", - [NSBundle mainBundle], - @"Attempt to connect to host timed out", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket maxed out error. - **/ -- (NSError *)readMaxedOutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError", - @"GCDAsyncSocket", - [NSBundle mainBundle], - @"Read operation reached set maximum length", - nil); - - NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket write timeout error. - **/ -- (NSError *)readTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError", - @"GCDAsyncSocket", - [NSBundle mainBundle], - @"Read operation timed out", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket write timeout error. - **/ -- (NSError *)writeTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError", - @"GCDAsyncSocket", - [NSBundle mainBundle], - @"Write operation timed out", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; -} - -- (NSError *)connectionClosedError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError", - @"GCDAsyncSocket", - [NSBundle mainBundle], - @"Socket closed by remote peer", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; -} - -- (NSError *)otherError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)isDisconnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (self->flags & kSocketStarted) ? NO : YES; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isConnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (self->flags & kConnected) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSString *)connectedHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (self->socket4FD != SOCKET_NULL) - result = [self connectedHostFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self connectedHostFromSocket6:self->socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)connectedPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool - - if (self->socket4FD != SOCKET_NULL) - result = [self connectedPortFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self connectedPortFromSocket6:self->socket6FD]; - }); - - return result; - } -} - -- (NSURL *)connectedUrl -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socketUN != SOCKET_NULL) - return [self connectedUrlFromSocketUN:socketUN]; - - return nil; - } - else - { - __block NSURL *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (self->socketUN != SOCKET_NULL) - result = [self connectedUrlFromSocketUN:self->socketUN]; - }}); - - return result; - } -} - -- (NSString *)localHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (self->socket4FD != SOCKET_NULL) - result = [self localHostFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self localHostFromSocket6:self->socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)localPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool - - if (self->socket4FD != SOCKET_NULL) - result = [self localPortFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self localPortFromSocket6:self->socket6FD]; - }); - - return result; - } -} - -- (NSString *)connectedHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)connectedHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)connectedPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)connectedPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)localHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)localHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)localPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)localPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)connectedHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)connectedHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)connectedPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)connectedPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSURL *)connectedUrlFromSocketUN:(int)socketFD -{ - struct sockaddr_un sockaddr; - socklen_t sockaddrlen = sizeof(sockaddr); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) - { - return 0; - } - return [[self class] urlFromSockaddrUN:&sockaddr]; -} - -- (NSString *)localHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)localHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)localPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)localPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSData *)connectedAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (self->socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(self->socket4FD, - (struct sockaddr *)&sockaddr4, - &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (self->socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(self->socket6FD, - (struct sockaddr *)&sockaddr6, - &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSData *)localAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (self->socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(self->socket4FD, - (struct sockaddr *)&sockaddr4, - &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (self->socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(self->socket6FD, - (struct sockaddr *)&sockaddr6, - &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv4 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket4FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (self->socket4FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isIPv6 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket6FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (self->socket6FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isSecure -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (flags & kSocketSecure) ? YES : NO; - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = (self->flags & kSocketSecure) ? YES : NO; - }); - - return result; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Finds the address of an interface description. - * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). - * - * The interface description may optionally contain a port number at the end, separated by a colon. - * If a non-zero port parameter is provided, any port number in the interface description is ignored. - * - * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object. - **/ -- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr - address6:(NSMutableData **)interfaceAddr6Ptr - fromDescription:(NSString *)interfaceDescription - port:(uint16_t)port -{ - NSMutableData *addr4 = nil; - NSMutableData *addr6 = nil; - - NSString *interface = nil; - - NSArray *components = [interfaceDescription componentsSeparatedByString:@":"]; - if ([components count] > 0) - { - NSString *temp = [components objectAtIndex:0]; - if ([temp length] > 0) - { - interface = temp; - } - } - if ([components count] > 1 && port == 0) - { - NSString *temp = [components objectAtIndex:1]; - long portL = strtol([temp UTF8String], NULL, 10); - - if (portL > 0 && portL <= UINT16_MAX) - { - port = (uint16_t)portL; - } - } - - if (interface == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interface UTF8String]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 - - struct sockaddr_in nativeAddr4; - memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET, - &nativeAddr4.sin_addr, - ip, - sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 - - struct sockaddr_in6 nativeAddr6; - memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET6, - &nativeAddr6.sin6_addr, - ip, - sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - } - - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; -} - -- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url -{ - NSString *path = url.path; - if (path.length == 0) { - return nil; - } - - struct sockaddr_un nativeAddr; - nativeAddr.sun_family = AF_UNIX; - strlcpy(nativeAddr.sun_path, - path.fileSystemRepresentation, - sizeof(nativeAddr.sun_path)); - nativeAddr.sun_len = (unsigned char)SUN_LEN(&nativeAddr); - NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)]; - - return interface; -} - -- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD -{ - readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, - socketFD, - 0, - socketQueue); - writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, - socketFD, - 0, - socketQueue); - - // Setup event handlers - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(readSource, - ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"readEventBlock"); - - strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource); - LogVerbose(@"socketFDBytesAvailable: %lu", - strongSelf->socketFDBytesAvailable); - - if (strongSelf->socketFDBytesAvailable > 0) - [strongSelf doReadData]; - else - [strongSelf doReadEOF]; - -#pragma clang diagnostic pop - }}); - - dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"writeEventBlock"); - - strongSelf->flags |= kSocketCanAcceptBytes; - [strongSelf doWriteData]; - -#pragma clang diagnostic pop - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - -#if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadSource = readSource; - dispatch_source_t theWriteSource = writeSource; -#endif - - dispatch_source_set_cancel_handler(readSource, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"readCancelBlock"); - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(readSource)"); - dispatch_release(theReadSource); -#endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - -#pragma clang diagnostic pop - }); - - dispatch_source_set_cancel_handler(writeSource, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"writeCancelBlock"); - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(writeSource)"); - dispatch_release(theWriteSource); -#endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - -#pragma clang diagnostic pop - }); - - // We will not be able to read until data arrives. - // But we should be able to write immediately. - - socketFDBytesAvailable = 0; - flags &= ~kReadSourceSuspended; - - LogVerbose(@"dispatch_resume(readSource)"); - dispatch_resume(readSource); - - flags |= kSocketCanAcceptBytes; - flags |= kWriteSourceSuspended; -} - -- (BOOL)usingCFStreamForTLS -{ -#if TARGET_OS_IPHONE - - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - - return YES; - } - -#endif - - return NO; -} - -- (BOOL)usingSecureTransportForTLS -{ - // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable) - -#if TARGET_OS_IPHONE - - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - - return NO; - } - -#endif - - return YES; -} - -- (void)suspendReadSource -{ - if (!(flags & kReadSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(readSource)"); - - dispatch_suspend(readSource); - flags |= kReadSourceSuspended; - } -} - -- (void)resumeReadSource -{ - if (flags & kReadSourceSuspended) - { - LogVerbose(@"dispatch_resume(readSource)"); - - dispatch_resume(readSource); - flags &= ~kReadSourceSuspended; - } -} - -- (void)suspendWriteSource -{ - if (!(flags & kWriteSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(writeSource)"); - - dispatch_suspend(writeSource); - flags |= kWriteSourceSuspended; - } -} - -- (void)resumeWriteSource -{ - if (flags & kWriteSourceSuspended) - { - LogVerbose(@"dispatch_resume(writeSource)"); - - dispatch_resume(writeSource); - flags &= ~kWriteSourceSuspended; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Reading -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag -{ - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:length - timeout:timeout - readLength:0 - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; -} - -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - if (length == 0) { - LogWarn(@"Cannot read: length == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:0 - timeout:timeout - readLength:length - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)maxLength - tag:(long)tag -{ - if ([data length] == 0) { - LogWarn(@"Cannot read: [data length] == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - if (maxLength > 0 && maxLength < [data length]) { - LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:maxLength - timeout:timeout - readLength:0 - terminator:data - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!self->currentRead || ![self->currentRead isKindOfClass:[GCDAsyncReadPacket class]]) - { - // We're not reading anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - // It's only possible to know the progress of our read if we're reading to a certain length. - // If we're reading to data, we of course have no idea when the data will arrive. - // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. - - NSUInteger done = self->currentRead->bytesDone; - NSUInteger total = self->currentRead->readLength; - - if (tagPtr != NULL) *tagPtr = self->currentRead->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - if (total > 0) - result = (float)done / (float)total; - else - result = 1.0F; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * This method starts a new read, if needed. - * - * It is called when: - * - a user requests a read - * - after a read request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. - **/ -- (void)maybeDequeueRead -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - // If we're not currently processing a read AND we have an available read stream - if ((currentRead == nil) && (flags & kConnected)) - { - if ([readQueue count] > 0) - { - // Dequeue the next object in the write queue - currentRead = [readQueue objectAtIndex:0]; - [readQueue removeObjectAtIndex:0]; - - - if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingReadTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncReadPacket"); - - // Setup read timer (if needed) - [self setupReadTimerWithTimeout:currentRead->timeout]; - - // Immediately read, if possible - [self doReadData]; - } - } - else if (flags & kDisconnectAfterReads) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - else if (flags & kSocketSecure) - { - [self flushSSLBuffers]; - - // Edge case: - // - // We just drained all data from the ssl buffers, - // and all known data from the socket (socketFDBytesAvailable). - // - // If we didn't get any data from this process, - // then we may have reached the end of the TCP stream. - // - // Be sure callbacks are enabled so we're notified about a disconnection. - - if ([preBuffer availableBytes] == 0) - { - if ([self usingCFStreamForTLS]) { - // Callbacks never disabled - } - else { - [self resumeReadSource]; - } - } - } - } -} - -- (void)flushSSLBuffers -{ - LogTrace(); - - NSAssert((flags & kSocketSecure), - @"Cannot flush ssl buffers on non-secure socket"); - - if ([preBuffer availableBytes] > 0) - { - // Only flush the ssl buffers if the prebuffer is empty. - // This is to avoid growing the prebuffer inifinitely large. - - return; - } - -#if TARGET_OS_IPHONE - - if ([self usingCFStreamForTLS]) - { - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - CFIndex defaultBytesToRead = (1024 * 4); - - [preBuffer ensureCapacityForWrite:defaultBytesToRead]; - - uint8_t *buffer = [preBuffer writeBuffer]; - - CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead); - LogVerbose(@"%@ - CFReadStreamRead(): result = %i", - THIS_METHOD, - (int)result); - - if (result > 0) - { - [preBuffer didWrite:result]; - } - - flags &= ~kSecureSocketHasBytesAvailable; - } - - return; - } - -#endif - - __block NSUInteger estimatedBytesAvailable = 0; - - dispatch_block_t updateEstimatedBytesAvailable = ^{ - - // Figure out if there is any data available to be read - // - // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket - // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket - // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered - // - // We call the variable "estimated" because we don't know how many decrypted bytes we'll get - // from the encrypted bytes in the sslPreBuffer. - // However, we do know this is an upper bound on the estimation. - - estimatedBytesAvailable = self->socketFDBytesAvailable + [self->sslPreBuffer availableBytes]; - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(self->sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - }; - - updateEstimatedBytesAvailable(); - - if (estimatedBytesAvailable > 0) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - BOOL done = NO; - do - { - LogVerbose(@"%@ - estimatedBytesAvailable = %lu", - THIS_METHOD, - (unsigned long)estimatedBytesAvailable); - - // Make sure there's enough room in the prebuffer - - [preBuffer ensureCapacityForWrite:estimatedBytesAvailable]; - - // Read data into prebuffer - - uint8_t *buffer = [preBuffer writeBuffer]; - size_t bytesRead = 0; - - OSStatus result = SSLRead(sslContext, - buffer, - (size_t)estimatedBytesAvailable, - &bytesRead); - LogVerbose(@"%@ - read from secure socket = %u", - THIS_METHOD, - (unsigned)bytesRead); - - if (bytesRead > 0) - { - [preBuffer didWrite:bytesRead]; - } - - LogVerbose(@"%@ - prebuffer.length = %zu", - THIS_METHOD, - [preBuffer availableBytes]); - - if (result != noErr) - { - done = YES; - } - else - { - updateEstimatedBytesAvailable(); - } - - } while (!done && estimatedBytesAvailable > 0); - } -} - -- (void)doReadData -{ - LogTrace(); - - // This method is called on the socketQueue. - // It might be called directly, or via the readSource when data is available to be read. - - if ((currentRead == nil) || (flags & kReadsPaused)) - { - LogVerbose(@"No currentRead or kReadsPaused"); - - // Unable to read at this time - - if (flags & kSocketSecure) - { - // Here's the situation: - // - // We have an established secure connection. - // There may not be a currentRead, but there might be encrypted data sitting around for us. - // When the user does get around to issuing a read, that encrypted data will need to be decrypted. - // - // So why make the user wait? - // We might as well get a head start on decrypting some data now. - // - // The other reason we do this has to do with detecting a socket disconnection. - // The SSL/TLS protocol has it's own disconnection handshake. - // So when a secure socket is closed, a "goodbye" packet comes across the wire. - // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection. - - [self flushSSLBuffers]; - } - - if ([self usingCFStreamForTLS]) - { - // CFReadStream only fires once when there is available data. - // It won't fire again until we've invoked CFReadStreamRead. - } - else - { - // If the readSource is firing, we need to pause it - // or else it will continue to fire over and over again. - // - // If the readSource is not firing, - // we want it to continue monitoring the socket. - - if (socketFDBytesAvailable > 0) - { - [self suspendReadSource]; - } - } - return; - } - - BOOL hasBytesAvailable = NO; - unsigned long estimatedBytesAvailable = 0; - - if ([self usingCFStreamForTLS]) - { -#if TARGET_OS_IPHONE - - // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS) - - estimatedBytesAvailable = 0; - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - hasBytesAvailable = YES; - else - hasBytesAvailable = NO; - -#endif - } - else - { - estimatedBytesAvailable = socketFDBytesAvailable; - - if (flags & kSocketSecure) - { - // There are 2 buffers to be aware of here. - // - // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP. - // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction. - // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport. - // SecureTransport then decrypts the data, and finally returns the decrypted data back to us. - // - // The first buffer is one we create. - // SecureTransport often requests small amounts of data. - // This has to do with the encypted packets that are coming across the TCP stream. - // But it's non-optimal to do a bunch of small reads from the BSD socket. - // So our SSLReadFunction reads all available data from the socket (optimizing the sys call) - // and may store excess in the sslPreBuffer. - - estimatedBytesAvailable += [sslPreBuffer availableBytes]; - - // The second buffer is within SecureTransport. - // As mentioned earlier, there are encrypted packets coming across the TCP stream. - // SecureTransport needs the entire packet to decrypt it. - // But if the entire packet produces X bytes of decrypted data, - // and we only asked SecureTransport for X/2 bytes of data, - // it must store the extra X/2 bytes of decrypted data for the next read. - // - // The SSLGetBufferedReadSize function will tell us the size of this internal buffer. - // From the documentation: - // - // "This function does not block or cause any low-level read operations to occur." - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - } - - hasBytesAvailable = (estimatedBytesAvailable > 0); - } - - if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) - { - LogVerbose(@"No data available to read..."); - - // No data available to read. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - } - return; - } - - if (flags & kStartingReadTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The readQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingWriteTLS) - { - if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) - { - // We are in the process of a SSL Handshake. - // We were waiting for incoming data which has just arrived. - - [self ssl_continueSSLHandshake]; - } - } - else - { - // We are still waiting for the writeQueue to drain and start the SSL/TLS process. - // We now know data is available to read. - - if (![self usingCFStreamForTLS]) - { - // Suspend the read source or else it will continue to fire nonstop. - - [self suspendReadSource]; - } - } - - return; - } - - BOOL done = NO; // Completed read operation - NSError *error = nil; // Error occurred - - NSUInteger totalBytesReadForCurrentRead = 0; - - // - // STEP 1 - READ FROM PREBUFFER - // - - if ([preBuffer availableBytes] > 0) - { - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - NSUInteger bytesToCopy; - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - } - else - { - // Read type #1 or #2 - - bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]]; - } - - // Make sure we have enough room in the buffer for our read. - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into packet buffer - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + - currentRead->bytesDone; - - memcpy(buffer, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the preBuffer - [preBuffer didRead:bytesToCopy]; - - LogVerbose(@"copied(%lu) preBufferLength(%zu)", - (unsigned long)bytesToCopy, - [preBuffer availableBytes]); - - // Update totals - - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - // - // We're done as soon as - // - we've read all available data (in prebuffer and socket) - // - we've read the maxLength of read packet. - - done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength)); - } - - } - - // - // STEP 2 - READ FROM SOCKET - // - - BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file) - BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more - - if (!done && !error && !socketEOF && hasBytesAvailable) - { - NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); - - BOOL readIntoPreBuffer = NO; - uint8_t *buffer = NULL; - size_t bytesRead = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { -#if TARGET_OS_IPHONE - - // Using CFStream, rather than SecureTransport, for TLS - - NSUInteger defaultReadLength = (1024 * 32); - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - CFIndex result = CFReadStreamRead(readStream, - buffer, - (CFIndex)bytesToRead); - LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream); - } - else if (result == 0) - { - socketEOF = YES; - } - else - { - waiting = YES; - bytesRead = (size_t)result; - } - - // We only know how many decrypted bytes were read. - // The actual number of bytes read was likely more due to the overhead of the encryption. - // So we reset our flag, and rely on the next callback to alert us of more data. - flags &= ~kSecureSocketHasBytesAvailable; - -#endif - } - else - { - // Using SecureTransport for TLS - // - // We know: - // - how many bytes are available on the socket - // - how many encrypted bytes are sitting in the sslPreBuffer - // - how many decypted bytes are sitting in the sslContext - // - // But we do NOT know: - // - how many encypted bytes are sitting in the sslContext - // - // So we play the regular game of using an upper bound instead. - - NSUInteger defaultReadLength = (1024 * 32); - - if (defaultReadLength < estimatedBytesAvailable) { - defaultReadLength = estimatedBytesAvailable + (1024 * 16); - } - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // The documentation from Apple states: - // - // "a read operation might return errSSLWouldBlock, - // indicating that less data than requested was actually transferred" - // - // However, starting around 10.7, the function will sometimes return noErr, - // even if it didn't read as much data as requested. So we need to watch out for that. - - OSStatus result; - do - { - void *loop_buffer = buffer + bytesRead; - size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead; - size_t loop_bytesRead = 0; - - result = SSLRead(sslContext, - loop_buffer, - loop_bytesToRead, - &loop_bytesRead); - LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead); - - bytesRead += loop_bytesRead; - - } while ((result == noErr) && (bytesRead < bytesToRead)); - - - if (result != noErr) - { - if (result == errSSLWouldBlock) - waiting = YES; - else - { - if (result == errSSLClosedGraceful || result == errSSLClosedAbort) - { - // We've reached the end of the stream. - // Handle this the same way we would an EOF from the socket. - socketEOF = YES; - sslErrCode = result; - } - else - { - error = [self sslError:result]; - } - } - // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock. - // This happens when the SSLRead function is able to read some data, - // but not the entire amount we requested. - - if (bytesRead <= 0) - { - bytesRead = 0; - } - } - - // Do not modify socketFDBytesAvailable. - // It will be updated via the SSLReadFunction(). - } - } - else - { - // Normal socket operation - - NSUInteger bytesToRead; - - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - // Read type #1 or #2 - - bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; - } - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3) - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); - LogVerbose(@"read from socket = %i", (int)result); - - if (result < 0) - { - if (errno == EWOULDBLOCK) - waiting = YES; - else - error = [self errorWithErrno:errno reason:@"Error in read() function"]; - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - socketEOF = YES; - socketFDBytesAvailable = 0; - } - else - { - bytesRead = result; - - if (bytesRead < bytesToRead) - { - // The read returned less data than requested. - // This means socketFDBytesAvailable was a bit off due to timing, - // because we read from the socket right when the readSource event was firing. - socketFDBytesAvailable = 0; - } - else - { - if (socketFDBytesAvailable <= bytesRead) - socketFDBytesAvailable = 0; - else - socketFDBytesAvailable -= bytesRead; - } - - if (socketFDBytesAvailable == 0) - { - waiting = YES; - } - } - } - - if (bytesRead > 0) - { - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - // - // Note: We should never be using a prebuffer when we're reading a specific length of data. - - NSAssert(readIntoPreBuffer == NO, @"Invalid logic"); - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - if (readIntoPreBuffer) - { - // We just read a big chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", - [preBuffer availableBytes]); - - // Search for the terminating sequence - - NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - LogVerbose(@"copying %lu bytes from preBuffer", - (unsigned long)bytesToCopy); - - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesToCopy]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Update totals - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above - } - else - { - // We just read a big chunk of data directly into the packet's buffer. - // We need to move any overflow into the prebuffer. - - NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead]; - - if (overflow == 0) - { - // Perfect match! - // Every byte we read stays in the read buffer, - // and the last byte we read was the last byte of the term. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = YES; - } - else if (overflow > 0) - { - // The term was found within the data that we read, - // and there are extra bytes that extend past the end of the term. - // We need to move these excess bytes out of the read packet and into the prebuffer. - - NSInteger underflow = bytesRead - overflow; - - // Copy excess data into preBuffer - - LogVerbose(@"copying %ld overflow bytes into preBuffer", - (long)overflow); - [preBuffer ensureCapacityForWrite:overflow]; - - uint8_t *overflowBuffer = buffer + underflow; - memcpy([preBuffer writeBuffer], overflowBuffer, overflow); - - [preBuffer didWrite:overflow]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Note: The completeCurrentRead method will trim the buffer for us. - - currentRead->bytesDone += underflow; - totalBytesReadForCurrentRead += underflow; - done = YES; - } - else - { - // The term was not found within the data that we read. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = NO; - } - } - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - - if (readIntoPreBuffer) - { - // We just read a chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - - // Now copy the data into the read packet. - // - // Recall that we didn't read directly into the packet's buffer to avoid - // over-allocating memory since we had no clue how much data was available to be read. - // - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesRead); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesRead]; - - // Update totals - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - else - { - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - - done = YES; - } - - } // if (bytesRead > 0) - - } // if (!done && !error && !socketEOF && hasBytesAvailable) - - - if (!done && currentRead->readLength == 0 && currentRead->term == nil) - { - // Read type #1 - read all available data - // - // We might arrive here if we read data from the prebuffer but not from the socket. - - done = (totalBytesReadForCurrentRead > 0); - } - - // Check to see if we're done, or if we've made progress - - if (done) - { - [self completeCurrentRead]; - - if (!error && (!socketEOF || [preBuffer availableBytes] > 0)) - { - [self maybeDequeueRead]; - } - } - else if (totalBytesReadForCurrentRead > 0) - { - // We're not done read type #2 or #3 yet, but we have read in some bytes - // - // We ensure that `waiting` is set in order to resume the readSource (if it is suspended). It is - // possible to reach this point and `waiting` not be set, if the current read's length is - // sufficiently large. In that case, we may have read to some upperbound successfully, but - // that upperbound could be smaller than the desired length. - waiting = YES; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) - { - long theReadTag = currentRead->tag; - - dispatch_async(delegateQueue, - ^{ @autoreleasepool { - - [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag]; - }}); - } - } - - // Check for errors - - if (error) - { - [self closeWithError:error]; - } - else if (socketEOF) - { - [self doReadEOF]; - } - else if (waiting) - { - if (![self usingCFStreamForTLS]) - { - // Monitor the socket for readability (if we're not already doing so) - [self resumeReadSource]; - } - } - - // Do not add any code here without first adding return statements in the error cases above. -} - -- (void)doReadEOF -{ - LogTrace(); - - // This method may be called more than once. - // If the EOF is read while there is still data in the preBuffer, - // then this method may be called continually after invocations of doReadData to see if it's time to disconnect. - - flags |= kSocketHasReadEOF; - - if (flags & kSocketSecure) - { - // If the SSL layer has any buffered data, flush it into the preBuffer now. - - [self flushSSLBuffers]; - } - - BOOL shouldDisconnect = NO; - NSError *error = nil; - - if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) - { - // We received an EOF during or prior to startTLS. - // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation. - - shouldDisconnect = YES; - - if ([self usingSecureTransportForTLS]) - { - error = [self sslError:errSSLClosedAbort]; - } - } - else if (flags & kReadStreamClosed) - { - // The preBuffer has already been drained. - // The config allows half-duplex connections. - // We've previously checked the socket, and it appeared writeable. - // So we marked the read stream as closed and notified the delegate. - // - // As per the half-duplex contract, the socket will be closed when a write fails, - // or when the socket is manually closed. - - shouldDisconnect = NO; - } - else if ([preBuffer availableBytes] > 0) - { - LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer"); - - // Although we won't be able to read any more data from the socket, - // there is existing data that has been prebuffered that we can read. - - shouldDisconnect = NO; - } - else if (config & kAllowHalfDuplexConnection) - { - // We just received an EOF (end of file) from the socket's read stream. - // This means the remote end of the socket (the peer we're connected to) - // has explicitly stated that it will not be sending us any more data. - // - // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us) - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - struct pollfd pfd[1]; - pfd[0].fd = socketFD; - pfd[0].events = POLLOUT; - pfd[0].revents = 0; - - poll(pfd, 1, 0); - - if (pfd[0].revents & POLLOUT) - { - // Socket appears to still be writeable - - shouldDisconnect = NO; - flags |= kReadStreamClosed; - - // Notify the delegate that we're going half-duplex - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidCloseReadStream:self]; - }}); - } - } - else - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - - - if (shouldDisconnect) - { - if (error == nil) - { - if ([self usingSecureTransportForTLS]) - { - if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) - { - error = [self sslError:sslErrCode]; - } - else - { - error = [self connectionClosedError]; - } - } - else - { - error = [self connectionClosedError]; - } - } - [self closeWithError:error]; - } - else - { - if (![self usingCFStreamForTLS]) - { - // Suspend the read source (if needed) - - [self suspendReadSource]; - } - } -} - -- (void)completeCurrentRead -{ - LogTrace(); - - NSAssert(currentRead, - @"Trying to complete current read when there is no current read."); - - - NSData *result = nil; - - if (currentRead->bufferOwner) - { - // We created the buffer on behalf of the user. - // Trim our buffer to be the proper size. - [currentRead->buffer setLength:currentRead->bytesDone]; - - result = currentRead->buffer; - } - else - { - // We did NOT create the buffer. - // The buffer is owned by the caller. - // Only trim the buffer if we had to increase its size. - - if ([currentRead->buffer length] > currentRead->originalBufferLength) - { - NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone; - NSUInteger origSize = currentRead->originalBufferLength; - - NSUInteger buffSize = MAX(readSize, origSize); - - [currentRead->buffer setLength:buffSize]; - } - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset; - - result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; - } - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)]) - { - GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReadData:result withTag:theRead->tag]; - }}); - } - - [self endCurrentRead]; -} - -- (void)endCurrentRead -{ - if (readTimer) - { - dispatch_source_cancel(readTimer); - readTimer = NULL; - } - - currentRead = nil; -} - -- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, - 0, - 0, - socketQueue); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doReadTimeout]; - -#pragma clang diagnostic pop - }}); - -#if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadTimer = readTimer; - dispatch_source_set_cancel_handler(readTimer, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(readTimer)"); - dispatch_release(theReadTimer); - -#pragma clang diagnostic pop - }); -#endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(readTimer); - } -} - -- (void)doReadTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kReadsPaused; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) - { - GCDAsyncReadPacket *theRead = currentRead; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag - elapsed:theRead->timeout - bytesDone:theRead->bytesDone]; - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - [self doReadTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doReadTimeoutWithExtension:0.0]; - } -} - -- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentRead) - { - if (timeoutExtension > 0.0) - { - currentRead->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause reads, and continue - flags &= ~kReadsPaused; - [self doReadData]; - } - else - { - LogVerbose(@"ReadTimeout"); - - [self closeWithError:[self readTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Writing -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - if ([data length] == 0) return; - - GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->writeQueue addObject:packet]; - [self maybeDequeueWrite]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!self->currentWrite || ![self->currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) - { - // We're not writing anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - NSUInteger done = self->currentWrite->bytesDone; - NSUInteger total = [self->currentWrite->buffer length]; - - if (tagPtr != NULL) *tagPtr = self->currentWrite->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - result = (float)done / (float)total; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * Conditionally starts a new write. - * - * It is called when: - * - a user requests a write - * - after a write request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. - **/ -- (void)maybeDequeueWrite -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - - // If we're not currently processing a write AND we have an available write stream - if ((currentWrite == nil) && (flags & kConnected)) - { - if ([writeQueue count] > 0) - { - // Dequeue the next object in the write queue - currentWrite = [writeQueue objectAtIndex:0]; - [writeQueue removeObjectAtIndex:0]; - - - if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingWriteTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncWritePacket"); - - // Setup write timer (if needed) - [self setupWriteTimerWithTimeout:currentWrite->timeout]; - - // Immediately write, if possible - [self doWriteData]; - } - } - else if (flags & kDisconnectAfterWrites) - { - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - } -} - -- (void)doWriteData -{ - LogTrace(); - - // This method is called by the writeSource via the socketQueue - - if ((currentWrite == nil) || (flags & kWritesPaused)) - { - LogVerbose(@"No currentWrite or kWritesPaused"); - - // Unable to write at this time - - if ([self usingCFStreamForTLS]) - { - // CFWriteStream only fires once when there is available data. - // It won't fire again until we've invoked CFWriteStreamWrite. - } - else - { - // If the writeSource is firing, we need to pause it - // or else it will continue to fire over and over again. - - if (flags & kSocketCanAcceptBytes) - { - [self suspendWriteSource]; - } - } - return; - } - - if (!(flags & kSocketCanAcceptBytes)) - { - LogVerbose(@"No space available to write..."); - - // No space available to write. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - } - return; - } - - if (flags & kStartingWriteTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The writeQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingReadTLS) - { - if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) - { - // We are in the process of a SSL Handshake. - // We were waiting for available space in the socket's internal OS buffer to continue writing. - - [self ssl_continueSSLHandshake]; - } - } - else - { - // We are still waiting for the readQueue to drain and start the SSL/TLS process. - // We now know we can write to the socket. - - if (![self usingCFStreamForTLS]) - { - // Suspend the write source or else it will continue to fire nonstop. - - [self suspendWriteSource]; - } - } - - return; - } - - // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet) - - BOOL waiting = NO; - NSError *error = nil; - size_t bytesWritten = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { -#if TARGET_OS_IPHONE - - // - // Writing data using CFStream (over internal TLS) - // - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - CFIndex result = CFWriteStreamWrite(writeStream, - buffer, - (CFIndex)bytesToWrite); - LogVerbose(@"CFWriteStreamWrite(%lu) = %li", - (unsigned long)bytesToWrite, - result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream); - } - else - { - bytesWritten = (size_t)result; - - // We always set waiting to true in this scenario. - // CFStream may have altered our underlying socket to non-blocking. - // Thus if we attempt to write without a callback, we may end up blocking our queue. - waiting = YES; - } - -#endif - } - else - { - // We're going to use the SSLWrite function. - // - // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed) - // - // Parameters: - // context - An SSL session context reference. - // data - A pointer to the buffer of data to write. - // dataLength - The amount, in bytes, of data to write. - // processed - On return, the length, in bytes, of the data actually written. - // - // It sounds pretty straight-forward, - // but there are a few caveats you should be aware of. - // - // The SSLWrite method operates in a non-obvious (and rather annoying) manner. - // According to the documentation: - // - // Because you may configure the underlying connection to operate in a non-blocking manner, - // a write operation might return errSSLWouldBlock, indicating that less data than requested - // was actually transferred. In this case, you should repeat the call to SSLWrite until some - // other result is returned. - // - // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock, - // then the SSLWrite method returns (with the proper errSSLWouldBlock return value), - // but it sets processed to dataLength !! - // - // In other words, if the SSLWrite function doesn't completely write all the data we tell it to, - // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to - // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written. - // - // You might be wondering: - // If the SSLWrite function doesn't tell us how many bytes were written, - // then how in the world are we supposed to update our parameters (buffer & bytesToWrite) - // for the next time we invoke SSLWrite? - // - // The answer is that SSLWrite cached all the data we told it to write, - // and it will push out that data next time we call SSLWrite. - // If we call SSLWrite with new data, it will push out the cached data first, and then the new data. - // If we call SSLWrite with empty data, then it will simply push out the cached data. - // - // For this purpose we're going to break large writes into a series of smaller writes. - // This allows us to report progress back to the delegate. - - OSStatus result; - - BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0); - BOOL hasNewDataToWrite = YES; - - if (hasCachedDataToWrite) - { - size_t processed = 0; - - result = SSLWrite(sslContext, NULL, 0, &processed); - - if (result == noErr) - { - bytesWritten = sslWriteCachedLength; - sslWriteCachedLength = 0; - - if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten)) - { - // We've written all data for the current write. - hasNewDataToWrite = NO; - } - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - } - else - { - error = [self sslError:result]; - } - - // Can't write any new data since we were unable to write the cached data. - hasNewDataToWrite = NO; - } - } - - if (hasNewDataToWrite) - { - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] - + currentWrite->bytesDone - + bytesWritten; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - size_t bytesRemaining = bytesToWrite; - - BOOL keepLooping = YES; - while (keepLooping) - { - const size_t sslMaxBytesToWrite = 32768; - size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite); - size_t sslBytesWritten = 0; - - result = SSLWrite(sslContext, - buffer, - sslBytesToWrite, - &sslBytesWritten); - - if (result == noErr) - { - buffer += sslBytesWritten; - bytesWritten += sslBytesWritten; - bytesRemaining -= sslBytesWritten; - - keepLooping = (bytesRemaining > 0); - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - sslWriteCachedLength = sslBytesToWrite; - } - else - { - error = [self sslError:result]; - } - - keepLooping = NO; - } - - } // while (keepLooping) - - } // if (hasNewDataToWrite) - } - } - else - { - // - // Writing data directly over raw socket - // - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite); - LogVerbose(@"wrote to socket = %zd", result); - - // Check results - if (result < 0) - { - if (errno == EWOULDBLOCK) - { - waiting = YES; - } - else - { - error = [self errorWithErrno:errno reason:@"Error in write() function"]; - } - } - else - { - bytesWritten = result; - } - } - - // We're done with our writing. - // If we explictly ran into a situation where the socket told us there was no room in the buffer, - // then we immediately resume listening for notifications. - // - // We must do this before we dequeue another write, - // as that may in turn invoke this method again. - // - // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode. - - if (waiting) - { - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - // Check our results - - BOOL done = NO; - - if (bytesWritten > 0) - { - // Update total amount read for the current write - currentWrite->bytesDone += bytesWritten; - LogVerbose(@"currentWrite->bytesDone = %lu", - (unsigned long)currentWrite->bytesDone); - - // Is packet done? - done = (currentWrite->bytesDone == [currentWrite->buffer length]); - } - - if (done) - { - [self completeCurrentWrite]; - - if (!error) - { - dispatch_async(socketQueue, ^{ @autoreleasepool{ - - [self maybeDequeueWrite]; - }}); - } - } - else - { - // We were unable to finish writing the data, - // so we're waiting for another callback to notify us of available space in the lower-level output buffer. - - if (!waiting && !error) - { - // This would be the case if our write was able to accept some data, but not all of it. - - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - if (bytesWritten > 0) - { - // We're not done with the entire write, but we have written some bytes - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) - { - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, - ^{ @autoreleasepool { - - [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag]; - }}); - } - } - } - - // Check for errors - - if (error) - { - [self closeWithError:[self errorWithErrno:errno reason:@"Error in write() function"]]; - } - - // Do not add any code here without first adding a return statement in the error case above. -} - -- (void)completeCurrentWrite -{ - LogTrace(); - - NSAssert(currentWrite, - @"Trying to complete current write when there is no current write."); - - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) - { - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWriteDataWithTag:theWriteTag]; - }}); - } - - [self endCurrentWrite]; -} - -- (void)endCurrentWrite -{ - if (writeTimer) - { - dispatch_source_cancel(writeTimer); - writeTimer = NULL; - } - - currentWrite = nil; -} - -- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, - 0, - 0, - socketQueue); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doWriteTimeout]; - -#pragma clang diagnostic pop - }}); - -#if !OS_OBJECT_USE_OBJC - dispatch_source_t theWriteTimer = writeTimer; - dispatch_source_set_cancel_handler(writeTimer, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(writeTimer)"); - dispatch_release(theWriteTimer); - -#pragma clang diagnostic pop - }); -#endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(writeTimer); - } -} - -- (void)doWriteTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kWritesPaused; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) - { - GCDAsyncWritePacket *theWrite = currentWrite; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag - elapsed:theWrite->timeout - bytesDone:theWrite->bytesDone]; - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - [self doWriteTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doWriteTimeoutWithExtension:0.0]; - } -} - -- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentWrite) - { - if (timeoutExtension > 0.0) - { - currentWrite->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause writes, and continue - flags &= ~kWritesPaused; - [self doWriteData]; - } - else - { - LogVerbose(@"WriteTimeout"); - - [self closeWithError:[self writeTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)startTLS:(NSDictionary *)tlsSettings -{ - LogTrace(); - - if (tlsSettings == nil) - { - // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, - // but causes problems if we later try to fetch the remote host's certificate. - // - // To be exact, it causes the following to return NULL instead of the normal result: - // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) - // - // So we use an empty dictionary instead, which works perfectly. - - tlsSettings = [NSDictionary dictionary]; - } - - GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; - - dispatch_async(socketQueue, - ^{ @autoreleasepool { - - if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self->writeQueue addObject:packet]; - - self->flags |= kQueuedTLS; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - }}); - -} - -- (void)maybeStartTLS -{ - // We can't start TLS until: - // - All queued reads prior to the user calling startTLS are complete - // - All queued writes prior to the user calling startTLS are complete - // - // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - BOOL useSecureTransport = YES; - -#if TARGET_OS_IPHONE - { - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = @{}; - if (tlsPacket) { - tlsSettings = tlsPacket->tlsSettings; - } - NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS]; - if (value && [value boolValue]) - useSecureTransport = NO; - } -#endif - - if (useSecureTransport) - { - [self ssl_startTLS]; - } - else - { -#if TARGET_OS_IPHONE - [self cf_startTLS]; -#endif - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via SecureTransport -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength -{ - LogVerbose(@"sslReadWithBuffer:%p length:%lu", - buffer, - (unsigned long)*bufferLength); - - if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0)) - { - LogVerbose(@"%@ - No data available to read...", THIS_METHOD); - - // No data available to read. - // - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t totalBytesRead = 0; - size_t totalBytesLeftToBeRead = *bufferLength; - - BOOL done = NO; - BOOL socketError = NO; - - // - // STEP 1 : READ FROM SSL PRE BUFFER - // - - size_t sslPreBufferLength = [sslPreBuffer availableBytes]; - - if (sslPreBufferLength > 0) - { - LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD); - - size_t bytesToCopy; - if (sslPreBufferLength > totalBytesLeftToBeRead) - bytesToCopy = totalBytesLeftToBeRead; - else - bytesToCopy = sslPreBufferLength; - - LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", - THIS_METHOD, - bytesToCopy); - - memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", - THIS_METHOD, - [sslPreBuffer availableBytes]); - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - - // - // STEP 2 : READ FROM SOCKET - // - - if (!done && (socketFDBytesAvailable > 0)) - { - LogVerbose(@"%@: Reading from socket...", THIS_METHOD); - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - BOOL readIntoPreBuffer; - size_t bytesToRead; - uint8_t *buf; - - if (socketFDBytesAvailable > totalBytesLeftToBeRead) - { - // Read all available data from socket into sslPreBuffer. - // Then copy requested amount into dataBuffer. - - LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD); - - [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable]; - - readIntoPreBuffer = YES; - bytesToRead = (size_t)socketFDBytesAvailable; - buf = [sslPreBuffer writeBuffer]; - } - else - { - // Read available data from socket directly into dataBuffer. - - LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD); - - readIntoPreBuffer = NO; - bytesToRead = totalBytesLeftToBeRead; - buf = (uint8_t *)buffer + totalBytesRead; - } - - ssize_t result = read(socketFD, buf, bytesToRead); - LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result); - - if (result < 0) - { - LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno); - - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - LogVerbose(@"%@: read EOF", THIS_METHOD); - - socketError = YES; - socketFDBytesAvailable = 0; - } - else - { - size_t bytesReadFromSocket = result; - - if (socketFDBytesAvailable > bytesReadFromSocket) - socketFDBytesAvailable -= bytesReadFromSocket; - else - socketFDBytesAvailable = 0; - - if (readIntoPreBuffer) - { - [sslPreBuffer didWrite:bytesReadFromSocket]; - - size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket); - - LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", - THIS_METHOD, - bytesToCopy); - - memcpy((uint8_t *)buffer + totalBytesRead, - [sslPreBuffer readBuffer], - bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", - THIS_METHOD, - [sslPreBuffer availableBytes]); - } - else - { - totalBytesRead += bytesReadFromSocket; - totalBytesLeftToBeRead -= bytesReadFromSocket; - } - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - } - - *bufferLength = totalBytesRead; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength -{ - if (!(flags & kSocketCanAcceptBytes)) - { - // Unable to write. - // - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t bytesToWrite = *bufferLength; - size_t bytesWritten = 0; - - BOOL done = NO; - BOOL socketError = NO; - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - ssize_t result = write(socketFD, buffer, bytesToWrite); - - if (result < 0) - { - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - flags &= ~kSocketCanAcceptBytes; - } - else if (result == 0) - { - flags &= ~kSocketCanAcceptBytes; - } - else - { - bytesWritten = result; - - done = (bytesWritten == bytesToWrite); - } - - *bufferLength = bytesWritten; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -static OSStatus SSLReadFunction(SSLConnectionRef connection, - void *data, - size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), - @"What the deuce?"); - - return [asyncSocket sslReadWithBuffer:data length:dataLength]; -} - -static OSStatus SSLWriteFunction(SSLConnectionRef connection, - const void *data, - size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), - @"What the deuce?"); - - return [asyncSocket sslWriteWithBuffer:data length:dataLength]; -} - -- (void)ssl_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via SecureTransport)..."); - - OSStatus status; - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - if (tlsPacket == nil) // Code to quiet the analyzer - { - NSAssert(NO, @"Logic error"); - - [self closeWithError:[self otherError:@"Logic error"]]; - return; - } - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - // Create SSLContext, and setup IO callbacks and connection ref - - NSNumber *isServerNumber = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer]; - BOOL isServer = [isServerNumber boolValue]; - -#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - { - if (isServer) - sslContext = SSLCreateContext(kCFAllocatorDefault, - kSSLServerSide, - kSSLStreamType); - else - sslContext = SSLCreateContext(kCFAllocatorDefault, - kSSLClientSide, - kSSLStreamType); - - if (sslContext == NULL) - { - [self closeWithError:[self otherError:@"Error in SSLCreateContext"]]; - return; - } - } -#else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - { - status = SSLNewContext(isServer, &sslContext); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLNewContext"]]; - return; - } - } -#endif - - status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]]; - return; - } - - status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; - return; - } - - - NSNumber *shouldManuallyEvaluateTrust = [tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust]; - if ([shouldManuallyEvaluateTrust boolValue]) - { - if (isServer) - { - [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]]; - return; - } - - status = SSLSetSessionOption(sslContext, - kSSLSessionOptionBreakOnServerAuth, - true); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]]; - return; - } - - } - - // Configure SSLContext from given settings - // - // Checklist: - // 1. kCFStreamSSLPeerName - // 2. kCFStreamSSLCertificates - // 3. GCDAsyncSocketSSLPeerID - // 4. GCDAsyncSocketSSLProtocolVersionMin - // 5. GCDAsyncSocketSSLProtocolVersionMax - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - // 8. GCDAsyncSocketSSLCipherSuites - // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) - // 10. GCDAsyncSocketSSLALPN - // - // Deprecated (throw error): - // 10. kCFStreamSSLAllowsAnyRoot - // 11. kCFStreamSSLAllowsExpiredRoots - // 12. kCFStreamSSLAllowsExpiredCertificates - // 13. kCFStreamSSLValidatesCertificateChain - // 14. kCFStreamSSLLevel - - NSObject *value; - - // 1. kCFStreamSSLPeerName - - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName]; - if ([value isKindOfClass:[NSString class]]) - { - NSString *peerName = (NSString *)value; - - const char *peer = [peerName UTF8String]; - size_t peerLen = strlen(peer); - - status = SSLSetPeerDomainName(sslContext, peer, peerLen); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]]; - return; - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]]; - return; - } - - // 2. kCFStreamSSLCertificates - - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates]; - if ([value isKindOfClass:[NSArray class]]) - { - NSArray *certs = (NSArray *)value; - - status = SSLSetCertificate(sslContext, (__bridge CFArrayRef)certs); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; - return; - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]]; - return; - } - - // 3. GCDAsyncSocketSSLPeerID - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *peerIdData = (NSData *)value; - - status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData." - @" (You can convert strings to data using a method like" - @" [string dataUsingEncoding:NSUTF8StringEncoding])"); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]]; - return; - } - - // 4. GCDAsyncSocketSSLProtocolVersionMin - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (minProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMin(sslContext, minProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]]; - return; - } - - // 5. GCDAsyncSocketSSLProtocolVersionMax - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (maxProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMax(sslContext, maxProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]]; - return; - } - - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart]; - if ([value isKindOfClass:[NSNumber class]]) - { - NSNumber *falseStart = (NSNumber *)value; - status = SSLSetSessionOption(sslContext, - kSSLSessionOptionFalseStart, - [falseStart boolValue]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]]; - return; - } - - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord]; - if ([value isKindOfClass:[NSNumber class]]) - { - NSNumber *oneByteRecord = (NSNumber *)value; - status = SSLSetSessionOption(sslContext, - kSSLSessionOptionSendOneByteRecord, - [oneByteRecord boolValue]); - if (status != noErr) - { - [self closeWithError: - [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord." - @" Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]]; - return; - } - - // 8. GCDAsyncSocketSSLCipherSuites - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; - if ([value isKindOfClass:[NSArray class]]) - { - NSArray *cipherSuites = (NSArray *)value; - NSUInteger numberCiphers = [cipherSuites count]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wvla" - SSLCipherSuite ciphers[numberCiphers]; -#pragma clang diagnostic pop - - NSUInteger cipherIndex; - for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) - { - NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; - ciphers[cipherIndex] = (SSLCipherSuite)[cipherObject unsignedIntValue]; - } - - status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]]; - return; - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]]; - return; - } - - // 9. GCDAsyncSocketSSLDiffieHellmanParameters - -#if !TARGET_OS_IPHONE - value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *diffieHellmanData = (NSData *)value; - - status = SSLSetDiffieHellmanParams(sslContext, - [diffieHellmanData bytes], - [diffieHellmanData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; - return; - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]]; - return; - } -#endif - - // 10. kCFStreamSSLCertificates - value = [tlsSettings objectForKey:GCDAsyncSocketSSLALPN]; - if ([value isKindOfClass:[NSArray class]]) - { - CFArrayRef protocols = (__bridge CFArrayRef)((NSArray *) value); - status = SSLSetALPNProtocols(sslContext, protocols); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetALPNProtocols"]]; - return; - } - } - else if (value) - { - NSAssert(NO, - @"Invalid value for GCDAsyncSocketSSLALPN. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLALPN."]]; - return; - } - - // DEPRECATED checks - - // 10. kCFStreamSSLAllowsAnyRoot - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot]; -#pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]]; - return; - } - - // 11. kCFStreamSSLAllowsExpiredRoots - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots]; -#pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]]; - return; - } - - // 12. kCFStreamSSLValidatesCertificateChain - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain]; -#pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]]; - return; - } - - // 13. kCFStreamSSLAllowsExpiredCertificates - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates]; -#pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]]; - return; - } - - // 14. kCFStreamSSLLevel - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel]; -#pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel" - @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]]; - return; - } - - // Setup the sslPreBuffer - // - // Any data in the preBuffer needs to be moved into the sslPreBuffer, - // as this data is now part of the secure read stream. - - sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - - size_t preBufferLength = [preBuffer availableBytes]; - - if (preBufferLength > 0) - { - [sslPreBuffer ensureCapacityForWrite:preBufferLength]; - - memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength); - [preBuffer didRead:preBufferLength]; - [sslPreBuffer didWrite:preBufferLength]; - } - - sslErrCode = lastSSLHandshakeError = noErr; - - // Start the SSL Handshake process - - [self ssl_continueSSLHandshake]; -} - -- (void)ssl_continueSSLHandshake -{ - LogTrace(); - - // If the return value is noErr, the session is ready for normal secure communication. - // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again. - // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the - // server and then call SSLHandshake again to resume the handshake or close the connection - // errSSLPeerBadCert SSL error. - // Otherwise, the return value indicates an error code. - - OSStatus status = SSLHandshake(sslContext); - lastSSLHandshakeError = status; - - if (status == noErr) - { - LogVerbose(@"SSLHandshake complete"); - - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - else if (status == errSSLPeerAuthCompleted) - { - LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval"); - - __block SecTrustRef trust = NULL; - status = SSLCopyPeerTrust(sslContext, &trust); - if (status != noErr) - { - [self closeWithError:[self sslError:status]]; - return; - } - - int aStateIndex = stateIndex; - dispatch_queue_t theSocketQueue = socketQueue; - - __weak GCDAsyncSocket *weakSelf = self; - - void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - dispatch_async(theSocketQueue, ^{ @autoreleasepool { - - if (trust) { - CFRelease(trust); - trust = NULL; - } - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf) - { - [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex]; - } - }}); - -#pragma clang diagnostic pop - }}; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)]) - { - dispatch_async(delegateQueue, - ^{ @autoreleasepool { - - [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler]; - }}); - } - else - { - if (trust) { - CFRelease(trust); - trust = NULL; - } - - NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings," - @" but delegate doesn't implement socket:shouldTrustPeer:"; - - [self closeWithError:[self otherError:msg]]; - return; - } - } - else if (status == errSSLWouldBlock) - { - LogVerbose(@"SSLHandshake continues..."); - - // Handshake continues... - // - // This method will be called again from doReadData or doWriteData. - } - else - { - [self closeWithError:[self sslError:status]]; - } -} - -- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex -{ - LogTrace(); - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)"); - - // One of the following is true - // - the socket was disconnected - // - the startTLS operation timed out - // - the completionHandler was already invoked once - - return; - } - - // Increment stateIndex to ensure completionHandler can only be called once. - stateIndex++; - - if (shouldTrust) - { - NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, - @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", - (int)lastSSLHandshakeError); - [self ssl_continueSSLHandshake]; - } - else - { - [self closeWithError:[self sslError:errSSLPeerBadCert]]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -- (void)cf_finishSSLHandshake -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } -} - -- (void)cf_abortSSLHandshake:(NSError *)error -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - [self closeWithError:error]; - } -} - -- (void)cf_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via CFStream)..."); - - if ([preBuffer availableBytes] > 0) - { - NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - [self suspendReadSource]; - [self suspendWriteSource]; - - socketFDBytesAvailable = 0; - flags &= ~kSocketCanAcceptBytes; - flags &= ~kSecureSocketHasBytesAvailable; - - flags |= kUsingCFStreamForTLS; - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:YES]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], - @"Invalid read packet for startTLS"); - NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], - @"Invalid write packet for startTLS"); - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings; - - // Getting an error concerning kCFStreamPropertySSLSettings ? - // CFNetwork/CFStream.h is imported for SSL constants needed by CFStream TLS support. - - BOOL r1 = CFReadStreamSetProperty(readStream, - kCFStreamPropertySSLSettings, - tlsSettings); - BOOL r2 = CFWriteStreamSetProperty(writeStream, - kCFStreamPropertySSLSettings, - tlsSettings); - - // For some reason, starting around the time of iOS 4.3, - // the first call to set the kCFStreamPropertySSLSettings will return true, - // but the second will return false. - // - // Order doesn't seem to matter. - // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order. - // Either way, the first call will return true, and the second returns false. - // - // Interestingly, this doesn't seem to affect anything. - // Which is not altogether unusual, as the documentation seems to suggest that (for many settings) - // setting it on one side of the stream automatically sets it for the other side of the stream. - // - // Although there isn't anything in the documentation to suggest that the second attempt would fail. - // - // Furthermore, this only seems to affect streams that are negotiating a security upgrade. - // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure - // connection, and then a startTLS is issued. - // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS). - - if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug. - { - [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error in CFStreamOpen"]]; - return; - } - - LogVerbose(@"Waiting for SSL Handshake to complete..."); -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -+ (void)ignore:(id)_ -{} - -+ (void)startCFStreamThreadIfNeeded -{ - LogTrace(); - - static dispatch_once_t predicate; - dispatch_once(&predicate, - ^{ - - cfstreamThreadRetainCount = 0; - cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", - DISPATCH_QUEUE_SERIAL); - }); - - dispatch_sync(cfstreamThreadSetupQueue, - ^{ @autoreleasepool { - - if (++cfstreamThreadRetainCount == 1) - { - cfstreamThread = [[NSThread alloc] initWithTarget:self - selector:@selector(cfstreamThread:) - object:nil]; - [cfstreamThread start]; - } - }}); -} - -+ (void)stopCFStreamThreadIfNeeded -{ - LogTrace(); - - // The creation of the cfstreamThread is relatively expensive. - // So we'd like to keep it available for recycling. - // However, there's a tradeoff here, because it shouldn't remain alive forever. - // So what we're going to do is use a little delay before taking it down. - // This way it can be reused properly in situations where multiple sockets are continually in flux. - - int delayInSeconds = 30; - dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wimplicit-retain-self" - - if (cfstreamThreadRetainCount == 0) - { - LogWarn(@"Logic error concerning cfstreamThread start / stop"); - return_from_block; - } - - if (--cfstreamThreadRetainCount == 0) - { - [cfstreamThread cancel]; // set isCancelled flag - - // wake up the thread - [[self class] performSelector:@selector(ignore:) - onThread:cfstreamThread - withObject:[NSNull null] - waitUntilDone:NO]; - - cfstreamThread = nil; - } - -#pragma clang diagnostic pop - }}); -} - -+ (void)cfstreamThread:(id)unused { @autoreleasepool - { - [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; - - LogInfo(@"CFStreamThread: Started"); - - // We can't run the run loop unless it has an associated input source or a timer. - // So we'll just create a timer that will never fire - unless the server runs for decades. - [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] - target:self - selector:@selector(ignore:) - userInfo:nil - repeats:YES]; - - NSThread *currentThread = [NSThread currentThread]; - NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; - - BOOL isCancelled = [currentThread isCancelled]; - - while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) - { - isCancelled = [currentThread isCancelled]; - } - - LogInfo(@"CFStreamThread: Stopped"); - }} - -+ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, - @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, - runLoop, - kCFRunLoopDefaultMode); -} - -+ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, - @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, - runLoop, - kCFRunLoopDefaultMode); -} - -static void CFReadStreamCallback (CFReadStreamRef stream, - CFStreamEventType type, - void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventHasBytesAvailable: - { - dispatch_async(asyncSocket->socketQueue, - ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFReadStreamHasBytesAvailable(asyncSocket->readStream)) - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket doReadData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, - ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - Other"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } -#pragma clang diagnostic pop -} - -static void CFWriteStreamCallback (CFWriteStreamRef stream, - CFStreamEventType type, - void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventCanAcceptBytes: - { - dispatch_async(asyncSocket->socketQueue, - ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream)) - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket doWriteData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, - ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - Other"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } -#pragma clang diagnostic pop -} - -- (BOOL)createReadAndWriteStream -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - - if (readStream || writeStream) - { - // Streams already created - return YES; - } - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - if (socketFD == SOCKET_NULL) - { - // Cannot create streams without a file descriptor - return NO; - } - - if (![self isConnected]) - { - // Cannot create streams until file descriptor is connected - return NO; - } - - LogVerbose(@"Creating read and write stream..."); - - CFStreamCreatePairWithSocket(NULL, - (CFSocketNativeHandle)socketFD, - &readStream, - &writeStream); - - // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case). - // But let's not take any chances. - - if (readStream) - CFReadStreamSetProperty(readStream, - kCFStreamPropertyShouldCloseNativeSocket, - kCFBooleanFalse); - if (writeStream) - CFWriteStreamSetProperty(writeStream, - kCFStreamPropertyShouldCloseNativeSocket, - kCFBooleanFalse); - - if ((readStream == NULL) || (writeStream == NULL)) - { - LogWarn(@"Unable to create read and write stream..."); - - if (readStream) - { - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - - return NO; - } - - return YES; -} - -- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite -{ - LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO")); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), - @"Read/Write stream is null"); - - streamContext.version = 0; - streamContext.info = (__bridge void *)(self); - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - readStreamEvents |= kCFStreamEventHasBytesAvailable; - - if (!CFReadStreamSetClient(readStream, - readStreamEvents, - &CFReadStreamCallback, - &streamContext)) - { - return NO; - } - - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - writeStreamEvents |= kCFStreamEventCanAcceptBytes; - - if (!CFWriteStreamSetClient(writeStream, - writeStreamEvents, - &CFWriteStreamCallback, - &streamContext)) - { - return NO; - } - - return YES; -} - -- (BOOL)addStreamsToRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), - @"Read/Write stream is null"); - - if (!(flags & kAddedStreamsToRunLoop)) - { - LogVerbose(@"Adding streams to runloop..."); - - [[self class] startCFStreamThreadIfNeeded]; - dispatch_sync(cfstreamThreadSetupQueue, ^{ - [[self class] performSelector:@selector(scheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - }); - flags |= kAddedStreamsToRunLoop; - } - - return YES; -} - -- (void)removeStreamsFromRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), - @"Read/Write stream is null"); - - if (flags & kAddedStreamsToRunLoop) - { - LogVerbose(@"Removing streams from runloop..."); - - dispatch_sync(cfstreamThreadSetupQueue, ^{ - [[self class] performSelector:@selector(unscheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - }); - [[self class] stopCFStreamThreadIfNeeded]; - - flags &= ~kAddedStreamsToRunLoop; - } -} - -- (BOOL)openStreams -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), - @"Read/Write stream is null"); - - CFStreamStatus readStatus = CFReadStreamGetStatus(readStream); - CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream); - - if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) - { - LogVerbose(@"Opening read and write stream..."); - - BOOL r1 = CFReadStreamOpen(readStream); - BOOL r2 = CFWriteStreamOpen(writeStream); - - if (!r1 || !r2) - { - LogError(@"Error in CFStreamOpen"); - return NO; - } - } - - return YES; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Advanced -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * See header file for big discussion of this method. - **/ -- (BOOL)autoDisconnectOnClosedReadStream -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kAllowHalfDuplexConnection) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((self->config & kAllowHalfDuplexConnection) == 0); - }); - - return result; - } -} - -/** - * See header file for big discussion of this method. - **/ -- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - dispatch_block_t block = ^{ - - if (flag) - self->config &= ~kAllowHalfDuplexConnection; - else - self->config |= kAllowHalfDuplexConnection; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - - -/** - * See header file for big discussion of this method. - **/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue -{ - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketNewTargetQueue, - IsOnSocketQueueOrTargetQueueKey, - nonNullUnusedPointer, - NULL); -} - -/** - * See header file for big discussion of this method. - **/ -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue -{ - dispatch_queue_set_specific(socketOldTargetQueue, - IsOnSocketQueueOrTargetQueueKey, - NULL, - NULL); -} - -/** - * See header file for big discussion of this method. - **/ -- (void)performBlock:(dispatch_block_t)block -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -/** - * Questions? Have you read the header file? - **/ -- (int)socketFD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return SOCKET_NULL; - } - - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; -} - -/** - * Questions? Have you read the header file? - **/ -- (int)socket4FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return SOCKET_NULL; - } - - return socket4FD; -} - -/** - * Questions? Have you read the header file? - **/ -- (int)socket6FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return SOCKET_NULL; - } - - return socket6FD; -} - -#if TARGET_OS_IPHONE - -/** - * Questions? Have you read the header file? - **/ -- (CFReadStreamRef)readStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return NULL; - } - - if (readStream == NULL) - [self createReadAndWriteStream]; - - return readStream; -} - -/** - * Questions? Have you read the header file? - **/ -- (CFWriteStreamRef)writeStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return NULL; - } - - if (writeStream == NULL) - [self createReadAndWriteStream]; - - return writeStream; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat -{ - if (![self createReadAndWriteStream]) - { - // Error occurred creating streams (perhaps socket isn't open) - return NO; - } - - BOOL r1, r2; - - LogVerbose(@"Enabling backgrouding on socket"); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - r1 = CFReadStreamSetProperty(readStream, - kCFStreamNetworkServiceType, - kCFStreamNetworkServiceTypeVoIP); - r2 = CFWriteStreamSetProperty(writeStream, - kCFStreamNetworkServiceType, - kCFStreamNetworkServiceTypeVoIP); -#pragma clang diagnostic pop - - if (!r1 || !r2) - { - return NO; - } - - if (!caveat) - { - if (![self openStreams]) - { - return NO; - } - } - - return YES; -} - -/** - * Questions? Have you read the header file? - **/ -- (BOOL)enableBackgroundingOnSocket -{ - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:NO]; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.??? -{ - // This method was created as a workaround for a bug in iOS. - // Apple has since fixed this bug. - // I'm not entirely sure which version of iOS they fixed it in... - - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:YES]; -} - -#endif - -- (SSLContextRef)sslContext -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", - THIS_METHOD); - return NULL; - } - - return sslContext; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr -{ - LogTrace(); - - NSMutableArray *addresses = nil; - NSError *error = nil; - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in nativeAddr4; - nativeAddr4.sin_len = sizeof(struct sockaddr_in); - nativeAddr4.sin_family = AF_INET; - nativeAddr4.sin_port = htons(port); - nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); - - struct sockaddr_in6 nativeAddr6; - nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); - nativeAddr6.sin6_family = AF_INET6; - nativeAddr6.sin6_port = htons(port); - nativeAddr6.sin6_flowinfo = 0; - nativeAddr6.sin6_addr = in6addr_loopback; - nativeAddr6.sin6_scope_id = 0; - - // Wrap the native address structures - - NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - - addresses = [NSMutableArray arrayWithCapacity:2]; - [addresses addObject:address4]; - [addresses addObject:address6]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - int gai_error = getaddrinfo([host UTF8String], - [portStr UTF8String], - &hints, - &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - NSUInteger capacity = 0; - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { - capacity++; - } - } - - addresses = [NSMutableArray arrayWithCapacity:capacity]; - - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET) - { - // Found IPv4 address. - // Wrap the native address structure, and add to results. - - NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address4]; - } - else if (res->ai_family == AF_INET6) - { - // Fixes connection issues with IPv6 - // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 - - // Found IPv6 address. - // Wrap the native address structure, and add to results. - - struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; - in_port_t *portPtr = &sockaddr->sin6_port; - if ((portPtr != NULL) && (*portPtr == 0)) { - *portPtr = htons(port); - } - - NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address6]; - } - } - freeaddrinfo(res0); - - if ([addresses count] == 0) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - if (errPtr) *errPtr = error; - return addresses; -} - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if (inet_ntop(AF_INET, - &pSockaddr4->sin_addr, - addrBuf, - (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if (inet_ntop(AF_INET6, - &pSockaddr6->sin6_addr, - addrBuf, - (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - return ntohs(pSockaddr4->sin_port); -} - -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - return ntohs(pSockaddr6->sin6_port); -} - -+ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr -{ - NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path]; - return [NSURL fileURLWithPath:path]; -} - -+ (NSString *)hostFromAddress:(NSData *)address -{ - NSString *host; - - if ([self getHost:&host port:NULL fromAddress:address]) - return host; - else - return nil; -} - -+ (uint16_t)portFromAddress:(NSData *)address -{ - uint16_t port; - - if ([self getHost:NULL port:&port fromAddress:address]) - return port; - else - return 0; -} - -+ (BOOL)isIPv4Address:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = (const struct sockaddr *)(const void *)[address bytes]; - - if (sockaddrX->sa_family == AF_INET) { - return YES; - } - } - - return NO; -} - -+ (BOOL)isIPv6Address:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = (const struct sockaddr *)(const void *)[address bytes]; - - if (sockaddrX->sa_family == AF_INET6) { - return YES; - } - } - - return NO; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address -{ - return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = (const struct sockaddr *)(const void *)[address bytes]; - - if (sockaddrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - struct sockaddr_in sockaddr4; - memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; - if (afPtr) *afPtr = AF_INET; - - return YES; - } - } - else if (sockaddrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - struct sockaddr_in6 sockaddr6; - memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; - if (afPtr) *afPtr = AF_INET6; - - return YES; - } - } - } - - return NO; -} - -+ (NSData *)CRLFData -{ - return [NSData dataWithBytes:"\x0D\x0A" length:2]; -} - -+ (NSData *)CRData -{ - return [NSData dataWithBytes:"\x0D" length:1]; -} - -+ (NSData *)LFData -{ - return [NSData dataWithBytes:"\x0A" length:1]; -} - -+ (NSData *)ZeroData -{ - return [NSData dataWithBytes:"" length:1]; -} - -@end - -#pragma clang diagnostic pop diff --git a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h deleted file mode 100644 index af327e0863..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h +++ /dev/null @@ -1,1036 +0,0 @@ -// -// GCDAsyncUdpSocket -// -// This class is in the public domain. -// Originally created by Robbie Hanson of Deusty LLC. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN -extern NSString *const GCDAsyncUdpSocketException; -extern NSString *const GCDAsyncUdpSocketErrorDomain; - -extern NSString *const GCDAsyncUdpSocketQueueName; -extern NSString *const GCDAsyncUdpSocketThreadName; - -typedef NS_ERROR_ENUM(GCDAsyncUdpSocketErrorDomain, GCDAsyncUdpSocketError) { - GCDAsyncUdpSocketNoError = 0, // Never used - GCDAsyncUdpSocketBadConfigError, // Invalid configuration - GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed - GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out - GCDAsyncUdpSocketClosedError, // The socket was closed - GCDAsyncUdpSocketOtherError, // Description provided in userInfo -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@class GCDAsyncUdpSocket; - -@protocol GCDAsyncUdpSocketDelegate -@optional - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection is successful. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection fails. - * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error; - -/** - * Called when the datagram with the given tag has been sent. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; - -/** - * Called if an error occurs while trying to send a datagram. - * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error; - -/** - * Called when the socket has received the requested datagram. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data - fromAddress:(NSData *)address - withFilterContext:(nullable id)filterContext; - -/** - * Called when the socket is closed. -**/ -- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error; - -@end - -/** - * You may optionally set a receive filter for the socket. - * A filter can provide several useful features: - * - * 1. Many times udp packets need to be parsed. - * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. - * The end result is a parallel socket io, datagram parsing, and packet processing. - * - * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. - * The filter can prevent such packets from arriving at the delegate. - * And because the filter can run in its own independent queue, this doesn't slow down the delegate. - * - * - Since the udp protocol does not guarantee delivery, udp packets may be lost. - * Many protocols built atop udp thus provide various resend/re-request algorithms. - * This sometimes results in duplicate packets arriving. - * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. - * - * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. - * Such packets need to be ignored. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * @param data - The packet that was received. - * @param address - The address the data was received from. - * See utilities section for methods to extract info from address. - * @param context - Out parameter you may optionally set, which will then be passed to the delegate method. - * For example, filter block can parse the data and then, - * pass the parsed data to the delegate. - * - * @returns - YES if the received packet should be passed onto the delegate. - * NO if the received packet should be discarded, and not reported to the delegete. - * - * Example: - * - * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { - * - * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; - * - * *context = response; - * return (response != nil); - * }; - * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; - * -**/ -typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context); - -/** - * You may optionally set a send filter for the socket. - * A filter can provide several interesting possibilities: - * - * 1. Optional caching of resolved addresses for domain names. - * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. - * - * 2. Reusable modules of code for bandwidth monitoring. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * @param data - The packet that was received. - * @param address - The address the data was received from. - * See utilities section for methods to extract info from address. - * @param tag - The tag that was passed in the send method. - * - * @returns - YES if the packet should actually be sent over the socket. - * NO if the packet should be silently dropped (not sent over the socket). - * - * Regardless of the return value, the delegate will be informed that the packet was successfully sent. - * -**/ -typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag); - - -@interface GCDAsyncUdpSocket : NSObject - -/** - * GCDAsyncUdpSocket uses the standard delegate paradigm, - * but executes all delegate callbacks on a given delegate dispatch queue. - * This allows for maximum concurrency, while at the same time providing easy thread safety. - * - * You MUST set a delegate AND delegate dispatch queue before attempting to - * use the socket, or you will get an error. - * - * The socket queue is optional. - * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue. - * If you choose to provide a socket queue, the socket queue must not be a concurrent queue, - * then please see the discussion for the method markSocketQueueTargetQueue. - * - * The delegate queue and socket queue can optionally be the same. -**/ -- (instancetype)init; -- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; -- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq; -- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER; - -#pragma mark Configuration - -- (nullable id)delegate; -- (void)setDelegate:(nullable id)delegate; -- (void)synchronouslySetDelegate:(nullable id)delegate; - -- (nullable dispatch_queue_t)delegateQueue; -- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; - -- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; -- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; - -/** - * By default, both IPv4 and IPv6 are enabled. - * - * This means GCDAsyncUdpSocket automatically supports both protocols, - * and can send to IPv4 or IPv6 addresses, - * as well as receive over IPv4 and IPv6. - * - * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6. - * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4. - * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6. - * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference. - * If IPv4 is preferred, then IPv4 is used. - * If IPv6 is preferred, then IPv6 is used. - * If neutral, then the first IP version in the resolved array will be used. - * - * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral. - * On prior systems the default IP preference is IPv4. - **/ -- (BOOL)isIPv4Enabled; -- (void)setIPv4Enabled:(BOOL)flag; - -- (BOOL)isIPv6Enabled; -- (void)setIPv6Enabled:(BOOL)flag; - -- (BOOL)isIPv4Preferred; -- (BOOL)isIPv6Preferred; -- (BOOL)isIPVersionNeutral; - -- (void)setPreferIPv4; -- (void)setPreferIPv6; -- (void)setIPVersionNeutral; - -/** - * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. - * The default maximum size is 65535 bytes. - * - * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. - * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. - * - * Since the OS/GCD notifies us of the size of each received UDP packet, - * the actual allocated buffer size for each packet is exact. - * And in practice the size of UDP packets is generally much smaller than the max. - * Indeed most protocols will send and receive packets of only a few bytes, - * or will set a limit on the size of packets to prevent fragmentation in the IP layer. - * - * If you set the buffer size too small, the sockets API in the OS will silently discard - * any extra data, and you will not be notified of the error. -**/ -- (uint16_t)maxReceiveIPv4BufferSize; -- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max; - -- (uint32_t)maxReceiveIPv6BufferSize; -- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max; - -/** - * Gets/Sets the maximum size of the buffer that will be allocated for send operations. - * The default maximum size is 65535 bytes. - * - * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be - * fragmented, and that’s both expensive and risky (if one fragment goes missing, the - * entire datagram is lost). You are much better off sending a large number of smaller - * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation. - * - * You must set it before the sockt is created otherwise it won't work. - * - **/ -- (uint16_t)maxSendBufferSize; -- (void)setMaxSendBufferSize:(uint16_t)max; - -/** - * User data allows you to associate arbitrary information with the socket. - * This data is not used internally in any way. -**/ -- (nullable id)userData; -- (void)setUserData:(nullable id)arbitraryUserData; - -#pragma mark Diagnostics - -/** - * Returns the local address info for the socket. - * - * The localAddress method returns a sockaddr structure wrapped in a NSData object. - * The localHost method returns the human readable IP address as a string. - * - * Note: Address info may not be available until after the socket has been binded, connected - * or until after data has been sent. -**/ -- (nullable NSData *)localAddress; -- (nullable NSString *)localHost; -- (uint16_t)localPort; - -- (nullable NSData *)localAddress_IPv4; -- (nullable NSString *)localHost_IPv4; -- (uint16_t)localPort_IPv4; - -- (nullable NSData *)localAddress_IPv6; -- (nullable NSString *)localHost_IPv6; -- (uint16_t)localPort_IPv6; - -/** - * Returns the remote address info for the socket. - * - * The connectedAddress method returns a sockaddr structure wrapped in a NSData object. - * The connectedHost method returns the human readable IP address as a string. - * - * Note: Since UDP is connectionless by design, connected address info - * will not be available unless the socket is explicitly connected to a remote host/port. - * If the socket is not connected, these methods will return nil / 0. -**/ -- (nullable NSData *)connectedAddress; -- (nullable NSString *)connectedHost; -- (uint16_t)connectedPort; - -/** - * Returns whether or not this socket has been connected to a single host. - * By design, UDP is a connectionless protocol, and connecting is not needed. - * If connected, the socket will only be able to send/receive data to/from the connected host. -**/ -- (BOOL)isConnected; - -/** - * Returns whether or not this socket has been closed. - * The only way a socket can be closed is if you explicitly call one of the close methods. -**/ -- (BOOL)isClosed; - -/** - * Returns whether or not this socket is IPv4. - * - * By default this will be true, unless: - * - IPv4 is disabled (via setIPv4Enabled:) - * - The socket is explicitly bound to an IPv6 address - * - The socket is connected to an IPv6 address -**/ -- (BOOL)isIPv4; - -/** - * Returns whether or not this socket is IPv6. - * - * By default this will be true, unless: - * - IPv6 is disabled (via setIPv6Enabled:) - * - The socket is explicitly bound to an IPv4 address - * _ The socket is connected to an IPv4 address - * - * This method will also return false on platforms that do not support IPv6. - * Note: The iPhone does not currently support IPv6. -**/ -- (BOOL)isIPv6; - -#pragma mark Binding - -/** - * Binds the UDP socket to the given port. - * Binding should be done for server sockets that receive data prior to sending it. - * Client sockets can skip binding, - * as the OS will automatically assign the socket an available port when it starts sending data. - * - * You may optionally pass a port number of zero to immediately bind the socket, - * yet still allow the OS to automatically assign an available port. - * - * You cannot bind a socket after its been connected. - * You can only bind a socket once. - * You can still connect a socket (if desired) after binding. - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. -**/ -- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * Binds the UDP socket to the given port and optional interface. - * Binding should be done for server sockets that receive data prior to sending it. - * Client sockets can skip binding, - * as the OS will automatically assign the socket an available port when it starts sending data. - * - * You may optionally pass a port number of zero to immediately bind the socket, - * yet still allow the OS to automatically assign an available port. - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept packets from the local machine. - * - * You cannot bind a socket after its been connected. - * You can only bind a socket once. - * You can still connect a socket (if desired) after binding. - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. -**/ -- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr; - -/** - * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * Binding should be done for server sockets that receive data prior to sending it. - * Client sockets can skip binding, - * as the OS will automatically assign the socket an available port when it starts sending data. - * - * You cannot bind a socket after its been connected. - * You can only bind a socket once. - * You can still connect a socket (if desired) after binding. - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. -**/ -- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr; - -#pragma mark Connecting - -/** - * Connects the UDP socket to the given host and port. - * By design, UDP is a connectionless protocol, and connecting is not needed. - * - * Choosing to connect to a specific host/port has the following effect: - * - You will only be able to send data to the connected host/port. - * - You will only be able to receive data from the connected host/port. - * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". - * - * The actual process of connecting a UDP socket does not result in any communication on the socket. - * It simply changes the internal state of the socket. - * - * You cannot bind a socket after it has been connected. - * You can only connect a socket once. - * - * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * - * This method is asynchronous as it requires a DNS lookup to resolve the given host name. - * If an obvious error is detected, this method immediately returns NO and sets errPtr. - * If you don't care about the error, you can pass nil for errPtr. - * Otherwise, this method returns YES and begins the asynchronous connection process. - * The result of the asynchronous connection process will be reported via the delegate methods. - **/ -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * By design, UDP is a connectionless protocol, and connecting is not needed. - * - * Choosing to connect to a specific address has the following effect: - * - You will only be able to send data to the connected address. - * - You will only be able to receive data from the connected address. - * - You will receive ICMP messages that come from the connected address, such as "connection refused". - * - * Connecting a UDP socket does not result in any communication on the socket. - * It simply changes the internal state of the socket. - * - * You cannot bind a socket after its been connected. - * You can only connect a socket once. - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. - * - * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup. - * Thus when this method returns, the connection has either failed or fully completed. - * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method. - * However, for compatibility and simplification of delegate code, if this method returns YES - * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -#pragma mark Multicast - -/** - * Join multicast group. - * Group should be an IP address (eg @"225.228.0.1"). - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. -**/ -- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; - -/** - * Join multicast group. - * Group should be an IP address (eg @"225.228.0.1"). - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. -**/ -- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; - -- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; -- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; - -/** - * Send multicast on a specified interface. - * For IPv4, interface should be the the IP address of the interface (eg @"192.168.10.1"). - * For IPv6, interface should be the a network interface name (eg @"en0"). - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. -**/ - -- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; -- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; - -#pragma mark Reuse Port - -/** - * By default, only one socket can be bound to a given IP address + port at a time. - * To enable multiple processes to simultaneously bind to the same address+port, - * you need to enable this functionality in the socket. All processes that wish to - * use the address+port simultaneously must all enable reuse port on the socket - * bound to that port. - **/ -- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; - -#pragma mark Broadcast - -/** - * By default, the underlying socket in the OS will not allow you to send broadcast messages. - * In order to send broadcast messages, you need to enable this functionality in the socket. - * - * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is - * delivered to every host on the network. - * The reason this is generally disabled by default (by the OS) is to prevent - * accidental broadcast messages from flooding the network. -**/ -- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; - -#pragma mark Sending - -/** - * Asynchronously sends the given data, with the given timeout and tag. - * - * This method may only be used with a connected socket. - * Recall that connecting is optional for a UDP socket. - * For connected sockets, data can only be sent to the connected address. - * For non-connected sockets, the remote destination is specified for each packet. - * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. - * - * @param data - * The data to send. - * If data is nil or zero-length, this method does nothing. - * If passing NSMutableData, please read the thread-safety notice below. - * - * @param timeout - * The timeout for the send opeartion. - * If the timeout value is negative, the send operation will not use a timeout. - * - * @param tag - * The tag is for your convenience. - * It is not sent or received over the socket in any manner what-so-ever. - * It is reported back as a parameter in the udpSocket:didSendDataWithTag: - * or udpSocket:didNotSendDataWithTag:dueToError: methods. - * You can use it as an array index, state id, type constant, etc. - * - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method - * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying - * that this particular send operation has completed. - * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. - * It simply retains it for performance reasons. - * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. - * Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time - * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. -**/ -- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. - * - * This method cannot be used with a connected socket. - * Recall that connecting is optional for a UDP socket. - * For connected sockets, data can only be sent to the connected address. - * For non-connected sockets, the remote destination is specified for each packet. - * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. - * - * @param data - * The data to send. - * If data is nil or zero-length, this method does nothing. - * If passing NSMutableData, please read the thread-safety notice below. - * - * @param host - * The destination to send the udp packet to. - * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * You may also use the convenience strings of "loopback" or "localhost". - * - * @param port - * The port of the host to send to. - * - * @param timeout - * The timeout for the send opeartion. - * If the timeout value is negative, the send operation will not use a timeout. - * - * @param tag - * The tag is for your convenience. - * It is not sent or received over the socket in any manner what-so-ever. - * It is reported back as a parameter in the udpSocket:didSendDataWithTag: - * or udpSocket:didNotSendDataWithTag:dueToError: methods. - * You can use it as an array index, state id, type constant, etc. - * - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method - * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying - * that this particular send operation has completed. - * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. - * It simply retains it for performance reasons. - * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. - * Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time - * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. -**/ -- (void)sendData:(NSData *)data - toHost:(NSString *)host - port:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - tag:(long)tag; - -/** - * Asynchronously sends the given data, with the given timeout and tag, to the given address. - * - * This method cannot be used with a connected socket. - * Recall that connecting is optional for a UDP socket. - * For connected sockets, data can only be sent to the connected address. - * For non-connected sockets, the remote destination is specified for each packet. - * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. - * - * @param data - * The data to send. - * If data is nil or zero-length, this method does nothing. - * If passing NSMutableData, please read the thread-safety notice below. - * - * @param remoteAddr - * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object). - * - * @param timeout - * The timeout for the send opeartion. - * If the timeout value is negative, the send operation will not use a timeout. - * - * @param tag - * The tag is for your convenience. - * It is not sent or received over the socket in any manner what-so-ever. - * It is reported back as a parameter in the udpSocket:didSendDataWithTag: - * or udpSocket:didNotSendDataWithTag:dueToError: methods. - * You can use it as an array index, state id, type constant, etc. - * - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method - * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying - * that this particular send operation has completed. - * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. - * It simply retains it for performance reasons. - * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. - * Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time - * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. -**/ -- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * You may optionally set a send filter for the socket. - * A filter can provide several interesting possibilities: - * - * 1. Optional caching of resolved addresses for domain names. - * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. - * - * 2. Reusable modules of code for bandwidth monitoring. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef. - * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. - * - * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), - * passing YES for the isAsynchronous parameter. -**/ -- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; - -/** - * The receive filter can be run via dispatch_async or dispatch_sync. - * Most typical situations call for asynchronous operation. - * - * However, there are a few situations in which synchronous operation is preferred. - * Such is the case when the filter is extremely minimal and fast. - * This is because dispatch_sync is faster than dispatch_async. - * - * If you choose synchronous operation, be aware of possible deadlock conditions. - * Since the socket queue is executing your block via dispatch_sync, - * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. - * For example, you can't query properties on the socket. -**/ -- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock - withQueue:(nullable dispatch_queue_t)filterQueue - isAsynchronous:(BOOL)isAsynchronous; - -#pragma mark Receiving - -/** - * There are two modes of operation for receiving packets: one-at-a-time & continuous. - * - * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. - * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, - * where your state machine may not always be ready to process incoming packets. - * - * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. - * Receiving packets continuously is better suited to real-time streaming applications. - * - * You may switch back and forth between one-at-a-time mode and continuous mode. - * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode. - * - * When a packet is received (and not filtered by the optional receive filter), - * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. - * - * If the socket is able to begin receiving packets, this method returns YES. - * Otherwise it returns NO, and sets the errPtr with appropriate error information. - * - * An example error: - * You created a udp socket to act as a server, and immediately called receive. - * You forgot to first bind the socket to a port number, and received a error with a message like: - * "Must bind socket before you can receive data." -**/ -- (BOOL)receiveOnce:(NSError **)errPtr; - -/** - * There are two modes of operation for receiving packets: one-at-a-time & continuous. - * - * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. - * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, - * where your state machine may not always be ready to process incoming packets. - * - * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. - * Receiving packets continuously is better suited to real-time streaming applications. - * - * You may switch back and forth between one-at-a-time mode and continuous mode. - * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode. - * - * For every received packet (not filtered by the optional receive filter), - * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. - * - * If the socket is able to begin receiving packets, this method returns YES. - * Otherwise it returns NO, and sets the errPtr with appropriate error information. - * - * An example error: - * You created a udp socket to act as a server, and immediately called receive. - * You forgot to first bind the socket to a port number, and received a error with a message like: - * "Must bind socket before you can receive data." -**/ -- (BOOL)beginReceiving:(NSError **)errPtr; - -/** - * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving. - * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again. - * - * Important Note: - * GCDAsyncUdpSocket may be running in parallel with your code. - * That is, your delegate is likely running on a separate thread/dispatch_queue. - * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked. - * Thus, if those delegate methods have already been dispatch_async'd, - * your didReceive delegate method may still be invoked after this method has been called. - * You should be aware of this, and program defensively. -**/ -- (void)pauseReceiving; - -/** - * You may optionally set a receive filter for the socket. - * This receive filter may be set to run in its own queue (independent of delegate queue). - * - * A filter can provide several useful features. - * - * 1. Many times udp packets need to be parsed. - * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. - * The end result is a parallel socket io, datagram parsing, and packet processing. - * - * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. - * The filter can prevent such packets from arriving at the delegate. - * And because the filter can run in its own independent queue, this doesn't slow down the delegate. - * - * - Since the udp protocol does not guarantee delivery, udp packets may be lost. - * Many protocols built atop udp thus provide various resend/re-request algorithms. - * This sometimes results in duplicate packets arriving. - * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. - * - * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. - * Such packets need to be ignored. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * Example: - * - * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { - * - * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; - * - * *context = response; - * return (response != nil); - * }; - * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; - * - * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef. - * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. - * - * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), - * passing YES for the isAsynchronous parameter. -**/ -- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; - -/** - * The receive filter can be run via dispatch_async or dispatch_sync. - * Most typical situations call for asynchronous operation. - * - * However, there are a few situations in which synchronous operation is preferred. - * Such is the case when the filter is extremely minimal and fast. - * This is because dispatch_sync is faster than dispatch_async. - * - * If you choose synchronous operation, be aware of possible deadlock conditions. - * Since the socket queue is executing your block via dispatch_sync, - * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. - * For example, you can't query properties on the socket. -**/ -- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock - withQueue:(nullable dispatch_queue_t)filterQueue - isAsynchronous:(BOOL)isAsynchronous; - -#pragma mark Closing - -/** - * Immediately closes the underlying socket. - * Any pending send operations are discarded. - * - * The GCDAsyncUdpSocket instance may optionally be used again. - * (it will setup/configure/use another unnderlying BSD socket). -**/ -- (void)close; - -/** - * Closes the underlying socket after all pending send operations have been sent. - * - * The GCDAsyncUdpSocket instance may optionally be used again. - * (it will setup/configure/use another unnderlying BSD socket). -**/ -- (void)closeAfterSending; - -#pragma mark Advanced -/** - * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. - * In most cases, the instance creates this queue itself. - * However, to allow for maximum flexibility, the internal queue may be passed in the init method. - * This allows for some advanced options such as controlling socket priority via target queues. - * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. - * - * For example, imagine there are 2 queues: - * dispatch_queue_t socketQueue; - * dispatch_queue_t socketTargetQueue; - * - * If you do this (pseudo-code): - * socketQueue.targetQueue = socketTargetQueue; - * - * Then all socketQueue operations will actually get run on the given socketTargetQueue. - * This is fine and works great in most situations. - * But if you run code directly from within the socketTargetQueue that accesses the socket, - * you could potentially get deadlock. Imagine the following code: - * - * - (BOOL)socketHasSomething - * { - * __block BOOL result = NO; - * dispatch_block_t block = ^{ - * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; - * } - * if (is_executing_on_queue(socketQueue)) - * block(); - * else - * dispatch_sync(socketQueue, block); - * - * return result; - * } - * - * What happens if you call this method from the socketTargetQueue? The result is deadlock. - * This is because the GCD API offers no mechanism to discover a queue's targetQueue. - * Thus we have no idea if our socketQueue is configured with a targetQueue. - * If we had this information, we could easily avoid deadlock. - * But, since these API's are missing or unfeasible, you'll have to explicitly set it. - * - * IF you pass a socketQueue via the init method, - * AND you've configured the passed socketQueue with a targetQueue, - * THEN you should pass the end queue in the target hierarchy. - * - * For example, consider the following queue hierarchy: - * socketQueue -> ipQueue -> moduleQueue - * - * This example demonstrates priority shaping within some server. - * All incoming client connections from the same IP address are executed on the same target queue. - * And all connections for a particular module are executed on the same target queue. - * Thus, the priority of all networking for the entire module can be changed on the fly. - * Additionally, networking traffic from a single IP cannot monopolize the module. - * - * Here's how you would accomplish something like that: - * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock - * { - * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); - * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; - * - * dispatch_set_target_queue(socketQueue, ipQueue); - * dispatch_set_target_queue(iqQueue, moduleQueue); - * - * return socketQueue; - * } - * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket - * { - * [clientConnections addObject:newSocket]; - * [newSocket markSocketQueueTargetQueue:moduleQueue]; - * } - * - * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. - * This is often NOT the case, as such queues are used solely for execution shaping. - **/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; - -/** - * It's not thread-safe to access certain variables from outside the socket's internal queue. - * - * For example, the socket file descriptor. - * File descriptors are simply integers which reference an index in the per-process file table. - * However, when one requests a new file descriptor (by opening a file or socket), - * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. - * So if we're not careful, the following could be possible: - * - * - Thread A invokes a method which returns the socket's file descriptor. - * - The socket is closed via the socket's internal queue on thread B. - * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. - * - Thread A is now accessing/altering the file instead of the socket. - * - * In addition to this, other variables are not actually objects, - * and thus cannot be retained/released or even autoreleased. - * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. - * - * Although there are internal variables that make it difficult to maintain thread-safety, - * it is important to provide access to these variables - * to ensure this class can be used in a wide array of environments. - * This method helps to accomplish this by invoking the current block on the socket's internal queue. - * The methods below can be invoked from within the block to access - * those generally thread-unsafe internal variables in a thread-safe manner. - * The given block will be invoked synchronously on the socket's internal queue. - * - * If you save references to any protected variables and use them outside the block, you do so at your own peril. -**/ -- (void)performBlock:(dispatch_block_t)block; - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's file descriptor(s). - * If the socket isn't connected, or explicity bound to a particular interface, - * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. -**/ -- (int)socketFD; -- (int)socket4FD; -- (int)socket6FD; - -#if TARGET_OS_IPHONE - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket. - * - * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.) - * However, if you need one for any reason, - * these methods are a convenient way to get access to a safe instance of one. -**/ -- (nullable CFReadStreamRef)readStream; -- (nullable CFWriteStreamRef)writeStream; - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Configures the socket to allow it to operate when the iOS application has been backgrounded. - * In other words, this method creates a read & write stream, and invokes: - * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * - * Returns YES if successful, NO otherwise. - * - * Example usage: - * - * [asyncUdpSocket performBlock:^{ - * [asyncUdpSocket enableBackgroundingOnSocket]; - * }]; - * - * - * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now). -**/ -//- (BOOL)enableBackgroundingOnSockets; - -#endif - -#pragma mark Utilities - -/** - * Extracting host/port/family information from raw address data. -**/ - -+ (nullable NSString *)hostFromAddress:(NSData *)address; -+ (uint16_t)portFromAddress:(NSData *)address; -+ (int)familyFromAddress:(NSData *)address; - -+ (BOOL)isIPv4Address:(NSData *)address; -+ (BOOL)isIPv6Address:(NSData *)address; - -+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address; -+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address; - -@end - -NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m deleted file mode 100755 index 23d05e730c..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m +++ /dev/null @@ -1,5868 +0,0 @@ -// -// GCDAsyncUdpSocket -// -// This class is in the public domain. -// Originally created by Robbie Hanson of Deusty LLC. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import "GCDAsyncUdpSocket.h" - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC -#endif - -#if TARGET_OS_IPHONE -#import -#import -#import -// Note: CFStream APIs are still used for backgrounding support and are part of CoreFoundation -// kCFStreamPropertyShouldCloseNativeSocket is available from CoreFoundation/CFStream.h -#endif - -#import -#import -#import -#import -#import -#import -#import - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" - -#if 0 - -// Logging Enabled - See log level below - -// Logging uses the CocoaLumberjack framework (which is also GCD based). -// https://github.com/robbiehanson/CocoaLumberjack -// -// It allows us to do a lot of logging without significantly slowing down the code. -#import "DDLog.h" - -#define LogAsync NO -#define LogContext 65535 - -#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) -#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) - -#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) - -// Log levels : off, error, warn, info, verbose -static const int logLevel = LOG_LEVEL_VERBOSE; - -#else - -// Logging Disabled - -#define LogError(frmt, ...) do {} while (0) -#define LogWarn(frmt, ...) do {} while (0) -#define LogInfo(frmt, ...) do {} while (0) -#define LogVerbose(frmt, ...) do {} while (0) - -#define LogCError(frmt, ...) do {} while (0) -#define LogCWarn(frmt, ...) do {} while (0) -#define LogCInfo(frmt, ...) do {} while (0) -#define LogCVerbose(frmt, ...) do {} while (0) - -#define LogTrace() do {} while (0) -#define LogCTrace(frmt, ...) do {} while (0) - -#endif - -/** - * Seeing a return statements within an inner block - * can sometimes be mistaken for a return point of the enclosing method. - * This makes inline blocks a bit easier to read. - **/ -#define return_from_block return - -/** - * A socket file descriptor is really just an integer. - * It represents the index of the socket within the kernel. - * This makes invalid file descriptor comparisons easier to read. - **/ -#define SOCKET_NULL -1 - -/** - * Just to type less code. - **/ -#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} - - -@class GCDAsyncUdpSendPacket; - -NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException"; -NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain"; - -NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket"; -NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream"; - -enum GCDAsyncUdpSocketFlags -{ - kDidCreateSockets = 1 << 0, // If set, the sockets have been created. - kDidBind = 1 << 1, // If set, bind has been called. - kConnecting = 1 << 2, // If set, a connection attempt is in progress. - kDidConnect = 1 << 3, // If set, socket is connected. - kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled - kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled - kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6. - kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4. - kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended. - kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended. - kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended. - kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended. - kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown. - kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown. - kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued. - kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued. - kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets. -#if TARGET_OS_IPHONE - kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread -#endif -}; - -enum GCDAsyncUdpSocketConfig -{ - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6 - kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4 -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface GCDAsyncUdpSocket () -{ -#if __has_feature(objc_arc_weak) - __weak id delegate; -#else - __unsafe_unretained id delegate; -#endif - dispatch_queue_t delegateQueue; - - GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; - dispatch_queue_t receiveFilterQueue; - BOOL receiveFilterAsync; - - GCDAsyncUdpSocketSendFilterBlock sendFilterBlock; - dispatch_queue_t sendFilterQueue; - BOOL sendFilterAsync; - - uint32_t flags; - uint16_t config; - - uint16_t max4ReceiveSize; - uint32_t max6ReceiveSize; - - uint16_t maxSendSize; - - int socket4FD; - int socket6FD; - - dispatch_queue_t socketQueue; - - dispatch_source_t send4Source; - dispatch_source_t send6Source; - dispatch_source_t receive4Source; - dispatch_source_t receive6Source; - dispatch_source_t sendTimer; - - GCDAsyncUdpSendPacket *currentSend; - NSMutableArray *sendQueue; - - unsigned long socket4FDBytesAvailable; - unsigned long socket6FDBytesAvailable; - - uint32_t pendingFilterOperations; - - NSData *cachedLocalAddress4; - NSString *cachedLocalHost4; - uint16_t cachedLocalPort4; - - NSData *cachedLocalAddress6; - NSString *cachedLocalHost6; - uint16_t cachedLocalPort6; - - NSData *cachedConnectedAddress; - NSString *cachedConnectedHost; - uint16_t cachedConnectedPort; - int cachedConnectedFamily; - - void *IsOnSocketQueueOrTargetQueueKey; - -#if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream4; - CFReadStreamRef readStream6; - CFWriteStreamRef writeStream4; - CFWriteStreamRef writeStream6; -#endif - - id userData; -} - -- (void)resumeSend4Source; -- (void)resumeSend6Source; -- (void)resumeReceive4Source; -- (void)resumeReceive6Source; -- (void)closeSockets; - -- (void)maybeConnect; -- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr; -- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr; - -- (void)maybeDequeueSend; -- (void)doPreSend; -- (void)doSend; -- (void)endCurrentSend; -- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout; - -- (void)doReceive; -- (void)doReceiveEOF; - -- (void)closeWithError:(NSError *)error; - -- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; - -#if TARGET_OS_IPHONE -- (BOOL)createReadAndWriteStreams:(NSError **)errPtr; -- (BOOL)registerForStreamCallbacks:(NSError **)errPtr; -- (BOOL)addStreamsToRunLoop:(NSError **)errPtr; -- (BOOL)openStreams:(NSError **)errPtr; -- (void)removeStreamsFromRunLoop; -- (void)closeReadAndWriteStreams; -#endif - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; - -#if TARGET_OS_IPHONE -// Forward declaration -+ (void)listenerThread:(id)unused; -#endif - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write. - **/ -@interface GCDAsyncUdpSendPacket : NSObject { -@public - NSData *buffer; - NSTimeInterval timeout; - long tag; - - BOOL resolveInProgress; - BOOL filterInProgress; - - NSArray *resolvedAddresses; - NSError *resolveError; - - NSData *address; - int addressFamily; -} - -- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; - -@end - -@implementation GCDAsyncUdpSendPacket - -// Cover the superclass' designated initializer -- (instancetype)init NS_UNAVAILABLE -{ - NSAssert(0, @"Use the designated initializer"); - return nil; -} - -- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i -{ - if ((self = [super init])) - { - buffer = d; - timeout = t; - tag = i; - - resolveInProgress = NO; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface GCDAsyncUdpSpecialPacket : NSObject { -@public - // uint8_t type; - - BOOL resolveInProgress; - - NSArray *addresses; - NSError *error; -} - -- (instancetype)init NS_DESIGNATED_INITIALIZER; - -@end - -@implementation GCDAsyncUdpSpecialPacket - -- (instancetype)init -{ - self = [super init]; - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation GCDAsyncUdpSocket - -- (instancetype)init -{ - LogTrace(); - - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; -} - -- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq -{ - LogTrace(); - - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; -} - -- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq -{ - LogTrace(); - - return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; -} - -- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq -{ - LogTrace(); - - if ((self = [super init])) - { - delegate = aDelegate; - - if (dq) - { - delegateQueue = dq; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(delegateQueue); -#endif - } - - max4ReceiveSize = 65535; - max6ReceiveSize = 65535; - - maxSendSize = 65535; - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(socketQueue); -#endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], - NULL); - } - - // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. - // From the documentation: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // - // We're just going to use the memory address of an ivar. - // Specifically an ivar that is explicitly named for our purpose to make the code more readable. - // - // However, it feels tedious (and less readable) to include the "&" all the time: - // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) - // - // So we're going to make it so it doesn't matter if we use the '&' or not, - // by assigning the value of the ivar to the address of the ivar. - // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; - - IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketQueue, - IsOnSocketQueueOrTargetQueueKey, - nonNullUnusedPointer, - NULL); - - currentSend = nil; - sendQueue = [[NSMutableArray alloc] initWithCapacity:5]; - -#if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:UIApplicationWillEnterForegroundNotification - object:nil]; -#endif - } - return self; -} - -- (void)dealloc -{ - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); - -#if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] removeObserver:self]; -#endif - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; -#if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); -#endif - delegateQueue = NULL; - -#if !OS_OBJECT_USE_OBJC - if (socketQueue) dispatch_release(socketQueue); -#endif - socketQueue = NULL; - - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (id)delegate -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegate; - } - else - { - __block id result = nil; - - dispatch_sync(socketQueue, ^{ - result = self->delegate; - }); - - return result; - } -} - -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - self->delegate = newDelegate; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:YES]; -} - -- (dispatch_queue_t)delegateQueue -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result = NULL; - - dispatch_sync(socketQueue, ^{ - result = self->delegateQueue; - }); - - return result; - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - -#if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); -#endif - - self->delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:YES]; -} - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; - - dispatch_sync(socketQueue, ^{ - dPtr = self->delegate; - dqPtr = self->delegateQueue; - }); - - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - self->delegate = newDelegate; - -#if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); -#endif - - self->delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; -} - -- (BOOL)isIPv4Enabled -{ - // Note: YES means kIPv4Disabled is OFF - - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - result = ((self->config & kIPv4Disabled) == 0); - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setIPv4Enabled:(BOOL)flag -{ - // Note: YES means kIPv4Disabled is OFF - - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); - - if (flag) - self->config &= ~kIPv4Disabled; - else - self->config |= kIPv4Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv6Enabled -{ - // Note: YES means kIPv6Disabled is OFF - - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - result = ((self->config & kIPv6Disabled) == 0); - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setIPv6Enabled:(BOOL)flag -{ - // Note: YES means kIPv6Disabled is OFF - - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); - - if (flag) - self->config &= ~kIPv6Disabled; - else - self->config |= kIPv6Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv4Preferred -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (self->config & kPreferIPv4) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv6Preferred -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (self->config & kPreferIPv6) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPVersionNeutral -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setPreferIPv4 -{ - dispatch_block_t block = ^{ - - LogTrace(); - - self->config |= kPreferIPv4; - self->config &= ~kPreferIPv6; - - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setPreferIPv6 -{ - dispatch_block_t block = ^{ - - LogTrace(); - - self->config &= ~kPreferIPv4; - self->config |= kPreferIPv6; - - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setIPVersionNeutral -{ - dispatch_block_t block = ^{ - - LogTrace(); - - self->config &= ~kPreferIPv4; - self->config &= ~kPreferIPv6; - - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (uint16_t)maxReceiveIPv4BufferSize -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - result = self->max4ReceiveSize; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max -{ - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - - self->max4ReceiveSize = max; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (uint32_t)maxReceiveIPv6BufferSize -{ - __block uint32_t result = 0; - - dispatch_block_t block = ^{ - - result = self->max6ReceiveSize; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max -{ - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - - self->max6ReceiveSize = max; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setMaxSendBufferSize:(uint16_t)max -{ - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - - self->maxSendSize = max; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (uint16_t)maxSendBufferSize -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - result = self->maxSendSize; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (id)userData -{ - __block id result = nil; - - dispatch_block_t block = ^{ - - result = self->userData; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setUserData:(id)arbitraryUserData -{ - dispatch_block_t block = ^{ - - if (self->userData != arbitraryUserData) - { - self->userData = arbitraryUserData; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Delegate Helpers -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)notifyDidConnectToAddress:(NSData *)anAddress -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) - { - NSData *address = [anAddress copy]; // In case param is NSMutableData - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didConnectToAddress:address]; - }}); - } -} - -- (void)notifyDidNotConnect:(NSError *)error -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didNotConnect:error]; - }}); - } -} - -- (void)notifyDidSendDataWithTag:(long)tag -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didSendDataWithTag:tag]; - }}); - } -} - -- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; - }}); - } -} - -- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context -{ - LogTrace(); - - SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:selector]) - { - dispatch_async(delegateQueue, - ^{ @autoreleasepool { - - [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; - }}); - } -} - -- (void)notifyDidCloseWithError:(NSError *)error -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocketDidClose:self withError:error]; - }}); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSError *)badConfigError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketBadConfigError - userInfo:userInfo]; -} - -- (NSError *)badParamError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketBadParamError - userInfo:userInfo]; -} - -- (NSError *)gaiError:(int)gai_error -{ - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; -} - -- (NSError *)errnoErrorWithReason:(NSString *)reason -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo; - - if (reason) - userInfo = @{NSLocalizedDescriptionKey : errMsg, - NSLocalizedFailureReasonErrorKey : reason}; - else - userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)errnoError -{ - return [self errnoErrorWithReason:nil]; -} - -/** - * Returns a standard send timeout error. - **/ -- (NSError *)sendTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError", - @"GCDAsyncUdpSocket", - [NSBundle mainBundle], - @"Send operation timed out", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketSendTimeoutError - userInfo:userInfo]; -} - -- (NSError *)socketClosedError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError", - @"GCDAsyncUdpSocket", - [NSBundle mainBundle], - @"Socket closed", - nil); - - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; -} - -- (NSError *)otherError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketOtherError - userInfo:userInfo]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)preOp:(NSError **)errPtr -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -/** - * This method executes on a global concurrent queue. - * When complete, it executes the given completion block on the socketQueue. - **/ -- (void)asyncResolveHost:(NSString *)aHost - port:(uint16_t)port - withCompletionBlock:(void (^)(NSArray *addresses, - NSError *error))completionBlock -{ - LogTrace(); - - // Check parameter(s) - - if (aHost == nil) - { - NSString *msg = @"The host param is nil. Should be domain name or IP address string."; - NSError *error = [self badParamError:msg]; - - // We should still use dispatch_async since this method is expected to be asynchronous - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - completionBlock(nil, error); - }}); - - return; - } - - // It's possible that the given aHost parameter is actually a NSMutableString. - // So we want to copy it now, within this block that will be executed synchronously. - // This way the asynchronous lookup block below doesn't have to worry about it changing. - - NSString *host = [aHost copy]; - - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0); - dispatch_async(globalConcurrentQueue, - ^{ @autoreleasepool { - - NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2]; - NSError *error = nil; - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(struct sockaddr_in); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(struct sockaddr_in6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - // Wrap the native address structures and add to list - [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]]; - [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, - *res, - *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - - int gai_error = getaddrinfo([host UTF8String], - [portStr UTF8String], - &hints, - &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - for(res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET) - { - // Found IPv4 address - // Wrap the native address structure and add to list - - [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; - } - else if (res->ai_family == AF_INET6) - { - - // Fixes connection issues with IPv6, it is the same solution for udp socket. - // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 - struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; - in_port_t *portPtr = &sockaddr->sin6_port; - if ((portPtr != NULL) && (*portPtr == 0)) { - *portPtr = htons(port); - } - - // Found IPv6 address - // Wrap the native address structure and add to list - [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; - } - } - freeaddrinfo(res0); - - if ([addresses count] == 0) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - completionBlock(addresses, error); - }}); - - }}); -} - -/** - * This method picks an address from the given list of addresses. - * The address picked depends upon which protocols are disabled, deactived, & preferred. - * - * Returns the address family (AF_INET or AF_INET6) of the picked address, - * or AF_UNSPEC and the corresponding error is there's a problem. - **/ -- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert([addresses count] > 0, @"Expected at least one address"); - - int resultAF = AF_UNSPEC; - NSData *resultAddress = nil; - NSError *resultError = nil; - - // Check for problems - - BOOL resolvedIPv4Address = NO; - BOOL resolvedIPv6Address = NO; - - for (NSData *address in addresses) - { - switch ([[self class] familyFromAddress:address]) - { - case AF_INET : resolvedIPv4Address = YES; break; - case AF_INET6 : resolvedIPv6Address = YES; break; - - default : NSAssert(NO, @"Addresses array contains invalid address"); - } - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && !resolvedIPv6Address) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - if (isIPv6Disabled && !resolvedIPv4Address) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO; - BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO; - - if (isIPv4Deactivated && !resolvedIPv6Address) - { - NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - if (isIPv6Deactivated && !resolvedIPv4Address) - { - NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - // Extract first IPv4 and IPv6 address in list - - BOOL ipv4WasFirstInList = YES; - NSData *address4 = nil; - NSData *address6 = nil; - - for (NSData *address in addresses) - { - int af = [[self class] familyFromAddress:address]; - - if (af == AF_INET) - { - if (address4 == nil) - { - address4 = address; - - if (address6) - break; - else - ipv4WasFirstInList = YES; - } - } - else // af == AF_INET6 - { - if (address6 == nil) - { - address6 = address; - - if (address4) - break; - else - ipv4WasFirstInList = NO; - } - } - } - - // Determine socket type - - BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO; - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - - BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil)); - BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); - - NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state"); - NSAssert(!(useIPv4 && useIPv6), @"Invalid logic"); - - if (useIPv4 || (!useIPv6 && ipv4WasFirstInList)) - { - resultAF = AF_INET; - resultAddress = address4; - } - else - { - resultAF = AF_INET6; - resultAddress = address6; - } - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; -} - -/** - * Finds the address(es) of an interface description. - * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). - **/ -- (void)convertIntefaceDescription:(NSString *)interfaceDescription - port:(uint16_t)port - intoAddress4:(NSData **)interfaceAddr4Ptr - address6:(NSData **)interfaceAddr6Ptr -{ - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (interfaceDescription == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interfaceDescription isEqualToString:@"localhost"] || - [interfaceDescription isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(struct sockaddr_in); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(struct sockaddr_in6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interfaceDescription UTF8String]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 - - struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr; - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - struct sockaddr_in nativeAddr4 = *addr; - nativeAddr4.sin_port = htons(port); - - addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; - - const char *conversion; - conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - struct sockaddr_in nativeAddr4 = *addr; - nativeAddr4.sin_port = htons(port); - - addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 - - const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - struct sockaddr_in6 nativeAddr6 = *addr; - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; - - const char *conversion; - conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - struct sockaddr_in6 nativeAddr6 = *addr; - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - } - - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; -} - -/** - * Converts a numeric hostname into its corresponding address. - * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34) - **/ -- (void)convertNumericHost:(NSString *)numericHost - port:(uint16_t)port - intoAddress4:(NSData **)addr4Ptr - address6:(NSData **)addr6Ptr -{ - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (numericHost) - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted - - if (getaddrinfo([numericHost UTF8String], - [portStr UTF8String], - &hints, - &res0) == 0) - { - for (res = res0; res; res = res->ai_next) - { - if ((addr4 == nil) && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structure - addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if ((addr6 == nil) && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structure - addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); - } - } - - if (addr4Ptr) *addr4Ptr = addr4; - if (addr6Ptr) *addr6Ptr = addr6; -} - -- (BOOL)isConnectedToAddress4:(NSData *)someAddr4 -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert(flags & kDidConnect, @"Not connected"); - NSAssert(cachedConnectedAddress, @"Expected cached connected address"); - - if (cachedConnectedFamily != AF_INET) - { - return NO; - } - - const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes]; - const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes]; - - if (memcmp(&sSockaddr4->sin_addr, - &cSockaddr4->sin_addr, - sizeof(struct in_addr)) != 0) - { - return NO; - } - if (memcmp(&sSockaddr4->sin_port, - &cSockaddr4->sin_port, - sizeof(in_port_t)) != 0) - { - return NO; - } - - return YES; -} - -- (BOOL)isConnectedToAddress6:(NSData *)someAddr6 -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert(flags & kDidConnect, @"Not connected"); - NSAssert(cachedConnectedAddress, @"Expected cached connected address"); - - if (cachedConnectedFamily != AF_INET6) - { - return NO; - } - - const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes]; - const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes]; - - if (memcmp(&sSockaddr6->sin6_addr, - &cSockaddr6->sin6_addr, - sizeof(struct in6_addr)) != 0) - { - return NO; - } - if (memcmp(&sSockaddr6->sin6_port, - &cSockaddr6->sin6_port, - sizeof(in_port_t)) != 0) - { - return NO; - } - - return YES; -} - -- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4 -{ - if (interfaceAddr4 == nil) - return 0; - if ([interfaceAddr4 length] != sizeof(struct sockaddr_in)) - return 0; - - int result = 0; - const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if (cursor->ifa_addr->sa_family == AF_INET) - { - // IPv4 - - const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr; - - if (memcmp(&addr->sin_addr, - &ifaceAddr->sin_addr, - sizeof(struct in_addr)) == 0) - { - result = if_nametoindex(cursor->ifa_name); - break; - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - - return result; -} - -- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6 -{ - if (interfaceAddr6 == nil) - return 0; - if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6)) - return 0; - - int result = 0; - const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if (cursor->ifa_addr->sa_family == AF_INET6) - { - // IPv6 - - const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; - - if (memcmp(&addr->sin6_addr, - &ifaceAddr->sin6_addr, - sizeof(struct in6_addr)) == 0) - { - result = if_nametoindex(cursor->ifa_name); - break; - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - - return result; -} - -- (void)setupSendAndReceiveSourcesForSocket4 -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, - socket4FD, - 0, - socketQueue); - receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, - socket4FD, - 0, - socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(send4Source, - ^{ @autoreleasepool { - - LogVerbose(@"send4EventBlock"); - LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", - dispatch_source_get_data(send4Source)); - - self->flags |= kSock4CanAcceptBytes; - - // If we're ready to send data, do so immediately. - // Otherwise pause the send source or it will continue to fire over and over again. - - if (self->currentSend == nil) - { - LogVerbose(@"Nothing to send"); - [self suspendSend4Source]; - } - else if (self->currentSend->resolveInProgress) - { - LogVerbose(@"currentSend - waiting for address resolve"); - [self suspendSend4Source]; - } - else if (self->currentSend->filterInProgress) - { - LogVerbose(@"currentSend - waiting on sendFilter"); - [self suspendSend4Source]; - } - else - { - [self doSend]; - } - - }}); - - dispatch_source_set_event_handler(receive4Source, - ^{ @autoreleasepool { - - LogVerbose(@"receive4EventBlock"); - - self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source); - LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); - - if (self->socket4FDBytesAvailable > 0) - [self doReceive]; - else - [self doReceiveEOF]; - - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - int theSocketFD = socket4FD; - -#if !OS_OBJECT_USE_OBJC - dispatch_source_t theSendSource = send4Source; - dispatch_source_t theReceiveSource = receive4Source; -#endif - - dispatch_source_set_cancel_handler(send4Source, ^{ - - LogVerbose(@"send4CancelBlock"); - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(send4Source)"); - dispatch_release(theSendSource); -#endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket4FD)"); - close(theSocketFD); - } - }); - - dispatch_source_set_cancel_handler(receive4Source, ^{ - - LogVerbose(@"receive4CancelBlock"); - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(receive4Source)"); - dispatch_release(theReceiveSource); -#endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket4FD)"); - close(theSocketFD); - } - }); - - // We will not be able to receive until the socket is bound to a port, - // either explicitly via bind, or implicitly by connect or by sending data. - // - // But we should be able to send immediately. - - socket4FDBytesAvailable = 0; - flags |= kSock4CanAcceptBytes; - - flags |= kSend4SourceSuspended; - flags |= kReceive4SourceSuspended; -} - -- (void)setupSendAndReceiveSourcesForSocket6 -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, - socket6FD, - 0, - socketQueue); - receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, - socket6FD, - 0, - socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(send6Source, - ^{ @autoreleasepool { - - LogVerbose(@"send6EventBlock"); - LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", - dispatch_source_get_data(send6Source)); - - self->flags |= kSock6CanAcceptBytes; - - // If we're ready to send data, do so immediately. - // Otherwise pause the send source or it will continue to fire over and over again. - - if (self->currentSend == nil) - { - LogVerbose(@"Nothing to send"); - [self suspendSend6Source]; - } - else if (self->currentSend->resolveInProgress) - { - LogVerbose(@"currentSend - waiting for address resolve"); - [self suspendSend6Source]; - } - else if (self->currentSend->filterInProgress) - { - LogVerbose(@"currentSend - waiting on sendFilter"); - [self suspendSend6Source]; - } - else - { - [self doSend]; - } - - }}); - - dispatch_source_set_event_handler(receive6Source, - ^{ @autoreleasepool { - - LogVerbose(@"receive6EventBlock"); - - self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source); - LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); - - if (self->socket6FDBytesAvailable > 0) - [self doReceive]; - else - [self doReceiveEOF]; - - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - int theSocketFD = socket6FD; - -#if !OS_OBJECT_USE_OBJC - dispatch_source_t theSendSource = send6Source; - dispatch_source_t theReceiveSource = receive6Source; -#endif - - dispatch_source_set_cancel_handler(send6Source, ^{ - - LogVerbose(@"send6CancelBlock"); - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(send6Source)"); - dispatch_release(theSendSource); -#endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket6FD)"); - close(theSocketFD); - } - }); - - dispatch_source_set_cancel_handler(receive6Source, ^{ - - LogVerbose(@"receive6CancelBlock"); - -#if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(receive6Source)"); - dispatch_release(theReceiveSource); -#endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket6FD)"); - close(theSocketFD); - } - }); - - // We will not be able to receive until the socket is bound to a port, - // either explicitly via bind, or implicitly by connect or by sending data. - // - // But we should be able to send immediately. - - socket6FDBytesAvailable = 0; - flags |= kSock6CanAcceptBytes; - - flags |= kSend6SourceSuspended; - flags |= kReceive6SourceSuspended; -} - -- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert(((flags & kDidCreateSockets) == 0), - @"Sockets have already been created"); - - // CreateSocket Block - // This block will be invoked below. - - int(^createSocket)(int) = ^int (int domain) { - - int socketFD = socket(domain, SOCK_DGRAM, 0); - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"]; - - close(socketFD); - return SOCKET_NULL; - } - - int reuseaddr = 1; - status = setsockopt(socketFD, - SOL_SOCKET, - SO_REUSEADDR, - &reuseaddr, - sizeof(reuseaddr)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"]; - - close(socketFD); - return SOCKET_NULL; - } - - int nosigpipe = 1; - status = setsockopt(socketFD, - SOL_SOCKET, - SO_NOSIGPIPE, - &nosigpipe, - sizeof(nosigpipe)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"]; - - close(socketFD); - return SOCKET_NULL; - } - - /** - * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. - * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. - * - * The default maximum size of the UDP buffer in iOS is 9216 bytes. - * - * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and - * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) - * - * - * Enlarge the maximum size of UDP packet. - * I can not ensure the protocol type now so that the max size is set to 65535 :) - **/ - - status = setsockopt(socketFD, - SOL_SOCKET, - SO_SNDBUF, - (const char*)&self->maxSendSize, - sizeof(int)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; - close(socketFD); - return SOCKET_NULL; - } - - status = setsockopt(socketFD, - SOL_SOCKET, - SO_RCVBUF, - (const char*)&self->maxSendSize, - sizeof(int)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; - close(socketFD); - return SOCKET_NULL; - } - - - return socketFD; - }; - - // Create sockets depending upon given configuration. - - if (useIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - - socket4FD = createSocket(AF_INET); - if (socket4FD == SOCKET_NULL) - { - // errPtr set in local createSocket() block - return NO; - } - } - - if (useIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - socket6FD = createSocket(AF_INET6); - if (socket6FD == SOCKET_NULL) - { - // errPtr set in local createSocket() block - - if (socket4FD != SOCKET_NULL) - { - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - return NO; - } - } - - // Setup send and receive sources - - if (useIPv4) - [self setupSendAndReceiveSourcesForSocket4]; - if (useIPv6) - [self setupSendAndReceiveSourcesForSocket6]; - - flags |= kDidCreateSockets; - return YES; -} - -- (BOOL)createSockets:(NSError **)errPtr -{ - LogTrace(); - - BOOL useIPv4 = [self isIPv4Enabled]; - BOOL useIPv6 = [self isIPv6Enabled]; - - return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr]; -} - -- (void)suspendSend4Source -{ - if (send4Source && !(flags & kSend4SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(send4Source)"); - - dispatch_suspend(send4Source); - flags |= kSend4SourceSuspended; - } -} - -- (void)suspendSend6Source -{ - if (send6Source && !(flags & kSend6SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(send6Source)"); - - dispatch_suspend(send6Source); - flags |= kSend6SourceSuspended; - } -} - -- (void)resumeSend4Source -{ - if (send4Source && (flags & kSend4SourceSuspended)) - { - LogVerbose(@"dispatch_resume(send4Source)"); - - dispatch_resume(send4Source); - flags &= ~kSend4SourceSuspended; - } -} - -- (void)resumeSend6Source -{ - if (send6Source && (flags & kSend6SourceSuspended)) - { - LogVerbose(@"dispatch_resume(send6Source)"); - - dispatch_resume(send6Source); - flags &= ~kSend6SourceSuspended; - } -} - -- (void)suspendReceive4Source -{ - if (receive4Source && !(flags & kReceive4SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(receive4Source)"); - - dispatch_suspend(receive4Source); - flags |= kReceive4SourceSuspended; - } -} - -- (void)suspendReceive6Source -{ - if (receive6Source && !(flags & kReceive6SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(receive6Source)"); - - dispatch_suspend(receive6Source); - flags |= kReceive6SourceSuspended; - } -} - -- (void)resumeReceive4Source -{ - if (receive4Source && (flags & kReceive4SourceSuspended)) - { - LogVerbose(@"dispatch_resume(receive4Source)"); - - dispatch_resume(receive4Source); - flags &= ~kReceive4SourceSuspended; - } -} - -- (void)resumeReceive6Source -{ - if (receive6Source && (flags & kReceive6SourceSuspended)) - { - LogVerbose(@"dispatch_resume(receive6Source)"); - - dispatch_resume(receive6Source); - flags &= ~kReceive6SourceSuspended; - } -} - -- (void)closeSocket4 -{ - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"dispatch_source_cancel(send4Source)"); - dispatch_source_cancel(send4Source); - - LogVerbose(@"dispatch_source_cancel(receive4Source)"); - dispatch_source_cancel(receive4Source); - - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - - [self resumeSend4Source]; - [self resumeReceive4Source]; - - // The sockets will be closed by the cancel handlers of the corresponding source - - send4Source = NULL; - receive4Source = NULL; - - socket4FD = SOCKET_NULL; - - // Clear socket states - - socket4FDBytesAvailable = 0; - flags &= ~kSock4CanAcceptBytes; - - // Clear cached info - - cachedLocalAddress4 = nil; - cachedLocalHost4 = nil; - cachedLocalPort4 = 0; - } -} - -- (void)closeSocket6 -{ - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"dispatch_source_cancel(send6Source)"); - dispatch_source_cancel(send6Source); - - LogVerbose(@"dispatch_source_cancel(receive6Source)"); - dispatch_source_cancel(receive6Source); - - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - - [self resumeSend6Source]; - [self resumeReceive6Source]; - - send6Source = NULL; - receive6Source = NULL; - - // The sockets will be closed by the cancel handlers of the corresponding source - - socket6FD = SOCKET_NULL; - - // Clear socket states - - socket6FDBytesAvailable = 0; - flags &= ~kSock6CanAcceptBytes; - - // Clear cached info - - cachedLocalAddress6 = nil; - cachedLocalHost6 = nil; - cachedLocalPort6 = 0; - } -} - -- (void)closeSockets -{ - [self closeSocket4]; - [self closeSocket6]; - - flags &= ~kDidCreateSockets; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)getLocalAddress:(NSData **)dataPtr - host:(NSString **)hostPtr - port:(uint16_t *)portPtr - forSocket:(int)socketFD - withFamily:(int)socketFamily -{ - - NSData *data = nil; - NSString *host = nil; - uint16_t port = 0; - - if (socketFamily == AF_INET) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, - (struct sockaddr *)&sockaddr4, - &sockaddr4len) == 0) - { - data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - host = [[self class] hostFromSockaddr4:&sockaddr4]; - port = [[self class] portFromSockaddr4:&sockaddr4]; - } - else - { - LogWarn(@"Error in getsockname: %@", [self errnoError]); - } - } - else if (socketFamily == AF_INET6) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, - (struct sockaddr *)&sockaddr6, - &sockaddr6len) == 0) - { - data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - host = [[self class] hostFromSockaddr6:&sockaddr6]; - port = [[self class] portFromSockaddr6:&sockaddr6]; - } - else - { - LogWarn(@"Error in getsockname: %@", [self errnoError]); - } - } - - if (dataPtr) *dataPtr = data; - if (hostPtr) *hostPtr = host; - if (portPtr) *portPtr = port; - - return (data != nil); -} - -- (void)maybeUpdateCachedLocalAddress4Info -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) ) - { - return; - } - - NSData *address = nil; - NSString *host = nil; - uint16_t port = 0; - - if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET]) - { - - cachedLocalAddress4 = address; - cachedLocalHost4 = host; - cachedLocalPort4 = port; - } -} - -- (void)maybeUpdateCachedLocalAddress6Info -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) ) - { - return; - } - - NSData *address = nil; - NSString *host = nil; - uint16_t port = 0; - - if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6]) - { - - cachedLocalAddress6 = address; - cachedLocalHost6 = host; - cachedLocalPort6 = port; - } -} - -- (NSData *)localAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - if (self->socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalAddress4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalAddress6; - } - - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)localHost -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - if (self->socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalHost4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalHost6; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)localPort -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - if (self->socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalPort4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalPort6; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSData *)localAddress_IPv4 -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalAddress4; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)localHost_IPv4 -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalHost4; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)localPort_IPv4 -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalPort4; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSData *)localAddress_IPv6 -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalAddress6; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)localHost_IPv6 -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalHost6; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)localPort_IPv6 -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalPort6; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (void)maybeUpdateCachedConnectedAddressInfo -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if (cachedConnectedAddress || (flags & kDidConnect) == 0) - { - return; - } - - NSData *data = nil; - NSString *host = nil; - uint16_t port = 0; - int family = AF_UNSPEC; - - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socket4FD, - (struct sockaddr *)&sockaddr4, - &sockaddr4len) == 0) - { - data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - host = [[self class] hostFromSockaddr4:&sockaddr4]; - port = [[self class] portFromSockaddr4:&sockaddr4]; - family = AF_INET; - } - else - { - LogWarn(@"Error in getpeername: %@", [self errnoError]); - } - } - else if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socket6FD, - (struct sockaddr *)&sockaddr6, - &sockaddr6len) == 0) - { - data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - host = [[self class] hostFromSockaddr6:&sockaddr6]; - port = [[self class] portFromSockaddr6:&sockaddr6]; - family = AF_INET6; - } - else - { - LogWarn(@"Error in getpeername: %@", [self errnoError]); - } - } - - - cachedConnectedAddress = data; - cachedConnectedHost = host; - cachedConnectedPort = port; - cachedConnectedFamily = family; -} - -- (NSData *)connectedAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedConnectedAddressInfo]; - result = self->cachedConnectedAddress; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)connectedHost -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedConnectedAddressInfo]; - result = self->cachedConnectedHost; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)connectedPort -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedConnectedAddressInfo]; - result = self->cachedConnectedPort; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (BOOL)isConnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (self->flags & kDidConnect) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isClosed -{ - __block BOOL result = YES; - - dispatch_block_t block = ^{ - - result = (self->flags & kDidCreateSockets) ? NO : YES; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv4 -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - if (self->flags & kDidCreateSockets) - { - result = (self->socket4FD != SOCKET_NULL); - } - else - { - result = [self isIPv4Enabled]; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv6 -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - if (self->flags & kDidCreateSockets) - { - result = (self->socket6FD != SOCKET_NULL); - } - else - { - result = [self isIPv6Enabled]; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Binding -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a bind attempt. - * It is shared between the various bind methods. - **/ -- (BOOL)preBind:(NSError **)errPtr -{ - if (![self preOp:errPtr]) - { - return NO; - } - - if (flags & kDidBind) - { - if (errPtr) - { - NSString *msg = @"Cannot bind a socket more than once."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self bindToPort:port interface:nil error:errPtr]; -} - -- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks - - if (![self preBind:&err]) - { - return_from_block; - } - - // Check the given interface - - NSData *interface4 = nil; - NSData *interface6 = nil; - - [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6]; - - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Determine protocol(s) - - BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil); - - // Create the socket(s) if needed - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) - { - return_from_block; - } - } - - // Bind the socket(s) - - LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface); - - if (useIPv4) - { - int status = bind(self->socket4FD, - (const struct sockaddr *)[interface4 bytes], - (socklen_t)[interface4 length]); - if (status == -1) - { - [self closeSockets]; - - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - return_from_block; - } - } - - if (useIPv6) - { - int status = bind(self->socket6FD, - (const struct sockaddr *)[interface6 bytes], - (socklen_t)[interface6 length]); - if (status == -1) - { - [self closeSockets]; - - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - return_from_block; - } - } - - // Update flags - - self->flags |= kDidBind; - - if (!useIPv4) self->flags |= kIPv4Deactivated; - if (!useIPv6) self->flags |= kIPv6Deactivated; - - result = YES; - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error binding to port/interface: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks - - if (![self preBind:&err]) - { - return_from_block; - } - - // Check the given address - - int addressFamily = [[self class] familyFromAddress:localAddr]; - - if (addressFamily == AF_UNSPEC) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; - - return_from_block; - } - - NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil; - NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil; - - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && localAddr4) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && localAddr6) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Determine protocol(s) - - BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil); - BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil); - - // Create the socket(s) if needed - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) - { - return_from_block; - } - } - - // Bind the socket(s) - - if (useIPv4 || useIPv6) - { - NSData *addressData = useIPv4 ? localAddr4 : localAddr6; - int socketFD = useIPv4 ? self->socket4FD : self->socket6FD; - NSString *protocol = useIPv4 ? @"IPv4" : @"IPv6"; - - LogVerbose(@"Binding socket to address(%@:%hu)", - [[self class] hostFromAddress:addressData], - [[self class] portFromAddress:addressData]); - - const struct sockaddr *addr = (const struct sockaddr *)[addressData bytes]; - if (addr == NULL) - { - [self closeSockets]; - - NSString *reason = [NSString stringWithFormat:@"Invalid address data for %@ bind", - protocol]; - err = [self badParamError:reason]; - - return_from_block; - } - - int status = bind(socketFD, addr, (socklen_t)[addressData length]); - if (status == -1) - { - [self closeSockets]; - - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - return_from_block; - } - } - - // Update flags - - self->flags |= kDidBind; - - if (!useIPv4) self->flags |= kIPv4Deactivated; - if (!useIPv6) self->flags |= kIPv6Deactivated; - - result = YES; - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error binding to address: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Connecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a connect attempt. - * It is shared between the various connect methods. - **/ -- (BOOL)preConnect:(NSError **)errPtr -{ - if (![self preOp:errPtr]) - { - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot connect a socket more than once."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks. - - if (![self preConnect:&err]) - { - return_from_block; - } - - // Check parameter(s) - - if (host == nil) - { - NSString *msg = @"The host param is nil. Should be domain name or IP address string."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Create the socket(s) if needed - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - // Create special connect packet - - GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; - packet->resolveInProgress = YES; - - // Start asynchronous DNS resolve for host:port on background queue - - LogVerbose(@"Dispatching DNS resolve for connect..."); - - [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, - NSError *error) { - - // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, - // and immediately returns. Once the async resolve task completes, - // this block is executed on our socketQueue. - - packet->resolveInProgress = NO; - - packet->addresses = addresses; - packet->error = error; - - [self maybeConnect]; - }]; - - // Updates flags, add connect packet to send queue, and pump send queue - - self->flags |= kConnecting; - - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error connecting to host/port: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks. - - if (![self preConnect:&err]) - { - return_from_block; - } - - // Check parameter(s) - - if (remoteAddr == nil) - { - NSString *msg = @"The address param is nil. Should be a valid address."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Create the socket(s) if needed - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - // The remoteAddr parameter could be of type NSMutableData. - // So we copy it to be safe. - - NSData *address = [remoteAddr copy]; - NSArray *addresses = [NSArray arrayWithObject:address]; - - GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; - packet->addresses = addresses; - - // Updates flags, add connect packet to send queue, and pump send queue - - self->flags |= kConnecting; - - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error connecting to address: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (void)maybeConnect -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - - BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]; - - if (sendQueueReady) - { - GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend; - - if (connectPacket->resolveInProgress) - { - LogVerbose(@"Waiting for DNS resolve..."); - } - else - { - if (connectPacket->error) - { - [self notifyDidNotConnect:connectPacket->error]; - } - else - { - NSData *address = nil; - NSError *error = nil; - - int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses]; - - // Perform connect - - BOOL result = NO; - - switch (addressFamily) - { - case AF_INET : result = [self connectWithAddress4:address error:&error]; break; - case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break; - default: break; - } - - if (result) - { - flags |= kDidBind; - flags |= kDidConnect; - - cachedConnectedAddress = address; - cachedConnectedHost = [[self class] hostFromAddress:address]; - cachedConnectedPort = [[self class] portFromAddress:address]; - cachedConnectedFamily = addressFamily; - - [self notifyDidConnectToAddress:address]; - } - else - { - [self notifyDidNotConnect:error]; - } - } - - flags &= ~kConnecting; - - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } -} - -- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - int status = connect(socket4FD, - (const struct sockaddr *)[address4 bytes], - (socklen_t)[address4 length]); - if (status != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; - - return NO; - } - - [self closeSocket6]; - flags |= kIPv6Deactivated; - - return YES; -} - -- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - int status = connect(socket6FD, - (const struct sockaddr *)[address6 bytes], - (socklen_t)[address6 length]); - if (status != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; - - return NO; - } - - [self closeSocket4]; - flags |= kIPv4Deactivated; - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Multicast -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)preJoin:(NSError **)errPtr -{ - if (![self preOp:errPtr]) - { - return NO; - } - - if (!(flags & kDidBind)) - { - if (errPtr) - { - NSString *msg = @"Must bind a socket before joining a multicast group."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot join a multicast group if connected."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr -{ - return [self joinMulticastGroup:group onInterface:nil error:errPtr]; -} - -- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr -{ - // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP - return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; -} - -- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr -{ - return [self leaveMulticastGroup:group onInterface:nil error:errPtr]; -} - -- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr -{ - // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP - return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; -} - -- (BOOL)performMulticastRequest:(int)requestType - forGroup:(NSString *)group - onInterface:(NSString *)interface - error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks - - if (![self preJoin:&err]) - { - return_from_block; - } - - // Convert group to address - - NSData *groupAddr4 = nil; - NSData *groupAddr6 = nil; - - [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6]; - - if ((groupAddr4 == nil) && (groupAddr6 == nil)) - { - NSString *msg = @"Unknown group. Specify valid group IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Convert interface to address - - NSData *interfaceAddr4 = nil; - NSData *interfaceAddr6 = nil; - - [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; - - if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Perform join - - if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4) - { - const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes]; - const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes]; - - struct ip_mreq imreq; - imreq.imr_multiaddr = nativeGroup->sin_addr; - imreq.imr_interface = nativeIface->sin_addr; - - int status = setsockopt(self->socket4FD, - IPPROTO_IP, - requestType, - (const void *)&imreq, - sizeof(imreq)); - if (status != 0) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - - // Using IPv4 only - [self closeSocket6]; - - result = YES; - } - else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) - { - const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes]; - - struct ipv6_mreq imreq; - imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; - imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6]; - - int status = setsockopt(self->socket6FD, - IPPROTO_IPV6, - requestType, - (const void *)&imreq, - sizeof(imreq)); - if (status != 0) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - - // Using IPv6 only - [self closeSocket4]; - - result = YES; - } - else - { - NSString *msg = @"Socket, group, and interface do not have matching IP versions"; - err = [self badParamError:msg]; - - return_from_block; - } - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (![self preOp:&err]) - { - return_from_block; - } - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - // Convert interface to address - - NSData *interfaceAddr4 = nil; - NSData *interfaceAddr6 = nil; - - [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; - - if (interfaceAddr4 == nil) - { - NSString *msg = @"Unknown interface. Specify valid interface by IP address."; - err = [self badParamError:msg]; - return_from_block; - } - - if (self->socket4FD != SOCKET_NULL) { - const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; - struct in_addr interface_addr = nativeIface->sin_addr; - int status = setsockopt(self->socket4FD, - IPPROTO_IP, - IP_MULTICAST_IF, - &interface_addr, - sizeof(interface_addr)); - if (status != 0) { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - return_from_block; - result = YES; - } - } - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (![self preOp:&err]) - { - return_from_block; - } - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - // Convert interface to address - - NSData *interfaceAddr4 = nil; - NSData *interfaceAddr6 = nil; - - [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; - - if (interfaceAddr6 == nil) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\")."; - err = [self badParamError:msg]; - return_from_block; - } - - if ((self->socket6FD != SOCKET_NULL)) { - uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6]; - int status = setsockopt(self->socket6FD, - IPPROTO_IPV6, - IPV6_MULTICAST_IF, - &scope_id, - sizeof(scope_id)); - if (status != 0) { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - return_from_block; - } - result = YES; - } - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Reuse port -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (![self preOp:&err]) - { - return_from_block; - } - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - int value = flag ? 1 : 0; - if (self->socket4FD != SOCKET_NULL) - { - int error = setsockopt(self->socket4FD, - SOL_SOCKET, - SO_REUSEPORT, - (const void *)&value, - sizeof(value)); - - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - result = YES; - } - - if (self->socket6FD != SOCKET_NULL) - { - int error = setsockopt(self->socket6FD, - SOL_SOCKET, - SO_REUSEPORT, - (const void *)&value, - sizeof(value)); - - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - result = YES; - } - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Broadcast -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (![self preOp:&err]) - { - return_from_block; - } - - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - if (self->socket4FD != SOCKET_NULL) - { - int value = flag ? 1 : 0; - int error = setsockopt(self->socket4FD, - SOL_SOCKET, - SO_BROADCAST, - (const void *)&value, - sizeof(value)); - - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - result = YES; - } - - // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link. - // The same effect can be achieved by sending a packet to the link-local all hosts multicast group. - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Sending -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)sendData:(NSData *)data withTag:(long)tag -{ - [self sendData:data withTimeout:-1.0 tag:tag]; -} - -- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - LogTrace(); - - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } - - - - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; - }}); - -} - -- (void)sendData:(NSData *)data - toHost:(NSString *)host - port:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - tag:(long)tag -{ - LogTrace(); - - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } - - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - packet->resolveInProgress = YES; - - [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, - NSError *error) { - - // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, - // and immediately returns. Once the async resolve task completes, - // this block is executed on our socketQueue. - - packet->resolveInProgress = NO; - - packet->resolvedAddresses = addresses; - packet->resolveError = error; - - if (packet == self->currentSend) - { - LogVerbose(@"currentSend - address resolved"); - [self doPreSend]; - } - }]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; - - }}); - -} - -- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - LogTrace(); - - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } - - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr]; - packet->address = remoteAddr; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; - }}); -} - -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue -{ - [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; -} - -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue - isAsynchronous:(BOOL)isAsynchronous -{ - GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL; - dispatch_queue_t newFilterQueue = NULL; - - if (filterBlock) - { - NSAssert(filterQueue, - @"Must provide a dispatch_queue in which to run the filter block."); - - newFilterBlock = [filterBlock copy]; - newFilterQueue = filterQueue; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(newFilterQueue); -#endif - } - - dispatch_block_t block = ^{ - -#if !OS_OBJECT_USE_OBJC - if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue); -#endif - - self->sendFilterBlock = newFilterBlock; - self->sendFilterQueue = newFilterQueue; - self->sendFilterAsync = isAsynchronous; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)maybeDequeueSend -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - // If we don't have a send operation already in progress - if (currentSend == nil) - { - // Create the sockets if needed - if ((flags & kDidCreateSockets) == 0) - { - NSError *err = nil; - if (![self createSockets:&err]) - { - [self closeWithError:err]; - return; - } - } - - while ([sendQueue count] > 0) - { - // Dequeue the next object in the queue - currentSend = [sendQueue objectAtIndex:0]; - [sendQueue removeObjectAtIndex:0]; - - if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]) - { - [self maybeConnect]; - - return; // The maybeConnect method, if it connects, will invoke this method again - } - else if (currentSend->resolveError) - { - // Notify delegate - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError]; - - // Clear currentSend - currentSend = nil; - - continue; - } - else - { - // Start preprocessing checks on the send packet - [self doPreSend]; - - break; - } - } - - if ((currentSend == nil) && (flags & kCloseAfterSends)) - { - [self closeWithError:nil]; - } - } -} - -/** - * This method is called after a sendPacket has been dequeued. - * It performs various preprocessing checks on the packet, - * and queries the sendFilter (if set) to determine if the packet can be sent. - * - * If the packet passes all checks, it will be passed on to the doSend method. - **/ -- (void)doPreSend -{ - LogTrace(); - - // - // 1. Check for problems with send packet - // - - BOOL waitingForResolve = NO; - NSError *error = nil; - - if (flags & kDidConnect) - { - // Connected socket - - if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError) - { - NSString *msg = @"Cannot specify destination of packet for connected socket"; - error = [self badConfigError:msg]; - } - else - { - currentSend->address = cachedConnectedAddress; - currentSend->addressFamily = cachedConnectedFamily; - } - } - else - { - // Non-Connected socket - - if (currentSend->resolveInProgress) - { - // We're waiting for the packet's destination to be resolved. - waitingForResolve = YES; - } - else if (currentSend->resolveError) - { - error = currentSend->resolveError; - } - else if (currentSend->address == nil) - { - if (currentSend->resolvedAddresses == nil) - { - NSString *msg = @"You must specify destination of packet for a non-connected socket"; - error = [self badConfigError:msg]; - } - else - { - // Pick the proper address to use (out of possibly several resolved addresses) - - NSData *address = nil; - int addressFamily = AF_UNSPEC; - - addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses]; - - currentSend->address = address; - currentSend->addressFamily = addressFamily; - } - } - } - - if (waitingForResolve) - { - // We're waiting for the packet's destination to be resolved. - - LogVerbose(@"currentSend - waiting for address resolve"); - - if (flags & kSock4CanAcceptBytes) { - [self suspendSend4Source]; - } - if (flags & kSock6CanAcceptBytes) { - [self suspendSend6Source]; - } - - return; - } - - if (error) - { - // Unable to send packet due to some error. - // Notify delegate and move on. - - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error]; - [self endCurrentSend]; - [self maybeDequeueSend]; - - return; - } - - // - // 2. Query sendFilter (if applicable) - // - - if (sendFilterBlock && sendFilterQueue) - { - // Query sendFilter - - if (sendFilterAsync) - { - // Scenario 1 of 3 - Need to asynchronously query sendFilter - - currentSend->filterInProgress = YES; - GCDAsyncUdpSendPacket *sendPacket = currentSend; - - dispatch_async(sendFilterQueue, - ^{ @autoreleasepool { - - BOOL allowed = self->sendFilterBlock(sendPacket->buffer, - sendPacket->address, - sendPacket->tag); - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - sendPacket->filterInProgress = NO; - if (sendPacket == self->currentSend) - { - if (allowed) - { - [self doSend]; - } - else - { - LogVerbose(@"currentSend - silently dropped by sendFilter"); - - [self notifyDidSendDataWithTag:self->currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } - }}); - }}); - } - else - { - // Scenario 2 of 3 - Need to synchronously query sendFilter - - __block BOOL allowed = YES; - - dispatch_sync(sendFilterQueue, - ^{ @autoreleasepool { - - allowed = self->sendFilterBlock(self->currentSend->buffer, - self->currentSend->address, - self->currentSend->tag); - }}); - - if (allowed) - { - [self doSend]; - } - else - { - LogVerbose(@"currentSend - silently dropped by sendFilter"); - - [self notifyDidSendDataWithTag:currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } - } - else // if (!sendFilterBlock || !sendFilterQueue) - { - // Scenario 3 of 3 - No sendFilter. Just go straight into sending. - - [self doSend]; - } -} - -/** - * This method performs the actual sending of data in the currentSend packet. - * It should only be called if the - **/ -- (void)doSend -{ - LogTrace(); - - NSAssert(currentSend != nil, @"Invalid logic"); - - // Perform the actual send - - ssize_t result = 0; - - if (flags & kDidConnect) - { - // Connected socket - - const void *buffer = [currentSend->buffer bytes]; - size_t length = (size_t)[currentSend->buffer length]; - - if (currentSend->addressFamily == AF_INET) - { - result = send(socket4FD, buffer, length, 0); - LogVerbose(@"send(socket4FD) = %d", result); - } - else - { - result = send(socket6FD, buffer, length, 0); - LogVerbose(@"send(socket6FD) = %d", result); - } - } - else - { - // Non-Connected socket - - const void *buffer = [currentSend->buffer bytes]; - size_t length = (size_t)[currentSend->buffer length]; - - const void *dst = [currentSend->address bytes]; - socklen_t dstSize = (socklen_t)[currentSend->address length]; - - if (currentSend->addressFamily == AF_INET) - { - result = sendto(socket4FD, buffer, length, 0, (const struct sockaddr *)dst, dstSize); - LogVerbose(@"sendto(socket4FD) = %d", result); - } - else - { - result = sendto(socket6FD, buffer, length, 0, (const struct sockaddr *)dst, dstSize); - LogVerbose(@"sendto(socket6FD) = %d", result); - } - } - - // If the socket wasn't bound before, it is now - - if ((flags & kDidBind) == 0) - { - flags |= kDidBind; - } - - // Check the results. - // - // From the send() & sendto() manpage: - // - // Upon successful completion, the number of bytes which were sent is returned. - // Otherwise, -1 is returned and the global variable errno is set to indicate the error. - - BOOL waitingForSocket = NO; - NSError *socketError = nil; - - if (result == 0) - { - waitingForSocket = YES; - } - else if (result < 0) - { - if (errno == EAGAIN) - waitingForSocket = YES; - else - socketError = [self errnoErrorWithReason:@"Error in send() function."]; - } - - if (waitingForSocket) - { - // Not enough room in the underlying OS socket send buffer. - // Wait for a notification of available space. - - LogVerbose(@"currentSend - waiting for socket"); - - if (!(flags & kSock4CanAcceptBytes)) { - [self resumeSend4Source]; - } - if (!(flags & kSock6CanAcceptBytes)) { - [self resumeSend6Source]; - } - - if ((sendTimer == NULL) && (currentSend->timeout >= 0.0)) - { - // Unable to send packet right away. - // Start timer to timeout the send operation. - - [self setupSendTimerWithTimeout:currentSend->timeout]; - } - } - else if (socketError) - { - [self closeWithError:socketError]; - } - else // done - { - [self notifyDidSendDataWithTag:currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } -} - -/** - * Releases all resources associated with the currentSend. - **/ -- (void)endCurrentSend -{ - if (sendTimer) - { - dispatch_source_cancel(sendTimer); -#if !OS_OBJECT_USE_OBJC - dispatch_release(sendTimer); -#endif - sendTimer = NULL; - } - - currentSend = nil; -} - -/** - * Performs the operations to timeout the current send operation, and move on. - **/ -- (void)doSendTimeout -{ - LogTrace(); - - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]]; - [self endCurrentSend]; - [self maybeDequeueSend]; -} - -/** - * Sets up a timer that fires to timeout the current send operation. - * This method should only be called once per send packet. - **/ -- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout -{ - NSAssert(sendTimer == NULL, @"Invalid logic"); - NSAssert(timeout >= 0.0, @"Invalid logic"); - - LogTrace(); - - sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, - 0, - 0, - socketQueue); - - dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool { - - [self doSendTimeout]; - }}); - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(sendTimer); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Receiving -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)receiveOnce:(NSError **)errPtr -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ - - if ((self->flags & kReceiveOnce) == 0) - { - if ((self->flags & kDidCreateSockets) == 0) - { - NSString *msg = @"Must bind socket before you can receive data. " - @"You can do this explicitly via bind, or implicitly via connect or by sending data."; - - err = [self badConfigError:msg]; - return_from_block; - } - - self->flags |= kReceiveOnce; // Enable - self->flags &= ~kReceiveContinuous; // Disable - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - [self doReceive]; - }}); - } - - result = YES; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error in beginReceiving: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)beginReceiving:(NSError **)errPtr -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ - - if ((self->flags & kReceiveContinuous) == 0) - { - if ((self->flags & kDidCreateSockets) == 0) - { - NSString *msg = @"Must bind socket before you can receive data. " - @"You can do this explicitly via bind, or implicitly via connect or by sending data."; - - err = [self badConfigError:msg]; - return_from_block; - } - - self->flags |= kReceiveContinuous; // Enable - self->flags &= ~kReceiveOnce; // Disable - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - [self doReceive]; - }}); - } - - result = YES; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error in beginReceiving: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (void)pauseReceiving -{ - LogTrace(); - - dispatch_block_t block = ^{ - - self->flags &= ~kReceiveOnce; // Disable - self->flags &= ~kReceiveContinuous; // Disable - - if (self->socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (self->socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue -{ - [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; -} - -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue - isAsynchronous:(BOOL)isAsynchronous -{ - GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL; - dispatch_queue_t newFilterQueue = NULL; - - if (filterBlock) - { - NSAssert(filterQueue, - @"Must provide a dispatch_queue in which to run the filter block."); - - newFilterBlock = [filterBlock copy]; - newFilterQueue = filterQueue; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(newFilterQueue); -#endif - } - - dispatch_block_t block = ^{ - -#if !OS_OBJECT_USE_OBJC - if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue); -#endif - - self->receiveFilterBlock = newFilterBlock; - self->receiveFilterQueue = newFilterQueue; - self->receiveFilterAsync = isAsynchronous; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)doReceive -{ - LogTrace(); - - if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0) - { - LogVerbose(@"Receiving is paused..."); - - if (socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - - return; - } - - if ((flags & kReceiveOnce) && (pendingFilterOperations > 0)) - { - LogVerbose(@"Receiving is temporarily paused (pending filter operations)..."); - - if (socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - - return; - } - - if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0)) - { - LogVerbose(@"No data available to receive..."); - - if (socket4FDBytesAvailable == 0) { - [self resumeReceive4Source]; - } - if (socket6FDBytesAvailable == 0) { - [self resumeReceive6Source]; - } - - return; - } - - // Figure out if we should receive on socket4 or socket6 - - BOOL doReceive4; - - if (flags & kDidConnect) - { - // Connected socket - - doReceive4 = (socket4FD != SOCKET_NULL); - } - else - { - // Non-Connected socket - - if (socket4FDBytesAvailable > 0) - { - if (socket6FDBytesAvailable > 0) - { - // Bytes available on socket4 & socket6 - - doReceive4 = (flags & kFlipFlop) ? YES : NO; - - flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit) - } - else { - // Bytes available on socket4, but not socket6 - doReceive4 = YES; - } - } - else { - // Bytes available on socket6, but not socket4 - doReceive4 = NO; - } - } - - // Perform socket IO - - ssize_t result = 0; - - NSData *data = nil; - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (doReceive4) - { - NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic"); - LogVerbose(@"Receiving on IPv4"); - - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - // #222: GCD does not necessarily return the size of an entire UDP packet - // from dispatch_source_get_data(), so we must use the maximum packet size. - size_t bufSize = max4ReceiveSize; - void *buf = malloc(bufSize); - - result = recvfrom(socket4FD, - buf, - bufSize, - 0, - (struct sockaddr *)&sockaddr4, - &sockaddr4len); - LogVerbose(@"recvfrom(socket4FD) = %i", (int)result); - - if (result > 0) - { - if ((size_t)result >= socket4FDBytesAvailable) - socket4FDBytesAvailable = 0; - else - socket4FDBytesAvailable -= result; - - if ((size_t)result != bufSize) { - buf = realloc(buf, result); - } - - data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; - addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - } - else - { - LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]); - socket4FDBytesAvailable = 0; - free(buf); - } - } - else - { - NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic"); - LogVerbose(@"Receiving on IPv6"); - - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - // #222: GCD does not necessarily return the size of an entire UDP packet - // from dispatch_source_get_data(), so we must use the maximum packet size. - size_t bufSize = max6ReceiveSize; - void *buf = malloc(bufSize); - - result = recvfrom(socket6FD, - buf, - bufSize, - 0, - (struct sockaddr *)&sockaddr6, - &sockaddr6len); - LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result); - - if (result > 0) - { - if ((size_t)result >= socket6FDBytesAvailable) - socket6FDBytesAvailable = 0; - else - socket6FDBytesAvailable -= result; - - if ((size_t)result != bufSize) { - buf = realloc(buf, result); - } - - data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - } - else - { - LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]); - socket6FDBytesAvailable = 0; - free(buf); - } - } - - - BOOL waitingForSocket = NO; - BOOL notifiedDelegate = NO; - BOOL ignored = NO; - - NSError *socketError = nil; - - if (result == 0) - { - waitingForSocket = YES; - } - else if (result < 0) - { - if (errno == EAGAIN) - waitingForSocket = YES; - else - socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"]; - } - else - { - if (flags & kDidConnect) - { - if (addr4 && ![self isConnectedToAddress4:addr4]) - ignored = YES; - if (addr6 && ![self isConnectedToAddress6:addr6]) - ignored = YES; - } - - NSData *addr = (addr4 != nil) ? addr4 : addr6; - - if (!ignored) - { - if (receiveFilterBlock && receiveFilterQueue) - { - // Run data through filter, and if approved, notify delegate - - __block id filterContext = nil; - __block BOOL allowed = NO; - - if (receiveFilterAsync) - { - pendingFilterOperations++; - dispatch_async(receiveFilterQueue, - ^{ @autoreleasepool { - - allowed = self->receiveFilterBlock(data, addr, &filterContext); - - // Transition back to socketQueue to get the current delegate / delegateQueue - dispatch_async(self->socketQueue, - ^{ @autoreleasepool { - - self->pendingFilterOperations--; - - if (allowed) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; - } - else - { - LogVerbose(@"received packet silently dropped by receiveFilter"); - } - - if (self->flags & kReceiveOnce) - { - if (allowed) - { - // The delegate has been notified, - // so our receive once operation has completed. - self->flags &= ~kReceiveOnce; - } - else if (self->pendingFilterOperations == 0) - { - // All pending filter operations have completed, - // and none were allowed through. - // Our receive once operation hasn't completed yet. - [self doReceive]; - } - } - }}); - }}); - } - else // if (!receiveFilterAsync) - { - dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { - - allowed = self->receiveFilterBlock(data, addr, &filterContext); - }}); - - if (allowed) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; - notifiedDelegate = YES; - } - else - { - LogVerbose(@"received packet silently dropped by receiveFilter"); - ignored = YES; - } - } - } - else // if (!receiveFilterBlock || !receiveFilterQueue) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil]; - notifiedDelegate = YES; - } - } - } - - if (waitingForSocket) - { - // Wait for a notification of available data. - - if (socket4FDBytesAvailable == 0) { - [self resumeReceive4Source]; - } - if (socket6FDBytesAvailable == 0) { - [self resumeReceive6Source]; - } - } - else if (socketError) - { - [self closeWithError:socketError]; - } - else - { - if (flags & kReceiveContinuous) - { - // Continuous receive mode - [self doReceive]; - } - else - { - // One-at-a-time receive mode - if (notifiedDelegate) - { - // The delegate has been notified (no set filter). - // So our receive once operation has completed. - flags &= ~kReceiveOnce; - } - else if (ignored) - { - [self doReceive]; - } - else - { - // Waiting on asynchronous receive filter... - } - } - } -} - -- (void)doReceiveEOF -{ - LogTrace(); - - [self closeWithError:[self socketClosedError]]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Closing -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)closeWithError:(NSError *)error -{ - LogVerbose(@"closeWithError: %@", error); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if (currentSend) [self endCurrentSend]; - - [sendQueue removeAllObjects]; - - // If a socket has been created, we should notify the delegate. - BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO; - - // Close all sockets, send/receive sources, cfstreams, etc -#if TARGET_OS_IPHONE - [self removeStreamsFromRunLoop]; - [self closeReadAndWriteStreams]; -#endif - [self closeSockets]; - - // Clear all flags (config remains as is) - flags = 0; - - if (shouldCallDelegate) - { - [self notifyDidCloseWithError:error]; - } -} - -- (void)close -{ - LogTrace(); - - dispatch_block_t block = ^{ @autoreleasepool { - - [self closeWithError:nil]; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (void)closeAfterSending -{ - LogTrace(); - - dispatch_block_t block = ^{ @autoreleasepool { - - self->flags |= kCloseAfterSends; - - if (self->currentSend == nil && [self->sendQueue count] == 0) - { - [self closeWithError:nil]; - } - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -static NSThread *listenerThread; - -+ (void)ignore:(id)_ -{} - -+ (void)startListenerThreadIfNeeded -{ - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ - - listenerThread = [[NSThread alloc] initWithTarget:self - selector:@selector(listenerThread:) - object:nil]; - [listenerThread start]; - }); -} - -+ (void)listenerThread:(id)unused -{ - @autoreleasepool { - - [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName]; - - LogInfo(@"ListenerThread: Started"); - - // We can't run the run loop unless it has an associated input source or a timer. - // So we'll just create a timer that will never fire - unless the server runs for a decades. - [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] - target:self - selector:@selector(ignore:) - userInfo:nil - repeats:YES]; - - [[NSRunLoop currentRunLoop] run]; - - LogInfo(@"ListenerThread: Stopped"); - } -} - -+ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == listenerThread, - @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncUdpSocket->readStream4) - CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncUdpSocket->readStream6) - CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream4) - CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream6) - CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, - runLoop, - kCFRunLoopDefaultMode); -} - -+ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == listenerThread, - @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncUdpSocket->readStream4) - CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncUdpSocket->readStream6) - CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream4) - CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, - runLoop, - kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream6) - CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, - runLoop, - kCFRunLoopDefaultMode); -} - -static void CFReadStreamCallback(CFReadStreamRef stream, - CFStreamEventType type, - void *pInfo) -{ - @autoreleasepool { - GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventOpenCompleted: - { - LogCVerbose(@"CFReadStreamCallback - Open"); - break; - } - case kCFStreamEventHasBytesAvailable: - { - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - break; - } - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncUdpSocket socketClosedError]; - } - - dispatch_async(asyncUdpSocket->socketQueue, - ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - %@", - (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); - - if (stream != asyncUdpSocket->readStream4 && - stream != asyncUdpSocket->readStream6 ) - { - LogCVerbose(@"CFReadStreamCallback - Ignored"); - return_from_block; - } - - [asyncUdpSocket closeWithError:error]; - - }}); - - break; - } - default: - { - LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type); - } - } -#pragma clang diagnostic pop - } -} - -static void CFWriteStreamCallback(CFWriteStreamRef stream, - CFStreamEventType type, - void *pInfo) -{ - @autoreleasepool { - GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventOpenCompleted: - { - LogCVerbose(@"CFWriteStreamCallback - Open"); - break; - } - case kCFStreamEventCanAcceptBytes: - { - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - break; - } - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncUdpSocket socketClosedError]; - } - - dispatch_async(asyncUdpSocket->socketQueue, - ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - %@", - (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); - - if (stream != asyncUdpSocket->writeStream4 && - stream != asyncUdpSocket->writeStream6 ) - { - LogCVerbose(@"CFWriteStreamCallback - Ignored"); - return_from_block; - } - - [asyncUdpSocket closeWithError:error]; - - }}); - - break; - } - default: - { - LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type); - } - } -#pragma clang diagnostic pop - } -} - -- (BOOL)createReadAndWriteStreams:(NSError **)errPtr -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - NSError *err = nil; - - if (readStream4 || writeStream4 || readStream6 || writeStream6) - { - // Streams already created - return YES; - } - - if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) - { - err = [self otherError:@"Cannot create streams without a file descriptor"]; - goto Failed; - } - - // Create streams - - LogVerbose(@"Creating read and write stream(s)..."); - - if (socket4FD != SOCKET_NULL) - { - CFStreamCreatePairWithSocket(NULL, - (CFSocketNativeHandle)socket4FD, - &readStream4, - &writeStream4); - if (!readStream4 || !writeStream4) - { - err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"]; - goto Failed; - } - } - - if (socket6FD != SOCKET_NULL) - { - CFStreamCreatePairWithSocket(NULL, - (CFSocketNativeHandle)socket6FD, - &readStream6, - &writeStream6); - if (!readStream6 || !writeStream6) - { - err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"]; - goto Failed; - } - } - - // Ensure the CFStream's don't close our underlying socket - - CFReadStreamSetProperty(readStream4, - kCFStreamPropertyShouldCloseNativeSocket, - kCFBooleanFalse); - CFWriteStreamSetProperty(writeStream4, - kCFStreamPropertyShouldCloseNativeSocket, - kCFBooleanFalse); - - CFReadStreamSetProperty(readStream6, - kCFStreamPropertyShouldCloseNativeSocket, - kCFBooleanFalse); - CFWriteStreamSetProperty(writeStream6, - kCFStreamPropertyShouldCloseNativeSocket, - kCFBooleanFalse); - - return YES; - -Failed: - if (readStream4) - { - CFReadStreamClose(readStream4); - CFRelease(readStream4); - readStream4 = NULL; - } - if (writeStream4) - { - CFWriteStreamClose(writeStream4); - CFRelease(writeStream4); - writeStream4 = NULL; - } - if (readStream6) - { - CFReadStreamClose(readStream6); - CFRelease(readStream6); - readStream6 = NULL; - } - if (writeStream6) - { - CFWriteStreamClose(writeStream6); - CFRelease(writeStream6); - writeStream6 = NULL; - } - - if (errPtr) - *errPtr = err; - - return NO; -} - -- (BOOL)registerForStreamCallbacks:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, - @"Read/Write streams are null"); - - NSError *err = nil; - - streamContext.version = 0; - streamContext.info = (__bridge void *)self; - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - - // readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable); - // writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes); - - if (socket4FD != SOCKET_NULL) - { - if (readStream4 == NULL || writeStream4 == NULL) - { - err = [self otherError:@"Read/Write stream4 is null"]; - goto Failed; - } - - BOOL r1 = CFReadStreamSetClient(readStream4, - readStreamEvents, - &CFReadStreamCallback, - &streamContext); - BOOL r2 = CFWriteStreamSetClient(writeStream4, - writeStreamEvents, - &CFWriteStreamCallback, - &streamContext); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"]; - goto Failed; - } - } - - if (socket6FD != SOCKET_NULL) - { - if (readStream6 == NULL || writeStream6 == NULL) - { - err = [self otherError:@"Read/Write stream6 is null"]; - goto Failed; - } - - BOOL r1 = CFReadStreamSetClient(readStream6, - readStreamEvents, - &CFReadStreamCallback, - &streamContext); - BOOL r2 = CFWriteStreamSetClient(writeStream6, - writeStreamEvents, - &CFWriteStreamCallback, - &streamContext); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"]; - goto Failed; - } - } - - return YES; - -Failed: - if (readStream4) { - CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); - } - if (writeStream4) { - CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); - } - if (readStream6) { - CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); - } - if (writeStream6) { - CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); - } - - if (errPtr) *errPtr = err; - return NO; -} - -- (BOOL)addStreamsToRunLoop:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, - @"Read/Write streams are null"); - - if (!(flags & kAddedStreamListener)) - { - [[self class] startListenerThreadIfNeeded]; - [[self class] performSelector:@selector(addStreamListener:) - onThread:listenerThread - withObject:self - waitUntilDone:YES]; - - flags |= kAddedStreamListener; - } - - return YES; -} - -- (BOOL)openStreams:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, - @"Read/Write streams are null"); - - NSError *err = nil; - - if (socket4FD != SOCKET_NULL) - { - BOOL r1 = CFReadStreamOpen(readStream4); - BOOL r2 = CFWriteStreamOpen(writeStream4); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamOpen() [IPv4]"]; - goto Failed; - } - } - - if (socket6FD != SOCKET_NULL) - { - BOOL r1 = CFReadStreamOpen(readStream6); - BOOL r2 = CFWriteStreamOpen(writeStream6); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamOpen() [IPv6]"]; - goto Failed; - } - } - - return YES; - -Failed: - if (errPtr) *errPtr = err; - return NO; -} - -- (void)removeStreamsFromRunLoop -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), - @"Must be dispatched on socketQueue"); - - if (flags & kAddedStreamListener) - { - [[self class] performSelector:@selector(removeStreamListener:) - onThread:listenerThread - withObject:self - waitUntilDone:YES]; - - flags &= ~kAddedStreamListener; - } -} - -- (void)closeReadAndWriteStreams -{ - LogTrace(); - - if (readStream4) - { - CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream4); - CFRelease(readStream4); - readStream4 = NULL; - } - if (writeStream4) - { - CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream4); - CFRelease(writeStream4); - writeStream4 = NULL; - } - if (readStream6) - { - CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream6); - CFRelease(readStream6); - readStream6 = NULL; - } - if (writeStream6) - { - CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream6); - CFRelease(writeStream6); - writeStream6 = NULL; - } -} - -#endif - -#if TARGET_OS_IPHONE -- (void)applicationWillEnterForeground:(NSNotification *)notification -{ - LogTrace(); - - // If the application was backgrounded, then iOS may have shut down our sockets. - // So we take a quick look to see if any of them received an EOF. - - dispatch_block_t block = ^{ @autoreleasepool { - - [self resumeReceive4Source]; - [self resumeReceive6Source]; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Advanced -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * See header file for big discussion of this method. - **/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue -{ - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketNewTargetQueue, - IsOnSocketQueueOrTargetQueueKey, - nonNullUnusedPointer, - NULL); -} - -/** - * See header file for big discussion of this method. - **/ -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue -{ - dispatch_queue_set_specific(socketOldTargetQueue, - IsOnSocketQueueOrTargetQueueKey, - NULL, - NULL); -} - -- (void)performBlock:(dispatch_block_t)block -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (int)socketFD -{ - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } - - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; -} - -- (int)socket4FD -{ - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } - - return socket4FD; -} - -- (int)socket6FD -{ - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } - - return socket6FD; -} - -#if TARGET_OS_IPHONE - -- (CFReadStreamRef)readStream -{ - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NULL; - } - - NSError *err = nil; - if (![self createReadAndWriteStreams:&err]) - { - LogError(@"Error creating CFStream(s): %@", err); - return NULL; - } - - // Todo... - - if (readStream4) - return readStream4; - else - return readStream6; -} - -- (CFWriteStreamRef)writeStream -{ - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NULL; - } - - NSError *err = nil; - if (![self createReadAndWriteStreams:&err]) - { - LogError(@"Error creating CFStream(s): %@", err); - return NULL; - } - - if (writeStream4) - return writeStream4; - else - return writeStream6; -} - -- (BOOL)enableBackgroundingOnSockets -{ - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NO; - } - - // Why is this commented out? - // See comments below. - - // NSError *err = nil; - // if (![self createReadAndWriteStreams:&err]) - // { - // LogError(@"Error creating CFStream(s): %@", err); - // return NO; - // } - // - // LogVerbose(@"Enabling backgrouding on socket"); - // - // BOOL r1, r2; - // - // if (readStream4 && writeStream4) - // { - // r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - // r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - // - // if (!r1 || !r2) - // { - // LogError(@"Error setting voip type (IPv4)"); - // return NO; - // } - // } - // - // if (readStream6 && writeStream6) - // { - // r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - // r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - // - // if (!r1 || !r2) - // { - // LogError(@"Error setting voip type (IPv6)"); - // return NO; - // } - // } - // - // return YES; - - // The above code will actually appear to work. - // The methods will return YES, and everything will appear fine. - // - // One tiny problem: the sockets will still get closed when the app gets backgrounded. - // - // Apple does not officially support backgrounding UDP sockets. - - return NO; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Methods -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if (inet_ntop(AF_INET, - &pSockaddr4->sin_addr, - addrBuf, - (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if (inet_ntop(AF_INET6, - &pSockaddr6->sin6_addr, - addrBuf, - (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - return ntohs(pSockaddr4->sin_port); -} - -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - return ntohs(pSockaddr6->sin6_port); -} - -+ (NSString *)hostFromAddress:(NSData *)address -{ - NSString *host = nil; - [self getHost:&host port:NULL family:NULL fromAddress:address]; - - return host; -} - -+ (uint16_t)portFromAddress:(NSData *)address -{ - uint16_t port = 0; - [self getHost:NULL port:&port family:NULL fromAddress:address]; - - return port; -} - -+ (int)familyFromAddress:(NSData *)address -{ - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; - - return af; -} - -+ (BOOL)isIPv4Address:(NSData *)address -{ - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; - - return (af == AF_INET); -} - -+ (BOOL)isIPv6Address:(NSData *)address -{ - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; - - return (af == AF_INET6); -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address -{ - return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *addrX = (const struct sockaddr *)[address bytes]; - - if (addrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX; - - if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; - if (afPtr) *afPtr = AF_INET; - - return YES; - } - } - else if (addrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX; - - if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:addr6]; - if (afPtr) *afPtr = AF_INET6; - - return YES; - } - } - } - - if (hostPtr) *hostPtr = nil; - if (portPtr) *portPtr = 0; - if (afPtr) *afPtr = AF_UNSPEC; - - return NO; -} - -@end - -#pragma clang diagnostic pop diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h deleted file mode 100644 index 26436103ea..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h +++ /dev/null @@ -1,12 +0,0 @@ -#import - - -@interface NSNumber (DDNumber) - -+ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum; -+ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum; - -+ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum; -+ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum; - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m deleted file mode 100644 index 2a9f207555..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m +++ /dev/null @@ -1,88 +0,0 @@ -#import "DDNumber.h" - - -@implementation NSNumber (DDNumber) - -+ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum -{ - if(str == nil) - { - *pNum = 0; - return NO; - } - - errno = 0; - - // On both 32-bit and 64-bit machines, long long = 64 bit - - *pNum = strtoll([str UTF8String], NULL, 10); - - if(errno != 0) - return NO; - else - return YES; -} - -+ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum -{ - if(str == nil) - { - *pNum = 0; - return NO; - } - - errno = 0; - - // On both 32-bit and 64-bit machines, unsigned long long = 64 bit - - *pNum = strtoull([str UTF8String], NULL, 10); - - if(errno != 0) - return NO; - else - return YES; -} - -+ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum -{ - if(str == nil) - { - *pNum = 0; - return NO; - } - - errno = 0; - - // On LP64, NSInteger = long = 64 bit - // Otherwise, NSInteger = int = long = 32 bit - - *pNum = strtol([str UTF8String], NULL, 10); - - if(errno != 0) - return NO; - else - return YES; -} - -+ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum -{ - if(str == nil) - { - *pNum = 0; - return NO; - } - - errno = 0; - - // On LP64, NSUInteger = unsigned long = 64 bit - // Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit - - *pNum = strtoul([str UTF8String], NULL, 10); - - if(errno != 0) - return NO; - else - return YES; -} - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h deleted file mode 100644 index e01db03f75..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * DDRange is the functional equivalent of a 64 bit NSRange. - * The HTTP Server is designed to support very large files. - * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers. - * This only supports a range of up to 4 gigabytes. - * By defining our own variant, we can support a range up to 16 exabytes. - * - * All effort is given such that DDRange functions EXACTLY the same as NSRange. - **/ - -#import -#import - -@class NSString; - -typedef struct _DDRange { - UInt64 location; - UInt64 length; -} DDRange; - -typedef DDRange *DDRangePointer; - -NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) { - DDRange r; - r.location = loc; - r.length = len; - return r; -} - -NS_INLINE UInt64 DDMaxRange(DDRange range) { - return (range.location + range.length); -} - -NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) { - return (loc - range.location < range.length); -} - -NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) { - return ((range1.location == range2.location) && (range1.length == range2.length)); -} - -FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2); -FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2); -FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range); -FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString); - -NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2); - -@interface NSValue (NSValueDDRangeExtensions) - -+ (NSValue *)valueWithDDRange:(DDRange)range; -- (DDRange)ddrangeValue; - -- (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue; - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m deleted file mode 100644 index d8c8c70ca9..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m +++ /dev/null @@ -1,100 +0,0 @@ -#import "DDRange.h" -#import "DDNumber.h" - -#pragma clang diagnostic ignored "-Wformat-non-iso" - -DDRange DDUnionRange(DDRange range1, DDRange range2) -{ - UInt64 location = MIN(range1.location, range2.location); - UInt64 length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - location; - - return DDMakeRange(location, length); -} - -DDRange DDIntersectionRange(DDRange range1, DDRange range2) -{ - if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location)) - { - return DDMakeRange(0, 0); - } - - return DDMakeRange(MAX(range1.location, range2.location), - MIN(DDMaxRange(range1), DDMaxRange(range2)) - MAX(range1.location, range2.location)); -} - -NSString *DDStringFromRange(DDRange range) -{ - return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length]; -} - -DDRange DDRangeFromString(NSString *aString) -{ - DDRange result = DDMakeRange(0, 0); - - // NSRange will ignore '-' characters, but not '+' characters - NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]; - - NSScanner *scanner = [NSScanner scannerWithString:aString]; - [scanner setCharactersToBeSkipped:[cset invertedSet]]; - - NSString *str1 = nil; - NSString *str2 = nil; - - BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1]; - BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2]; - - if(found1) [NSNumber parseString:str1 intoUInt64:&result.location]; - if(found2) [NSNumber parseString:str2 intoUInt64:&result.length]; - - return result; -} - -NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2) -{ - // Comparison basis: - // Which range would you encouter first if you started at zero, and began walking towards infinity. - // If you encouter both ranges at the same time, which range would end first. - - if(pDDRange1->location < pDDRange2->location) - { - return NSOrderedAscending; - } - if(pDDRange1->location > pDDRange2->location) - { - return NSOrderedDescending; - } - if(pDDRange1->length < pDDRange2->length) - { - return NSOrderedAscending; - } - if(pDDRange1->length > pDDRange2->length) - { - return NSOrderedDescending; - } - - return NSOrderedSame; -} - -@implementation NSValue (NSValueDDRangeExtensions) - -+ (NSValue *)valueWithDDRange:(DDRange)range -{ - return [NSValue valueWithBytes:&range objCType:@encode(DDRange)]; -} - -- (DDRange)ddrangeValue -{ - DDRange result; - [self getValue:&result]; - return result; -} - -- (NSInteger)ddrangeCompare:(NSValue *)other -{ - DDRange r1 = [self ddrangeValue]; - DDRange r2 = [other ddrangeValue]; - - return DDRangeCompare(&r1, &r2); -} - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h deleted file mode 100644 index 8d409bf714..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h +++ /dev/null @@ -1,109 +0,0 @@ -#import - -@class GCDAsyncSocket; -@class HTTPMessage; -@class HTTPServer; -@class WebSocket; -@protocol HTTPResponse; - - -#define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie" - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface HTTPConfig : NSObject -{ - HTTPServer __unsafe_unretained *server; - NSString __strong *documentRoot; - dispatch_queue_t queue; -} - -- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot; -- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q; - -@property (nonatomic, unsafe_unretained, readonly) HTTPServer *server; -@property (nonatomic, strong, readonly) NSString *documentRoot; -@property (nonatomic, readonly) dispatch_queue_t queue; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface HTTPConnection : NSObject -{ - dispatch_queue_t connectionQueue; - GCDAsyncSocket *asyncSocket; - HTTPConfig *config; - - BOOL started; - - HTTPMessage *request; - unsigned int numHeaderLines; - - BOOL sentResponseHeaders; - - NSObject *httpResponse; - - NSMutableArray *ranges; - NSMutableArray *ranges_headers; - NSString *ranges_boundry; - int rangeIndex; - - UInt64 requestContentLength; - UInt64 requestContentLengthReceived; - UInt64 requestChunkSize; - UInt64 requestChunkSizeReceived; - - NSMutableArray *responseDataSizes; -} - -- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig; - -- (void)start; -- (void)stop; - -- (void)startConnection; - -- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path; -- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path; - -- (NSDictionary *)parseParams:(NSString *)query; -- (NSDictionary *)parseGetParams; - -- (NSString *)requestURI; - -- (NSArray *)directoryIndexFileNames; -- (NSString *)filePathForURI:(NSString *)path; -- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory; -- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path; -- (WebSocket *)webSocketForURI:(NSString *)path; - -- (void)prepareForBodyWithSize:(UInt64)contentLength; -- (void)processBodyData:(NSData *)postDataChunk; -- (void)finishBody; -- (UInt64)maxRequestBodySize; - -- (void)handleVersionNotSupported:(NSString *)version; -- (void)handleRequestBodyTooLarge; -- (void)handleResourceNotFound; -- (void)handleInvalidRequest:(NSData *)data; -- (void)handleUnknownMethod:(NSString *)method; - -- (NSData *)preprocessResponse:(HTTPMessage *)response; -- (NSData *)preprocessErrorResponse:(HTTPMessage *)response; - -- (void)finishResponse; - -- (BOOL)shouldDie; -- (void)die; - -@end - -@interface HTTPConnection (AsynchronousHTTPResponse) -- (void)responseHasAvailableData:(NSObject *)sender; -- (void)responseDidAbort:(NSObject *)sender; -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m deleted file mode 100644 index 3e2a11893e..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m +++ /dev/null @@ -1,2281 +0,0 @@ -#import "HTTPServer.h" -#import "HTTPConnection.h" -#import "HTTPMessage.h" -#import "HTTPResponse.h" -#import "DDNumber.h" -#import "DDRange.h" -#import "HTTPLogging.h" - -#import "GCDAsyncSocket.h" - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -#pragma clang diagnostic ignored "-Wunknown-warning-option" -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Wimplicit-retain-self" -#pragma clang diagnostic ignored "-Wformat-non-iso" -#pragma clang diagnostic ignored "-Wunused-variable" -#pragma clang diagnostic ignored "-Wsign-compare" -#pragma clang diagnostic ignored "-Wformat-nonliteral" -#pragma clang diagnostic ignored "-Wunreachable-code" -#pragma clang diagnostic ignored "-Wfloat-conversion" - -// Log levels: off, error, warn, info, verbose -// Other flags: trace -static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; - -// Define chunk size used to read in data for responses -// This is how much data will be read from disk into RAM at a time -#if TARGET_OS_IPHONE -#define READ_CHUNKSIZE (1024 * 256) -#else -#define READ_CHUNKSIZE (1024 * 512) -#endif - -// Define chunk size used to read in POST upload data -#if TARGET_OS_IPHONE -#define POST_CHUNKSIZE (1024 * 256) -#else -#define POST_CHUNKSIZE (1024 * 512) -#endif - -// Define the various timeouts (in seconds) for various parts of the HTTP process -#define TIMEOUT_READ_FIRST_HEADER_LINE 30 -#define TIMEOUT_READ_SUBSEQUENT_HEADER_LINE 30 -#define TIMEOUT_READ_BODY -1 -#define TIMEOUT_WRITE_HEAD 30 -#define TIMEOUT_WRITE_BODY -1 -#define TIMEOUT_WRITE_ERROR 30 -#define TIMEOUT_NONCE 300 - -// Define the various limits -// MAX_HEADER_LINE_LENGTH: Max length (in bytes) of any single line in a header (including \r\n) -// MAX_HEADER_LINES : Max number of lines in a single header (including first GET line) -#define MAX_HEADER_LINE_LENGTH 8190 -#define MAX_HEADER_LINES 100 -// MAX_CHUNK_LINE_LENGTH : For accepting chunked transfer uploads, max length of chunk size line (including \r\n) -#define MAX_CHUNK_LINE_LENGTH 200 - -// Define the various tags we'll use to differentiate what it is we're currently doing -#define HTTP_REQUEST_HEADER 10 -#define HTTP_REQUEST_BODY 11 -#define HTTP_REQUEST_CHUNK_SIZE 12 -#define HTTP_REQUEST_CHUNK_DATA 13 -#define HTTP_REQUEST_CHUNK_TRAILER 14 -#define HTTP_REQUEST_CHUNK_FOOTER 15 -#define HTTP_PARTIAL_RESPONSE 20 -#define HTTP_PARTIAL_RESPONSE_HEADER 21 -#define HTTP_PARTIAL_RESPONSE_BODY 22 -#define HTTP_CHUNKED_RESPONSE_HEADER 30 -#define HTTP_CHUNKED_RESPONSE_BODY 31 -#define HTTP_CHUNKED_RESPONSE_FOOTER 32 -#define HTTP_PARTIAL_RANGE_RESPONSE_BODY 40 -#define HTTP_PARTIAL_RANGES_RESPONSE_BODY 50 -#define HTTP_RESPONSE 90 -#define HTTP_FINAL_RESPONSE 91 - -// A quick note about the tags: -// -// The HTTP_RESPONSE and HTTP_FINAL_RESPONSE are designated tags signalling that the response is completely sent. -// That is, in the onSocket:didWriteDataWithTag: method, if the tag is HTTP_RESPONSE or HTTP_FINAL_RESPONSE, -// it is assumed that the response is now completely sent. -// Use HTTP_RESPONSE if it's the end of a response, and you want to start reading more requests afterwards. -// Use HTTP_FINAL_RESPONSE if you wish to terminate the connection after sending the response. -// -// If you are sending multiple data segments in a custom response, make sure that only the last segment has -// the HTTP_RESPONSE tag. For all other segments prior to the last segment use HTTP_PARTIAL_RESPONSE, or some other -// tag of your own invention. - -@interface HTTPConnection (PrivateAPI) -- (void)startReadingRequest; -- (void)sendResponseHeadersAndBody; -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation HTTPConnection - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Init, Dealloc: -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Sole Constructor. - * Associates this new HTTP connection with the given AsyncSocket. - * This HTTP connection object will become the socket's delegate and take over responsibility for the socket. - **/ -- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig -{ - if ((self = [super init])) - { - HTTPLogTrace(); - - if (aConfig.queue) - { - connectionQueue = aConfig.queue; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(connectionQueue); -#endif - } - else - { - connectionQueue = dispatch_queue_create("HTTPConnection", NULL); - } - - // Take over ownership of the socket - asyncSocket = newSocket; - [asyncSocket setDelegate:(id)self delegateQueue:connectionQueue]; - - - // Store configuration - config = aConfig; - - // Create a new HTTP message - request = [[HTTPMessage alloc] initEmptyRequest]; - - numHeaderLines = 0; - - responseDataSizes = [[NSMutableArray alloc] initWithCapacity:5]; - } - return self; -} - -/** - * Standard Deconstructor. - **/ -- (void)dealloc -{ - HTTPLogTrace(); - -#if !OS_OBJECT_USE_OBJC - dispatch_release(connectionQueue); -#endif - - [asyncSocket setDelegate:nil delegateQueue:NULL]; - [asyncSocket disconnect]; - - if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) - { - [httpResponse connectionDidClose]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Method Support -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Returns whether or not the server will accept messages of a given method - * at a particular URI. - **/ -- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path -{ - HTTPLogTrace(); - - // Override me to support methods such as POST. - // - // Things you may want to consider: - // - Does the given path represent a resource that is designed to accept this method? - // - If accepting an upload, is the size of the data being uploaded too big? - // To do this you can check the requestContentLength variable. - // - // For more information, you can always access the HTTPMessage request variable. - // - // You should fall through with a call to [super supportsMethod:method atPath:path] - // - // See also: expectsRequestBodyFromMethod:atPath: - - if ([method isEqualToString:@"GET"]) - return YES; - - if ([method isEqualToString:@"HEAD"]) - return YES; - - return NO; -} - -/** - * Returns whether or not the server expects a body from the given method. - * - * In other words, should the server expect a content-length header and associated body from this method. - * This would be true in the case of a POST, where the client is sending data, - * or for something like PUT where the client is supposed to be uploading a file. - **/ -- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path -{ - HTTPLogTrace(); - - // Override me to add support for other methods that expect the client - // to send a body along with the request header. - // - // You should fall through with a call to [super expectsRequestBodyFromMethod:method atPath:path] - // - // See also: supportsMethod:atPath: - - if ([method isEqualToString:@"POST"]) - return YES; - - if ([method isEqualToString:@"PUT"]) - return YES; - - return NO; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Core -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Starting point for the HTTP connection after it has been fully initialized (including subclasses). - * This method is called by the HTTP server. - **/ -- (void)start -{ - dispatch_async(connectionQueue, ^{ @autoreleasepool { - - if (!started) - { - started = YES; - [self startConnection]; - } - }}); -} - -/** - * This method is called by the HTTPServer if it is asked to stop. - * The server, in turn, invokes stop on each HTTPConnection instance. - **/ -- (void)stop -{ - dispatch_async(connectionQueue, ^{ @autoreleasepool { - - // Disconnect the socket. - // The socketDidDisconnect delegate method will handle everything else. - [asyncSocket disconnect]; - }}); -} - -/** - * Starting point for the HTTP connection. - **/ -- (void)startConnection -{ - // Override me to do any custom work before the connection starts. - // - // Be sure to invoke [super startConnection] when you're done. - - HTTPLogTrace(); - - [self startReadingRequest]; -} - -/** - * Starts reading an HTTP request. - **/ -- (void)startReadingRequest -{ - HTTPLogTrace(); - - [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] - withTimeout:TIMEOUT_READ_FIRST_HEADER_LINE - maxLength:MAX_HEADER_LINE_LENGTH - tag:HTTP_REQUEST_HEADER]; -} - -/** - * Parses the given query string. - * - * For example, if the query is "q=John%20Mayer%20Trio&num=50" - * then this method would return the following dictionary: - * { - * q = "John Mayer Trio" - * num = "50" - * } - **/ -- (NSDictionary *)parseParams:(NSString *)query -{ - NSArray *components = [query componentsSeparatedByString:@"&"]; - NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[components count]]; - - NSUInteger i; - for (i = 0; i < [components count]; i++) - { - NSString *component = [components objectAtIndex:i]; - if ([component length] > 0) - { - NSRange range = [component rangeOfString:@"="]; - if (range.location != NSNotFound) - { - NSString *escapedKey = [component substringToIndex:(range.location + 0)]; - NSString *escapedValue = [component substringFromIndex:(range.location + 1)]; - - if ([escapedKey length] > 0) - { - CFStringRef k, v; - - k = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedKey, CFSTR("")); - v = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedValue, CFSTR("")); - - NSString *key, *value; - - key = (__bridge_transfer NSString *)k; - value = (__bridge_transfer NSString *)v; - - if (key) - { - if (value) - [result setObject:value forKey:key]; - else - [result setObject:[NSNull null] forKey:key]; - } - } - } - } - } - - return result; -} - -/** - * Parses the query variables in the request URI. - * - * For example, if the request URI was "/search.html?q=John%20Mayer%20Trio&num=50" - * then this method would return the following dictionary: - * { - * q = "John Mayer Trio" - * num = "50" - * } - **/ -- (NSDictionary *)parseGetParams -{ - if(![request isHeaderComplete]) return nil; - - NSDictionary *result = nil; - - NSURL *url = [request url]; - if(url) - { - NSString *query = [url query]; - if (query) - { - result = [self parseParams:query]; - } - } - - return result; -} - -/** - * Attempts to parse the given range header into a series of sequential non-overlapping ranges. - * If successfull, the variables 'ranges' and 'rangeIndex' will be updated, and YES will be returned. - * Otherwise, NO is returned, and the range request should be ignored. - **/ -- (BOOL)parseRangeRequest:(NSString *)rangeHeader withContentLength:(UInt64)contentLength -{ - HTTPLogTrace(); - - // Examples of byte-ranges-specifier values (assuming an entity-body of length 10000): - // - // - The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499 - // - // - The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999 - // - // - The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500 - // - // - Or bytes=9500- - // - // - The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1 - // - // - Several legal but not canonical specifications of the second 500 bytes (byte offsets 500-999, inclusive): - // bytes=500-600,601-999 - // bytes=500-700,601-999 - // - - NSRange eqsignRange = [rangeHeader rangeOfString:@"="]; - - if(eqsignRange.location == NSNotFound) return NO; - - NSUInteger tIndex = eqsignRange.location; - NSUInteger fIndex = eqsignRange.location + eqsignRange.length; - - NSMutableString *rangeType = [[rangeHeader substringToIndex:tIndex] mutableCopy]; - NSMutableString *rangeValue = [[rangeHeader substringFromIndex:fIndex] mutableCopy]; - - CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeType); - CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeValue); - - if([rangeType caseInsensitiveCompare:@"bytes"] != NSOrderedSame) return NO; - - NSArray *rangeComponents = [rangeValue componentsSeparatedByString:@","]; - - if([rangeComponents count] == 0) return NO; - - ranges = [[NSMutableArray alloc] initWithCapacity:[rangeComponents count]]; - - rangeIndex = 0; - - // Note: We store all range values in the form of DDRange structs, wrapped in NSValue objects. - // Since DDRange consists of UInt64 values, the range extends up to 16 exabytes. - - NSUInteger i; - for (i = 0; i < [rangeComponents count]; i++) - { - NSString *rangeComponent = [rangeComponents objectAtIndex:i]; - - NSRange dashRange = [rangeComponent rangeOfString:@"-"]; - - if (dashRange.location == NSNotFound) - { - // We're dealing with an individual byte number - - UInt64 byteIndex; - if(![NSNumber parseString:rangeComponent intoUInt64:&byteIndex]) return NO; - - if(byteIndex >= contentLength) return NO; - - [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(byteIndex, 1)]]; - } - else - { - // We're dealing with a range of bytes - - tIndex = dashRange.location; - fIndex = dashRange.location + dashRange.length; - - NSString *r1str = [rangeComponent substringToIndex:tIndex]; - NSString *r2str = [rangeComponent substringFromIndex:fIndex]; - - UInt64 r1, r2; - - BOOL hasR1 = [NSNumber parseString:r1str intoUInt64:&r1]; - BOOL hasR2 = [NSNumber parseString:r2str intoUInt64:&r2]; - - if (!hasR1) - { - // We're dealing with a "-[#]" range - // - // r2 is the number of ending bytes to include in the range - - if(!hasR2) return NO; - if(r2 > contentLength) return NO; - - UInt64 startIndex = contentLength - r2; - - [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(startIndex, r2)]]; - } - else if (!hasR2) - { - // We're dealing with a "[#]-" range - // - // r1 is the starting index of the range, which goes all the way to the end - - if(r1 >= contentLength) return NO; - - [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, contentLength - r1)]]; - } - else - { - // We're dealing with a normal "[#]-[#]" range - // - // Note: The range is inclusive. So 0-1 has a length of 2 bytes. - - if(r1 > r2) return NO; - if(r2 >= contentLength) return NO; - - [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, r2 - r1 + 1)]]; - } - } - } - - if([ranges count] == 0) return NO; - - // Now make sure none of the ranges overlap - - for (i = 0; i < [ranges count] - 1; i++) - { - DDRange range1 = [[ranges objectAtIndex:i] ddrangeValue]; - - NSUInteger j; - for (j = i+1; j < [ranges count]; j++) - { - DDRange range2 = [[ranges objectAtIndex:j] ddrangeValue]; - - DDRange iRange = DDIntersectionRange(range1, range2); - - if(iRange.length != 0) - { - return NO; - } - } - } - - // Sort the ranges - - [ranges sortUsingSelector:@selector(ddrangeCompare:)]; - - return YES; -} - -- (NSString *)requestURI -{ - if(request == nil) return nil; - - return [[request url] relativeString]; -} - -/** - * This method is called after a full HTTP request has been received. - * The current request is in the HTTPMessage request variable. - **/ -- (void)replyToHTTPRequest -{ - HTTPLogTrace(); - - if (HTTP_LOG_VERBOSE) - { - NSData *tempData = [request messageData]; - - NSString *tempStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding]; - HTTPLogVerbose(@"%@[%p]: Received HTTP request:\n%@", THIS_FILE, self, tempStr); - } - - // Check the HTTP version - // We only support version 1.0 and 1.1 - - NSString *version = [request version]; - if (![version isEqualToString:HTTPVersion1_1] && ![version isEqualToString:HTTPVersion1_0]) - { - [self handleVersionNotSupported:version]; - return; - } - - // Extract requested URI - NSString *uri = [self requestURI]; - - // Extract the method - NSString *method = [request method]; - - // Note: We already checked to ensure the method was supported in onSocket:didReadData:withTag: - - // Respond properly to HTTP 'GET' and 'HEAD' commands - httpResponse = [self httpResponseForMethod:method URI:uri]; - - if (httpResponse == nil) - { - [self handleResourceNotFound]; - return; - } - - [self sendResponseHeadersAndBody]; -} - -/** - * Prepares a single-range response. - * - * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it. - **/ -- (HTTPMessage *)newUniRangeResponse:(UInt64)contentLength -{ - HTTPLogTrace(); - - // Status Code 206 - Partial Content - HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1]; - - DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; - - NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", range.length]; - [response setHeaderField:@"Content-Length" value:contentLengthStr]; - - NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1]; - NSString *contentRangeStr = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength]; - [response setHeaderField:@"Content-Range" value:contentRangeStr]; - - return response; -} - -/** - * Prepares a multi-range response. - * - * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it. - **/ -- (HTTPMessage *)newMultiRangeResponse:(UInt64)contentLength -{ - HTTPLogTrace(); - - // Status Code 206 - Partial Content - HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1]; - - // We have to send each range using multipart/byteranges - // So each byterange has to be prefix'd and suffix'd with the boundry - // Example: - // - // HTTP/1.1 206 Partial Content - // Content-Length: 220 - // Content-Type: multipart/byteranges; boundary=4554d24e986f76dd6 - // - // - // --4554d24e986f76dd6 - // Content-Range: bytes 0-25/4025 - // - // [...] - // --4554d24e986f76dd6 - // Content-Range: bytes 3975-4024/4025 - // - // [...] - // --4554d24e986f76dd6-- - - ranges_headers = [[NSMutableArray alloc] initWithCapacity:[ranges count]]; - - CFUUIDRef theUUID = CFUUIDCreate(NULL); - ranges_boundry = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID); - CFRelease(theUUID); - - NSString *startingBoundryStr = [NSString stringWithFormat:@"\r\n--%@\r\n", ranges_boundry]; - NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry]; - - UInt64 actualContentLength = 0; - - NSUInteger i; - for (i = 0; i < [ranges count]; i++) - { - DDRange range = [[ranges objectAtIndex:i] ddrangeValue]; - - NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1]; - NSString *contentRangeVal = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength]; - NSString *contentRangeStr = [NSString stringWithFormat:@"Content-Range: %@\r\n\r\n", contentRangeVal]; - - NSString *fullHeader = [startingBoundryStr stringByAppendingString:contentRangeStr]; - NSData *fullHeaderData = [fullHeader dataUsingEncoding:NSUTF8StringEncoding]; - - [ranges_headers addObject:fullHeaderData]; - - actualContentLength += [fullHeaderData length]; - actualContentLength += range.length; - } - - NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding]; - - actualContentLength += [endingBoundryData length]; - - NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", actualContentLength]; - [response setHeaderField:@"Content-Length" value:contentLengthStr]; - - NSString *contentTypeStr = [NSString stringWithFormat:@"multipart/byteranges; boundary=%@", ranges_boundry]; - [response setHeaderField:@"Content-Type" value:contentTypeStr]; - - return response; -} - -/** - * Returns the chunk size line that must precede each chunk of data when using chunked transfer encoding. - * This consists of the size of the data, in hexadecimal, followed by a CRLF. - **/ -- (NSData *)chunkedTransferSizeLineForLength:(NSUInteger)length -{ - return [[NSString stringWithFormat:@"%lx\r\n", (unsigned long)length] dataUsingEncoding:NSUTF8StringEncoding]; -} - -/** - * Returns the data that signals the end of a chunked transfer. - **/ -- (NSData *)chunkedTransferFooter -{ - // Each data chunk is preceded by a size line (in hex and including a CRLF), - // followed by the data itself, followed by another CRLF. - // After every data chunk has been sent, a zero size line is sent, - // followed by optional footer (which are just more headers), - // and followed by a CRLF on a line by itself. - - return [@"\r\n0\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]; -} - -- (void)sendResponseHeadersAndBody -{ - if ([httpResponse respondsToSelector:@selector(delayResponseHeaders)]) - { - if ([httpResponse delayResponseHeaders]) - { - return; - } - } - - BOOL isChunked = NO; - - if ([httpResponse respondsToSelector:@selector(isChunked)]) - { - isChunked = [httpResponse isChunked]; - } - - // If a response is "chunked", this simply means the HTTPResponse object - // doesn't know the content-length in advance. - - UInt64 contentLength = 0; - - if (!isChunked) - { - contentLength = [httpResponse contentLength]; - } - - // Check for specific range request - NSString *rangeHeader = [request headerField:@"Range"]; - - BOOL isRangeRequest = NO; - - // If the response is "chunked" then we don't know the exact content-length. - // This means we'll be unable to process any range requests. - // This is because range requests might include a range like "give me the last 100 bytes" - - if (!isChunked && rangeHeader) - { - if ([self parseRangeRequest:rangeHeader withContentLength:contentLength]) - { - isRangeRequest = YES; - } - } - - HTTPMessage *response; - - if (!isRangeRequest) - { - // Create response - // Default status code: 200 - OK - NSInteger status = 200; - - if ([httpResponse respondsToSelector:@selector(status)]) - { - status = [httpResponse status]; - } - response = [[HTTPMessage alloc] initResponseWithStatusCode:status description:nil version:HTTPVersion1_1]; - - if (isChunked) - { - [response setHeaderField:@"Transfer-Encoding" value:@"chunked"]; - } - else - { - NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", contentLength]; - [response setHeaderField:@"Content-Length" value:contentLengthStr]; - } - } - else - { - if ([ranges count] == 1) - { - response = [self newUniRangeResponse:contentLength]; - } - else - { - response = [self newMultiRangeResponse:contentLength]; - } - } - - BOOL isZeroLengthResponse = !isChunked && (contentLength == 0); - - // If they issue a 'HEAD' command, we don't have to include the file - // If they issue a 'GET' command, we need to include the file - - if ([[request method] isEqualToString:@"HEAD"] || isZeroLengthResponse) - { - NSData *responseData = [self preprocessResponse:response]; - [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; - - sentResponseHeaders = YES; - } - else - { - // Write the header response - NSData *responseData = [self preprocessResponse:response]; - [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; - - sentResponseHeaders = YES; - - // Now we need to send the body of the response - if (!isRangeRequest) - { - // Regular request - NSData *data = [httpResponse readDataOfLength:READ_CHUNKSIZE]; - - if ([data length] > 0) - { - [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; - - if (isChunked) - { - NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]]; - [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER]; - - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY]; - - if ([httpResponse isDone]) - { - NSData *footer = [self chunkedTransferFooter]; - [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; - } - else - { - NSData *footer = [GCDAsyncSocket CRLFData]; - [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER]; - } - } - else - { - long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY; - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; - } - } - } - else - { - // Client specified a byte range in request - - if ([ranges count] == 1) - { - // Client is requesting a single range - DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; - - [httpResponse setOffset:range.location]; - - NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE; - - NSData *data = [httpResponse readDataOfLength:bytesToRead]; - - if ([data length] > 0) - { - [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; - - long tag = [data length] == range.length ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY; - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; - } - } - else - { - // Client is requesting multiple ranges - // We have to send each range using multipart/byteranges - - // Write range header - NSData *rangeHeaderData = [ranges_headers objectAtIndex:0]; - [asyncSocket writeData:rangeHeaderData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; - - // Start writing range body - DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; - - [httpResponse setOffset:range.location]; - - NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE; - - NSData *data = [httpResponse readDataOfLength:bytesToRead]; - - if ([data length] > 0) - { - [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; - - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; - } - } - } - } - -} - -/** - * Returns the number of bytes of the http response body that are sitting in asyncSocket's write queue. - * - * We keep track of this information in order to keep our memory footprint low while - * working with asynchronous HTTPResponse objects. - **/ -- (NSUInteger)writeQueueSize -{ - NSUInteger result = 0; - - NSUInteger i; - for(i = 0; i < [responseDataSizes count]; i++) - { - result += [[responseDataSizes objectAtIndex:i] unsignedIntegerValue]; - } - - return result; -} - -/** - * Sends more data, if needed, without growing the write queue over its approximate size limit. - * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. - * - * This method should only be called for standard (non-range) responses. - **/ -- (void)continueSendingStandardResponseBody -{ - HTTPLogTrace(); - - // This method is called when either asyncSocket has finished writing one of the response data chunks, - // or when an asynchronous HTTPResponse object informs us that it has more available data for us to send. - // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data, - // and shove it onto asyncSocket's write queue. - // Doing so could negatively affect the memory footprint of the application. - // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. - // - // Note that this does not affect the rate at which the HTTPResponse object may generate data. - // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. - // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely - // use the calls to readDataOfLength as an indication to start generating more data. - // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate - // at which the socket is able to send it. - - NSUInteger writeQueueSize = [self writeQueueSize]; - - if(writeQueueSize >= READ_CHUNKSIZE) return; - - NSUInteger available = READ_CHUNKSIZE - writeQueueSize; - NSData *data = [httpResponse readDataOfLength:available]; - - if ([data length] > 0) - { - [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; - - BOOL isChunked = NO; - - if ([httpResponse respondsToSelector:@selector(isChunked)]) - { - isChunked = [httpResponse isChunked]; - } - - if (isChunked) - { - NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]]; - [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER]; - - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY]; - - if([httpResponse isDone]) - { - NSData *footer = [self chunkedTransferFooter]; - [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; - } - else - { - NSData *footer = [GCDAsyncSocket CRLFData]; - [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER]; - } - } - else - { - long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY; - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; - } - } -} - -/** - * Sends more data, if needed, without growing the write queue over its approximate size limit. - * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. - * - * This method should only be called for single-range responses. - **/ -- (void)continueSendingSingleRangeResponseBody -{ - HTTPLogTrace(); - - // This method is called when either asyncSocket has finished writing one of the response data chunks, - // or when an asynchronous response informs us that is has more available data for us to send. - // In the case of the asynchronous response, we don't want to blindly grab the new data, - // and shove it onto asyncSocket's write queue. - // Doing so could negatively affect the memory footprint of the application. - // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. - // - // Note that this does not affect the rate at which the HTTPResponse object may generate data. - // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. - // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely - // use the calls to readDataOfLength as an indication to start generating more data. - // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate - // at which the socket is able to send it. - - NSUInteger writeQueueSize = [self writeQueueSize]; - - if(writeQueueSize >= READ_CHUNKSIZE) return; - - DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; - - UInt64 offset = [httpResponse offset]; - UInt64 bytesRead = offset - range.location; - UInt64 bytesLeft = range.length - bytesRead; - - if (bytesLeft > 0) - { - NSUInteger available = READ_CHUNKSIZE - writeQueueSize; - NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available; - - NSData *data = [httpResponse readDataOfLength:bytesToRead]; - - if ([data length] > 0) - { - [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; - - long tag = [data length] == bytesLeft ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY; - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; - } - } -} - -/** - * Sends more data, if needed, without growing the write queue over its approximate size limit. - * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. - * - * This method should only be called for multi-range responses. - **/ -- (void)continueSendingMultiRangeResponseBody -{ - HTTPLogTrace(); - - // This method is called when either asyncSocket has finished writing one of the response data chunks, - // or when an asynchronous HTTPResponse object informs us that is has more available data for us to send. - // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data, - // and shove it onto asyncSocket's write queue. - // Doing so could negatively affect the memory footprint of the application. - // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. - // - // Note that this does not affect the rate at which the HTTPResponse object may generate data. - // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. - // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely - // use the calls to readDataOfLength as an indication to start generating more data. - // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate - // at which the socket is able to send it. - - NSUInteger writeQueueSize = [self writeQueueSize]; - - if(writeQueueSize >= READ_CHUNKSIZE) return; - - DDRange range = [[ranges objectAtIndex:rangeIndex] ddrangeValue]; - - UInt64 offset = [httpResponse offset]; - UInt64 bytesRead = offset - range.location; - UInt64 bytesLeft = range.length - bytesRead; - - if (bytesLeft > 0) - { - NSUInteger available = READ_CHUNKSIZE - writeQueueSize; - NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available; - - NSData *data = [httpResponse readDataOfLength:bytesToRead]; - - if ([data length] > 0) - { - [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; - - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; - } - } - else - { - if (++rangeIndex < [ranges count]) - { - // Write range header - NSData *rangeHeader = [ranges_headers objectAtIndex:rangeIndex]; - [asyncSocket writeData:rangeHeader withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; - - // Start writing range body - range = [[ranges objectAtIndex:rangeIndex] ddrangeValue]; - - [httpResponse setOffset:range.location]; - - NSUInteger available = READ_CHUNKSIZE - writeQueueSize; - NSUInteger bytesToRead = range.length < available ? (NSUInteger)range.length : available; - - NSData *data = [httpResponse readDataOfLength:bytesToRead]; - - if ([data length] > 0) - { - [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; - - [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; - } - } - else - { - // We're not done yet - we still have to send the closing boundry tag - NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry]; - NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding]; - - [asyncSocket writeData:endingBoundryData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Responses -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Returns an array of possible index pages. - * For example: {"index.html", "index.htm"} - **/ -- (NSArray *)directoryIndexFileNames -{ - HTTPLogTrace(); - - // Override me to support other index pages. - - return [NSArray arrayWithObjects:@"index.html", @"index.htm", nil]; -} - -- (NSString *)filePathForURI:(NSString *)path -{ - return [self filePathForURI:path allowDirectory:NO]; -} - -/** - * Converts relative URI path into full file-system path. - **/ -- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory -{ - HTTPLogTrace(); - - // Override me to perform custom path mapping. - // For example you may want to use a default file other than index.html, or perhaps support multiple types. - - NSString *documentRoot = [config documentRoot]; - - // Part 0: Validate document root setting. - // - // If there is no configured documentRoot, - // then it makes no sense to try to return anything. - - if (documentRoot == nil) - { - HTTPLogWarn(@"%@[%p]: No configured document root", THIS_FILE, self); - return nil; - } - - // Part 1: Strip parameters from the url - // - // E.g.: /page.html?q=22&var=abc -> /page.html - - NSURL *docRoot = [NSURL fileURLWithPath:documentRoot isDirectory:YES]; - if (docRoot == nil) - { - HTTPLogWarn(@"%@[%p]: Document root is invalid file path", THIS_FILE, self); - return nil; - } - - NSString *relativePath = [[NSURL URLWithString:path relativeToURL:docRoot] relativePath]; - - // Part 2: Append relative path to document root (base path) - // - // E.g.: relativePath="/images/icon.png" - // documentRoot="/Users/robbie/Sites" - // fullPath="/Users/robbie/Sites/images/icon.png" - // - // We also standardize the path. - // - // E.g.: "Users/robbie/Sites/images/../index.html" -> "/Users/robbie/Sites/index.html" - - NSString *fullPath = [[documentRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath]; - - if ([relativePath isEqualToString:@"/"]) - { - fullPath = [fullPath stringByAppendingString:@"/"]; - } - - // Part 3: Prevent serving files outside the document root. - // - // Sneaky requests may include ".." in the path. - // - // E.g.: relativePath="../Documents/TopSecret.doc" - // documentRoot="/Users/robbie/Sites" - // fullPath="/Users/robbie/Documents/TopSecret.doc" - // - // E.g.: relativePath="../Sites_Secret/TopSecret.doc" - // documentRoot="/Users/robbie/Sites" - // fullPath="/Users/robbie/Sites_Secret/TopSecret" - - if (![documentRoot hasSuffix:@"/"]) - { - documentRoot = [documentRoot stringByAppendingString:@"/"]; - } - - if (![fullPath hasPrefix:documentRoot]) - { - HTTPLogWarn(@"%@[%p]: Request for file outside document root", THIS_FILE, self); - return nil; - } - - // Part 4: Search for index page if path is pointing to a directory - if (!allowDirectory) - { - BOOL isDir = NO; - if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir) - { - NSArray *indexFileNames = [self directoryIndexFileNames]; - - for (NSString *indexFileName in indexFileNames) - { - NSString *indexFilePath = [fullPath stringByAppendingPathComponent:indexFileName]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:indexFilePath isDirectory:&isDir] && !isDir) - { - return indexFilePath; - } - } - - // No matching index files found in directory - return nil; - } - } - - return fullPath; -} - -/** - * This method is called to get a response for a request. - * You may return any object that adopts the HTTPResponse protocol. - * The HTTPServer comes with two such classes: HTTPFileResponse and HTTPDataResponse. - * HTTPFileResponse is a wrapper for an NSFileHandle object, and is the preferred way to send a file response. - * HTTPDataResponse is a wrapper for an NSData object, and may be used to send a custom response. - **/ -- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path -{ - HTTPLogTrace(); - - // Override me to provide custom responses. - - return nil; -} - -- (WebSocket *)webSocketForURI:(NSString *)path -{ - HTTPLogTrace(); - - // Override me to provide custom WebSocket responses. - // To do so, simply override the base WebSocket implementation, and add your custom functionality. - // Then return an instance of your custom WebSocket here. - // - // For example: - // - // if ([path isEqualToString:@"/myAwesomeWebSocketStream"]) - // { - // return [[[MyWebSocket alloc] initWithRequest:request socket:asyncSocket] autorelease]; - // } - // - // return [super webSocketForURI:path]; - - return nil; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Uploads -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method is called after receiving all HTTP headers, but before reading any of the request body. - **/ -- (void)prepareForBodyWithSize:(UInt64)contentLength -{ - // Override me to allocate buffers, file handles, etc. -} - -/** - * This method is called to handle data read from a POST / PUT. - * The given data is part of the request body. - **/ -- (void)processBodyData:(NSData *)postDataChunk -{ - // Override me to do something useful with a POST / PUT. - // If the post is small, such as a simple form, you may want to simply append the data to the request. - // If the post is big, such as a file upload, you may want to store the file to disk. - // - // Remember: In order to support LARGE POST uploads, the data is read in chunks. - // This prevents a 50 MB upload from being stored in RAM. - // The size of the chunks are limited by the POST_CHUNKSIZE definition. - // Therefore, this method may be called multiple times for the same POST request. -} - -/** - * This method is called after the request body has been fully read but before the HTTP request is processed. - **/ -- (void)finishBody -{ - // Override me to perform any final operations on an upload. - // For example, if you were saving the upload to disk this would be - // the hook to flush any pending data to disk and maybe close the file. -} - -/** - * Returns the maximum request body size this connection accepts. - **/ -- (UInt64)maxRequestBodySize -{ - return (UInt64)-1; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Called if the HTML version is other than what is supported - **/ -- (void)handleVersionNotSupported:(NSString *)version -{ - // Override me for custom error handling of unsupported http version responses - // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. - // You can also use preprocessErrorResponse: to add an optional HTML body. - - HTTPLogWarn(@"HTTP Server: Error 505 - Version Not Supported: %@ (%@)", version, [self requestURI]); - - HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:505 description:nil version:HTTPVersion1_1]; - [response setHeaderField:@"Content-Length" value:@"0"]; - - NSData *responseData = [self preprocessErrorResponse:response]; - [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; - -} - -/** - * Called if the HTTP request body is larger than the configured limit. - **/ -- (void)handleRequestBodyTooLarge -{ - HTTPLogWarn(@"HTTP Server: Error 413 - Request Entity Too Large (%@)", [self requestURI]); - - HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:413 description:nil version:HTTPVersion1_1]; - [response setHeaderField:@"Content-Length" value:@"0"]; - [response setHeaderField:@"Connection" value:@"close"]; - - NSData *responseData = [self preprocessErrorResponse:response]; - [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE]; -} - -/** - * Called if we receive some sort of malformed HTTP request. - * The data parameter is the invalid HTTP header line, including CRLF, as read from GCDAsyncSocket. - * The data parameter may also be nil if the request as a whole was invalid, such as a POST with no Content-Length. - **/ -- (void)handleInvalidRequest:(NSData *)data -{ - // Override me for custom error handling of invalid HTTP requests - // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. - // You can also use preprocessErrorResponse: to add an optional HTML body. - - HTTPLogWarn(@"HTTP Server: Error 400 - Bad Request (%@)", [self requestURI]); - - // Status Code 400 - Bad Request - HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:400 description:nil version:HTTPVersion1_1]; - [response setHeaderField:@"Content-Length" value:@"0"]; - [response setHeaderField:@"Connection" value:@"close"]; - - NSData *responseData = [self preprocessErrorResponse:response]; - [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE]; - - - // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent. - // We do this because we couldn't parse the request, - // so we won't be able to recover and move on to another request afterwards. - // In other words, we wouldn't know where the first request ends and the second request begins. -} - -/** - * Called if we receive a HTTP request with a method other than GET or HEAD. - **/ -- (void)handleUnknownMethod:(NSString *)method -{ - // Override me for custom error handling of 405 method not allowed responses. - // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. - // You can also use preprocessErrorResponse: to add an optional HTML body. - // - // See also: supportsMethod:atPath: - - HTTPLogWarn(@"HTTP Server: Error 405 - Method Not Allowed: %@ (%@)", method, [self requestURI]); - - // Status code 405 - Method Not Allowed - HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:405 description:nil version:HTTPVersion1_1]; - [response setHeaderField:@"Content-Length" value:@"0"]; - [response setHeaderField:@"Connection" value:@"close"]; - - NSData *responseData = [self preprocessErrorResponse:response]; - [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE]; - - - // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent. - // We do this because the method may include an http body. - // Since we can't be sure, we should close the connection. -} - -/** - * Called if we're unable to find the requested resource. - **/ -- (void)handleResourceNotFound -{ - // Override me for custom error handling of 404 not found responses - // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. - // You can also use preprocessErrorResponse: to add an optional HTML body. - - HTTPLogInfo(@"HTTP Server: Error 404 - Not Found (%@)", [self requestURI]); - - // Status Code 404 - Not Found - HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:404 description:nil version:HTTPVersion1_1]; - [response setHeaderField:@"Content-Length" value:@"0"]; - - NSData *responseData = [self preprocessErrorResponse:response]; - [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; - -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Headers -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Gets the current date and time, formatted properly (according to RFC) for insertion into an HTTP header. - **/ -- (NSString *)dateAsString:(NSDate *)date -{ - // From Apple's Documentation (Data Formatting Guide -> Date Formatters -> Cache Formatters for Efficiency): - // - // "Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, - // it is typically more efficient to cache a single instance than to create and dispose of multiple instances. - // One approach is to use a static variable." - // - // This was discovered to be true in massive form via issue #46: - // - // "Was doing some performance benchmarking using instruments and httperf. Using this single optimization - // I got a 26% speed improvement - from 1000req/sec to 3800req/sec. Not insignificant. - // The culprit? Why, NSDateFormatter, of course!" - // - // Thus, we are using a static NSDateFormatter here. - - static NSDateFormatter *df; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - - // Example: Sun, 06 Nov 1994 08:49:37 GMT - - df = [[NSDateFormatter alloc] init]; - [df setFormatterBehavior:NSDateFormatterBehavior10_4]; - [df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; - [df setDateFormat:@"EEE, dd MMM y HH:mm:ss 'GMT'"]; - [df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; - - // For some reason, using zzz in the format string produces GMT+00:00 - }); - - return [df stringFromDate:date]; -} - -/** - * This method is called immediately prior to sending the response headers. - * This method adds standard header fields, and then converts the response to an NSData object. - **/ -- (NSData *)preprocessResponse:(HTTPMessage *)response -{ - HTTPLogTrace(); - - // Override me to customize the response headers - // You'll likely want to add your own custom headers, and then return [super preprocessResponse:response] - - // Add standard headers - NSString *now = [self dateAsString:[NSDate date]]; - [response setHeaderField:@"Date" value:now]; - - // Add server capability headers - [response setHeaderField:@"Accept-Ranges" value:@"bytes"]; - - // Add optional response headers - if ([httpResponse respondsToSelector:@selector(httpHeaders)]) - { - NSDictionary *responseHeaders = [httpResponse httpHeaders]; - - NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator]; - NSString *key; - - while ((key = [keyEnumerator nextObject])) - { - NSString *value = [responseHeaders objectForKey:key]; - - [response setHeaderField:key value:value]; - } - } - - return [response messageData]; -} - -/** - * This method is called immediately prior to sending the response headers (for an error). - * This method adds standard header fields, and then converts the response to an NSData object. - **/ -- (NSData *)preprocessErrorResponse:(HTTPMessage *)response -{ - HTTPLogTrace(); - - // Override me to customize the error response headers - // You'll likely want to add your own custom headers, and then return [super preprocessErrorResponse:response] - // - // Notes: - // You can use [response statusCode] to get the type of error. - // You can use [response setBody:data] to add an optional HTML body. - // If you add a body, don't forget to update the Content-Length. - // - // if ([response statusCode] == 404) - // { - // NSString *msg = @"Error 404 - Not Found"; - // NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; - // - // [response setBody:msgData]; - // - // NSString *contentLengthStr = [NSString stringWithFormat:@"%lu", (unsigned long)[msgData length]]; - // [response setHeaderField:@"Content-Length" value:contentLengthStr]; - // } - - // Add standard headers - NSString *now = [self dateAsString:[NSDate date]]; - [response setHeaderField:@"Date" value:now]; - - // Add server capability headers - [response setHeaderField:@"Accept-Ranges" value:@"bytes"]; - - // Add optional response headers - if ([httpResponse respondsToSelector:@selector(httpHeaders)]) - { - NSDictionary *responseHeaders = [httpResponse httpHeaders]; - - NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator]; - NSString *key; - - while((key = [keyEnumerator nextObject])) - { - NSString *value = [responseHeaders objectForKey:key]; - - [response setHeaderField:key value:value]; - } - } - - return [response messageData]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark GCDAsyncSocket Delegate -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method is called after the socket has successfully read data from the stream. - * Remember that this method will only be called after the socket reaches a CRLF, or after it's read the proper length. - **/ -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag -{ - if (tag == HTTP_REQUEST_HEADER) - { - // Append the header line to the http message - BOOL result = [request appendData:data]; - if (!result) - { - HTTPLogWarn(@"%@[%p]: Malformed request", THIS_FILE, self); - - [self handleInvalidRequest:data]; - } - else if (![request isHeaderComplete]) - { - // We don't have a complete header yet - // That is, we haven't yet received a CRLF on a line by itself, indicating the end of the header - if (++numHeaderLines > MAX_HEADER_LINES) - { - // Reached the maximum amount of header lines in a single HTTP request - // This could be an attempted DOS attack - [asyncSocket disconnect]; - - // Explictly return to ensure we don't do anything after the socket disconnect - return; - } - else - { - [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] - withTimeout:TIMEOUT_READ_SUBSEQUENT_HEADER_LINE - maxLength:MAX_HEADER_LINE_LENGTH - tag:HTTP_REQUEST_HEADER]; - } - } - else - { - // We have an entire HTTP request header from the client - - // Extract the method (such as GET, HEAD, POST, etc) - NSString *method = [request method]; - - // Extract the uri (such as "/index.html") - NSString *uri = [self requestURI]; - - // Check for a Transfer-Encoding field - NSString *transferEncoding = [request headerField:@"Transfer-Encoding"]; - - // Check for a Content-Length field - NSString *contentLength = [request headerField:@"Content-Length"]; - - // Content-Length MUST be present for upload methods (such as POST or PUT) - // and MUST NOT be present for other methods. - BOOL expectsUpload = [self expectsRequestBodyFromMethod:method atPath:uri]; - - if (expectsUpload) - { - if (transferEncoding && ![transferEncoding caseInsensitiveCompare:@"Chunked"]) - { - requestContentLength = -1; - } - else - { - if (contentLength == nil) - { - HTTPLogWarn(@"%@[%p]: Method expects request body, but had no specified Content-Length", - THIS_FILE, self); - - [self handleInvalidRequest:nil]; - return; - } - - if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength]) - { - HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number", - THIS_FILE, self); - - [self handleInvalidRequest:nil]; - return; - } - - if (requestContentLength > [self maxRequestBodySize]) - { - HTTPLogWarn(@"%@[%p]: Request body size %llu exceeds the configured limit %llu", - THIS_FILE, self, requestContentLength, [self maxRequestBodySize]); - - [self handleRequestBodyTooLarge]; - return; - } - } - } - else - { - if (contentLength != nil) - { - // Received Content-Length header for method not expecting an upload. - // This better be zero... - - if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength]) - { - HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number", - THIS_FILE, self); - - [self handleInvalidRequest:nil]; - return; - } - - if (requestContentLength > 0) - { - HTTPLogWarn(@"%@[%p]: Method not expecting request body had non-zero Content-Length", - THIS_FILE, self); - - [self handleInvalidRequest:nil]; - return; - } - } - - requestContentLength = 0; - requestContentLengthReceived = 0; - } - - // Check to make sure the given method is supported - if (![self supportsMethod:method atPath:uri]) - { - // The method is unsupported - either in general, or for this specific request - // Send a 405 - Method not allowed response - [self handleUnknownMethod:method]; - return; - } - - if (expectsUpload) - { - // Reset the total amount of data received for the upload - requestContentLengthReceived = 0; - - // Prepare for the upload - [self prepareForBodyWithSize:requestContentLength]; - - if (requestContentLength > 0) - { - // Start reading the request body - if (requestContentLength == -1) - { - // Chunked transfer - - [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] - withTimeout:TIMEOUT_READ_BODY - maxLength:MAX_CHUNK_LINE_LENGTH - tag:HTTP_REQUEST_CHUNK_SIZE]; - } - else - { - NSUInteger bytesToRead; - if (requestContentLength < POST_CHUNKSIZE) - bytesToRead = (NSUInteger)requestContentLength; - else - bytesToRead = POST_CHUNKSIZE; - - [asyncSocket readDataToLength:bytesToRead - withTimeout:TIMEOUT_READ_BODY - tag:HTTP_REQUEST_BODY]; - } - } - else - { - // Empty upload - [self finishBody]; - [self replyToHTTPRequest]; - } - } - else - { - // Now we need to reply to the request - [self replyToHTTPRequest]; - } - } - } - else - { - BOOL doneReadingRequest = NO; - - // A chunked message body contains a series of chunks, - // followed by a line with "0" (zero), - // followed by optional footers (just like headers), - // and a blank line. - // - // Each chunk consists of two parts: - // - // 1. A line with the size of the chunk data, in hex, - // possibly followed by a semicolon and extra parameters you can ignore (none are currently standard), - // and ending with CRLF. - // 2. The data itself, followed by CRLF. - // - // Part 1 is represented by HTTP_REQUEST_CHUNK_SIZE - // Part 2 is represented by HTTP_REQUEST_CHUNK_DATA and HTTP_REQUEST_CHUNK_TRAILER - // where the trailer is the CRLF that follows the data. - // - // The optional footers and blank line are represented by HTTP_REQUEST_CHUNK_FOOTER. - - if (tag == HTTP_REQUEST_CHUNK_SIZE) - { - // We have just read in a line with the size of the chunk data, in hex, - // possibly followed by a semicolon and extra parameters that can be ignored, - // and ending with CRLF. - - NSString *sizeLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - - errno = 0; // Reset errno before calling strtoull() to ensure it is always zero on success - requestChunkSize = (UInt64)strtoull([sizeLine UTF8String], NULL, 16); - requestChunkSizeReceived = 0; - - if (errno != 0) - { - HTTPLogWarn(@"%@[%p]: Method expects chunk size, but received something else", THIS_FILE, self); - - [self handleInvalidRequest:nil]; - return; - } - - if (requestChunkSize > 0) - { - UInt64 maxRequestBodySize = [self maxRequestBodySize]; - if (requestChunkSize > maxRequestBodySize || - requestContentLengthReceived > maxRequestBodySize - requestChunkSize) - { - HTTPLogWarn(@"%@[%p]: Chunked request body exceeds the configured limit %llu", - THIS_FILE, self, maxRequestBodySize); - - [self handleRequestBodyTooLarge]; - return; - } - - NSUInteger bytesToRead; - bytesToRead = (requestChunkSize < POST_CHUNKSIZE) ? (NSUInteger)requestChunkSize : POST_CHUNKSIZE; - - [asyncSocket readDataToLength:bytesToRead - withTimeout:TIMEOUT_READ_BODY - tag:HTTP_REQUEST_CHUNK_DATA]; - } - else - { - // This is the "0" (zero) line, - // which is to be followed by optional footers (just like headers) and finally a blank line. - - [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] - withTimeout:TIMEOUT_READ_BODY - maxLength:MAX_HEADER_LINE_LENGTH - tag:HTTP_REQUEST_CHUNK_FOOTER]; - } - - return; - } - else if (tag == HTTP_REQUEST_CHUNK_DATA) - { - // We just read part of the actual data. - - requestContentLengthReceived += [data length]; - requestChunkSizeReceived += [data length]; - - [self processBodyData:data]; - - UInt64 bytesLeft = requestChunkSize - requestChunkSizeReceived; - if (bytesLeft > 0) - { - NSUInteger bytesToRead = (bytesLeft < POST_CHUNKSIZE) ? (NSUInteger)bytesLeft : POST_CHUNKSIZE; - - [asyncSocket readDataToLength:bytesToRead - withTimeout:TIMEOUT_READ_BODY - tag:HTTP_REQUEST_CHUNK_DATA]; - } - else - { - // We've read in all the data for this chunk. - // The data is followed by a CRLF, which we need to read (and basically ignore) - - [asyncSocket readDataToLength:2 - withTimeout:TIMEOUT_READ_BODY - tag:HTTP_REQUEST_CHUNK_TRAILER]; - } - - return; - } - else if (tag == HTTP_REQUEST_CHUNK_TRAILER) - { - // This should be the CRLF following the data. - // Just ensure it's a CRLF. - - if (![data isEqualToData:[GCDAsyncSocket CRLFData]]) - { - HTTPLogWarn(@"%@[%p]: Method expects chunk trailer, but is missing", THIS_FILE, self); - - [self handleInvalidRequest:nil]; - return; - } - - // Now continue with the next chunk - - [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] - withTimeout:TIMEOUT_READ_BODY - maxLength:MAX_CHUNK_LINE_LENGTH - tag:HTTP_REQUEST_CHUNK_SIZE]; - - } - else if (tag == HTTP_REQUEST_CHUNK_FOOTER) - { - if (++numHeaderLines > MAX_HEADER_LINES) - { - // Reached the maximum amount of header lines in a single HTTP request - // This could be an attempted DOS attack - [asyncSocket disconnect]; - - // Explictly return to ensure we don't do anything after the socket disconnect - return; - } - - if ([data length] > 2) - { - // We read in a footer. - // In the future we may want to append these to the request. - // For now we ignore, and continue reading the footers, waiting for the final blank line. - - [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] - withTimeout:TIMEOUT_READ_BODY - maxLength:MAX_HEADER_LINE_LENGTH - tag:HTTP_REQUEST_CHUNK_FOOTER]; - } - else - { - doneReadingRequest = YES; - } - } - else // HTTP_REQUEST_BODY - { - // Handle a chunk of data from the POST body - - requestContentLengthReceived += [data length]; - [self processBodyData:data]; - - if (requestContentLengthReceived < requestContentLength) - { - // We're not done reading the post body yet... - - UInt64 bytesLeft = requestContentLength - requestContentLengthReceived; - - NSUInteger bytesToRead = bytesLeft < POST_CHUNKSIZE ? (NSUInteger)bytesLeft : POST_CHUNKSIZE; - - [asyncSocket readDataToLength:bytesToRead - withTimeout:TIMEOUT_READ_BODY - tag:HTTP_REQUEST_BODY]; - } - else - { - doneReadingRequest = YES; - } - } - - // Now that the entire body has been received, we need to reply to the request - - if (doneReadingRequest) - { - [self finishBody]; - [self replyToHTTPRequest]; - } - } -} - -/** - * This method is called after the socket has successfully written data to the stream. - **/ -- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag -{ - BOOL doneSendingResponse = NO; - - if (tag == HTTP_PARTIAL_RESPONSE_BODY) - { - // Update the amount of data we have in asyncSocket's write queue - if ([responseDataSizes count] > 0) { - [responseDataSizes removeObjectAtIndex:0]; - } - - // We only wrote a part of the response - there may be more - [self continueSendingStandardResponseBody]; - } - else if (tag == HTTP_CHUNKED_RESPONSE_BODY) - { - // Update the amount of data we have in asyncSocket's write queue. - // This will allow asynchronous responses to continue sending more data. - if ([responseDataSizes count] > 0) { - [responseDataSizes removeObjectAtIndex:0]; - } - // Don't continue sending the response yet. - // The chunked footer that was sent after the body will tell us if we have more data to send. - } - else if (tag == HTTP_CHUNKED_RESPONSE_FOOTER) - { - // Normal chunked footer indicating we have more data to send (non final footer). - [self continueSendingStandardResponseBody]; - } - else if (tag == HTTP_PARTIAL_RANGE_RESPONSE_BODY) - { - // Update the amount of data we have in asyncSocket's write queue - if ([responseDataSizes count] > 0) { - [responseDataSizes removeObjectAtIndex:0]; - } - // We only wrote a part of the range - there may be more - [self continueSendingSingleRangeResponseBody]; - } - else if (tag == HTTP_PARTIAL_RANGES_RESPONSE_BODY) - { - // Update the amount of data we have in asyncSocket's write queue - if ([responseDataSizes count] > 0) { - [responseDataSizes removeObjectAtIndex:0]; - } - // We only wrote part of the range - there may be more, or there may be more ranges - [self continueSendingMultiRangeResponseBody]; - } - else if (tag == HTTP_RESPONSE || tag == HTTP_FINAL_RESPONSE) - { - // Update the amount of data we have in asyncSocket's write queue - if ([responseDataSizes count] > 0) - { - [responseDataSizes removeObjectAtIndex:0]; - } - - doneSendingResponse = YES; - } - - if (doneSendingResponse) - { - // Inform the http response that we're done - if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) - { - [httpResponse connectionDidClose]; - } - - - if (tag == HTTP_FINAL_RESPONSE) - { - // Cleanup after the last request - [self finishResponse]; - - // Terminate the connection - [asyncSocket disconnect]; - - // Explictly return to ensure we don't do anything after the socket disconnects - return; - } - else - { - if ([self shouldDie]) - { - // Cleanup after the last request - // Note: Don't do this before calling shouldDie, as it needs the request object still. - [self finishResponse]; - - // The only time we should invoke [self die] is from socketDidDisconnect, - // or if the socket gets taken over by someone else like a WebSocket. - - [asyncSocket disconnect]; - } - else - { - // Cleanup after the last request - [self finishResponse]; - - // Prepare for the next request - - // If this assertion fails, it likely means you overrode the - // finishBody method and forgot to call [super finishBody]. - NSAssert(request == nil, @"Request not properly released in finishBody"); - - request = [[HTTPMessage alloc] initEmptyRequest]; - - numHeaderLines = 0; - sentResponseHeaders = NO; - - // And start listening for more requests - [self startReadingRequest]; - } - } - } -} - -/** - * Sent after the socket has been disconnected. - **/ -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err -{ - HTTPLogTrace(); - - asyncSocket = nil; - - [self die]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark HTTPResponse Notifications -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method may be called by asynchronous HTTPResponse objects. - * That is, HTTPResponse objects that return YES in their "- (BOOL)isAsynchronous" method. - * - * This informs us that the response object has generated more data that we may be able to send. - **/ -- (void)responseHasAvailableData:(NSObject *)sender -{ - HTTPLogTrace(); - - // We always dispatch this asynchronously onto our connectionQueue, - // even if the connectionQueue is the current queue. - // - // We do this to give the HTTPResponse classes the flexibility to call - // this method whenever they want, even from within a readDataOfLength method. - - dispatch_async(connectionQueue, ^{ @autoreleasepool { - - if (sender != httpResponse) - { - HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD); - return; - } - - if (!sentResponseHeaders) - { - [self sendResponseHeadersAndBody]; - } - else - { - if (ranges == nil) - { - [self continueSendingStandardResponseBody]; - } - else - { - if ([ranges count] == 1) - [self continueSendingSingleRangeResponseBody]; - else - [self continueSendingMultiRangeResponseBody]; - } - } - }}); -} - -/** - * This method is called if the response encounters some critical error, - * and it will be unable to fullfill the request. - **/ -- (void)responseDidAbort:(NSObject *)sender -{ - HTTPLogTrace(); - - // We always dispatch this asynchronously onto our connectionQueue, - // even if the connectionQueue is the current queue. - // - // We do this to give the HTTPResponse classes the flexibility to call - // this method whenever they want, even from within a readDataOfLength method. - - dispatch_async(connectionQueue, ^{ @autoreleasepool { - - if (sender != httpResponse) - { - HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD); - return; - } - - [asyncSocket disconnectAfterWriting]; - }}); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Post Request -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method is called after each response has been fully sent. - * Since a single connection may handle multiple request/responses, this method may be called multiple times. - * That is, it will be called after completion of each response. - **/ -- (void)finishResponse -{ - HTTPLogTrace(); - - // Override me if you want to perform any custom actions after a response has been fully sent. - // This is the place to release memory or resources associated with the last request. - // - // If you override this method, you should take care to invoke [super finishResponse] at some point. - - request = nil; - - httpResponse = nil; - - ranges = nil; - ranges_headers = nil; - ranges_boundry = nil; -} - -/** - * This method is called after each successful response has been fully sent. - * It determines whether the connection should stay open and handle another request. - **/ -- (BOOL)shouldDie -{ - HTTPLogTrace(); - - // Override me if you have any need to force close the connection. - // You may do so by simply returning YES. - // - // If you override this method, you should take care to fall through with [super shouldDie] - // instead of returning NO. - - - BOOL shouldDie = NO; - - NSString *version = [request version]; - if ([version isEqualToString:HTTPVersion1_1]) - { - // HTTP version 1.1 - // Connection should only be closed if request included "Connection: close" header - - NSString *connection = [request headerField:@"Connection"]; - - shouldDie = (connection && ([connection caseInsensitiveCompare:@"close"] == NSOrderedSame)); - } - else if ([version isEqualToString:HTTPVersion1_0]) - { - // HTTP version 1.0 - // Connection should be closed unless request included "Connection: Keep-Alive" header - - NSString *connection = [request headerField:@"Connection"]; - - if (connection == nil) - shouldDie = YES; - else - shouldDie = [connection caseInsensitiveCompare:@"Keep-Alive"] != NSOrderedSame; - } - - return shouldDie; -} - -- (void)die -{ - HTTPLogTrace(); - - // Override me if you want to perform any custom actions when a connection is closed. - // Then call [super die] when you're done. - // - // See also the finishResponse method. - // - // Important: There is a rare timing condition where this method might get invoked twice. - // If you override this method, you should be prepared for this situation. - - // Inform the http response that we're done - if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) - { - [httpResponse connectionDidClose]; - } - - // Release the http response so we don't call it's connectionDidClose method again in our dealloc method - httpResponse = nil; - - // Post notification of dead connection - // This will allow our server to release us from its array of connections - [[NSNotificationCenter defaultCenter] postNotificationName:HTTPConnectionDidDieNotification object:self]; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation HTTPConfig - -@synthesize server; -@synthesize documentRoot; -@synthesize queue; - -- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot -{ - if ((self = [super init])) - { - server = aServer; - documentRoot = aDocumentRoot; - } - return self; -} - -- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot queue:(dispatch_queue_t)q -{ - if ((self = [super init])) - { - server = aServer; - - documentRoot = [aDocumentRoot stringByStandardizingPath]; - if ([documentRoot hasSuffix:@"/"]) - { - documentRoot = [documentRoot stringByAppendingString:@"/"]; - } - - if (q) - { - queue = q; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(queue); -#endif - } - } - return self; -} - -- (void)dealloc -{ -#if !OS_OBJECT_USE_OBJC - if (queue) dispatch_release(queue); -#endif -} - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h deleted file mode 100644 index 4c277f1db4..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h +++ /dev/null @@ -1,122 +0,0 @@ -/** - * In order to provide fast and flexible logging, this project uses Cocoa Lumberjack. - * - * The Google Code page has a wealth of documentation if you have any questions. - * https://github.com/robbiehanson/CocoaLumberjack - * - * Here's what you need to know concerning how logging is setup for CocoaHTTPServer: - * - * There are 4 log levels: - * - Error - * - Warning - * - Info - * - Verbose - * - * In addition to this, there is a Trace flag that can be enabled. - * When tracing is enabled, it spits out the methods that are being called. - * - * Please note that tracing is separate from the log levels. - * For example, one could set the log level to warning, and enable tracing. - * - * All logging is asynchronous, except errors. - * To use logging within your own custom files, follow the steps below. - * - * Step 1: - * Import this header in your implementation file: - * - * #import "HTTPLogging.h" - * - * Step 2: - * Define your logging level in your implementation file: - * - * // Log levels: off, error, warn, info, verbose - * static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; - * - * If you wish to enable tracing, you could do something like this: - * - * // Debug levels: off, error, warn, info, verbose - * static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE; - * - * Step 3: - * Replace your NSLog statements with HTTPLog statements according to the severity of the message. - * - * NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!"); - * - * HTTPLog works exactly the same as NSLog. - * This means you can pass it multiple variables just like NSLog. - **/ - -// Define logging context for every log message coming from the HTTP server. -// The logging context can be extracted from the DDLogMessage from within the logging framework, -// which gives loggers, formatters, and filters the ability to optionally process them differently. - -#define HTTP_LOG_CONTEXT 80 - -// Configure log levels. - -#define HTTP_LOG_FLAG_ERROR (1 << 0) // 0...00001 -#define HTTP_LOG_FLAG_WARN (1 << 1) // 0...00010 -#define HTTP_LOG_FLAG_INFO (1 << 2) // 0...00100 -#define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000 - -#define HTTP_LOG_LEVEL_OFF 0 // 0...00000 -#define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001 -#define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011 -#define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111 -#define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111 - -// Setup fine grained logging. -// The first 4 bits are being used by the standard log levels (0 - 3) -// -// We're going to add tracing, but NOT as a log level. -// Tracing can be turned on and off independently of log level. - -#define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000 - -// Setup the usual boolean macros. - -#define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR) -#define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN) -#define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO) -#define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE) -#define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE) - -// Configure asynchronous logging. -// We follow the default configuration, -// but we reserve a special macro to easily disable asynchronous logging for debugging purposes. - -#define HTTP_LOG_ASYNC_ENABLED YES - -#define HTTP_LOG_ASYNC_ERROR ( NO && HTTP_LOG_ASYNC_ENABLED) -#define HTTP_LOG_ASYNC_WARN (YES && HTTP_LOG_ASYNC_ENABLED) -#define HTTP_LOG_ASYNC_INFO (YES && HTTP_LOG_ASYNC_ENABLED) -#define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED) -#define HTTP_LOG_ASYNC_TRACE (YES && HTTP_LOG_ASYNC_ENABLED) - -// Define logging primitives. - -#define HTTPLogError(...) do {} while (0) - -#define HTTPLogWarn(...) do {} while (0) - -#define HTTPLogInfo(...) do {} while (0) - -#define HTTPLogVerbose(...) do {} while (0) - -#define HTTPLogTrace() do {} while (0) - -#define HTTPLogTrace2(...) do {} while (0) - - -#define HTTPLogCError(...) do {} while (0) - -#define HTTPLogCWarn(...) do {} while (0) - -#define HTTPLogCInfo(...) do {} while (0) - -#define HTTPLogCVerbose(...) do {} while (0) - -#define HTTPLogCTrace() do {} while (0) - -#define HTTPLogCTrace2(...) do {} while (0) - diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h deleted file mode 100644 index 401830e56f..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * The HTTPMessage class is a simple Objective-C wrapper for HTTP message parsing. - * Migrated from CFHTTPMessage to use Foundation and Network framework. - **/ - -#import - -#define HTTPVersion1_0 @"HTTP/1.0" -#define HTTPVersion1_1 @"HTTP/1.1" - - -@interface HTTPMessage : NSObject -{ - NSMutableDictionary *_headers; - NSMutableData *_body; - NSString *_version; - NSString *_method; - NSURL *_url; - NSInteger _statusCode; - NSString *_statusDescription; - BOOL _isRequest; - BOOL _headerComplete; - NSMutableData *_rawData; -} - -- (id)initEmptyRequest; - -- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version; - -- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version; - -- (BOOL)appendData:(NSData *)data; - -- (BOOL)isHeaderComplete; - -- (NSString *)version; - -- (NSString *)method; -- (NSURL *)url; - -- (NSInteger)statusCode; - -- (NSDictionary *)allHeaderFields; -- (NSString *)headerField:(NSString *)headerField; - -- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue; - -- (NSData *)messageData; - -- (NSData *)body; -- (void)setBody:(NSData *)body; - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m deleted file mode 100644 index 44eaee1800..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m +++ /dev/null @@ -1,357 +0,0 @@ -#import "HTTPMessage.h" - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" - -@implementation HTTPMessage - -- (id)init -{ - if ((self = [super init])) - { - _headers = [[NSMutableDictionary alloc] init]; - _body = [[NSMutableData alloc] init]; - _rawData = [[NSMutableData alloc] init]; - _version = HTTPVersion1_1; - _headerComplete = NO; - _isRequest = YES; - } - return self; -} - -- (id)initEmptyRequest -{ - if ((self = [self init])) - { - _isRequest = YES; - } - return self; -} - -- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version -{ - if ((self = [self init])) - { - _isRequest = YES; - _method = [method copy]; - _url = [url copy]; - _version = version ? [version copy] : HTTPVersion1_1; - } - return self; -} - -- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version -{ - if ((self = [self init])) - { - _isRequest = NO; - _statusCode = code; - _statusDescription = [description copy]; - _version = version ? [version copy] : HTTPVersion1_1; - } - return self; -} - -- (BOOL)appendData:(NSData *)data -{ - if (!data || [data length] == 0) - { - return NO; - } - - [_rawData appendData:data]; - - if (!_headerComplete) - { - // Look for the end of headers (CRLF CRLF or LF LF) - NSData *headerEndMarker = [@"\r\n\r\n" dataUsingEncoding:NSASCIIStringEncoding]; - NSRange headerEndRange = [_rawData rangeOfData:headerEndMarker options:(NSDataSearchOptions)0 range:NSMakeRange(0, [_rawData length])]; - - if (headerEndRange.location == NSNotFound) - { - // Also check for LF LF (some clients use this) - NSData *lfMarker = [@"\n\n" dataUsingEncoding:NSASCIIStringEncoding]; - headerEndRange = [_rawData rangeOfData:lfMarker options:(NSDataSearchOptions)0 range:NSMakeRange(0, [_rawData length])]; - } - - if (headerEndRange.location != NSNotFound) - { - _headerComplete = YES; - - // Parse the header data - NSData *headerData = [_rawData subdataWithRange:NSMakeRange(0, headerEndRange.location + headerEndRange.length)]; - NSString *headerString = [[NSString alloc] initWithData:headerData encoding:NSASCIIStringEncoding]; - - if (headerString) - { - [self parseHeaders:headerString]; - } - - // Extract body data if any - NSUInteger bodyStart = headerEndRange.location + headerEndRange.length; - if ([_rawData length] > bodyStart) - { - NSData *bodyData = [_rawData subdataWithRange:NSMakeRange(bodyStart, [_rawData length] - bodyStart)]; - [_body appendData:bodyData]; - } - - [_rawData setLength:0]; - } - } - else - { - // Headers are complete, append to body - [_body appendData:data]; - } - - return YES; -} - -- (void)parseHeaders:(NSString *)headerString -{ - NSArray *lines; - - // Try splitting by "\r\n" first (standard HTTP line ending) - // Check if the string actually contains "\r\n" delimiter - if ([headerString rangeOfString:@"\r\n"].location != NSNotFound) - { - // Found "\r\n" delimiter, use this split - lines = [headerString componentsSeparatedByString:@"\r\n"]; - } - else - { - // No "\r\n" found, try "\n" (some clients use just LF) - lines = [headerString componentsSeparatedByString:@"\n"]; - } - - // componentsSeparatedByString: always returns at least one element, - // so check if we have meaningful content (non-empty first line) - if ([lines count] == 0 || [[lines objectAtIndex:0] length] == 0) - { - return; - } - - // Parse first line (request line or status line) - NSString *firstLine = [lines objectAtIndex:0]; - NSArray *firstLineParts = [firstLine componentsSeparatedByString:@" "]; - - if (_isRequest && [firstLineParts count] >= 3) - { - // Request line: METHOD URL VERSION - _method = [[firstLineParts objectAtIndex:0] copy]; - NSString *urlString = [firstLineParts objectAtIndex:1]; - - // Handle both absolute URLs and relative paths - // Try absolute URL first - NSURL *parsedURL = [NSURL URLWithString:urlString]; - - // If that fails (nil), it's likely a relative path like "/endpoint" - // Create a URL with a base URL to handle relative paths - if (!parsedURL) - { - // Use a dummy base URL to allow relative path parsing - NSURL *baseURL = [NSURL URLWithString:@"http://localhost"]; - parsedURL = [NSURL URLWithString:urlString relativeToURL:baseURL]; - } - - _url = [parsedURL copy]; - if ([firstLineParts count] >= 3) - { - _version = [[firstLineParts objectAtIndex:2] copy]; - } - } - else if (!_isRequest && [firstLineParts count] >= 3) - { - // Status line: VERSION CODE DESCRIPTION - _version = [[firstLineParts objectAtIndex:0] copy]; - _statusCode = [[firstLineParts objectAtIndex:1] integerValue]; - NSMutableArray *descParts = [NSMutableArray arrayWithArray:firstLineParts]; - [descParts removeObjectAtIndex:0]; - [descParts removeObjectAtIndex:0]; - _statusDescription = [[descParts componentsJoinedByString:@" "] copy]; - } - - // Parse header fields - for (NSUInteger i = 1; i < [lines count]; i++) - { - NSString *line = [lines objectAtIndex:i]; - if ([line length] == 0) - { - continue; - } - - NSRange colonRange = [line rangeOfString:@":"]; - if (colonRange.location != NSNotFound) - { - NSString *headerName = [[line substringToIndex:colonRange.location] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - NSString *headerValue = [[line substringFromIndex:colonRange.location + 1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - if ([headerName length] > 0) - { - // HTTP headers are case-insensitive, but we'll store them with their original case - // For lookup, we'll use case-insensitive comparison - [_headers setObject:headerValue forKey:headerName]; - } - } - } -} - -- (BOOL)isHeaderComplete -{ - return _headerComplete; -} - -- (NSString *)version -{ - return _version; -} - -- (NSString *)method -{ - return _method; -} - -- (NSURL *)url -{ - return _url; -} - -- (NSInteger)statusCode -{ - return _statusCode; -} - -- (NSDictionary *)allHeaderFields -{ - return [_headers copy]; -} - -- (NSString *)headerField:(NSString *)headerField -{ - // Case-insensitive lookup - for (NSString *key in [_headers allKeys]) - { - if ([key caseInsensitiveCompare:headerField] == NSOrderedSame) - { - return [_headers objectForKey:key]; - } - } - return nil; -} - -- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue -{ - if (headerField && headerFieldValue) - { - // Remove existing header with same name (case-insensitive) - NSMutableArray *keysToRemove = [NSMutableArray array]; - for (NSString *key in [_headers allKeys]) - { - if ([key caseInsensitiveCompare:headerField] == NSOrderedSame) - { - [keysToRemove addObject:key]; - } - } - [_headers removeObjectsForKeys:keysToRemove]; - - // Add new header - [_headers setObject:headerFieldValue forKey:headerField]; - } -} - -- (NSData *)messageData -{ - NSMutableString *messageString = [NSMutableString string]; - - if (_isRequest) - { - // Request line - // For relative URLs, use the path component; for absolute URLs, use absoluteString - NSString *urlString = nil; - if (_url) - { - // If it's a relative URL (has a base), use the relative path - // Otherwise use absoluteString or path - if ([_url baseURL]) - { - // Relative URL - use the relative portion - urlString = [_url relativeString]; - } - else - { - // Absolute URL - urlString = [_url absoluteString]; - if (!urlString) - { - urlString = [_url path]; - } - } - } - [messageString appendFormat:@"%@ %@ %@\r\n", _method ?: @"GET", urlString ?: @"/", _version ?: HTTPVersion1_1]; - } - else - { - // Status line - [messageString appendFormat:@"%@ %ld %@\r\n", _version ?: HTTPVersion1_1, (long)_statusCode, _statusDescription ?: @""]; - } - - // Headers - for (NSString *key in [_headers allKeys]) - { - NSString *value = [_headers objectForKey:key]; - [messageString appendFormat:@"%@: %@\r\n", key, value]; - } - - // Empty line to separate headers from body - [messageString appendString:@"\r\n"]; - - NSMutableData *data = [NSMutableData dataWithData:(id)[messageString dataUsingEncoding:NSASCIIStringEncoding]]; - - // Append body if present - if ([_body length] > 0) - { - [data appendData:_body]; - } - - return data; -} - -- (NSData *)body -{ - return [_body copy]; -} - -- (void)setBody:(NSData *)body -{ - if (body) - { - _body = [body mutableCopy]; - } - else - { - _body = [[NSMutableData alloc] init]; - } -} - -- (void)dealloc -{ - // ARC automatically releases all instance variables, but we include this - // for clarity and to match the pattern of the original CFNetwork implementation. - // All Objective-C objects (_headers, _body, _rawData, _version, _method, _url, _statusDescription) - // will be automatically released by ARC when this object is deallocated. -#if ! __has_feature(objc_arc) - [_headers release]; - [_body release]; - [_rawData release]; - [_version release]; - [_method release]; - [_url release]; - [_statusDescription release]; - [super dealloc]; -#endif -} - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h deleted file mode 100644 index 726ca5d4df..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h +++ /dev/null @@ -1,149 +0,0 @@ -#import - - -@protocol HTTPResponse - -/** - * Returns the length of the data in bytes. - * If you don't know the length in advance, implement the isChunked method and have it return YES. - **/ -- (UInt64)contentLength; - -/** - * The HTTP server supports range requests in order to allow things like - * file download resumption and optimized streaming on mobile devices. - **/ -- (UInt64)offset; -- (void)setOffset:(UInt64)offset; - -/** - * Returns the data for the response. - * You do not have to return data of the exact length that is given. - * You may optionally return data of a lesser length. - * However, you must never return data of a greater length than requested. - * Doing so could disrupt proper support for range requests. - * - * To support asynchronous responses, read the discussion at the bottom of this header. - **/ -- (NSData *)readDataOfLength:(NSUInteger)length; - -/** - * Should only return YES after the HTTPConnection has read all available data. - * That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method. - **/ -- (BOOL)isDone; - -@optional - -/** - * If you need time to calculate any part of the HTTP response headers (status code or header fields), - * this method allows you to delay sending the headers so that you may asynchronously execute the calculations. - * Simply implement this method and return YES until you have everything you need concerning the headers. - * - * This method ties into the asynchronous response architecture of the HTTPConnection. - * You should read the full discussion at the bottom of this header. - * - * If you return YES from this method, - * the HTTPConnection will wait for you to invoke the responseHasAvailableData method. - * After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers. - * - * You should only delay sending the headers until you have everything you need concerning just the headers. - * Asynchronously generating the body of the response is not an excuse to delay sending the headers. - * Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method. - * - * Important: You should read the discussion at the bottom of this header. - **/ -- (BOOL)delayResponseHeaders; - -/** - * Status code for response. - * Allows for responses such as redirect (301), etc. - **/ -- (NSInteger)status; - -/** - * If you want to add any extra HTTP headers to the response, - * simply return them in a dictionary in this method. - **/ -- (NSDictionary *)httpHeaders; - -/** - * If you don't know the content-length in advance, - * implement this method in your custom response class and return YES. - * - * Important: You should read the discussion at the bottom of this header. - **/ -- (BOOL)isChunked; - -/** - * This method is called from the HTTPConnection class when the connection is closed, - * or when the connection is finished with the response. - * If your response is asynchronous, you should implement this method so you know not to - * invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated). - **/ -- (void)connectionDidClose; - -@end - - -/** - * Important notice to those implementing custom asynchronous and/or chunked responses: - * - * HTTPConnection supports asynchronous responses. All you have to do in your custom response class is - * asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method. - * You don't have to wait until you have all of the response ready to invoke this method. For example, if you - * generate the response in incremental chunks, you could call responseHasAvailableData after generating - * each chunk. Please see the HTTPAsyncFileResponse class for an example of how to do this. - * - * The normal flow of events for an HTTPConnection while responding to a request is like this: - * - Send http resopnse headers - * - Get data from response via readDataOfLength method. - * - Add data to asyncSocket's write queue. - * - Wait for asyncSocket to notify it that the data has been sent. - * - Get more data from response via readDataOfLength method. - * - ... continue this cycle until the entire response has been sent. - * - * With an asynchronous response, the flow is a little different. - * - * First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers. - * This allows the response to asynchronously execute any code needed to calculate a part of the header. - * An example might be the response needs to generate some custom header fields, - * or perhaps the response needs to look for a resource on network-attached storage. - * Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet. - * In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES. - * After returning YES from this method, the HTTPConnection will wait until the response invokes its - * responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders - * method to see if the response is ready to send the headers. - * This cycle will continue until the delayResponseHeaders method returns NO. - * - * You should only delay sending the response headers until you have everything you need concerning just the headers. - * Asynchronously generating the body of the response is not an excuse to delay sending the headers. - * - * After the response headers have been sent, the HTTPConnection calls your readDataOfLength method. - * You may or may not have any available data at this point. If you don't, then simply return nil. - * You should later invoke HTTPConnection's responseHasAvailableData when you have data to send. - * - * You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked - * responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and - * return nil in your readDataOfLength whenever you don't have any available data in the requested range. - * HTTPConnection will automatically detect when it should be requesting new data and will act appropriately. - * - * It's important that you also keep in mind that the HTTP server supports range requests. - * The setOffset method is mandatory, and should not be ignored. - * Make sure you take into account the offset within the readDataOfLength method. - * You should also be aware that the HTTPConnection automatically sorts any range requests. - * So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99. - * - * HTTPConnection can also help you keep your memory footprint small. - * Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into - * RAM, and sit around waiting for HTTPConnection to slowly send it out over the network. All you need to do - * is pay attention to when HTTPConnection requests more data via readDataOfLength. This is because HTTPConnection - * will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should - * consider how you might be able to take advantage of this fact to generate your asynchronous response on demand, - * while at the same time keeping your memory footprint small, and your application lightning fast. - * - * If you don't know the content-length in advanced, you should also implement the isChunked method. - * This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked". - * There's a good chance that if your response is asynchronous and dynamic, it's also chunked. - * If your response is chunked, you don't need to worry about range requests. - **/ diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h deleted file mode 100644 index 6934321f18..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h +++ /dev/null @@ -1,126 +0,0 @@ -#import - -@class GCDAsyncSocket; -@class WebSocket; - -#if TARGET_OS_IPHONE -#define IMPLEMENTED_PROTOCOLS -#else -#define IMPLEMENTED_PROTOCOLS -#endif - - -@interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS -{ - // Underlying asynchronous TCP/IP socket - GCDAsyncSocket *asyncSocket; - - // Dispatch queues - dispatch_queue_t serverQueue; - dispatch_queue_t connectionQueue; - void *IsOnServerQueueKey; - void *IsOnConnectionQueueKey; - - // HTTP server configuration - NSString *documentRoot; - Class connectionClass; - NSString *interface; - UInt16 port; - - // Connection management - NSMutableArray *connections; - NSLock *connectionsLock; - - BOOL isRunning; -} - -/** - * Specifies the document root to serve files from. - * For example, if you set this to "/Users//Sites", - * then it will serve files out of the local Sites directory (including subdirectories). - * - * The default value is nil. - * The default server configuration will not serve any files until this is set. - * - * If you change the documentRoot while the server is running, - * the change will affect future incoming http connections. - **/ -- (NSString *)documentRoot; -- (void)setDocumentRoot:(NSString *)value; - -/** - * The connection class is the class used to handle incoming HTTP connections. - * - * The default value is [HTTPConnection class]. - * You can override HTTPConnection, and then set this to [MyHTTPConnection class]. - * - * If you change the connectionClass while the server is running, - * the change will affect future incoming http connections. - **/ -- (Class)connectionClass; -- (void)setConnectionClass:(Class)value; - -/** - * Set what interface you'd like the server to listen on. - * By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc. - * - * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept connections from the local machine. - **/ -- (NSString *)interface; -- (void)setInterface:(NSString *)value; - -/** - * The port number to run the HTTP server on. - * - * The default port number is zero, meaning the server will automatically use any available port. - * This is the recommended port value, as it avoids possible port conflicts with other applications. - * Technologies such as Bonjour can be used to allow other applications to automatically discover the port number. - * - * Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024. - * - * You can change the port property while the server is running, but it won't affect the running server. - * To actually change the port the server is listening for connections on you'll need to restart the server. - * - * The listeningPort method will always return the port number the running server is listening for connections on. - * If the server is not running this method returns 0. - **/ -- (UInt16)port; -- (UInt16)listeningPort; -- (void)setPort:(UInt16)value; - -/** - * Attempts to starts the server on the configured port, interface, etc. - * - * If an error occurs, this method returns NO and sets the errPtr (if given). - * Otherwise returns YES on success. - * - * Some examples of errors that might occur: - * - You specified the server listen on a port which is already in use by another application. - * - You specified the server listen on a port number below 1024, which requires root priviledges. - * - * Code Example: - * - * NSError *err = nil; - * if (![httpServer start:&err]) - * { - * NSLog(@"Error starting http server: %@", err); - * } - **/ -- (BOOL)start:(NSError **)errPtr; - -/** - * Stops the server, preventing it from accepting any new connections. - * You may specify whether or not you want to close the existing client connections. - * - * The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO]) - **/ -- (void)stop; -- (void)stop:(BOOL)keepExistingConnections; - -- (BOOL)isRunning; - -- (NSUInteger)numberOfHTTPConnections; - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m b/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m deleted file mode 100644 index 4a0ca7f3f8..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m +++ /dev/null @@ -1,372 +0,0 @@ -#import "HTTPServer.h" -#import "HTTPConnection.h" -#import "HTTPLogging.h" - -#import "GCDAsyncSocket.h" - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Wimplicit-retain-self" -#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" -#pragma clang diagnostic ignored "-Wunused" - -// Log levels: off, error, warn, info, verbose -// Other flags: trace -static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation HTTPServer - -/** - * Standard Constructor. - * Instantiates an HTTP server, but does not start it. - **/ -- (id)init -{ - if ((self = [super init])) - { - HTTPLogTrace(); - - // Setup underlying dispatch queues - serverQueue = dispatch_queue_create("HTTPServer", NULL); - connectionQueue = dispatch_queue_create("HTTPConnection", NULL); - - IsOnServerQueueKey = &IsOnServerQueueKey; - IsOnConnectionQueueKey = &IsOnConnectionQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; // Whatever, just not null - - dispatch_queue_set_specific(serverQueue, IsOnServerQueueKey, nonNullUnusedPointer, NULL); - dispatch_queue_set_specific(connectionQueue, IsOnConnectionQueueKey, nonNullUnusedPointer, NULL); - - // Initialize underlying GCD based tcp socket - asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:(id)self delegateQueue:serverQueue]; - - // Use default connection class of HTTPConnection - connectionClass = [HTTPConnection self]; - - // By default bind on all available interfaces, en1, wifi etc - interface = nil; - - // Use a default port of 0 - // This will allow the kernel to automatically pick an open port for us - port = 0; - - // Initialize arrays to hold all the HTTP connections - connections = [[NSMutableArray alloc] init]; - - connectionsLock = [[NSLock alloc] init]; - - // Register for notifications of closed connections - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(connectionDidDie:) - name:HTTPConnectionDidDieNotification - object:nil]; - - isRunning = NO; - } - return self; -} - -/** - * Standard Deconstructor. - * Stops the server, and clients, and releases any resources connected with this instance. - **/ -- (void)dealloc -{ - HTTPLogTrace(); - - // Remove notification observer - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - // Stop the server if it's running - [self stop]; - - // Release all instance variables - -#if !OS_OBJECT_USE_OBJC - dispatch_release(serverQueue); - dispatch_release(connectionQueue); -#endif - - [asyncSocket setDelegate:nil delegateQueue:NULL]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Server Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The document root is filesystem root for the webserver. - * Thus requests for /index.html will be referencing the index.html file within the document root directory. - * All file requests are relative to this document root. - **/ -- (NSString *)documentRoot -{ - __block NSString *result; - - dispatch_sync(serverQueue, ^{ - result = documentRoot; - }); - - return result; -} - -- (void)setDocumentRoot:(NSString *)value -{ - HTTPLogTrace(); - - // Document root used to be of type NSURL. - // Add type checking for early warning to developers upgrading from older versions. - - if (value && ![value isKindOfClass:[NSString class]]) - { - HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter", - THIS_FILE, THIS_METHOD, NSStringFromClass([value class])); - return; - } - - NSString *valueCopy = [value copy]; - - dispatch_async(serverQueue, ^{ - documentRoot = valueCopy; - }); - -} - -/** - * The connection class is the class that will be used to handle connections. - * That is, when a new connection is created, an instance of this class will be intialized. - * The default connection class is HTTPConnection. - * If you use a different connection class, it is assumed that the class extends HTTPConnection - **/ -- (Class)connectionClass -{ - __block Class result; - - dispatch_sync(serverQueue, ^{ - result = connectionClass; - }); - - return result; -} - -- (void)setConnectionClass:(Class)value -{ - HTTPLogTrace(); - - dispatch_async(serverQueue, ^{ - connectionClass = value; - }); -} - -/** - * What interface to bind the listening socket to. - **/ -- (NSString *)interface -{ - __block NSString *result; - - dispatch_sync(serverQueue, ^{ - result = interface; - }); - - return result; -} - -- (void)setInterface:(NSString *)value -{ - NSString *valueCopy = [value copy]; - - dispatch_async(serverQueue, ^{ - interface = valueCopy; - }); - -} - -/** - * The port to listen for connections on. - * By default this port is initially set to zero, which allows the kernel to pick an available port for us. - * After the HTTP server has started, the port being used may be obtained by this method. - **/ -- (UInt16)port -{ - __block UInt16 result; - - dispatch_sync(serverQueue, ^{ - result = port; - }); - - return result; -} - -- (UInt16)listeningPort -{ - __block UInt16 result; - - dispatch_sync(serverQueue, ^{ - if (isRunning) - result = [asyncSocket localPort]; - else - result = 0; - }); - - return result; -} - -- (void)setPort:(UInt16)value -{ - HTTPLogTrace(); - - dispatch_async(serverQueue, ^{ - port = value; - }); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Server Control -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)start:(NSError **)errPtr -{ - HTTPLogTrace(); - - __block BOOL success = YES; - __block NSError *err = nil; - - dispatch_sync(serverQueue, ^{ @autoreleasepool { - - success = [asyncSocket acceptOnInterface:interface port:port error:&err]; - if (success) - { - HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]); - - isRunning = YES; - } - else - { - HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err); - } - }}); - - if (errPtr) - *errPtr = err; - - return success; -} - -- (void)stop -{ - [self stop:NO]; -} - -- (void)stop:(BOOL)keepExistingConnections -{ - HTTPLogTrace(); - - dispatch_sync(serverQueue, ^{ @autoreleasepool { - // Stop listening / accepting incoming connections - [asyncSocket disconnect]; - isRunning = NO; - - if (!keepExistingConnections) - { - // Stop all HTTP connections the server owns - [connectionsLock lock]; - for (HTTPConnection *connection in connections) - { - [connection stop]; - } - [connections removeAllObjects]; - [connectionsLock unlock]; - } - }}); -} - -- (BOOL)isRunning -{ - __block BOOL result; - - dispatch_sync(serverQueue, ^{ - result = isRunning; - }); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Server Status -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Returns the number of http client connections that are currently connected to the server. - **/ -- (NSUInteger)numberOfHTTPConnections -{ - NSUInteger result = 0; - - [connectionsLock lock]; - result = [connections count]; - [connectionsLock unlock]; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Incoming Connections -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (HTTPConfig *)config -{ - // Override me if you want to provide a custom config to the new connection. - // - // Generally this involves overriding the HTTPConfig class to include any custom settings, - // and then having this method return an instance of 'MyHTTPConfig'. - - // Note: Think you can make the server faster by putting each connection on its own queue? - // Then benchmark it before and after and discover for yourself the shocking truth! - // - // Try the apache benchmark tool (already installed on your Mac): - // $ ab -n 1000 -c 1 http://localhost:/some_path.html - - return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue]; -} - -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket -{ - HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket - configuration:[self config]]; - [connectionsLock lock]; - [connections addObject:newConnection]; - [connectionsLock unlock]; - - [newConnection start]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Notifications -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted. - * It allows us to remove the connection from our array. - **/ -- (void)connectionDidDie:(NSNotification *)notification -{ - // Note: This method is called on the connection queue that posted the notification - - [connectionsLock lock]; - - HTTPLogTrace(); - [connections removeObject:[notification object]]; - - [connectionsLock unlock]; -} - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/LICENSE b/WebDriverAgentLib/Vendor/CocoaHTTPServer/LICENSE deleted file mode 100644 index 64c3c902bf..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -Software License Agreement (BSD License) - -Copyright (c) 2011, Deusty, LLC -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of Deusty nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h deleted file mode 100644 index 309a6d9e73..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h +++ /dev/null @@ -1,13 +0,0 @@ -#import -#import "HTTPResponse.h" - - -@interface HTTPDataResponse : NSObject -{ - NSUInteger offset; - NSData *data; -} - -- (id)initWithData:(NSData *)data; - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m deleted file mode 100644 index 79c5bca57b..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m +++ /dev/null @@ -1,83 +0,0 @@ -#import "HTTPDataResponse.h" -#import "HTTPLogging.h" - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Wcast-qual" -#pragma clang diagnostic ignored "-Wunused-variable" - -// Log levels : off, error, warn, info, verbose -// Other flags: trace -static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; - - -@implementation HTTPDataResponse - -- (id)initWithData:(NSData *)dataParam -{ - if((self = [super init])) - { - HTTPLogTrace(); - - offset = 0; - data = dataParam; - } - return self; -} - -- (void)dealloc -{ - HTTPLogTrace(); - -} - -- (UInt64)contentLength -{ - UInt64 result = (UInt64)[data length]; - - HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result); - - return result; -} - -- (UInt64)offset -{ - HTTPLogTrace(); - - return offset; -} - -- (void)setOffset:(UInt64)offsetParam -{ - HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset); - - offset = (NSUInteger)offsetParam; -} - -- (NSData *)readDataOfLength:(NSUInteger)lengthParameter -{ - HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter); - - NSUInteger remaining = [data length] - offset; - NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; - - void *bytes = (void *)(((char*)[data bytes]) + offset); - - offset += length; - - return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; -} - -- (BOOL)isDone -{ - BOOL result = (offset == [data length]); - - HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); - - return result; -} - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h deleted file mode 100644 index 0b4fed96a8..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h +++ /dev/null @@ -1,9 +0,0 @@ -#import "HTTPResponse.h" - -@interface HTTPErrorResponse : NSObject { - NSInteger _status; -} - -- (id)initWithErrorCode:(int)httpErrorCode; - -@end diff --git a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m b/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m deleted file mode 100644 index a11552008c..0000000000 --- a/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m +++ /dev/null @@ -1,38 +0,0 @@ -#import "HTTPErrorResponse.h" - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" - -@implementation HTTPErrorResponse - --(id)initWithErrorCode:(int)httpErrorCode -{ - if ((self = [super init])) - { - _status = httpErrorCode; - } - - return self; -} - -- (UInt64) contentLength { - return 0; -} - -- (UInt64) offset { - return 0; -} - -- (void)setOffset:(UInt64)offset {} - -- (NSData*) readDataOfLength:(NSUInteger)length { - return nil; -} - -- (BOOL) isDone { - return YES; -} - -- (NSInteger) status { - return _status; -} -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h b/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h deleted file mode 100644 index e3930fcc34..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h +++ /dev/null @@ -1,13 +0,0 @@ -#import -#import "HTTPResponse.h" - -// Wraps an HTTPResponse object to allow setting a custom status code -// without needing to create subclasses of every response. -@interface HTTPResponseProxy : NSObject - -@property (nonatomic) NSObject *response; -@property (nonatomic) NSInteger status; - -- (NSInteger)customStatus; - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m b/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m deleted file mode 100644 index f74f3ad1f2..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m +++ /dev/null @@ -1,84 +0,0 @@ -#import "HTTPResponseProxy.h" - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" - -@implementation HTTPResponseProxy - -@synthesize response; -@synthesize status; - -- (NSInteger)status { - if (status != 0) { - return status; - } else if ([response respondsToSelector:@selector(status)]) { - return [response status]; - } - - return 200; -} - -- (void)setStatus:(NSInteger)statusCode { - status = statusCode; -} - -- (NSInteger)customStatus { - return status; -} - -// Implement the required HTTPResponse methods -- (UInt64)contentLength { - if (response) { - return [response contentLength]; - } else { - return 0; - } -} - -- (UInt64)offset { - if (response) { - return [response offset]; - } else { - return 0; - } -} - -- (void)setOffset:(UInt64)offset { - if (response) { - [response setOffset:offset]; - } -} - -- (NSData *)readDataOfLength:(NSUInteger)length { - if (response) { - return [response readDataOfLength:length]; - } else { - return nil; - } -} - -- (BOOL)isDone { - if (response) { - return [response isDone]; - } else { - return YES; - } -} - -// Forward all other invocations to the actual response object -- (void)forwardInvocation:(NSInvocation *)invocation { - if ([response respondsToSelector:[invocation selector]]) { - [invocation invokeWithTarget:response]; - } else { - [super forwardInvocation:invocation]; - } -} - -- (BOOL)respondsToSelector:(SEL)selector { - if ([super respondsToSelector:selector]) - return YES; - - return [response respondsToSelector:selector]; -} - -@end - diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE b/WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE deleted file mode 100644 index 717caf79b6..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011 Matt Stevens - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h b/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h deleted file mode 100644 index 185a2b7e63..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h +++ /dev/null @@ -1,18 +0,0 @@ -#import -#import "RoutingHTTPServer.h" - -@interface Route : NSObject - -@property (nonatomic) NSRegularExpression *regex; -@property (nonatomic, copy) RequestHandler handler; - -#if __has_feature(objc_arc_weak) -@property (nonatomic, weak) id target; -#else -@property (nonatomic, assign) id target; -#endif - -@property (nonatomic, assign) SEL selector; -@property (nonatomic) NSArray *keys; - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m b/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m deleted file mode 100644 index 8c9e7e56b4..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m +++ /dev/null @@ -1,11 +0,0 @@ -#import "Route.h" - -@implementation Route - -@synthesize regex; -@synthesize handler; -@synthesize target; -@synthesize selector; -@synthesize keys; - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h deleted file mode 100644 index 0219addee9..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h +++ /dev/null @@ -1,16 +0,0 @@ -#import -@class HTTPMessage; - -@interface RouteRequest : NSObject - -@property (nonatomic, readonly) NSDictionary *headers; -@property (nonatomic, readonly) NSDictionary *params; - -- (id)initWithHTTPMessage:(HTTPMessage *)msg parameters:(NSDictionary *)params; -- (NSString *)header:(NSString *)field; -- (id)param:(NSString *)name; -- (NSString *)method; -- (NSURL *)url; -- (NSData *)body; - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m deleted file mode 100644 index 50046d03e8..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m +++ /dev/null @@ -1,50 +0,0 @@ -#import "RouteRequest.h" -#import "HTTPMessage.h" - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Widiomatic-parentheses" - -@implementation RouteRequest { - HTTPMessage *message; -} - -@synthesize params; - -- (id)initWithHTTPMessage:(HTTPMessage *)msg parameters:(NSDictionary *)parameters { - if (self = [super init]) { - params = parameters; - message = msg; - } - return self; -} - -- (NSDictionary *)headers { - return [message allHeaderFields]; -} - -- (NSString *)header:(NSString *)field { - return [message headerField:field]; -} - -- (id)param:(NSString *)name { - return [params objectForKey:name]; -} - -- (NSString *)method { - return [message method]; -} - -- (NSURL *)url { - return [message url]; -} - -- (NSData *)body { - return [message body]; -} - -- (NSString *)description { - NSData *data = [message messageData]; - return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; -} - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h deleted file mode 100644 index 688002f859..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h +++ /dev/null @@ -1,20 +0,0 @@ -#import -#import "HTTPResponse.h" -@class HTTPConnection; -@class HTTPResponseProxy; - -@interface RouteResponse : NSObject - -@property (nonatomic, unsafe_unretained, readonly) HTTPConnection *connection; -@property (nonatomic, readonly) NSDictionary *headers; -@property (nonatomic, strong) NSObject *response; -@property (nonatomic, readonly) NSObject *proxiedResponse; -@property (nonatomic) NSInteger statusCode; - -- (id)initWithConnection:(HTTPConnection *)theConnection; -- (void)setHeader:(NSString *)field value:(NSString *)value; -- (void)respondWithString:(NSString *)string; -- (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding; -- (void)respondWithData:(NSData *)data; - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m deleted file mode 100644 index 47db7b6fad..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m +++ /dev/null @@ -1,66 +0,0 @@ -#import "RouteResponse.h" -#import "HTTPConnection.h" -#import "HTTPDataResponse.h" -#import "HTTPResponseProxy.h" - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Widiomatic-parentheses" - -@implementation RouteResponse { - NSMutableDictionary *headers; - HTTPResponseProxy *proxy; -} - -@synthesize connection; -@synthesize headers; - -- (id)initWithConnection:(HTTPConnection *)theConnection { - if (self = [super init]) { - connection = theConnection; - headers = [[NSMutableDictionary alloc] init]; - proxy = [[HTTPResponseProxy alloc] init]; - } - return self; -} - -- (NSObject *)response { - return proxy.response; -} - -- (void)setResponse:(NSObject *)response { - proxy.response = response; -} - -- (NSObject *)proxiedResponse { - if (proxy.response != nil || proxy.customStatus != 0 || [headers count] > 0) { - return proxy; - } - - return nil; -} - -- (NSInteger)statusCode { - return proxy.status; -} - -- (void)setStatusCode:(NSInteger)status { - proxy.status = status; -} - -- (void)setHeader:(NSString *)field value:(NSString *)value { - [headers setObject:value forKey:field]; -} - -- (void)respondWithString:(NSString *)string { - [self respondWithString:string encoding:NSUTF8StringEncoding]; -} - -- (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding { - [self respondWithData:[string dataUsingEncoding:encoding]]; -} - -- (void)respondWithData:(NSData *)data { - self.response = [[HTTPDataResponse alloc] initWithData:data]; -} - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h deleted file mode 100644 index 1f6cd27d00..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h +++ /dev/null @@ -1,5 +0,0 @@ -#import -#import "HTTPConnection.h" - -@interface RoutingConnection : HTTPConnection -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m deleted file mode 100644 index 3eee19267e..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m +++ /dev/null @@ -1,142 +0,0 @@ -#import "RoutingConnection.h" -#import "RoutingHTTPServer.h" -#import "HTTPMessage.h" -#import "HTTPResponseProxy.h" - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Widiomatic-parentheses" -#pragma clang diagnostic ignored "-Wundeclared-selector" - -@implementation RoutingConnection { - __unsafe_unretained RoutingHTTPServer *http; - NSDictionary *headers; -} - -- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig { - if (self = [super initWithAsyncSocket:newSocket configuration:aConfig]) { - NSAssert([config.server isKindOfClass:[RoutingHTTPServer class]], - @"A RoutingConnection is being used with a server that is not a %@", - NSStringFromClass([RoutingHTTPServer class])); - - http = (RoutingHTTPServer *)config.server; - } - return self; -} - -- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { - - if ([http supportsMethod:method]) - return YES; - - return [super supportsMethod:method atPath:path]; -} - -- (BOOL)shouldHandleRequestForMethod:(NSString *)method atPath:(NSString *)path { - // The default implementation is strict about the use of Content-Length. Either - // a given method + path combination must *always* include data or *never* - // include data. The routing connection is lenient, a POST that sometimes does - // not include data or a GET that sometimes does is fine. It is up to the route - // implementations to decide how to handle these situations. - return YES; -} - -- (void)processBodyData:(NSData *)postDataChunk { - BOOL result = [request appendData:postDataChunk]; - if (!result) { - // TODO: Log - } -} - -- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { - NSURL *url = [request url]; - NSString *query = nil; - NSDictionary *params = [NSDictionary dictionary]; - headers = nil; - - if (url) { - path = [url path]; // Strip the query string from the path - query = [url query]; - if (query) { - params = [self parseParams:query]; - } - } - - RouteResponse *response = [http routeMethod:method withPath:path parameters:params request:request connection:self]; - if (response != nil) { - headers = response.headers; - return response.proxiedResponse; - } - - // Set a MIME type for static files if possible - NSObject *staticResponse = [super httpResponseForMethod:method URI:path]; - if (staticResponse && [staticResponse respondsToSelector:@selector(filePath)]) { - NSString *mimeType = [http mimeTypeForPath:[staticResponse performSelector:@selector(filePath)]]; - if (mimeType) { - headers = [NSDictionary dictionaryWithObject:mimeType forKey:@"Content-Type"]; - } - } - return staticResponse; -} - -- (void)responseHasAvailableData:(NSObject *)sender { - HTTPResponseProxy *proxy = (HTTPResponseProxy *)httpResponse; - if (proxy.response == sender) { - [super responseHasAvailableData:httpResponse]; - } -} - -- (void)responseDidAbort:(NSObject *)sender { - HTTPResponseProxy *proxy = (HTTPResponseProxy *)httpResponse; - if (proxy.response == sender) { - [super responseDidAbort:httpResponse]; - } -} - -- (void)setHeadersForResponse:(HTTPMessage *)response isError:(BOOL)isError { - [http.defaultHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *stop) { - [response setHeaderField:field value:value]; - }]; - - if (headers && !isError) { - [headers enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *stop) { - [response setHeaderField:field value:value]; - }]; - } - - // Set the connection header if not already specified - NSString *connection = [response headerField:@"Connection"]; - if (!connection) { - connection = [self shouldDie] ? @"close" : @"keep-alive"; - [response setHeaderField:@"Connection" value:connection]; - } -} - -- (NSData *)preprocessResponse:(HTTPMessage *)response { - [self setHeadersForResponse:response isError:NO]; - return [super preprocessResponse:response]; -} - -- (NSData *)preprocessErrorResponse:(HTTPMessage *)response { - [self setHeadersForResponse:response isError:YES]; - return [super preprocessErrorResponse:response]; -} - -- (BOOL)shouldDie { - __block BOOL shouldDie = [super shouldDie]; - - // Allow custom headers to determine if the connection should be closed - if (!shouldDie && headers) { - [headers enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *stop) { - if ([field caseInsensitiveCompare:@"connection"] == NSOrderedSame) { - if ([value caseInsensitiveCompare:@"close"] == NSOrderedSame) { - shouldDie = YES; - } - *stop = YES; - } - }]; - } - - return shouldDie; -} - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h deleted file mode 100644 index 91c7768c5b..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h +++ /dev/null @@ -1,55 +0,0 @@ -#import - -//! Project version number for Peertalk. -FOUNDATION_EXPORT double RoutingHTTPServerVersionNumber; - -//! Project version string for Peertalk. -FOUNDATION_EXPORT const unsigned char RoutingHTTPServerVersionString[]; - -#import "HTTPServer.h" -#import "HTTPConnection.h" -#import "HTTPResponse.h" -#import "RouteResponse.h" -#import "RouteRequest.h" -#import "RoutingConnection.h" - -#import "GCDAsyncSocket.h" - -typedef void (^RequestHandler)(RouteRequest *request, RouteResponse *response); - -@interface RoutingHTTPServer : HTTPServer - -@property (nonatomic, readonly) NSDictionary *defaultHeaders; - -// Specifies headers that will be set on every response. -// These headers can be overridden by RouteResponses. -- (void)setDefaultHeaders:(NSDictionary *)headers; -- (void)setDefaultHeader:(NSString *)field value:(NSString *)value; - -// Returns the dispatch queue on which routes are processed. -// By default this is NULL and routes are processed on CocoaHTTPServer's -// connection queue. You can specify a queue to process routes on, such as -// dispatch_get_main_queue() to process all routes on the main thread. -- (dispatch_queue_t)routeQueue; -- (void)setRouteQueue:(dispatch_queue_t)queue; - -- (NSDictionary *)mimeTypes; -- (void)setMIMETypes:(NSDictionary *)types; -- (void)setMIMEType:(NSString *)type forExtension:(NSString *)ext; -- (NSString *)mimeTypeForPath:(NSString *)path; - -// Convenience methods. Yes I know, this is Cocoa and we don't use convenience -// methods because typing lengthy primitives over and over and over again is -// elegant with the beauty and the poetry. These are just, you know, here. -- (void)get:(NSString *)path withBlock:(RequestHandler)block; -- (void)post:(NSString *)path withBlock:(RequestHandler)block; -- (void)put:(NSString *)path withBlock:(RequestHandler)block; -- (void)delete:(NSString *)path withBlock:(RequestHandler)block; - -- (void)handleMethod:(NSString *)method withPath:(NSString *)path block:(RequestHandler)block; -- (void)handleMethod:(NSString *)method withPath:(NSString *)path target:(id)target selector:(SEL)selector; - -- (BOOL)supportsMethod:(NSString *)method; -- (RouteResponse *)routeMethod:(NSString *)method withPath:(NSString *)path parameters:(NSDictionary *)params request:(HTTPMessage *)request connection:(HTTPConnection *)connection; - -@end diff --git a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m b/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m deleted file mode 100644 index 68e6a274aa..0000000000 --- a/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m +++ /dev/null @@ -1,303 +0,0 @@ -#import "RoutingHTTPServer.h" -#import "RoutingConnection.h" -#import "Route.h" - -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Widiomatic-parentheses" - -@implementation RoutingHTTPServer { - NSMutableDictionary *routes; - NSMutableDictionary *defaultHeaders; - NSMutableDictionary *mimeTypes; - dispatch_queue_t routeQueue; -} - -@synthesize defaultHeaders; - -- (id)init { - if (self = [super init]) { - connectionClass = [RoutingConnection self]; - routes = [[NSMutableDictionary alloc] init]; - defaultHeaders = [[NSMutableDictionary alloc] init]; - [self setupMIMETypes]; - } - return self; -} - -#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE -- (void)dealloc { - if (routeQueue) - dispatch_release(routeQueue); -} -#endif - -- (void)setDefaultHeaders:(NSDictionary *)headers { - if (headers) { - defaultHeaders = [headers mutableCopy]; - } else { - defaultHeaders = [[NSMutableDictionary alloc] init]; - } -} - -- (void)setDefaultHeader:(NSString *)field value:(NSString *)value { - [defaultHeaders setObject:value forKey:field]; -} - -- (dispatch_queue_t)routeQueue { - return routeQueue; -} - -- (void)setRouteQueue:(dispatch_queue_t)queue { -#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE - if (queue) - dispatch_retain(queue); - - if (routeQueue) - dispatch_release(routeQueue); -#endif - - routeQueue = queue; -} - -- (NSDictionary *)mimeTypes { - return mimeTypes; -} - -- (void)setMIMETypes:(NSDictionary *)types { - NSMutableDictionary *newTypes; - if (types) { - newTypes = [types mutableCopy]; - } else { - newTypes = [[NSMutableDictionary alloc] init]; - } - - mimeTypes = newTypes; -} - -- (void)setMIMEType:(NSString *)theType forExtension:(NSString *)ext { - [mimeTypes setObject:theType forKey:ext]; -} - -- (NSString *)mimeTypeForPath:(NSString *)path { - NSString *ext = [[path pathExtension] lowercaseString]; - if (!ext || [ext length] < 1) - return nil; - - return [mimeTypes objectForKey:ext]; -} - -- (void)get:(NSString *)path withBlock:(RequestHandler)block { - [self handleMethod:@"GET" withPath:path block:block]; -} - -- (void)post:(NSString *)path withBlock:(RequestHandler)block { - [self handleMethod:@"POST" withPath:path block:block]; -} - -- (void)put:(NSString *)path withBlock:(RequestHandler)block { - [self handleMethod:@"PUT" withPath:path block:block]; -} - -- (void)delete:(NSString *)path withBlock:(RequestHandler)block { - [self handleMethod:@"DELETE" withPath:path block:block]; -} - -- (void)handleMethod:(NSString *)method - withPath:(NSString *)path - block:(RequestHandler)block { - Route *route = [self routeWithPath:path]; - route.handler = block; - - [self addRoute:route forMethod:method]; -} - -- (void)handleMethod:(NSString *)method - withPath:(NSString *)path - target:(id)target - selector:(SEL)selector { - Route *route = [self routeWithPath:path]; - route.target = target; - route.selector = selector; - - [self addRoute:route forMethod:method]; -} - -- (void)addRoute:(Route *)route forMethod:(NSString *)method { - method = [method uppercaseString]; - NSMutableArray *methodRoutes = [routes objectForKey:method]; - if (methodRoutes == nil) { - methodRoutes = [NSMutableArray array]; - [routes setObject:methodRoutes forKey:method]; - } - - [methodRoutes addObject:route]; - - // Define a HEAD route for all GET routes - if ([method isEqualToString:@"GET"]) { - [self addRoute:route forMethod:@"HEAD"]; - } -} - -- (Route *)routeWithPath:(NSString *)path { - Route *route = [[Route alloc] init]; - NSMutableArray *keys = [NSMutableArray array]; - - if ([path length] > 2 && [path characterAtIndex:0] == '{') { - // This is a custom regular expression, just remove the {} - path = [path substringWithRange:NSMakeRange(1, [path length] - 2)]; - } else { - NSRegularExpression *regex = nil; - - // Escape regex characters - regex = [NSRegularExpression regularExpressionWithPattern:@"[.+()]" options:(NSRegularExpressionOptions)0 error:nil]; - path = [regex stringByReplacingMatchesInString:path options:(NSMatchingOptions)0 range:NSMakeRange(0, path.length) withTemplate:@"\\\\$0"]; - - // Parse any :parameters and * in the path - regex = [NSRegularExpression regularExpressionWithPattern:@"(:(\\w+)|\\*)" - options:(NSRegularExpressionOptions)0 - error:nil]; - NSMutableString *regexPath = [NSMutableString stringWithString:path]; - __block NSInteger diff = 0; - [regex enumerateMatchesInString:path options:(NSMatchingOptions)0 range:NSMakeRange(0, path.length) - usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { - NSRange replacementRange = NSMakeRange(diff + result.range.location, result.range.length); - NSString *replacementString; - - NSString *capturedString = [path substringWithRange:result.range]; - if ([capturedString isEqualToString:@"*"]) { - [keys addObject:@"wildcards"]; - replacementString = @"(.*?)"; - } else { - NSString *keyString = [path substringWithRange:[result rangeAtIndex:2]]; - [keys addObject:keyString]; - replacementString = @"([^/]+)"; - } - - [regexPath replaceCharactersInRange:replacementRange withString:replacementString]; - diff += replacementString.length - result.range.length; - }]; - - path = [NSString stringWithFormat:@"^%@$", regexPath]; - } - - route.regex = [NSRegularExpression regularExpressionWithPattern:path options:NSRegularExpressionCaseInsensitive error:nil]; - if ([keys count] > 0) { - route.keys = keys; - } - - return route; -} - -- (BOOL)supportsMethod:(NSString *)method { - return ([routes objectForKey:method] != nil); -} - -- (void)handleRoute:(Route *)route - withRequest:(RouteRequest *)request - response:(RouteResponse *)response { - if (route.handler) { - route.handler(request, response); - } else { - id target = route.target; - SEL selector = route.selector; - NSMethodSignature *signature = [target methodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:selector]; - [invocation setArgument:&request atIndex:2]; - [invocation setArgument:&response atIndex:3]; - [invocation invokeWithTarget:target]; - } -} - -- (RouteResponse *)routeMethod:(NSString *)method - withPath:(NSString *)path - parameters:(NSDictionary *)params - request:(HTTPMessage *)httpMessage - connection:(HTTPConnection *)connection { - NSMutableArray *methodRoutes = [routes objectForKey:method]; - if (methodRoutes == nil) - return nil; - - for (Route *route in methodRoutes) { - NSTextCheckingResult *result = [route.regex firstMatchInString:path options:(NSMatchingOptions)0 range:NSMakeRange(0, path.length)]; - if (!result) - continue; - - // The first range is all of the text matched by the regex. - NSUInteger captureCount = [result numberOfRanges]; - - if (route.keys) { - // Add the route's parameters to the parameter dictionary, accounting for - // the first range containing the matched text. - if (captureCount == [route.keys count] + 1) { - NSMutableDictionary *newParams = [params mutableCopy]; - NSUInteger index = 1; - BOOL firstWildcard = YES; - for (NSString *key in route.keys) { - NSString *capture = [path substringWithRange:[result rangeAtIndex:index]]; - if ([key isEqualToString:@"wildcards"]) { - NSMutableArray *wildcards = [newParams objectForKey:key]; - if (firstWildcard) { - // Create a new array and replace any existing object with the same key - wildcards = [NSMutableArray array]; - [newParams setObject:wildcards forKey:key]; - firstWildcard = NO; - } - [wildcards addObject:capture]; - } else { - [newParams setObject:capture forKey:key]; - } - index++; - } - params = newParams; - } - } else if (captureCount > 1) { - // For custom regular expressions place the anonymous captures in the captures parameter - NSMutableDictionary *newParams = [params mutableCopy]; - NSMutableArray *captures = [NSMutableArray array]; - for (NSUInteger i = 1; i < captureCount; i++) { - [captures addObject:[path substringWithRange:[result rangeAtIndex:i]]]; - } - [newParams setObject:captures forKey:@"captures"]; - params = newParams; - } - - RouteRequest *request = [[RouteRequest alloc] initWithHTTPMessage:httpMessage parameters:params]; - RouteResponse *response = [[RouteResponse alloc] initWithConnection:connection]; - if (!routeQueue) { - [self handleRoute:route withRequest:request response:response]; - } else { - // Process the route on the specified queue - dispatch_sync(routeQueue, ^{ - @autoreleasepool { - [self handleRoute:route withRequest:request response:response]; - } - }); - } - return response; - } - - return nil; -} - -- (void)setupMIMETypes { - mimeTypes = [[NSMutableDictionary alloc] initWithObjectsAndKeys: - @"application/x-javascript", @"js", - @"image/gif", @"gif", - @"image/jpeg", @"jpg", - @"image/jpeg", @"jpeg", - @"image/png", @"png", - @"image/svg+xml", @"svg", - @"image/tiff", @"tif", - @"image/tiff", @"tiff", - @"image/x-icon", @"ico", - @"image/x-ms-bmp", @"bmp", - @"text/css", @"css", - @"text/html", @"html", - @"text/html", @"htm", - @"text/plain", @"txt", - @"text/xml", @"xml", - nil]; -} - -@end