├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── CocoaSPDY.podspec ├── LICENSE ├── README.md ├── SPDY.iphoneos └── SPDY.iphoneos-Prefix.pch ├── SPDY.macosx └── SPDY.macosx-Prefix.pch ├── SPDY.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── SPDY.iphoneos.arm64.xcscheme │ ├── SPDY.iphoneos.xcscheme │ ├── SPDY.iphonesimulator.xcscheme │ ├── SPDY.macosx.xcscheme │ ├── SPDY.xcscheme │ └── SPDYUnitTests.xcscheme ├── SPDY ├── NSURLRequest+SPDYURLRequest.h ├── NSURLRequest+SPDYURLRequest.m ├── SPDY-Info.plist ├── SPDY-Prefix.pch ├── SPDYCanonicalRequest.h ├── SPDYCanonicalRequest.m ├── SPDYCommonLogger.h ├── SPDYCommonLogger.m ├── SPDYDefinitions.h ├── SPDYError.h ├── SPDYFrame.h ├── SPDYFrame.m ├── SPDYFrameDecoder.h ├── SPDYFrameDecoder.m ├── SPDYFrameEncoder.h ├── SPDYFrameEncoder.m ├── SPDYHeaderBlockCompressor.h ├── SPDYHeaderBlockCompressor.m ├── SPDYHeaderBlockDecompressor.h ├── SPDYHeaderBlockDecompressor.m ├── SPDYLogger.h ├── SPDYMetadata+Utils.h ├── SPDYMetadata+Utils.m ├── SPDYOrigin.h ├── SPDYOrigin.m ├── SPDYOriginEndpoint.h ├── SPDYOriginEndpoint.m ├── SPDYOriginEndpointManager.h ├── SPDYOriginEndpointManager.m ├── SPDYProtocol+Project.h ├── SPDYProtocol.h ├── SPDYProtocol.m ├── SPDYSession.h ├── SPDYSession.m ├── SPDYSessionManager.h ├── SPDYSessionManager.m ├── SPDYSessionPool.h ├── SPDYSessionPool.m ├── SPDYSettingsStore.h ├── SPDYSettingsStore.m ├── SPDYSocket.h ├── SPDYSocket.m ├── SPDYSocketOps.h ├── SPDYSocketOps.m ├── SPDYStopwatch.h ├── SPDYStopwatch.m ├── SPDYStream.h ├── SPDYStream.m ├── SPDYStreamManager.h ├── SPDYStreamManager.m ├── SPDYTLSTrustEvaluator.h ├── SPDYZLibCommon.h └── en.lproj │ └── InfoPlist.strings ├── SPDYUnitTests ├── SPDYFrameCodecTest.m ├── SPDYLoggingTest.m ├── SPDYMetadataTest.m ├── SPDYMockFrameDecoderDelegate.h ├── SPDYMockFrameDecoderDelegate.m ├── SPDYMockFrameEncoderDelegate.h ├── SPDYMockFrameEncoderDelegate.m ├── SPDYMockOriginEndpointManager.h ├── SPDYMockOriginEndpointManager.m ├── SPDYMockURLProtocolClient.h ├── SPDYMockURLProtocolClient.m ├── SPDYOriginEndpointTest.m ├── SPDYOriginTest.m ├── SPDYProtocolTest.m ├── SPDYSenTestLog.m ├── SPDYSessionManagerTest.m ├── SPDYSessionTest.m ├── SPDYSettingsStoreTest.m ├── SPDYSocket+SPDYSocketMock.h ├── SPDYSocket+SPDYSocketMock.m ├── SPDYSocketOpsTest.m ├── SPDYSocketTest.m ├── SPDYStopwatchTest.m ├── SPDYStreamManagerTest.m ├── SPDYStreamTest.m ├── SPDYURLRequestTest.m ├── SPDYUnitTests-Info.plist ├── SPDYUnitTests-Prefix.pch └── en.lproj │ └── InfoPlist.strings └── scripts └── build-universal-framework.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X temp files 2 | .DS_Store 3 | *.swp 4 | *.lock 5 | profile 6 | 7 | # AppCode stuff 8 | .idea 9 | 10 | # Xcode stuff 11 | build/ 12 | xcuserdata/ 13 | contents.xcworkspacedata 14 | *.xccheckout 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | before_install: 3 | - brew update 4 | - sudo easy_install cpp-coveralls 5 | script: "xcodebuild test -scheme SPDYUnitTests -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6'" 6 | after_success: 7 | - cp -r ${HOME}/Library/Developer/Xcode/DerivedData/SPDY-*/Build/Intermediates/SPDY.build/Coverage-iphonesimulator/SPDYUnitTests.build/Objects-normal/*/ gcov 8 | - rm -f gcov/*Test.* 9 | - rm -f gcov/*Mock* 10 | - rm -f gcov/SPDYSenTestLog.* 11 | - coveralls 12 | - rm -rf gcov 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Looking to contribute something? Here's how you can help. 4 | 5 | ## Bug reports 6 | 7 | A bug is a _demonstrable problem_ that is caused by the code in the 8 | repository. Good bug reports are extremely helpful - thank you! 9 | 10 | Guidelines for bug reports: 11 | 12 | 1. **Use the GitHub issue search** - check if the issue has already been 13 | reported. 14 | 15 | 2. **Check if the issue has been fixed** - try to reproduce it using the 16 | latest `master` or development branch in the repository. 17 | 18 | 3. **Isolate the problem** - ideally create a reduced test case and a live 19 | example. 20 | 21 | 4. Please try to be as detailed as possible in your report. Include specific 22 | information about the environment - operating system and version, and 23 | steps required to reproduce. 24 | 25 | 26 | ## Feature requests & contribution enquiries 27 | 28 | Feature requests are welcome. But take a moment to find out whether your idea 29 | fits with the scope and aims of the project. It's up to *you* to make a strong 30 | case for the inclusion of your feature. Please provide as much detail and 31 | context as possible. 32 | 33 | Contribution enquiries should take place before any significant pull request, 34 | otherwise you risk spending a lot of time working on something that we might 35 | have good reasons for rejecting. 36 | 37 | ## Pull requests 38 | 39 | Good pull requests - patches, improvements, new features - are a fantastic 40 | help. They should remain focused in scope and avoid containing unrelated 41 | commits. 42 | 43 | Make sure to adhere to the coding conventions used throughout the codebase 44 | (indentation, accurate comments, etc.) and any other requirements (such as test 45 | coverage). 46 | 47 | Please follow this process; it's the best way to get your work included in the 48 | project: 49 | 50 | 1. Create a new topic branch to contain your feature, change, or fix: 51 | 52 | 2. Commit your changes in logical chunks. Provide clear and explanatory commit 53 | messages. Use git's [interactive rebase](https://help.github.com/articles/interactive-rebase) 54 | feature to tidy up your commits before making them public. 55 | 56 | 3. Locally merge (or rebase) the upstream development branch into your topic branch: 57 | 58 | 4. Push your topic branch up to your fork: 59 | 60 | 5. [Open a Pull Request](http://help.github.com/send-pull-requests/) with a 61 | clear title and description. 62 | 63 | ## License 64 | 65 | By contributing your code, 66 | 67 | You agree to license your contribution under the terms of the Apache Public License 2.0 68 | https://github.com/twitter/CocoaSPDY/blob/master/LICENSE 69 | -------------------------------------------------------------------------------- /CocoaSPDY.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | pod_name = "SPDY" 3 | name = "Cocoa#{pod_name}" 4 | url = "https://github.com/twitter/#{name}" 5 | git_url = "#{url}.git" 6 | version = "1.2" 7 | source_files = "#{pod_name}/**/*.{h,m}" 8 | 9 | s.name = name 10 | s.version = version 11 | s.summary = "SPDY for iOS and OS X" 12 | s.description = <<-DESC 13 | The SPDY framework is designed to work seamlessly with your existing apps and projects. 14 | If you are using the NSURL stack to issue requests (or any library that provides an abstraction over it), 15 | you can simply add the pod your project. 16 | 17 | DESC 18 | 19 | s.homepage = url 20 | s.license = 'Apache' 21 | s.author = { "Twitter, Inc." => "opensource@twitter.com" } 22 | 23 | s.source = { :git => git_url, :tag => "v#{version}"} 24 | 25 | 26 | s.ios.deployment_target = '5.0' 27 | s.osx.deployment_target = '10.7' 28 | 29 | s.source_files = source_files 30 | s.requires_arc = true 31 | s.frameworks = 'CFNetwork' 32 | s.libraries = 'z' 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CocoaSPDY 2 | #### A SPDY/3.1 framework for iOS and Mac OS X 3 | 4 | Branch | Build | Code Coverage 5 | ------ | ----- | ------------- 6 | master | [![Build Status](https://travis-ci.org/twitter/CocoaSPDY.png?branch=master)](https://travis-ci.org/twitter/CocoaSPDY) | [![Coverage Status](https://coveralls.io/repos/twitter/CocoaSPDY/badge.png?branch=master)](https://coveralls.io/r/twitter/CocoaSPDY?branch=master) 7 | develop | [![Build Status](https://travis-ci.org/twitter/CocoaSPDY.png?branch=develop)](https://travis-ci.org/twitter/CocoaSPDY) | [![Coverage Status](https://coveralls.io/repos/twitter/CocoaSPDY/badge.png?branch=develop)](https://coveralls.io/r/twitter/CocoaSPDY?branch=develop) 8 | 9 | ### [Download v1.2](https://github.com/twitter/CocoaSPDY/releases/download/v1.2/SPDY.framework.tar.gz) 10 | 11 | ## The SPDY protocol 12 | The short version is that [SPDY](http://en.wikipedia.org/wiki/SPDY) can make your HTTP requests faster. Sometimes a lot faster. For more details, see the following: 13 | 14 | http://www.chromium.org/spdy/spdy-whitepaper 15 | http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1 16 | 17 | SPDY was originally designed at Google as an experimental successor to HTTP. It's a binary protocol (rather than human-readable, like HTTP), but is fully compatible with HTTP. In fact, current draft work on [HTTP/2.0](https://github.com/http2/http2-spec) is largely based on the SPDY protocol and its real-world success. 18 | 19 | In order to make HTTP requests go faster SPDY makes several improvements: 20 | 21 | The first, and arguably most important, is request multiplexing. Rather than sending one request at a time over one TCP connection, SPDY can issue many requests simultaneously over a single TCP session and handle responses in any order, as soon as they're available. 22 | 23 | Second, SPDY compresses both request and response headers. Headers are often nearly identical to each other across requests, generally contain lots of duplicated text, and can be quite large. This makes them an ideal candidate for compression.1 24 | 25 | Finally, SPDY introduces server push.2 This can allow a server to push content that the client doesn't know it needs yet. Such content can range from additional assets like styles and images, to notifications about realtime events. 26 | 27 | 1. Please see the note below about the CRIME attack. 28 | 2. Not currently supported in this framework, but coming soon. 29 | 30 | ## Getting Started 31 | The SPDY framework is designed to work seamlessly with your existing apps and projects. If you are using the NSURL stack to issue requests (or any library that provides an abstraction over it, like AFNetworking), you can simply add the SPDY framework bundle to your project, link it to your targets, and enable the protocol. 32 | 33 | The framework contains a multi-architecture/multi-platform ("fat") binary that supports versions of iOS 6 and above, and OS X Lion and above, as well as all hardware capable of running those operating systems. When you distribute your application, the size of the included binary will be dramatically reduced, provided you have code stripping enabled. 34 | 35 | ## Enabling SPDY 36 | 37 | To use the SPDY framework you'll need to link CFNetwork.framework and libz.dylib in your project. This can be done in the "Link Binary with Libraries" section under "Build Phases" for your compilation target. 38 | 39 | The way you enable SPDY in your application will be slightly different depending on whether you are using NSURLConnection or NSURLSession to manage your HTTP calls. In order to cause requests issued via the NSURLConnection stack to be carried over SPDY, you'll make a method call to specify one or more origins (protocol-host-port tuple) to be handled by SPDY: 40 | 41 | #import 42 | ... 43 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com:443"]; 44 | 45 | Note that origins containing "http" vs. "https" are distinct from each other, will be handled by separate SPDY sessions, and must be registered independently. Only sessions for origins containing "https" will be encrypted with TLS. 46 | 47 | For NSURLSession, you can configure sessions to use SPDY via NSURLSessionConfiguration: 48 | 49 | #import 50 | ... 51 | NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 52 | configuration.protocolClasses = @[[SPDYURLSessionProtocol class]]; 53 | 54 | You can freely use either or both methods, and existing SPDY sessions will be shared across both networking stacks. If you do use the former approach, note that registered origins will also be handled by SPDY with the default NSURLSession. 55 | 56 | Either of the above one-liners is all you need to do to shift HTTP requests transparently over to SPDY. Of course you still need a server that speaks SPDY! Some possibilities are: 57 | 58 | * [netty](http://netty.io/4.0/api/io/netty/handler/codec/spdy/package-summary.html) 59 | * [jetty](http://www.eclipse.org/jetty/documentation/current/spdy.html) 60 | * apache (with [mod_spdy](https://code.google.com/p/mod-spdy/)) 61 | * [nginx](http://nginx.org/) 62 | * [Tengine](https://github.com/alibaba/tengine) 63 | 64 | ## A note on NPN 65 | Most existing SPDY implementations use a TLS extension called Next Protocol Implementation (NPN) to negotiate SPDY instead of HTTP. Unfortunately, this extension isn't supported by Secure Transport (Apple's TLS implementation), and so in order to use SPDY in your application, you'll either need to issue requests to a server that's configured to speak SPDY on a dedicated port, or use a server that's smart enough to examine the incoming request and determine whether the connection will be SPDY or HTTP based on what it looks like. At Twitter we do the latter, but the former solution may be simpler for most applications. 66 | 67 | In order to aid with protocol inference, this SPDY implementation includes a non-standard settings id at index 0: `SETTINGS_MINOR_VERSION`. This is necessary to differentiate between SPDY/3 and SPDY/3.1 connections that were not negotiated with NPN, since only the major version is included in the frame header. Because not all servers may support this particular setting, sending it can be disabled at runtime through protocol configuration. 68 | 69 | ## Implementation Notes 70 | ### CRIME attack 71 | The [CRIME attack](http://en.wikipedia.org/wiki/CRIME) is a plaintext injection technique that exploits the fact that information can be inferred from compressed content length to potentially reveal the contents of an encrypted stream. This is a serious issue for browsers, which are subject to hijacks that may allow an attacker to issue an arbitrary number of requests with known plaintext header content and observe the resulting effect on compression. 72 | 73 | In the context of an application that doesn't issue arbitrary requests, this is less likely to be an issue. However, before you ship a project with header compression enabled, you should understand the details of this attack and whether your application could be vulnerable. 74 | 75 | ## Building the Framework Yourself 76 | If you wish to compile the framework yourself, the process is fairly straightforward, and the build process should just work out of the box in Xcode. However, there are still a couple of things to note. 77 | 78 | Prior to Xcode 5, if you wanted to compile the framework to a dual-platform binary (as in the distribution version), you were required to set 'iOS Device' as your platform target for the framework. This was due to a quirk in the Xcode build process that would otherwise exclude some (but not all) versions of the ARM architecture from the final binary. With the release of Xcode 5, any platform target should result in the same final universal binary (the setting is essentially ignored). 79 | 80 | To create this binary, the build process actually depends on several static library targets and uses lipo to combine them. 81 | 82 | ## Getting involved and Future work 83 | We are always looking for people to get involved with the project. 84 | 85 | In the near future, we will be working on: 86 | 87 | * [Server Push](https://github.com/twitter/CocoaSPDY/issues/1) 88 | 89 | ## Adopters 90 | 91 | * [Amahi](https://github.com/twitter/CocoaSPDY/issues/9#issuecomment-31307581) 92 | * [Twitter](https://twitter.com/TwitterOSS/status/413746448367230976) 93 | 94 | Please feel free to send us a pull request to add yourself to this list (bonus points to link to a tweet). 95 | 96 | ## Problems? 97 | If you find any issues please [report them](https://github.com/twitter/CocoaSPDY/issues) or better, 98 | send a [pull request](https://github.com/twitter/CocoaSPDY/pulls). 99 | 100 | ## Authors 101 | * Michael Schore 102 | * Jeffrey Pinner 103 | * Kevin Goodier 104 | 105 | ## License 106 | Copyright 2014 Twitter, Inc. and other contributors. 107 | 108 | Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 109 | -------------------------------------------------------------------------------- /SPDY.iphoneos/SPDY.iphoneos-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SPDY.iphoneos' target in the 'SPDY.iphoneos' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /SPDY.macosx/SPDY.macosx-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SPDY.macosx' target in the 'SPDY.macosx' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /SPDY.xcodeproj/xcshareddata/xcschemes/SPDY.iphoneos.arm64.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SPDY.xcodeproj/xcshareddata/xcschemes/SPDY.iphoneos.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SPDY.xcodeproj/xcshareddata/xcschemes/SPDY.iphonesimulator.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SPDY.xcodeproj/xcshareddata/xcschemes/SPDY.macosx.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SPDY.xcodeproj/xcshareddata/xcschemes/SPDY.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SPDY.xcodeproj/xcshareddata/xcschemes/SPDYUnitTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 63 | 64 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SPDY/NSURLRequest+SPDYURLRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLRequest+SPDYURLRequest.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | @protocol SPDYExtendedDelegate; 15 | 16 | @interface NSURLRequest (SPDYURLRequest) 17 | 18 | /** 19 | If present, the stream specified will be used as the HTTP body for the 20 | request. This circumvents a bug in CFNetwork with HTTPBodyStream, but will 21 | not allow a stream to be replayed in the event of an authentication 22 | challenge or redirect. If either of those responses is a possibility, use 23 | HTTPBody or SPDYBodyFile instead. 24 | */ 25 | @property (nonatomic, readonly) NSInputStream *SPDYBodyStream; 26 | 27 | /** 28 | If present, the file path specified will be used as the HTTP body for the 29 | request. This is the preferred secondary mechanism for specifying the body 30 | of a request when HTTPBody is not sufficient. 31 | */ 32 | @property (nonatomic, readonly) NSString *SPDYBodyFile; 33 | 34 | /** 35 | Priority per the SPDY draft spec. Defaults to 0. 36 | */ 37 | @property (nonatomic, readonly) NSUInteger SPDYPriority; 38 | 39 | /** 40 | If set to > 0, indicates the maximum time interval the request dispatch may 41 | be deferred to optimize battery/power usage for less time-sensitive 42 | requests. 43 | 44 | Note the request's idle timeoutInterval still applies and must be set large 45 | enough to allow for both a discretionary delay and normal request transit. 46 | */ 47 | @property (nonatomic, readonly) NSTimeInterval SPDYDeferrableInterval; 48 | 49 | /** 50 | If set, SPDYProtocol will decline to handle the request and instead pass 51 | it along to the next registered protocol (e.g. NSHTTPURLProtocol). 52 | */ 53 | @property (nonatomic, readonly) BOOL SPDYBypass; 54 | 55 | /** 56 | Contextual NSURLSession that was associated with this request. The application 57 | should set this if using NSURLSession to load the request in order to provide 58 | proper per-request configuration information, and if support for the 59 | extended SPDYURLSessionDelegate is desired. 60 | */ 61 | @property (nonatomic, readonly) NSURLSession *SPDYURLSession; 62 | 63 | /** 64 | Request header fields canonicalized to SPDY format. 65 | */ 66 | - (NSDictionary *)allSPDYHeaderFields; 67 | 68 | @end 69 | 70 | @interface NSMutableURLRequest (SPDYURLRequest) 71 | @property (nonatomic) NSInputStream *SPDYBodyStream; 72 | @property (nonatomic) NSString *SPDYBodyFile; 73 | @property (nonatomic) NSTimeInterval SPDYDeferrableInterval; 74 | @property (nonatomic) NSUInteger SPDYPriority; 75 | @property (nonatomic) BOOL SPDYBypass; 76 | @property (nonatomic) NSURLSession *SPDYURLSession; 77 | @end 78 | -------------------------------------------------------------------------------- /SPDY/NSURLRequest+SPDYURLRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLRequest+SPDYURLRequest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import 17 | #import "NSURLRequest+SPDYURLRequest.h" 18 | #import "SPDYProtocol.h" 19 | 20 | @implementation NSURLRequest (SPDYURLRequest) 21 | 22 | - (NSUInteger)SPDYPriority 23 | { 24 | return [[SPDYProtocol propertyForKey:@"SPDYPriority" inRequest:self] unsignedIntegerValue]; 25 | } 26 | 27 | - (NSTimeInterval)SPDYDeferrableInterval 28 | { 29 | return [[SPDYProtocol propertyForKey:@"SPDYDeferrableInterval" inRequest:self] doubleValue]; 30 | } 31 | 32 | - (BOOL)SPDYBypass 33 | { 34 | return [[SPDYProtocol propertyForKey:@"SPDYBypass" inRequest:self] boolValue]; 35 | } 36 | 37 | - (NSInputStream *)SPDYBodyStream 38 | { 39 | return [SPDYProtocol propertyForKey:@"SPDYBodyStream" inRequest:self]; 40 | } 41 | 42 | - (NSString *)SPDYBodyFile 43 | { 44 | return [SPDYProtocol propertyForKey:@"SPDYBodyFile" inRequest:self]; 45 | } 46 | 47 | - (NSURLSession *)SPDYURLSession 48 | { 49 | return [self spdy_indirectObjectForKey:@"SPDYURLSession"]; 50 | } 51 | 52 | - (NSString *)SPDYURLSessionRequestIdentifier 53 | { 54 | return [SPDYProtocol propertyForKey:@"SPDYURLSession" inRequest:self]; 55 | } 56 | 57 | - (NSDictionary *)allSPDYHeaderFields 58 | { 59 | NSDictionary *httpHeaders = self.allHTTPHeaderFields; 60 | NSURL *url = self.URL; 61 | 62 | static NSSet *invalidKeys; 63 | static NSSet *reservedKeys; 64 | static dispatch_once_t initialized; 65 | dispatch_once(&initialized, ^{ 66 | invalidKeys = [[NSSet alloc] initWithObjects: 67 | @"connection", @"keep-alive", @"proxy-connection", @"transfer-encoding", nil 68 | ]; 69 | 70 | reservedKeys = [[NSSet alloc] initWithObjects: 71 | @"method", @"path", @"version", @"host", @"scheme", nil 72 | ]; 73 | }); 74 | 75 | NSString *escapedPath = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes( 76 | kCFAllocatorDefault, 77 | (__bridge CFStringRef)url.path, 78 | NULL, 79 | CFSTR("?"), 80 | kCFStringEncodingUTF8)); 81 | 82 | NSMutableString *path = [[NSMutableString alloc] initWithString:escapedPath]; 83 | NSString *query = url.query; 84 | if (query) { 85 | [path appendFormat:@"?%@", query]; 86 | } 87 | 88 | NSString *fragment = url.fragment; 89 | if (fragment) { 90 | [path appendFormat:@"#%@", fragment]; 91 | } 92 | 93 | // Allow manually-set headers to override request properties 94 | NSMutableDictionary *spdyHeaders = [[NSMutableDictionary alloc] initWithDictionary:@{ 95 | @":method" : self.HTTPMethod, 96 | @":path" : path, 97 | @":version" : @"HTTP/1.1", 98 | @":host" : url.host, 99 | @":scheme" : url.scheme 100 | }]; 101 | 102 | // Proxy all application-provided HTTP headers, if allowed, over to SPDY headers. 103 | for (NSString *key in httpHeaders) { 104 | NSString *lowercaseKey = [key lowercaseString]; 105 | if (![invalidKeys containsObject:lowercaseKey]) { 106 | if ([reservedKeys containsObject:lowercaseKey]) { 107 | spdyHeaders[[@":" stringByAppendingString:lowercaseKey]] = httpHeaders[key]; 108 | } else { 109 | spdyHeaders[lowercaseKey] = httpHeaders[key]; 110 | } 111 | } 112 | } 113 | 114 | // The current implementation here will always override cookies retrieved from cookie storage 115 | // by those set manually in headers. 116 | // TODO: confirm behavior for Cocoa's API and send cookies from both sources, as appropriate 117 | BOOL cookiesOn = NO; 118 | NSHTTPCookieStorage *cookieStore = nil; 119 | 120 | NSURLSessionConfiguration *config = self.SPDYURLSession.configuration; 121 | if (config) { 122 | if (config.HTTPShouldSetCookies) { 123 | cookieStore = config.HTTPCookieStorage; 124 | cookiesOn = (cookieStore != nil); 125 | } 126 | } else { 127 | cookiesOn = self.HTTPShouldHandleCookies; 128 | cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 129 | } 130 | 131 | if (cookiesOn) { 132 | NSString *requestCookies = spdyHeaders[@"cookie"]; 133 | if (!requestCookies || requestCookies.length == 0) { 134 | NSArray *cookies = [cookieStore cookiesForURL:url]; 135 | if (cookies.count > 0) { 136 | NSDictionary *cookieHeaders = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; 137 | NSString *cookie = cookieHeaders[@"Cookie"]; 138 | if (cookie) { 139 | spdyHeaders[@"cookie"] = cookie; 140 | } 141 | } 142 | } 143 | } 144 | 145 | return spdyHeaders; 146 | } 147 | 148 | - (id)spdy_indirectObjectForKey:(NSString *)key 149 | { 150 | NSString *contextString = [SPDYProtocol propertyForKey:key inRequest:self]; 151 | return (contextString) ? objc_getAssociatedObject(contextString, @selector(spdy_indirectObjectForKey:)) : nil; 152 | } 153 | 154 | @end 155 | 156 | @implementation NSMutableURLRequest (SPDYURLRequest) 157 | 158 | - (void)setSPDYPriority:(NSUInteger)priority 159 | { 160 | [SPDYProtocol setProperty:@(priority) forKey:@"SPDYPriority" inRequest:self]; 161 | } 162 | 163 | - (void)setSPDYDeferrableInterval:(NSTimeInterval)deferrableInterval 164 | { 165 | [SPDYProtocol setProperty:@(deferrableInterval) forKey:@"SPDYDeferrableInterval" inRequest:self]; 166 | } 167 | 168 | - (void)setSPDYBypass:(BOOL)bypass 169 | { 170 | [SPDYProtocol setProperty:@(bypass) forKey:@"SPDYBypass" inRequest:self]; 171 | } 172 | 173 | - (void)setSPDYBodyStream:(NSInputStream *)SPDYBodyStream 174 | { 175 | if (SPDYBodyStream == nil) { 176 | [SPDYProtocol removePropertyForKey:@"SPDYBodyStream" inRequest:self]; 177 | } else { 178 | [SPDYProtocol setProperty:SPDYBodyStream forKey:@"SPDYBodyStream" inRequest:self]; 179 | } 180 | } 181 | 182 | - (void)setSPDYBodyFile:(NSString *)SPDYBodyFile 183 | { 184 | if (SPDYBodyFile == nil) { 185 | [SPDYProtocol removePropertyForKey:@"SPDYBodyFile" inRequest:self]; 186 | } else { 187 | [SPDYProtocol setProperty:SPDYBodyFile forKey:@"SPDYBodyFile" inRequest:self]; 188 | } 189 | } 190 | 191 | - (void)setSPDYURLSession:(NSURLSession *)SPDYURLSession 192 | { 193 | [self spdy_setIndirectObject:SPDYURLSession forKey:@"SPDYURLSession"]; 194 | } 195 | 196 | - (void)spdy_setIndirectObject:(id)object forKey:(NSString *)key 197 | { 198 | if (object == nil) { 199 | [SPDYProtocol removePropertyForKey:key inRequest:self]; 200 | } else { 201 | NSString *contextString = [[NSUUID UUID] UUIDString]; 202 | objc_setAssociatedObject(contextString, @selector(spdy_indirectObjectForKey:), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 203 | [SPDYProtocol setProperty:contextString forKey:key inRequest:self]; 204 | } 205 | } 206 | 207 | @end 208 | -------------------------------------------------------------------------------- /SPDY/SPDY-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.twitter.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2014 Twitter. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /SPDY/SPDY-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SPDY' target in the 'SPDY' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | 9 | -------------------------------------------------------------------------------- /SPDY/SPDYCanonicalRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYCanonicalRequest.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Derived from code in Apple, Inc.'s CustomHTTPProtocol sample 10 | // project, found as of this notice at 11 | // https://developer.apple.com/LIBRARY/IOS/samplecode/CustomHTTPProtocol 12 | // 13 | 14 | #import 15 | 16 | /* 17 | The Foundation URL loading system needs to be able to canonicalize URL requests 18 | for various reasons (for example, to look for cache hits). The default HTTP/HTTPS 19 | protocol has a complex chunk of code to perform this function. Unfortunately 20 | there's no way for third party code to access this. Instead, we have to reimplement 21 | it all ourselves. This is split off into a separate file to emphasise that this 22 | is standard boilerplate that you probably don't need to look at. 23 | 24 | IMPORTANT: While you can take most of this code as read, you might want to tweak 25 | the handling of the "Accept-Language" in the CanonicaliseHeaders routine. 26 | */ 27 | 28 | extern NSMutableURLRequest *SPDYCanonicalRequestForRequest(NSURLRequest *request); 29 | -------------------------------------------------------------------------------- /SPDY/SPDYCommonLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYCommonLogger.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYLogger.h" 14 | 15 | extern volatile SPDYLogLevel __sharedLoggerLevel; 16 | 17 | #define LOG_LEVEL_ENABLED(l) ((l) <= __sharedLoggerLevel) 18 | 19 | #define SPDY_DEBUG(message, ...) do { \ 20 | if (LOG_LEVEL_ENABLED(SPDYLogLevelDebug)) { \ 21 | [SPDYCommonLogger log:message atLevel:SPDYLogLevelDebug, ##__VA_ARGS__]; \ 22 | } \ 23 | } while (0) 24 | 25 | #define SPDY_INFO(message, ...) do { \ 26 | if (LOG_LEVEL_ENABLED(SPDYLogLevelInfo)) { \ 27 | [SPDYCommonLogger log:message atLevel:SPDYLogLevelInfo, ##__VA_ARGS__]; \ 28 | } \ 29 | } while (0) 30 | 31 | #define SPDY_WARNING(message, ...) do { \ 32 | if (LOG_LEVEL_ENABLED(SPDYLogLevelWarning)) { \ 33 | [SPDYCommonLogger log:message atLevel:SPDYLogLevelWarning, ##__VA_ARGS__]; \ 34 | } \ 35 | } while (0) 36 | 37 | #define SPDY_ERROR(message, ...) do { \ 38 | if (LOG_LEVEL_ENABLED(SPDYLogLevelError)) { \ 39 | [SPDYCommonLogger log:message atLevel:SPDYLogLevelError, ##__VA_ARGS__]; \ 40 | } \ 41 | } while (0) 42 | 43 | @interface SPDYCommonLogger : NSObject 44 | + (void)setLogger:(id)logger; 45 | + (id)currentLogger; 46 | + (void)setLoggerLevel:(SPDYLogLevel)level; 47 | + (SPDYLogLevel)currentLoggerLevel; 48 | + (void)log:(NSString *)format atLevel:(SPDYLogLevel)level, ... NS_FORMAT_FUNCTION(1,3); 49 | + (void)flush; 50 | @end 51 | -------------------------------------------------------------------------------- /SPDY/SPDYCommonLogger.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYCommonLogger.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYCommonLogger.h" 17 | 18 | static const NSString *logLevels[4] = { @"ERROR", @"WARNING", @"INFO", @"DEBUG" }; 19 | 20 | @implementation SPDYCommonLogger 21 | 22 | static dispatch_once_t __initialized; 23 | dispatch_queue_t __sharedLoggerQueue; 24 | static id __sharedLogger; 25 | volatile SPDYLogLevel __sharedLoggerLevel; 26 | 27 | + (void)initialize 28 | { 29 | dispatch_once(&__initialized, ^{ 30 | __sharedLoggerQueue = dispatch_queue_create("com.twitter.SPDYProtocolLoggerQueue", DISPATCH_QUEUE_SERIAL); 31 | __sharedLogger = nil; 32 | #ifdef DEBUG 33 | __sharedLoggerLevel = SPDYLogLevelDebug; 34 | #else 35 | __sharedLoggerLevel = SPDYLogLevelError; 36 | #endif 37 | }); 38 | } 39 | 40 | + (void)setLogger:(id)logger 41 | { 42 | dispatch_async(__sharedLoggerQueue, ^{ 43 | __sharedLogger = logger; 44 | }); 45 | } 46 | 47 | + (id)currentLogger 48 | { 49 | id __block sharedLogger; 50 | dispatch_sync(__sharedLoggerQueue, ^{ 51 | sharedLogger = __sharedLogger; 52 | }); 53 | return sharedLogger; 54 | } 55 | 56 | + (void)setLoggerLevel:(SPDYLogLevel)level 57 | { 58 | __sharedLoggerLevel = level; 59 | } 60 | 61 | + (SPDYLogLevel)currentLoggerLevel 62 | { 63 | return __sharedLoggerLevel; 64 | } 65 | 66 | + (void)log:(NSString *)format atLevel:(SPDYLogLevel)level, ... NS_FORMAT_FUNCTION(1,3) 67 | { 68 | va_list args; 69 | va_start(args, level); 70 | NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; 71 | va_end(args); 72 | 73 | #ifdef DEBUG 74 | if (__sharedLogger == nil) { 75 | NSLog(@"SPDY [%@] %@", logLevels[level], message); 76 | } 77 | #endif 78 | 79 | dispatch_async(__sharedLoggerQueue, ^{ 80 | if (__sharedLogger) { 81 | [__sharedLogger log:message atLevel:level]; 82 | } 83 | }); 84 | } 85 | 86 | + (void)flush 87 | { 88 | dispatch_sync(__sharedLoggerQueue, ^{ 89 | }); 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /SPDY/SPDYDefinitions.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYDefinitions.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import "SPDYError.h" 13 | 14 | typedef enum : uint32_t { 15 | SPDY_SYN_STREAM_FRAME = 1, 16 | SPDY_SYN_REPLY_FRAME, 17 | SPDY_RST_STREAM_FRAME, 18 | SPDY_SETTINGS_FRAME, 19 | SPDY_NOOP_FRAME, 20 | SPDY_PING_FRAME, 21 | SPDY_GOAWAY_FRAME, 22 | SPDY_HEADERS_FRAME, 23 | SPDY_WINDOW_UPDATE_FRAME, 24 | SPDY_CREDENTIAL_FRAME 25 | } SPDYControlFrameType; 26 | 27 | typedef enum : uint32_t { 28 | SPDY_STREAM_PROTOCOL_ERROR = 1, 29 | SPDY_STREAM_INVALID_STREAM, 30 | SPDY_STREAM_REFUSED_STREAM, 31 | SPDY_STREAM_UNSUPPORTED_VERSION, 32 | SPDY_STREAM_CANCEL, 33 | SPDY_STREAM_INTERNAL_ERROR, 34 | SPDY_STREAM_FLOW_CONTROL_ERROR, 35 | SPDY_STREAM_STREAM_IN_USE, 36 | SPDY_STREAM_STREAM_ALREADY_CLOSED, 37 | SPDY_STREAM_INVALID_CREDENTIALS, 38 | SPDY_STREAM_FRAME_TOO_LARGE 39 | } SPDYStreamStatus; 40 | 41 | typedef enum : uint32_t { 42 | SPDY_SESSION_OK = 0, 43 | SPDY_SESSION_PROTOCOL_ERROR, 44 | SPDY_SESSION_INTERNAL_ERROR 45 | } SPDYSessionStatus; 46 | 47 | typedef enum : uint32_t { 48 | _SPDY_SETTINGS_RANGE_START = 0, 49 | SPDY_SETTINGS_MINOR_VERSION = _SPDY_SETTINGS_RANGE_START, 50 | SPDY_SETTINGS_UPLOAD_BANDWIDTH, 51 | SPDY_SETTINGS_DOWNLOAD_BANDWIDTH, 52 | SPDY_SETTINGS_ROUND_TRIP_TIME, 53 | SPDY_SETTINGS_MAX_CONCURRENT_STREAMS, 54 | SPDY_SETTINGS_CURRENT_CWND, 55 | SPDY_SETTINGS_DOWNLOAD_RETRANS_RATE, 56 | SPDY_SETTINGS_INITIAL_WINDOW_SIZE, 57 | SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE, 58 | _SPDY_SETTINGS_RANGE_END, 59 | } SPDYSettingsId; 60 | 61 | #define SPDY_SETTINGS_LENGTH _SPDY_SETTINGS_RANGE_END 62 | #define SPDY_SETTINGS_ITERATOR(i) for (SPDYSettingsId i = _SPDY_SETTINGS_RANGE_START; i < _SPDY_SETTINGS_RANGE_END; i++) 63 | 64 | typedef struct { 65 | bool set; 66 | uint8_t flags; 67 | int32_t value; 68 | } SPDYSettings; 69 | 70 | typedef uint32_t SPDYPingId; 71 | typedef uint32_t SPDYStreamId; 72 | typedef double SPDYTimeInterval; 73 | 74 | static const SPDYStreamId kSPDYSessionStreamId = 0; 75 | 76 | static const uint8_t SPDY_FLAG_FIN = 0x01; 77 | static const uint8_t SPDY_FLAG_UNIDIRECTIONAL = 0x02; 78 | 79 | static const uint8_t SPDY_DATA_FLAG_FIN = 0x01; 80 | 81 | static const uint8_t SPDY_SETTINGS_FLAG_CLEAR_SETTINGS = 0x01; 82 | static const uint8_t SPDY_SETTINGS_FLAG_PERSIST_VALUE = 0x01; 83 | static const uint8_t SPDY_SETTINGS_FLAG_PERSISTED = 0x02; 84 | 85 | #define SPDY_STREAM_ERROR(CODE, MESSAGE) \ 86 | [[NSError alloc] initWithDomain:SPDYStreamErrorDomain code:CODE userInfo:@{ NSLocalizedDescriptionKey: MESSAGE}] 87 | 88 | #define SPDY_SESSION_ERROR(CODE, MESSAGE) \ 89 | [[NSError alloc] initWithDomain:SPDYSessionErrorDomain code:CODE userInfo:@{ NSLocalizedDescriptionKey: MESSAGE}] 90 | 91 | #define SPDY_SOCKET_ERROR(CODE, MESSAGE) \ 92 | [[NSError alloc] initWithDomain:SPDYSocketErrorDomain code:CODE userInfo:@{ NSLocalizedDescriptionKey: MESSAGE}] 93 | 94 | #define SPDY_CODEC_ERROR(CODE, MESSAGE) \ 95 | [[NSError alloc] initWithDomain:SPDYCodecErrorDomain code:CODE userInfo:@{ NSLocalizedDescriptionKey: MESSAGE}] 96 | -------------------------------------------------------------------------------- /SPDY/SPDYError.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYError.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | FOUNDATION_EXTERN NSString *const SPDYStreamErrorDomain; 15 | FOUNDATION_EXTERN NSString *const SPDYSessionErrorDomain; 16 | FOUNDATION_EXTERN NSString *const SPDYCodecErrorDomain; 17 | FOUNDATION_EXTERN NSString *const SPDYSocketErrorDomain; 18 | 19 | // These errors map one-to-one with the status code in a RST_STREAM message. 20 | typedef enum { 21 | SPDYStreamProtocolError = 1, 22 | SPDYStreamInvalidStream, 23 | SPDYStreamRefusedStream, 24 | SPDYStreamUnsupportedVersion, 25 | SPDYStreamCancel, 26 | SPDYStreamInternalError, 27 | SPDYStreamFlowControlError, 28 | SPDYStreamStreamInUse, 29 | SPDYStreamStreamAlreadyClosed, 30 | SPDYStreamInvalidCredentials, 31 | SPDYStreamFrameTooLarge 32 | } SPDYStreamError; 33 | 34 | // These errors map one-to-one with the status code in a GOAWAY message. 35 | typedef enum { 36 | SPDYSessionProtocolError = 1, 37 | SPDYSessionInternalError 38 | } SPDYSessionError; 39 | 40 | typedef enum { 41 | SPDYHeaderBlockEncodingError = 1, 42 | SPDYHeaderBlockDecodingError 43 | } SPDYCodecError; 44 | 45 | typedef enum { 46 | SPDYSocketCFSocketError = kCFSocketError, // From CFSocketError enum. 47 | SPDYSocketConnectCanceled = 1, // socketWillConnect: returned NO. 48 | SPDYSocketConnectTimeout, 49 | SPDYSocketReadTimeout, 50 | SPDYSocketWriteTimeout, 51 | SPDYSocketTLSVerificationFailed, 52 | SPDYSocketTransportError, 53 | SPDYSocketProxyError 54 | } SPDYSocketError; 55 | 56 | -------------------------------------------------------------------------------- /SPDY/SPDYFrame.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYFrame.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYDefinitions.h" 14 | 15 | @interface SPDYFrame : NSObject 16 | @property (nonatomic, assign) NSUInteger encodedLength; 17 | - (id)initWithLength:(NSUInteger)encodedLength; 18 | @end 19 | 20 | @interface SPDYHeaderBlockFrame : SPDYFrame 21 | @property (nonatomic, strong) NSDictionary *headers; 22 | @end 23 | 24 | @interface SPDYDataFrame : SPDYFrame 25 | @property (nonatomic, strong) NSData *data; 26 | @property (nonatomic) SPDYStreamId streamId; 27 | @property (nonatomic) bool last; 28 | @end 29 | 30 | @interface SPDYSynStreamFrame : SPDYHeaderBlockFrame 31 | @property (nonatomic) SPDYStreamId streamId; 32 | @property (nonatomic) SPDYStreamId associatedToStreamId; 33 | @property (nonatomic) uint8_t priority; 34 | @property (nonatomic) uint8_t slot; 35 | @property (nonatomic) bool last; 36 | @property (nonatomic) bool unidirectional; 37 | @end 38 | 39 | @interface SPDYSynReplyFrame : SPDYHeaderBlockFrame 40 | @property (nonatomic) SPDYStreamId streamId; 41 | @property (nonatomic) bool last; 42 | @end 43 | 44 | @interface SPDYRstStreamFrame : SPDYFrame 45 | @property (nonatomic) SPDYStreamId streamId; 46 | @property (nonatomic) SPDYStreamStatus statusCode; 47 | @end 48 | 49 | @interface SPDYSettingsFrame : SPDYFrame 50 | @property (nonatomic, readonly) SPDYSettings *settings; 51 | @property (nonatomic) bool clearSettings; 52 | @end 53 | 54 | @interface SPDYPingFrame : SPDYFrame 55 | @property (nonatomic) SPDYPingId pingId; 56 | @end 57 | 58 | @interface SPDYGoAwayFrame : SPDYFrame 59 | @property (nonatomic) SPDYStreamId lastGoodStreamId; 60 | @property (nonatomic) SPDYSessionStatus statusCode; 61 | @end 62 | 63 | @interface SPDYHeadersFrame : SPDYHeaderBlockFrame 64 | @property (nonatomic) SPDYStreamId streamId; 65 | @property (nonatomic) bool last; 66 | @end 67 | 68 | @interface SPDYWindowUpdateFrame : SPDYFrame 69 | @property (nonatomic) SPDYStreamId streamId; 70 | @property (nonatomic) uint32_t deltaWindowSize; 71 | @end 72 | -------------------------------------------------------------------------------- /SPDY/SPDYFrame.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYFrame.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYFrame.h" 17 | 18 | @implementation SPDYFrame 19 | 20 | - (id)initWithLength:(NSUInteger)encodedLength 21 | { 22 | self = [self init]; 23 | if (self) { 24 | _encodedLength = encodedLength; 25 | } 26 | return self; 27 | } 28 | 29 | @end 30 | 31 | @implementation SPDYHeaderBlockFrame 32 | @end 33 | 34 | @implementation SPDYDataFrame 35 | @end 36 | 37 | @implementation SPDYSynStreamFrame 38 | @end 39 | 40 | @implementation SPDYSynReplyFrame 41 | @end 42 | 43 | @implementation SPDYRstStreamFrame 44 | @end 45 | 46 | @implementation SPDYSettingsFrame 47 | { 48 | SPDYSettings _settings[SPDY_SETTINGS_LENGTH]; 49 | } 50 | 51 | - (id)init 52 | { 53 | self = [super init]; 54 | if (self) { 55 | SPDY_SETTINGS_ITERATOR(i) { 56 | _settings[i].set = NO; 57 | } 58 | } 59 | return self; 60 | } 61 | 62 | - (SPDYSettings *)settings 63 | { 64 | return _settings; 65 | } 66 | 67 | @end 68 | 69 | @implementation SPDYPingFrame 70 | @end 71 | 72 | @implementation SPDYGoAwayFrame 73 | @end 74 | 75 | @implementation SPDYHeadersFrame 76 | @end 77 | 78 | @implementation SPDYWindowUpdateFrame 79 | @end 80 | -------------------------------------------------------------------------------- /SPDY/SPDYFrameDecoder.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYFrameDecoder.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYFrame.h" 14 | 15 | @class SPDYFrameDecoder; 16 | 17 | @protocol SPDYFrameDecoderDelegate 18 | 19 | - (void)didReadDataFrame:(SPDYDataFrame *)dataFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 20 | - (void)didReadSynStreamFrame:(SPDYSynStreamFrame *)synStreamFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 21 | - (void)didReadSynReplyFrame:(SPDYSynReplyFrame *)synReplyFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 22 | - (void)didReadRstStreamFrame:(SPDYRstStreamFrame *)rstStreamFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 23 | - (void)didReadSettingsFrame:(SPDYSettingsFrame *)settingsFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 24 | - (void)didReadPingFrame:(SPDYPingFrame *)pingFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 25 | - (void)didReadGoAwayFrame:(SPDYGoAwayFrame *)goAwayFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 26 | - (void)didReadHeadersFrame:(SPDYHeadersFrame *)headersFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 27 | - (void)didReadWindowUpdateFrame:(SPDYWindowUpdateFrame *)windowUpdateFrame frameDecoder:(SPDYFrameDecoder *)frameDecoder; 28 | 29 | @end 30 | 31 | @interface SPDYFrameDecoder : NSObject 32 | @property (nonatomic, weak) id delegate; 33 | 34 | - (id)initWithDelegate:(id)delegate; 35 | 36 | // returns the number of bytes consumed; the caller is responsible for accumulating unprocessed bytes 37 | - (NSUInteger)decode:(uint8_t *)buffer length:(NSUInteger)len error:(NSError **)pError; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /SPDY/SPDYFrameEncoder.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYFrameEncoder.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYFrame.h" 14 | 15 | // GZIP header is 12 bytes, so this is an upper bound on compressed size 16 | #define COMPRESSED_FRAME_HEADER_LENGTH 12 17 | #define MAX_HEADER_BLOCK_LENGTH (16384 - COMPRESSED_FRAME_HEADER_LENGTH) 18 | #define MAX_COMPRESSED_HEADER_BLOCK_LENGTH (MAX_HEADER_BLOCK_LENGTH + COMPRESSED_FRAME_HEADER_LENGTH) 19 | 20 | @class SPDYFrameEncoder; 21 | 22 | @protocol SPDYFrameEncoderDelegate 23 | - (void)didEncodeData:(NSData *)data frameEncoder:(SPDYFrameEncoder *)encoder; 24 | - (void)didEncodeData:(NSData *)data withTag:(uint32_t)tag frameEncoder:(SPDYFrameEncoder *)encoder; 25 | @end 26 | 27 | @interface SPDYFrameEncoder : NSObject 28 | @property (nonatomic, weak) id delegate; 29 | - (id)initWithDelegate:(id )delegate headerCompressionLevel:(NSUInteger)headerCompressionLevel; 30 | 31 | // All of the encode methods return the number of bytes encoded, or -1 if an error occurred. 32 | - (NSInteger)encodeDataFrame:(SPDYDataFrame *)dataFrame; 33 | - (NSInteger)encodeSynStreamFrame:(SPDYSynStreamFrame *)synStreamFrame error:(NSError**)pError; 34 | - (NSInteger)encodeSynReplyFrame:(SPDYSynReplyFrame *)synReplyFrame error:(NSError**)pError; 35 | - (NSInteger)encodeRstStreamFrame:(SPDYRstStreamFrame *)rstStreamFrame; 36 | - (NSInteger)encodeSettingsFrame:(SPDYSettingsFrame *)settingsFrame; 37 | - (NSInteger)encodePingFrame:(SPDYPingFrame *)pingFrame; 38 | - (NSInteger)encodeGoAwayFrame:(SPDYGoAwayFrame *)goAwayFrame; 39 | - (NSInteger)encodeHeadersFrame:(SPDYHeadersFrame *)headersFrame error:(NSError**)pError; 40 | - (NSInteger)encodeWindowUpdateFrame:(SPDYWindowUpdateFrame *)windowUpdateFrame; 41 | @end 42 | -------------------------------------------------------------------------------- /SPDY/SPDYHeaderBlockCompressor.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYHeaderBlockCompressor.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | @interface SPDYHeaderBlockCompressor : NSObject 15 | - (id)initWithCompressionLevel:(NSUInteger)compressionLevel; 16 | - (NSUInteger)deflate:(uint8_t *)inputBuffer availIn:(NSUInteger)inputLength outputBuffer:(uint8_t *)outputBuffer availOut:(NSUInteger)outputLength error:(NSError **)pError; 17 | @end 18 | -------------------------------------------------------------------------------- /SPDY/SPDYHeaderBlockCompressor.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYHeaderBlockCompressor.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYDefinitions.h" 17 | #import "SPDYHeaderBlockCompressor.h" 18 | #import "SPDYZLibCommon.h" 19 | 20 | // See https://groups.google.com/group/spdy-dev/browse_thread/thread/dfaf498542fac792 21 | #define ZLIB_COMPRESSION_LEVEL 9 22 | #define ZLIB_WINDOW_SIZE 11 23 | #define ZLIB_MEMORY_LEVEL 1 24 | 25 | @implementation SPDYHeaderBlockCompressor 26 | { 27 | z_stream _zlibStream; 28 | int _zlibStreamStatus; 29 | } 30 | 31 | - (id)init 32 | { 33 | return [self initWithCompressionLevel:ZLIB_COMPRESSION_LEVEL]; 34 | } 35 | 36 | - (id)initWithCompressionLevel:(NSUInteger)compressionLevel 37 | { 38 | self = [super init]; 39 | if (self) { 40 | bzero(&_zlibStream, sizeof(_zlibStream)); 41 | 42 | _zlibStream.zalloc = Z_NULL; 43 | _zlibStream.zfree = Z_NULL; 44 | _zlibStream.opaque = Z_NULL; 45 | 46 | _zlibStream.avail_in = 0; 47 | _zlibStream.next_in = Z_NULL; 48 | 49 | _zlibStreamStatus = deflateInit2(&_zlibStream, compressionLevel, Z_DEFLATED, ZLIB_WINDOW_SIZE, ZLIB_MEMORY_LEVEL, Z_DEFAULT_STRATEGY); 50 | NSAssert(_zlibStreamStatus == Z_OK, @"unable to initialize zlib stream"); 51 | 52 | _zlibStreamStatus = deflateSetDictionary(&_zlibStream, kSPDYDict, sizeof(kSPDYDict)); 53 | NSAssert(_zlibStreamStatus == Z_OK, @"unable to set zlib dictionary"); 54 | } 55 | return self; 56 | } 57 | 58 | - (void)dealloc 59 | { 60 | (void)deflateEnd(&_zlibStream); 61 | } 62 | 63 | // Always consumes ALL available input or sets an error, and returns the number of bytes written to the output buffer. 64 | - (NSUInteger)deflate:(uint8_t *)inputBuffer availIn:(NSUInteger)inputLength outputBuffer:(uint8_t *)outputBuffer availOut:(NSUInteger)outputLength error:(NSError **)pError 65 | { 66 | if (_zlibStreamStatus != Z_OK) { 67 | if (pError) *pError = SPDY_CODEC_ERROR(SPDYHeaderBlockEncodingError, @"invalid zlib stream state"); 68 | return 0; 69 | } 70 | 71 | _zlibStream.next_in = inputBuffer; 72 | _zlibStream.avail_in = (uInt)inputLength; 73 | 74 | _zlibStream.next_out = outputBuffer; 75 | _zlibStream.avail_out = (uInt)outputLength; 76 | 77 | _zlibStreamStatus = deflate(&_zlibStream, Z_SYNC_FLUSH); 78 | 79 | if (_zlibStreamStatus != Z_OK && pError) { 80 | *pError = SPDY_CODEC_ERROR(SPDYHeaderBlockEncodingError, @"error compressing header block"); 81 | } 82 | 83 | return _zlibStream.next_out - outputBuffer; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /SPDY/SPDYHeaderBlockDecompressor.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYHeaderBlockDecompressor.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | 15 | @interface SPDYHeaderBlockDecompressor : NSObject 16 | - (NSUInteger)inflate:(uint8_t *)inputBuffer availIn:(NSUInteger)inputLength outputBuffer:(uint8_t *)outputBuffer availOut:(NSUInteger)outputLength error:(NSError **)pError; 17 | @end 18 | -------------------------------------------------------------------------------- /SPDY/SPDYHeaderBlockDecompressor.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYHeaderBlockDecompressor.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYDefinitions.h" 17 | #import "SPDYHeaderBlockDecompressor.h" 18 | #import "SPDYZLibCommon.h" 19 | 20 | @implementation SPDYHeaderBlockDecompressor 21 | { 22 | z_stream _zlibStream; 23 | int _zlibStreamStatus; 24 | } 25 | 26 | - (id)init 27 | { 28 | self = [super init]; 29 | if (self) { 30 | bzero(&_zlibStream, sizeof(_zlibStream)); 31 | 32 | _zlibStream.zalloc = Z_NULL; 33 | _zlibStream.zfree = Z_NULL; 34 | _zlibStream.opaque = Z_NULL; 35 | 36 | _zlibStream.avail_in = 0; 37 | _zlibStream.next_in = Z_NULL; 38 | 39 | _zlibStreamStatus = inflateInit(&_zlibStream); 40 | NSAssert(_zlibStreamStatus == Z_OK, @"unable to initialize zlib stream"); 41 | } 42 | return self; 43 | } 44 | 45 | - (void)dealloc 46 | { 47 | inflateEnd(&_zlibStream); 48 | } 49 | 50 | // Always consumes ALL available input or sets an error, and returns the number of bytes written to the output buffer. 51 | - (NSUInteger)inflate:(uint8_t *)inputBuffer availIn:(NSUInteger)inputLength outputBuffer:(uint8_t *)outputBuffer availOut:(NSUInteger)outputLength error:(NSError **)pError 52 | { 53 | if (_zlibStreamStatus != Z_OK) { 54 | if (pError) *pError = SPDY_CODEC_ERROR(SPDYHeaderBlockDecodingError, @"invalid zlib stream state"); 55 | return 0; 56 | } 57 | 58 | _zlibStream.next_in = inputBuffer; 59 | _zlibStream.avail_in = (uInt)inputLength; 60 | 61 | _zlibStream.next_out = outputBuffer; 62 | _zlibStream.avail_out = (uInt)outputLength; 63 | 64 | while (_zlibStream.avail_in > 0 && !*pError) { 65 | _zlibStreamStatus = inflate(&_zlibStream, Z_SYNC_FLUSH); 66 | 67 | switch (_zlibStreamStatus) { 68 | case Z_NEED_DICT: 69 | // We can't set the dictionary ahead of time due to zlib funkiness. 70 | _zlibStreamStatus = inflateSetDictionary(&_zlibStream, kSPDYDict, sizeof(kSPDYDict)); 71 | NSAssert(_zlibStreamStatus == Z_OK, @"unable to set zlib dictionary"); 72 | break; 73 | 74 | case Z_STREAM_END: 75 | break; 76 | 77 | case Z_BUF_ERROR: 78 | case Z_OK: 79 | // For simplicity's sake, if avail_out == 0, we treat the header block 80 | // as too large for this implementation to handle. 81 | if (_zlibStream.avail_out == 0) { 82 | *pError = SPDY_CODEC_ERROR(SPDYHeaderBlockDecodingError, @"header block is too large"); 83 | } 84 | break; 85 | 86 | case Z_STREAM_ERROR: 87 | case Z_DATA_ERROR: 88 | case Z_MEM_ERROR: 89 | *pError = SPDY_CODEC_ERROR(SPDYHeaderBlockDecodingError, @"error decompressing header block"); 90 | break; 91 | } 92 | } 93 | 94 | return _zlibStream.next_out - outputBuffer; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /SPDY/SPDYLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYLogger.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | typedef enum { 15 | SPDYLogLevelDisabled = -1, 16 | SPDYLogLevelError = 0, 17 | SPDYLogLevelWarning, 18 | SPDYLogLevelInfo, 19 | SPDYLogLevelDebug 20 | } SPDYLogLevel; 21 | 22 | @protocol SPDYLogger 23 | - (void)log:(NSString *)message atLevel:(SPDYLogLevel)logLevel; 24 | @end 25 | -------------------------------------------------------------------------------- /SPDY/SPDYMetadata+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMetadata+Utils.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import "SPDYProtocol.h" 13 | 14 | // Private readwrite property accessors for CocoaSPDY internal usage. 15 | @interface SPDYMetadata () 16 | 17 | @property (nonatomic) NSUInteger blockedMs; 18 | @property (nonatomic) BOOL cellular; 19 | @property (nonatomic) NSUInteger connectedMs; 20 | @property (nonatomic, copy) NSString *hostAddress; 21 | @property (nonatomic) NSUInteger hostPort; 22 | @property (nonatomic) NSInteger latencyMs; 23 | @property (nonatomic) SPDYProxyStatus proxyStatus; 24 | @property (nonatomic) NSUInteger rxBytes; 25 | @property (nonatomic) NSUInteger txBytes; 26 | @property (nonatomic) NSUInteger streamId; 27 | @property (nonatomic, copy) NSString *version; 28 | @property (nonatomic) BOOL viaProxy; 29 | @property (nonatomic) NSTimeInterval timeSessionConnected; 30 | @property (nonatomic) NSTimeInterval timeStreamCreated; 31 | @property (nonatomic) NSTimeInterval timeStreamRequestStarted; 32 | @property (nonatomic) NSTimeInterval timeStreamRequestLastHeader; 33 | @property (nonatomic) NSTimeInterval timeStreamRequestFirstData; 34 | @property (nonatomic) NSTimeInterval timeStreamRequestLastData; 35 | @property (nonatomic) NSTimeInterval timeStreamRequestEnded; 36 | @property (nonatomic) NSTimeInterval timeStreamResponseStarted; 37 | @property (nonatomic) NSTimeInterval timeStreamResponseLastHeader; 38 | @property (nonatomic) NSTimeInterval timeStreamResponseFirstData; 39 | @property (nonatomic) NSTimeInterval timeStreamResponseLastData; 40 | @property (nonatomic) NSTimeInterval timeStreamResponseEnded; 41 | @property (nonatomic) NSTimeInterval timeStreamClosed; 42 | 43 | @end 44 | 45 | // Private helper utilities 46 | @interface SPDYMetadata (Utils) 47 | 48 | + (void)setMetadata:(SPDYMetadata *)metadata forAssociatedDictionary:(NSMutableDictionary *)dictionary; 49 | + (SPDYMetadata *)metadataForAssociatedDictionary:(NSDictionary *)dictionary; 50 | 51 | @end -------------------------------------------------------------------------------- /SPDY/SPDYMetadata+Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMetadata+Utils.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import 13 | #import "SPDYMetadata+Utils.h" 14 | 15 | static const char *kMetadataAssociatedObjectKey = "SPDYMetadataAssociatedObject"; 16 | 17 | @implementation SPDYMetadata (Utils) 18 | 19 | /** 20 | Note about the SPDYMetadata identifier: 21 | 22 | This provides a mechanism for the metadata to be retrieved by the application at any point 23 | during processing of a request (well, after receiving the response or error). The application 24 | can request the metadata multiple times if it wants to track progress, or else wait until the 25 | connectionDidFinishLoading callback to get the final metadata. 26 | 27 | This is achieved by associating an instance of SPDYMetadata with an NSString instance used 28 | as the identifier. As long as that identifier is alive, the metadata will be available. 29 | */ 30 | 31 | static NSString * const SPDYMetadataIdentifierKey = @"x-spdy-metadata-identifier"; 32 | 33 | + (void)setMetadata:(SPDYMetadata *)metadata forAssociatedDictionary:(NSMutableDictionary *)dictionary 34 | { 35 | // We need to create a new instance of each identifier we assign to a dictionary. The value 36 | // of the identifier doesn't actually matter, but a unique one is useful for debugging. 37 | CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent(); 38 | NSString *identifier = [NSString stringWithFormat:@"%f/%tx", timestamp, (NSUInteger)metadata]; 39 | objc_setAssociatedObject(identifier, kMetadataAssociatedObjectKey, metadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 40 | dictionary[SPDYMetadataIdentifierKey] = identifier; 41 | } 42 | 43 | + (SPDYMetadata *)metadataForAssociatedDictionary:(NSDictionary *)dictionary; 44 | { 45 | NSString *identifier = dictionary[SPDYMetadataIdentifierKey]; 46 | if (identifier.length > 0) { 47 | id associatedObject = objc_getAssociatedObject(identifier, kMetadataAssociatedObjectKey); 48 | if ([associatedObject isKindOfClass:[SPDYMetadata class]]) { 49 | return associatedObject; 50 | } 51 | } 52 | return nil; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /SPDY/SPDYOrigin.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYOrigin.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | /** 15 | Representation for RFC 6454 origin. 16 | 17 | http://www.ietf.org/rfc/rfc6454.txt 18 | */ 19 | @interface SPDYOrigin : NSObject 20 | @property (nonatomic, readonly) NSString *scheme; 21 | @property (nonatomic, readonly) NSString *host; 22 | @property (nonatomic, readonly) in_port_t port; 23 | 24 | - (id)initWithString:(NSString *)urlString error:(NSError **)pError; 25 | - (id)initWithURL:(NSURL *)url error:(NSError **)pError; 26 | - (id)initWithScheme:(NSString *)scheme 27 | host:(NSString *)host 28 | port:(in_port_t)port 29 | error:(NSError **)pError; 30 | @end 31 | -------------------------------------------------------------------------------- /SPDY/SPDYOrigin.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYOrigin.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYOrigin.h" 17 | 18 | @interface SPDYOrigin () 19 | - (id)initCopyWithScheme:(NSString *)scheme 20 | host:(NSString *)host 21 | port:(in_port_t)port 22 | serialization:(NSString *)serialization; 23 | @end 24 | 25 | @implementation SPDYOrigin 26 | { 27 | NSString *_serialization; 28 | } 29 | 30 | - (id)initWithString:(NSString *)urlString error:(NSError **)pError 31 | { 32 | NSURL *url = [[NSURL alloc] initWithString:urlString]; 33 | return [self initWithURL:url error:pError]; 34 | } 35 | 36 | - (id)initWithURL:(NSURL *)url error:(NSError **)pError 37 | { 38 | return [self initWithScheme:url.scheme 39 | host:url.host 40 | port:url.port.unsignedShortValue 41 | error:pError]; 42 | } 43 | 44 | - (id)initWithScheme:(NSString *)scheme 45 | host:(NSString *)host 46 | port:(in_port_t)port 47 | error:(NSError **)pError 48 | { 49 | self = [super init]; 50 | if (self) { 51 | _scheme = [scheme lowercaseString]; 52 | if (![_scheme isEqualToString:@"http"] && ![_scheme isEqualToString:@"https"]) { 53 | if (pError) { 54 | NSString *message = [[NSString alloc] initWithFormat:@"unsupported scheme (%@) - only http and https are supported", scheme]; 55 | NSDictionary *info = @{ NSLocalizedDescriptionKey: message }; 56 | *pError = [[NSError alloc] initWithDomain:NSURLErrorDomain 57 | code:NSURLErrorBadURL 58 | userInfo:info]; 59 | } 60 | return nil; 61 | } 62 | 63 | if (host) { 64 | _host = [host lowercaseString]; 65 | } else { 66 | if (pError) { 67 | NSString *message = @"host must be specified"; 68 | NSDictionary *info = @{ NSLocalizedDescriptionKey: message }; 69 | *pError = [[NSError alloc] initWithDomain:NSURLErrorDomain 70 | code:NSURLErrorBadURL 71 | userInfo:info]; 72 | } 73 | return nil; 74 | } 75 | 76 | if (port == 0) { 77 | _port = [_scheme isEqualToString:@"http"] ? 80 : 443; 78 | } else { 79 | _port = port; 80 | } 81 | 82 | _serialization = [[NSString alloc] initWithFormat:@"%@://%@:%u", _scheme, _host, _port]; 83 | } 84 | return self; 85 | } 86 | 87 | - (id)initCopyWithScheme:(NSString *)scheme 88 | host:(NSString *)host 89 | port:(in_port_t)port 90 | serialization:(NSString *)serialization 91 | { 92 | self = [super init]; 93 | if (self) { 94 | _scheme = scheme; 95 | _host = host; 96 | _port = port; 97 | _serialization = serialization; 98 | } 99 | return self; 100 | } 101 | 102 | - (NSString *)description 103 | { 104 | return [NSString stringWithFormat:@"", _serialization]; 105 | } 106 | 107 | - (NSUInteger)hash 108 | { 109 | return [_serialization hash]; 110 | } 111 | 112 | - (BOOL)isEqual:(id)object 113 | { 114 | return self == object || ( 115 | [object isMemberOfClass:[self class]] && 116 | [_serialization isEqualToString:((SPDYOrigin *)object)->_serialization] 117 | ); 118 | } 119 | 120 | - (id)copyWithZone:(NSZone *)zone 121 | { 122 | SPDYOrigin *copy = [[SPDYOrigin allocWithZone:zone] initCopyWithScheme:[_scheme copyWithZone:zone] 123 | host:[_host copyWithZone:zone] 124 | port:_port 125 | serialization:[_serialization copyWithZone:zone]]; 126 | return copy; 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /SPDY/SPDYOriginEndpoint.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYOriginEndpoint.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | @class SPDYOrigin; 13 | 14 | // Some clarification: 15 | // 16 | // What Apple calls an "HTTPS" proxy, kCFProxyTypeHTTPS, means it is used for https:// requests. It 17 | // is still a HTTP proxy, but requires the use of the CONNECT message, since that is the only way 18 | // to establish an opaque session, as required by SPDY, with the origin. 19 | // 20 | // An "HTTP" proxy, kCFProxyTypeHTTP, as defined by Apple, is an HTTP proxy that does not use a 21 | // CONNECT message. We can't support those. 22 | // 23 | // Direct, kCFProxyTypeNone, means no proxy. 24 | // 25 | // As far as I can tell, there is no system-supported way to configure a proxy that requires a TLS 26 | // session to connect to it, which would serve to obscure the CONNECT destination. This is 27 | // potentially a feature we could add later, though it would be up to the app to supply the 28 | // configuration. If we did, the name would be something like SPDYOriginEndpointTypeTlsHttpsProxy. 29 | 30 | typedef enum { 31 | SPDYOriginEndpointTypeDirect, 32 | SPDYOriginEndpointTypeHttpsProxy 33 | } SPDYOriginEndpointType; 34 | 35 | @interface SPDYOriginEndpoint : NSObject 36 | 37 | @property (nonatomic, readonly) SPDYOrigin *origin; 38 | @property (nonatomic, readonly) NSString *host; 39 | @property (nonatomic, readonly) in_port_t port; 40 | @property (nonatomic, readonly) NSString *user; 41 | @property (nonatomic, readonly) NSString *password; 42 | @property (nonatomic, readonly) SPDYOriginEndpointType type; 43 | 44 | - (id)initWithHost:(NSString *)host 45 | port:(in_port_t)port 46 | user:(NSString *)user 47 | password:(NSString *)password 48 | type:(SPDYOriginEndpointType)type 49 | origin:(SPDYOrigin *)origin; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /SPDY/SPDYOriginEndpoint.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYOriginEndpoint.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYCommonLogger.h" 17 | #import "SPDYOrigin.h" 18 | #import "SPDYOriginEndpoint.h" 19 | 20 | @implementation SPDYOriginEndpoint 21 | 22 | - (id)initWithHost:(NSString *)host 23 | port:(in_port_t)port 24 | user:(NSString *)user 25 | password:(NSString *)password 26 | type:(SPDYOriginEndpointType)type 27 | origin:(SPDYOrigin *)origin 28 | { 29 | self = [super init]; 30 | if (self) { 31 | _host = [host copy]; 32 | _port = port; 33 | _user = [user copy]; 34 | _password = [password copy]; 35 | _type = type; 36 | _origin = origin; 37 | } 38 | return self; 39 | } 40 | 41 | - (NSString *)description 42 | { 43 | if (_type == SPDYOriginEndpointTypeDirect) { 44 | return [NSString stringWithFormat:@"", 45 | _host, _port, 46 | _origin]; 47 | } else { 48 | return [NSString stringWithFormat:@"", 49 | _host, _port, 50 | _type == SPDYOriginEndpointTypeHttpsProxy ? @"https" : @"unknown", 51 | _user ? @" with credentials" : @"", 52 | _origin]; 53 | } 54 | } 55 | @end 56 | 57 | -------------------------------------------------------------------------------- /SPDY/SPDYOriginEndpointManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYOriginEndpointManager.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import "SPDYError.h" 13 | #import "SPDYProtocol.h" 14 | 15 | @class SPDYOrigin; 16 | @class SPDYOriginEndpoint; 17 | 18 | @interface SPDYOriginEndpointManager : NSObject 19 | 20 | @property (nonatomic, readonly) SPDYOrigin *origin; 21 | @property (nonatomic, readonly) SPDYOriginEndpoint *endpoint; 22 | @property (nonatomic, readonly) NSUInteger remaining; 23 | @property (nonatomic, readonly) SPDYProxyStatus proxyStatus; 24 | @property (nonatomic) bool authRequired; // writable since only the socket knows the answer 25 | 26 | - (id)initWithOrigin:(SPDYOrigin *)origin; 27 | - (void)resolveEndpointsWithCompletionHandler:(void (^)())completionHandler; 28 | - (SPDYOriginEndpoint *)moveToNextEndpoint; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /SPDY/SPDYProtocol+Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYProtocol+Project.h 3 | // SPDY 4 | // 5 | // Created by Nolan O'Brien on 4/17/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "SPDYProtocol.h" 10 | 11 | @interface SPDYProtocol (Project) 12 | 13 | @property (nonatomic, readonly) NSURLSession *associatedSession; 14 | @property (nonatomic, readonly, weak) NSURLSessionTask *associatedSessionTask; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SPDY/SPDYSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSession.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | @class SPDYConfiguration; 15 | @class SPDYOrigin; 16 | @class SPDYProtocol; 17 | @class SPDYSessionManager; 18 | @class SPDYSession; 19 | @class SPDYStream; 20 | 21 | @protocol SPDYSessionDelegate 22 | - (void)session:(SPDYSession *)session capacityIncreased:(NSUInteger)capacity; 23 | - (void)session:(SPDYSession *)session connectedToNetwork:(bool)cellular; 24 | - (void)session:(SPDYSession *)session refusedStream:(SPDYStream *)stream; 25 | - (void)sessionClosed:(SPDYSession *)session; 26 | @end 27 | 28 | @interface SPDYSession : NSObject 29 | 30 | @property (nonatomic, weak) id delegate; 31 | @property (nonatomic, readonly) SPDYOrigin *origin; 32 | 33 | /** 34 | @return available capacity for new local streams 35 | */ 36 | @property (nonatomic, assign, readonly) NSUInteger capacity; 37 | 38 | /** 39 | @return number of in-flight, local streams 40 | */ 41 | @property (nonatomic, assign, readonly) NSUInteger load; 42 | 43 | /** 44 | @return YES if the session is associated with a cellular network interface 45 | */ 46 | @property (nonatomic, readonly) bool isCellular; 47 | 48 | /** 49 | @return whether the session has a connected socket 50 | */ 51 | @property (nonatomic, readonly) bool isConnected; 52 | 53 | /** 54 | @return YES after the session has successfully handled at least one request 55 | */ 56 | @property (nonatomic, readonly) bool isEstablished; 57 | 58 | /** 59 | @return YES until the session has received a GOAWAY or been disconnected 60 | */ 61 | @property (nonatomic, readonly) bool isOpen; 62 | 63 | - (id)initWithOrigin:(SPDYOrigin *)origin 64 | delegate:(id)delegate 65 | configuration:(SPDYConfiguration *)configuration 66 | cellular:(bool)cellular 67 | error:(NSError **)pError; 68 | - (void)openStream:(SPDYStream *)stream; 69 | - (void)close; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /SPDY/SPDYSessionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSessionManager.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | @protocol SPDYSessionDelegate; 15 | 16 | @interface SPDYSessionManager : NSObject 17 | 18 | + (SPDYSessionManager *)localManagerForOrigin:(SPDYOrigin *)origin; 19 | - (void)queueStream:(SPDYStream *)stream; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /SPDY/SPDYSessionPool.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSessionPool.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #import 13 | 14 | @class SPDYSession; 15 | @class SPDYSessionManager; 16 | 17 | @interface SPDYSessionPool : NSObject 18 | 19 | @property (nonatomic, assign, readonly) NSUInteger count; 20 | @property (nonatomic, assign) NSUInteger pendingCount; 21 | 22 | - (bool)contains:(SPDYSession *)session; 23 | - (void)add:(SPDYSession *)session; 24 | - (NSUInteger)count; 25 | - (NSUInteger)remove:(SPDYSession *)session; 26 | - (SPDYSession *)nextSession; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /SPDY/SPDYSessionPool.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSessionPool.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYSession.h" 17 | #import "SPDYSessionPool.h" 18 | 19 | @implementation SPDYSessionPool 20 | { 21 | NSMutableArray *_sessions; 22 | } 23 | 24 | - (id)init 25 | { 26 | self = [super init]; 27 | if (self) { 28 | _sessions = [[NSMutableArray alloc] init]; 29 | } 30 | return self; 31 | } 32 | 33 | - (bool)contains:(SPDYSession *)session 34 | { 35 | return [_sessions containsObject:session]; 36 | } 37 | 38 | - (void)add:(SPDYSession *)session 39 | { 40 | [_sessions addObject:session]; 41 | } 42 | 43 | - (NSUInteger)count 44 | { 45 | return _sessions.count; 46 | } 47 | 48 | - (NSUInteger)remove:(SPDYSession *)session 49 | { 50 | [_sessions removeObject:session]; 51 | return _sessions.count; 52 | } 53 | 54 | - (SPDYSession *)nextSession 55 | { 56 | SPDYSession *session; 57 | 58 | if (_sessions.count == 0) { 59 | return nil; 60 | } 61 | 62 | session = _sessions[0]; 63 | NSAssert(session.isOpen, @"Should never contain closed sessions."); 64 | 65 | // TODO: clean this up 66 | while (!session.isOpen) { 67 | if ([self remove:session] == 0) return nil; 68 | session = _sessions[0]; 69 | } 70 | 71 | // Rotate 72 | if (_sessions.count > 1) { 73 | [_sessions removeObjectAtIndex:0]; 74 | [_sessions addObject:session]; 75 | } 76 | 77 | return session; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /SPDY/SPDYSettingsStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSettingsStore.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYDefinitions.h" 14 | 15 | @class SPDYOrigin; 16 | 17 | @interface SPDYSettingsStore : NSObject 18 | + (SPDYSettings *)settingsForOrigin:(SPDYOrigin *)origin; 19 | + (void)persistSettings:(SPDYSettings *)settings forOrigin:(SPDYOrigin *)origin; 20 | + (void)clearSettingsForOrigin:(SPDYOrigin *)origin; 21 | @end 22 | -------------------------------------------------------------------------------- /SPDY/SPDYSettingsStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSettingsStore.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #if !defined(__has_feature) || !__has_feature(objc_arc) 13 | #error "This file requires ARC support." 14 | #endif 15 | 16 | #import "SPDYSettingsStore.h" 17 | #import "SPDYOrigin.h" 18 | 19 | // Convenience wrapper for SPDYSettings array 20 | @interface SPDYSettingsObj : NSObject 21 | @property (nonatomic, readonly) SPDYSettings *settings; 22 | @end 23 | 24 | @implementation SPDYSettingsObj 25 | { 26 | SPDYSettings _settings[SPDY_SETTINGS_LENGTH]; 27 | } 28 | 29 | - (id)init 30 | { 31 | self = [super init]; 32 | if (self) { 33 | SPDY_SETTINGS_ITERATOR(i) { 34 | _settings[i].set = NO; 35 | } 36 | } 37 | return self; 38 | } 39 | 40 | - (SPDYSettings *)settings 41 | { 42 | return _settings; 43 | } 44 | 45 | @end 46 | 47 | @interface SPDYSettingsStore () 48 | + (NSMutableDictionary *)_sharedStore; 49 | @end 50 | 51 | @implementation SPDYSettingsStore 52 | 53 | + (SPDYSettings *)settingsForOrigin:(SPDYOrigin *)origin 54 | { 55 | NSMutableDictionary *sharedStore = [SPDYSettingsStore _sharedStore]; 56 | SPDYSettingsObj *storedSettingsObj = sharedStore[origin]; 57 | 58 | if (!storedSettingsObj) { 59 | return NULL; 60 | } 61 | 62 | return storedSettingsObj.settings; 63 | } 64 | 65 | + (void)persistSettings:(SPDYSettings *)settings forOrigin:(SPDYOrigin *)origin 66 | { 67 | NSMutableDictionary *sharedStore = [SPDYSettingsStore _sharedStore]; 68 | SPDYSettingsObj *storedSettingsObj = sharedStore[origin]; 69 | 70 | if (!storedSettingsObj) { 71 | storedSettingsObj = [[SPDYSettingsObj alloc] init]; 72 | sharedStore[origin] = storedSettingsObj; 73 | } 74 | 75 | SPDYSettings *storedSettings = storedSettingsObj.settings; 76 | 77 | SPDY_SETTINGS_ITERATOR(i) { 78 | if (settings[i].set && settings[i].flags == SPDY_SETTINGS_FLAG_PERSIST_VALUE) { 79 | storedSettings[i].set = YES; 80 | storedSettings[i].flags = SPDY_SETTINGS_FLAG_PERSISTED; 81 | storedSettings[i].value = settings[i].value; 82 | } 83 | } 84 | } 85 | 86 | 87 | + (void)clearSettingsForOrigin:(SPDYOrigin *)origin 88 | { 89 | NSMutableDictionary *sharedStore = [SPDYSettingsStore _sharedStore]; 90 | SPDYSettingsObj *storedSettingsObj = sharedStore[origin]; 91 | 92 | if (!storedSettingsObj) { 93 | return; 94 | } 95 | 96 | SPDYSettings *storedSettings = storedSettingsObj.settings; 97 | 98 | SPDY_SETTINGS_ITERATOR(i) { 99 | storedSettings[i].set = NO; 100 | } 101 | } 102 | 103 | #pragma mark private methods 104 | 105 | + (NSMutableDictionary *)_sharedStore 106 | { 107 | static dispatch_once_t pred; 108 | static NSMutableDictionary *sharedStore; 109 | dispatch_once(&pred, ^{ 110 | sharedStore = [[NSMutableDictionary alloc] init]; 111 | }); 112 | return sharedStore; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /SPDY/SPDYSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSocket.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Substantially based on the CocoaAsyncSocket library, originally 10 | // created by Dustin Voss, and currently maintained at 11 | // https://github.com/robbiehanson/CocoaAsyncSocket 12 | // 13 | 14 | @class SPDYSocket; 15 | @class SPDYSocketReadOp; 16 | @class SPDYSocketWriteOp; 17 | @class SPDYOrigin; 18 | @class SPDYOriginEndpoint; 19 | 20 | extern NSString *const SPDYSocketException; 21 | 22 | #pragma mark SPDYSocketDelegate 23 | 24 | @protocol SPDYSocketDelegate 25 | @optional 26 | 27 | /** 28 | Called when a socket encounters an error and will be closing. 29 | 30 | You may call [SPDYSocket unreadData] during this callback to retrieve 31 | remaining data off the socket. This delegate method may be called before 32 | socket:didAcceptNewSocket: or socket:didConnectToHost:port:. 33 | */ 34 | - (void)socket:(SPDYSocket *)socket willDisconnectWithError:(NSError *)error; 35 | 36 | /** 37 | Called when a socket disconnects with or without error. 38 | 39 | The SPDYSocket may be safely released during this callback. If you call 40 | [SPDYSocket disconnect], and the socket wasn't already disconnected, this 41 | delegate method will be called before the disconnect method returns. 42 | */ 43 | - (void)socketDidDisconnect:(SPDYSocket *)socket; 44 | 45 | /** 46 | Called when a socket accepts a connection. 47 | 48 | Another SPDYSocket is spawned to handle it. The new socket will have 49 | the same delegate and will call socket:didConnectToHost:port:. 50 | */ 51 | - (void)socket:(SPDYSocket *)socket didAcceptNewSocket:(SPDYSocket *)newSocket; 52 | 53 | /** 54 | Called when a new socket is spawned to handle a connection. 55 | 56 | This method should return the run loop on which the new socket and its 57 | delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used. 58 | */ 59 | - (NSRunLoop *)socket:(SPDYSocket *)socket wantsRunLoopForNewSocket:(SPDYSocket *)newSocket; 60 | 61 | /** 62 | Called when a socket is about to connect. 63 | 64 | If [SPDYSocket connectToHost:onPort:error:] was called, the delegate will be 65 | able to access and configure the CFReadStream and CFWriteStream as desired 66 | prior to connection. 67 | 68 | If [SPDYSocket connectToAddress:error:] was called, the delegate will be able 69 | to access and configure the CFSocket and CFSocketNativeHandle (BSD socket) as 70 | desired prior to connection. You will be able to access and configure the 71 | CFReadStream and CFWriteStream during socket:didConnectToHost:port:. 72 | 73 | @return YES to continue, NO to abort resulting in a SPDYSocketConnectCanceled 74 | */ 75 | - (bool)socketWillConnect:(SPDYSocket *)socket; 76 | 77 | /** 78 | Called when a socket connects and is ready for reading and writing. 79 | 80 | @param host IP address of the connected host 81 | */ 82 | - (void)socket:(SPDYSocket *)socket didConnectToHost:(NSString *)host port:(in_port_t)port; 83 | 84 | /** 85 | Called when a socket has completed reading the requested data into memory. 86 | */ 87 | - (void)socket:(SPDYSocket *)socket didReadData:(NSData *)data withTag:(long)tag; 88 | 89 | /** 90 | Called when a socket has read in data, but has not yet completed the read. 91 | 92 | This would occur if using readToData: or readToLength: methods. 93 | */ 94 | - (void)socket:(SPDYSocket *)socket didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; 95 | 96 | /** 97 | Called when a socket has completed writing the requested data. 98 | */ 99 | - (void)socket:(SPDYSocket *)socket didWriteDataWithTag:(long)tag; 100 | 101 | /** 102 | Called when a socket has written data, but has not yet completed the write. 103 | */ 104 | - (void)socket:(SPDYSocket *)socket didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; 105 | 106 | /** 107 | Called if a read operation has reached its timeout without completing. 108 | 109 | @param elapsed total elapsed time since the read began 110 | @param length number of bytes that have been read so far 111 | @return a positive value to optionally extend the read's timeout 112 | */ 113 | - (NSTimeInterval)socket:(SPDYSocket *)socket 114 | willTimeoutReadWithTag:(long)tag 115 | elapsed:(NSTimeInterval)elapsed 116 | bytesDone:(NSUInteger)length; 117 | 118 | /** 119 | Called if a write operation has reached its timeout without completing. 120 | 121 | @param elapsed total elapsed time since the write began 122 | @param length number of bytes that have been write so far 123 | @return a positive value to optionally extend the write's timeout 124 | */ 125 | - (NSTimeInterval)socket:(SPDYSocket *)socket 126 | willTimeoutWriteWithTag:(long)tag 127 | elapsed:(NSTimeInterval)elapsed 128 | bytesDone:(NSUInteger)length; 129 | 130 | /** 131 | Called when a socket has successfully completed SSL/TLS negotiation. 132 | 133 | If the delegate does not implement this method, use of the newly opened 134 | TLS channel will always proceed as if this method had returned YES. 135 | 136 | @param trust the X.509 trust object created to evaluate the TLS channel 137 | @return YES to continue, NO to close the connection with the error 138 | SPDYSocketTLSVerificationFailed 139 | */ 140 | - (bool)socket:(SPDYSocket *)socket securedWithTrust:(SecTrustRef)trust; 141 | 142 | @end 143 | 144 | #pragma mark SPDYSocket 145 | 146 | @interface SPDYSocket : NSObject 147 | @property (nonatomic, strong) id delegate; 148 | @property (nonatomic, readonly) bool isCellular; 149 | 150 | - (id)initWithDelegate:(id)delegate; 151 | - (CFSocketRef)cfSocket; 152 | - (CFReadStreamRef)cfReadStream; 153 | - (CFWriteStreamRef)cfWriteStream; 154 | 155 | /** 156 | Connects to the given host and port. 157 | 158 | @param timeout use a negative value for no connection timeout 159 | **/ 160 | - (bool)connectToOrigin:(SPDYOrigin *)origin 161 | withTimeout:(NSTimeInterval)timeout 162 | error:(NSError **)pError; 163 | 164 | /** 165 | Disconnects immediately; any pending reads or writes are dropped. 166 | */ 167 | - (void)disconnect; 168 | 169 | /** 170 | Disconnects after all pending reads have completed. 171 | */ 172 | - (void)disconnectAfterReads; 173 | 174 | /** 175 | Disconnects after all pending writes have completed. 176 | */ 177 | - (void)disconnectAfterWrites; 178 | 179 | /** 180 | Disconnects after all pending reads and writes have completed. 181 | */ 182 | - (void)disconnectAfterReadsAndWrites; 183 | 184 | /** 185 | @return YES when the socket streams are open and connected 186 | */ 187 | - (bool)connected; 188 | 189 | /** 190 | @return YES if a proxy server is being used 191 | */ 192 | - (bool)connectedToProxy; 193 | 194 | /** 195 | @return the IP address of the host to which the socket is connected 196 | */ 197 | - (NSString *)connectedHost; 198 | 199 | /** 200 | @return the port to which the socket is connected 201 | */ 202 | - (in_port_t)connectedPort; 203 | 204 | /** 205 | @return YES if the socket has been closed after attempting to connect 206 | */ 207 | - (bool)closed; 208 | 209 | /** 210 | @return YES if the socket is IPv4 211 | */ 212 | - (bool)isIPv4; 213 | 214 | /** 215 | @return YES if the socket is IPv6 216 | */ 217 | - (bool)isIPv6; 218 | 219 | /** 220 | Asynchronously read the first available bytes on the socket. 221 | 222 | When the read is complete the socket:didReadData:withTag: delegate method 223 | will be called. 224 | 225 | @param timeout use a negative value for no timeout 226 | @param tag an arbitrary tag to associate with the delegate callback 227 | */ 228 | - (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; 229 | 230 | /** 231 | Asynchronously read the first available bytes on the socket. 232 | 233 | When the read is complete the socket:didReadData:withTag: delegate method 234 | will be called, referencing new bytes written to the specified buffer. 235 | 236 | @param timeout use a negative value for no timeout 237 | @param buffer the buffer to use for reading 238 | @param offset the index to write to in the buffer 239 | @param tag an arbitrary tag to associate with the delegate callback 240 | */ 241 | 242 | - (void)readDataWithTimeout:(NSTimeInterval)timeout 243 | buffer:(NSMutableData *)buffer 244 | bufferOffset:(NSUInteger)offset 245 | tag:(long)tag; 246 | 247 | /** 248 | Asynchronously read the first available bytes on the socket. 249 | 250 | When the read is complete the socket:didReadData:withTag: delegate method 251 | will be called, referencing new bytes written to the specified buffer. 252 | 253 | @param timeout use a negative value for no timeout 254 | @param buffer the buffer to use for reading 255 | @param offset the index to write to in the buffer 256 | @param maxLength the maximum number of bytes to read with this operation 257 | @param tag an arbitrary tag to associate with the delegate callback 258 | */ 259 | - (void)readDataWithTimeout:(NSTimeInterval)timeout 260 | buffer:(NSMutableData *)buffer 261 | bufferOffset:(NSUInteger)offset 262 | maxLength:(NSUInteger)length 263 | tag:(long)tag; 264 | 265 | /** 266 | Asynchronously read the specified number of bytes off the socket. 267 | 268 | When the read is complete the socket:didReadData:withTag: delegate method 269 | will be called. 270 | 271 | @param length the number of bytes to read before calling the delegate 272 | @param timeout use a negative value for no timeout 273 | @param tag an arbitrary tag to associate with the delegate callback 274 | */ 275 | - (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; 276 | 277 | /** 278 | Asynchronously read the specified number of bytes off the socket. 279 | 280 | When the read is complete the socket:didReadData:withTag: delegate method 281 | will be called, referencing new bytes written to the specified buffer. 282 | 283 | @param length the number of bytes to read before calling the delegate 284 | @param timeout use a negative value for no timeout 285 | @param buffer the buffer to use for reading 286 | @param offset the index to write to in the buffer 287 | @param tag an arbitrary tag to associate with the delegate callback 288 | **/ 289 | - (void)readDataToLength:(NSUInteger)length 290 | withTimeout:(NSTimeInterval)timeout 291 | buffer:(NSMutableData *)buffer 292 | bufferOffset:(NSUInteger)offset 293 | tag:(long)tag; 294 | 295 | /** 296 | Asynchronously writes data to the socket. 297 | 298 | When the write is complete the socket:didWriteDataWithTag: delegate method 299 | will be called. 300 | 301 | @param timeout use a negative value for no timeout 302 | @param tag an arbitrary tag to associate with the delegate callback 303 | */ 304 | - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 305 | 306 | /** 307 | Secures the connection using TLS. 308 | 309 | This method may be called at any time, and the TLS handshake will occur after 310 | all pending reads and writes are finished. 311 | 312 | @param tlsSettings a dictionary of TLS settings to use for the connection 313 | 314 | Possible keys and values for TLS settings can be found in CFSocketStream.h 315 | Some possible keys are: 316 | - kCFStreamSSLLevel 317 | - kCFStreamSSLAllowsExpiredCertificates 318 | - kCFStreamSSLAllowsExpiredRoots 319 | - kCFStreamSSLAllowsAnyRoot 320 | - kCFStreamSSLValidatesCertificateChain 321 | - kCFStreamSSLPeerName 322 | - kCFStreamSSLCertificates 323 | - kCFStreamSSLIsServer 324 | 325 | If you pass nil or an empty dictionary, Apple default settings will be used. 326 | */ 327 | - (void)secureWithTLS:(NSDictionary *)tlsSettings; 328 | 329 | /** 330 | Reschedule the SPDYSocket on a different runloop. 331 | */ 332 | - (bool)setRunLoop:(NSRunLoop *)runLoop; 333 | 334 | /** 335 | Configures the runloop modes the SPDYSocket will operate on. 336 | 337 | The default set is limited to NSDefaultRunLoopMode. 338 | 339 | If you'd like your socket to continue operation during other modes, you may want to add modes such as 340 | NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. 341 | */ 342 | - (bool)setRunLoopModes:(NSArray *)runLoopModes; 343 | - (bool)addRunLoopMode:(NSString *)runLoopMode; 344 | - (bool)removeRunLoopMode:(NSString *)runLoopMode; 345 | 346 | /** 347 | @return the current runloop modes the SPDYSocket is scheduled on 348 | */ 349 | - (NSArray *)runLoopModes; 350 | 351 | /** 352 | Call during socket:willDisconnectWithError: to read any leftover data on the socket. 353 | 354 | @return any remaining data off the socket buffer 355 | */ 356 | - (NSData *)unreadData; 357 | 358 | @end 359 | -------------------------------------------------------------------------------- /SPDY/SPDYSocketOps.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSocketOps.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | @class SPDYOrigin; 13 | 14 | #define PROXY_READ_SIZE 8192 // Max size of proxy response 15 | #define READ_CHUNK_SIZE 65536 // Limit on size of each read pass 16 | 17 | /** 18 | Encompasses the instructions for any given read operation. 19 | 20 | [SPDYSocketDelegate socket:didReadData:withTag:] is called when a read 21 | operation completes. If _fixedLength is set, the delegate will not be called 22 | until exactly that number of bytes is read. If _maxLength is set, the 23 | delegate will be called as soon as 0 < bytes <= maxLength are read. If 24 | neither is set, the delegate will be called as soon as any bytes are read. 25 | */ 26 | @interface SPDYSocketReadOp : NSObject { 27 | @public 28 | NSMutableData *_buffer; 29 | NSUInteger _bytesRead; 30 | NSUInteger _startOffset; 31 | NSUInteger _maxLength; 32 | NSUInteger _fixedLength; 33 | NSUInteger _originalBufferLength; 34 | NSTimeInterval _timeout; 35 | bool _bufferOwner; 36 | long _tag; 37 | } 38 | 39 | - (id)initWithData:(NSMutableData *)data 40 | startOffset:(NSUInteger)startOffset 41 | maxLength:(NSUInteger)maxLength 42 | timeout:(NSTimeInterval)timeout 43 | fixedLength:(NSUInteger)fixedLength 44 | tag:(long)tag; 45 | 46 | - (NSUInteger)safeReadLength; 47 | @end 48 | 49 | 50 | /** 51 | Encompasses the instructions for connecting to a proxy 52 | */ 53 | @interface SPDYSocketProxyReadOp : SPDYSocketReadOp { 54 | @public 55 | NSString *_version; 56 | NSInteger _statusCode; 57 | NSString *_remaining; 58 | NSUInteger _bytesParsed; 59 | } 60 | 61 | - (id)initWithTimeout:(NSTimeInterval)timeout; 62 | - (bool)tryParseResponse; 63 | - (bool)success; 64 | - (bool)needsAuth; 65 | 66 | @end 67 | 68 | 69 | /** 70 | Encompasses the instructions for any given write operation. 71 | */ 72 | @interface SPDYSocketWriteOp : NSObject { 73 | @public 74 | NSData *_buffer; 75 | NSUInteger _bytesWritten; 76 | NSTimeInterval _timeout; 77 | long _tag; 78 | } 79 | 80 | - (id)initWithData:(NSData *)data timeout:(NSTimeInterval)timeout tag:(long)tag; 81 | 82 | @end 83 | 84 | 85 | /** 86 | Encompasses the instructions for connecting to a proxy 87 | */ 88 | @interface SPDYSocketProxyWriteOp : SPDYSocketWriteOp 89 | 90 | - (id)initWithOrigin:(SPDYOrigin *)origin timeout:(NSTimeInterval)timeout; 91 | 92 | @end 93 | 94 | 95 | /** 96 | Encompasses instructions for TLS. 97 | */ 98 | @interface SPDYSocketTLSOp : NSObject { 99 | @public 100 | NSDictionary *_tlsSettings; 101 | } 102 | 103 | - (id)initWithTLSSettings:(NSDictionary *)settings; 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /SPDY/SPDYSocketOps.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSocketOps.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #import "SPDYCommonLogger.h" 13 | #import "SPDYOrigin.h" 14 | #import "SPDYSocketOps.h" 15 | 16 | @implementation SPDYSocketReadOp 17 | 18 | - (id)initWithData:(NSMutableData *)data 19 | startOffset:(NSUInteger)startOffset 20 | maxLength:(NSUInteger)maxLength 21 | timeout:(NSTimeInterval)timeout 22 | fixedLength:(NSUInteger)fixedLength 23 | tag:(long)tag 24 | { 25 | self = [super init]; 26 | if (self) { 27 | if (data) { 28 | _buffer = data; 29 | _startOffset = startOffset; 30 | _bufferOwner = NO; 31 | _originalBufferLength = data.length; 32 | } else { 33 | _buffer = [[NSMutableData alloc] initWithLength:MAX(0, fixedLength)]; 34 | _startOffset = 0; 35 | _bufferOwner = YES; 36 | _originalBufferLength = 0; 37 | } 38 | 39 | _bytesRead = 0; 40 | _maxLength = maxLength; 41 | _timeout = timeout; 42 | _fixedLength = fixedLength; 43 | _tag = tag; 44 | } 45 | return self; 46 | } 47 | 48 | /** 49 | Returns the safe length of data that can be read relative to the buffer. 50 | */ 51 | - (NSUInteger)safeReadLength 52 | { 53 | if (_fixedLength > 0) { 54 | return _fixedLength - _bytesRead; 55 | } else { 56 | NSUInteger result = READ_CHUNK_SIZE; 57 | 58 | if (_maxLength > 0) { 59 | result = MIN(result, (_maxLength - _bytesRead)); 60 | } 61 | 62 | if (!_bufferOwner && _buffer.length == _originalBufferLength) { 63 | NSUInteger bufferSize = _buffer.length; 64 | NSUInteger bufferSpace = bufferSize - _startOffset - _bytesRead; 65 | 66 | if (bufferSpace > 0) { 67 | result = MIN(result, bufferSpace); 68 | } 69 | } 70 | 71 | return result; 72 | } 73 | } 74 | 75 | - (NSString *)description 76 | { 77 | return [NSString stringWithFormat: 78 | @"", 79 | (unsigned long)_startOffset, (unsigned long)_maxLength, (unsigned long)_fixedLength, (unsigned long)_timeout, (unsigned long)_bytesRead]; 80 | } 81 | @end 82 | 83 | 84 | @implementation SPDYSocketProxyReadOp 85 | 86 | - (id)initWithTimeout:(NSTimeInterval)timeout 87 | { 88 | return [super initWithData:nil 89 | startOffset:0 90 | maxLength:0 91 | timeout:timeout 92 | fixedLength:PROXY_READ_SIZE 93 | tag:0]; 94 | } 95 | 96 | - (bool)tryParseResponse 97 | { 98 | if (_bytesRead == 0) { 99 | return NO; 100 | } 101 | 102 | // Response will look something like the following. Note we ignore any additional headers. 103 | // HTTP/1.1 200 Connection established\r\n 104 | // 105 | // \r\n 106 | // 107 | // Assumptions: 108 | // - always ends in \r\n\r\n. No extra data. 109 | // - single space only for whitespace 110 | 111 | uint8_t const *buffer = _buffer.mutableBytes + _startOffset; 112 | NSUInteger bufferLength = _bytesRead; 113 | const NSUInteger minimumValidResponseSize = 7; // "A 1\r\n\r\n" 114 | NSUInteger index = 0; 115 | 116 | if (bufferLength < minimumValidResponseSize || 117 | buffer[bufferLength - 4] != '\r' || 118 | buffer[bufferLength - 3] != '\n' || 119 | buffer[bufferLength - 2] != '\r' || 120 | buffer[bufferLength - 1] != '\n') { 121 | return NO; 122 | } 123 | 124 | // We know buffer ends in "\r\n\r\n" so use '\r' as the terminator. 125 | 126 | NSUInteger versionStart = index; 127 | while (buffer[index] != ' ' && buffer[index] != '\r') { 128 | ++index; 129 | } 130 | if (index == versionStart) { 131 | return NO; 132 | } 133 | _version = [[NSString alloc] initWithBytesNoCopy:(void *)buffer length:index encoding:NSUTF8StringEncoding freeWhenDone:NO]; 134 | 135 | NSUInteger statusCodeStart = ++index; // skip space 136 | while (buffer[index] != ' ' && buffer[index] != '\r') { 137 | ++index; 138 | } 139 | if (index == statusCodeStart) { 140 | return NO; 141 | } 142 | _statusCode = [[[NSString alloc] initWithBytesNoCopy:(void *)(buffer + statusCodeStart) length:(index - statusCodeStart) encoding:NSUTF8StringEncoding freeWhenDone:NO] integerValue]; 143 | 144 | NSUInteger remainingStart = (buffer[index] == ' ') ? ++index : index; // skip space 145 | if ((bufferLength - remainingStart) < 4) { 146 | return NO; 147 | } 148 | _remaining = [[NSString alloc] initWithBytesNoCopy:(void *)(buffer + remainingStart) length:(bufferLength - remainingStart) encoding:NSUTF8StringEncoding freeWhenDone:NO]; 149 | 150 | _bytesParsed = bufferLength; 151 | return YES; 152 | } 153 | 154 | - (bool)success 155 | { 156 | return _statusCode >= 200 && _statusCode < 300 && [_version hasPrefix:@"HTTP/1"]; 157 | } 158 | 159 | - (bool)needsAuth 160 | { 161 | return _statusCode == 407 && [_version hasPrefix:@"HTTP/1"]; 162 | } 163 | 164 | - (NSString *)description 165 | { 166 | return [NSString stringWithFormat: 167 | @"", 168 | (unsigned long)_fixedLength, (unsigned long)_timeout, (unsigned long)_bytesRead, _version, (unsigned long)_statusCode]; 169 | } 170 | 171 | @end 172 | 173 | 174 | @implementation SPDYSocketWriteOp 175 | 176 | - (id)initWithData:(NSData *)data timeout:(NSTimeInterval)timeout tag:(long)tag 177 | { 178 | self = [super init]; 179 | if (self) { 180 | _buffer = data; 181 | _bytesWritten = 0; 182 | _timeout = timeout; 183 | _tag = tag; 184 | } 185 | return self; 186 | } 187 | 188 | - (NSString *)description 189 | { 190 | return [NSString stringWithFormat: 191 | @"", 192 | (unsigned long)_timeout, _tag, (unsigned long)_buffer.length, (unsigned long)_bytesWritten]; 193 | } 194 | 195 | @end 196 | 197 | 198 | @implementation SPDYSocketProxyWriteOp 199 | 200 | - (id)initWithOrigin:(SPDYOrigin *)origin timeout:(NSTimeInterval)timeout 201 | { 202 | NSString *httpConnect = [NSString stringWithFormat: 203 | @"CONNECT %@:%u HTTP/1.1\r\nHost: %@:%u\r\nConnection: keep-alive\r\nUser-Agent: SPDYTest\r\n\r\n", 204 | origin.host, 205 | origin.port, 206 | origin.host, 207 | origin.port]; 208 | NSData *httpConnectData = [httpConnect dataUsingEncoding:NSUTF8StringEncoding]; 209 | self = [super initWithData:httpConnectData timeout:timeout tag:0]; 210 | return self; 211 | } 212 | 213 | - (NSString *)description 214 | { 215 | NSString *httpConnect = [[NSString alloc] initWithData:_buffer encoding:NSUTF8StringEncoding]; 216 | return [NSString stringWithFormat: 217 | @" connect: %@", 218 | (unsigned long)_timeout, _tag, (unsigned long)_buffer.length, (unsigned long)_bytesWritten, httpConnect]; 219 | } 220 | 221 | @end 222 | 223 | 224 | @implementation SPDYSocketTLSOp 225 | 226 | - (id)initWithTLSSettings:(NSDictionary *)settings 227 | { 228 | self = [super init]; 229 | if (self) { 230 | _tlsSettings = [settings copy]; 231 | } 232 | return self; 233 | } 234 | 235 | - (NSString *)description 236 | { 237 | return [NSString stringWithFormat:@"", _tlsSettings]; 238 | } 239 | 240 | @end 241 | 242 | 243 | -------------------------------------------------------------------------------- /SPDY/SPDYStopwatch.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStopwatch.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #import "SPDYDefinitions.h" 13 | 14 | @interface SPDYStopwatch : NSObject 15 | 16 | + (SPDYTimeInterval)currentSystemTime; 17 | + (SPDYTimeInterval)currentAbsoluteTime; 18 | 19 | @property (nonatomic, readonly) SPDYTimeInterval startTime; 20 | @property (nonatomic, readonly) SPDYTimeInterval startSystemTime; 21 | 22 | - (id)init; 23 | - (void)reset; 24 | - (SPDYTimeInterval)elapsedSeconds; 25 | 26 | // Unit tests only 27 | #if COVERAGE 28 | + (void)sleep:(SPDYTimeInterval)delay; 29 | #endif 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /SPDY/SPDYStopwatch.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStopwatch.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import 13 | #import "SPDYStopwatch.h" 14 | 15 | @implementation SPDYStopwatch 16 | 17 | static dispatch_once_t __initTimebase; 18 | static double __machTimebaseToSeconds; 19 | static mach_timebase_info_data_t __machTimebase; 20 | #if COVERAGE 21 | static SPDYTimeInterval __currentTimeOffset; 22 | #endif 23 | 24 | + (void)initialize 25 | { 26 | dispatch_once(&__initTimebase, ^{ 27 | kern_return_t status = mach_timebase_info(&__machTimebase); 28 | // Everything will be 0 if this fails. 29 | if (status != KERN_SUCCESS) { 30 | __machTimebase.numer = 0; 31 | __machTimebase.denom = 1; 32 | } 33 | __machTimebaseToSeconds = (double)__machTimebase.numer / ((double)__machTimebase.denom * 1000000000.0); 34 | #if COVERAGE 35 | __currentTimeOffset = 0; 36 | #endif 37 | }); 38 | } 39 | 40 | + (SPDYTimeInterval)currentSystemTime 41 | { 42 | #if COVERAGE 43 | return (SPDYTimeInterval)mach_absolute_time() * __machTimebaseToSeconds + __currentTimeOffset; 44 | #else 45 | return (SPDYTimeInterval)mach_absolute_time() * __machTimebaseToSeconds; 46 | #endif 47 | } 48 | 49 | + (SPDYTimeInterval)currentAbsoluteTime 50 | { 51 | #if COVERAGE 52 | return CFAbsoluteTimeGetCurrent() + __currentTimeOffset; 53 | #else 54 | return CFAbsoluteTimeGetCurrent(); 55 | #endif 56 | } 57 | 58 | #if COVERAGE 59 | + (void)sleep:(SPDYTimeInterval)delay 60 | { 61 | __currentTimeOffset += delay; 62 | } 63 | #endif 64 | 65 | - (id)init 66 | { 67 | self = [super init]; 68 | if (self) { 69 | _startTime = [SPDYStopwatch currentAbsoluteTime]; 70 | _startSystemTime = [SPDYStopwatch currentSystemTime]; 71 | } 72 | return self; 73 | } 74 | 75 | - (void)reset 76 | { 77 | _startTime = [SPDYStopwatch currentAbsoluteTime]; 78 | _startSystemTime = [SPDYStopwatch currentSystemTime]; 79 | } 80 | 81 | - (SPDYTimeInterval)elapsedSeconds 82 | { 83 | return [SPDYStopwatch currentAbsoluteTime] - _startTime; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /SPDY/SPDYStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStream.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYDefinitions.h" 14 | 15 | @class SPDYProtocol; 16 | @class SPDYMetadata; 17 | @class SPDYStream; 18 | 19 | @protocol SPDYStreamDelegate 20 | - (void)streamCanceled:(SPDYStream *)stream; 21 | @optional 22 | - (void)streamClosed:(SPDYStream *)stream; 23 | - (void)streamDataAvailable:(SPDYStream *)stream; 24 | - (void)streamDataFinished:(SPDYStream *)stream; 25 | @end 26 | 27 | @interface SPDYStream : NSObject 28 | @property (nonatomic, weak) id client; 29 | @property (nonatomic, weak) id delegate; 30 | @property (nonatomic) SPDYMetadata *metadata; 31 | @property (nonatomic) NSData *data; 32 | @property (nonatomic) NSInputStream *dataStream; 33 | @property (nonatomic, weak) NSURLRequest *request; 34 | @property (nonatomic, weak) SPDYProtocol *protocol; 35 | @property (nonatomic) SPDYStreamId streamId; 36 | @property (nonatomic) uint8_t priority; 37 | @property (nonatomic) bool local; 38 | @property (nonatomic) bool localSideClosed; 39 | @property (nonatomic) bool remoteSideClosed; 40 | @property (nonatomic, readonly) bool closed; 41 | @property (nonatomic) bool receivedReply; 42 | @property (nonatomic, readonly) bool hasDataAvailable; 43 | @property (nonatomic, readonly) bool hasDataPending; 44 | @property (nonatomic) uint32_t sendWindowSize; 45 | @property (nonatomic) uint32_t receiveWindowSize; 46 | @property (nonatomic) uint32_t sendWindowSizeLowerBound; 47 | @property (nonatomic) uint32_t receiveWindowSizeLowerBound; 48 | 49 | - (id)initWithProtocol:(SPDYProtocol *)protocol; 50 | - (void)startWithStreamId:(SPDYStreamId)id sendWindowSize:(uint32_t)sendWindowSize receiveWindowSize:(uint32_t)receiveWindowSize; 51 | - (bool)reset; 52 | - (NSData *)readData:(NSUInteger)length error:(NSError **)pError; 53 | - (void)cancel; 54 | - (void)closeWithError:(NSError *)error; 55 | - (void)didReceiveResponse:(NSDictionary *)headers; 56 | - (void)didLoadData:(NSData *)data; 57 | - (void)markBlocked; 58 | - (void)markUnblocked; 59 | @end 60 | -------------------------------------------------------------------------------- /SPDY/SPDYStreamManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStreamManager.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYDefinitions.h" 14 | 15 | @class SPDYProtocol; 16 | @class SPDYStream; 17 | 18 | /** 19 | Data structure for management of SPDYStreams. 20 | */ 21 | @interface SPDYStreamManager : NSObject 22 | 23 | @property (nonatomic, readonly) NSUInteger count; 24 | @property (nonatomic, readonly) NSUInteger localCount; 25 | @property (nonatomic, readonly) NSUInteger remoteCount; 26 | - (void)addStream:(SPDYStream *)stream; 27 | - (id)objectAtIndexedSubscript:(NSUInteger)idx; 28 | - (id)objectForKeyedSubscript:(id)key; 29 | - (SPDYStream *)nextPriorityStream; 30 | - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx; 31 | - (void)removeStreamWithStreamId:(SPDYStreamId)streamId; 32 | - (void)removeStreamForProtocol:(SPDYProtocol *)protocol; 33 | - (void)removeAllStreams; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /SPDY/SPDYStreamManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStreamManager.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYStreamManager.h" 14 | #import "SPDYProtocol.h" 15 | #import "SPDYStream.h" 16 | 17 | 18 | @interface SPDYStreamNode : NSObject 19 | @end 20 | 21 | @implementation SPDYStreamNode 22 | { 23 | @public 24 | __strong SPDYStreamNode *next; 25 | __strong SPDYStreamNode *prev; 26 | __strong SPDYStream *stream; 27 | __unsafe_unretained SPDYProtocol *protocol; 28 | SPDYStreamId streamId; 29 | } 30 | @end 31 | 32 | @interface SPDYStreamManager () 33 | - (void)_removeListNode:(SPDYStreamNode *)node; 34 | @end 35 | 36 | @implementation SPDYStreamManager 37 | { 38 | SPDYStreamNode *_priorityHead[8]; 39 | SPDYStreamNode *_priorityLast[8]; 40 | CFMutableDictionaryRef _nodesByStreamId; 41 | CFMutableDictionaryRef _nodesByProtocol; 42 | NSUInteger _localCount; 43 | NSUInteger _remoteCount; 44 | unsigned long _mutations; 45 | } 46 | 47 | Boolean SPDYStreamIdEqual(const void *key1, const void *key2) { 48 | return (SPDYStreamId)key1 == (SPDYStreamId)key2; 49 | } 50 | 51 | CFHashCode SPDYStreamIdHash(const void *key) { 52 | return (CFHashCode)((SPDYStreamId)key); 53 | } 54 | 55 | CFStringRef SPDYStreamIdCopyDescription(const void *key) { 56 | return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), (SPDYStreamId)key); 57 | } 58 | 59 | - (id)init 60 | { 61 | NSAssert(sizeof(void *) <= sizeof(unsigned long), @"pointer width must be <= unsigned long width"); 62 | NSAssert(sizeof(void *) >= sizeof(SPDYStreamId), @"pointer width must be >= SPDYStreamId width"); 63 | 64 | self = [super init]; 65 | if (self) { 66 | CFDictionaryKeyCallBacks SPDYStreamIdKeyCallbacks = { 67 | 0, NULL, NULL, 68 | SPDYStreamIdCopyDescription, 69 | SPDYStreamIdEqual, 70 | SPDYStreamIdHash 71 | }; 72 | _nodesByStreamId = CFDictionaryCreateMutable( 73 | kCFAllocatorDefault, 100, 74 | &SPDYStreamIdKeyCallbacks, 75 | &kCFTypeDictionaryValueCallBacks 76 | ); 77 | _nodesByProtocol = CFDictionaryCreateMutable( 78 | kCFAllocatorDefault, 100, 79 | &kCFTypeDictionaryKeyCallBacks, 80 | &kCFTypeDictionaryValueCallBacks 81 | ); 82 | _localCount = 0; 83 | _remoteCount = 0; 84 | _mutations = 0; 85 | } 86 | return self; 87 | } 88 | 89 | - (void)dealloc 90 | { 91 | CFRelease(_nodesByStreamId); 92 | CFRelease(_nodesByProtocol); 93 | } 94 | 95 | - (NSUInteger)count 96 | { 97 | return _localCount + _remoteCount; 98 | } 99 | 100 | - (id)objectAtIndexedSubscript:(NSUInteger)idx 101 | { 102 | SPDYStreamNode *node = (id)CFDictionaryGetValue(_nodesByStreamId, (void *)idx); 103 | return node ? node->stream : nil; 104 | } 105 | 106 | - (id)objectForKeyedSubscript:(id)key 107 | { 108 | SPDYStreamNode *node = (id)CFDictionaryGetValue(_nodesByProtocol, (__bridge CFTypeRef)key); 109 | return node ? node->stream : nil; 110 | } 111 | 112 | - (SPDYStream *)nextPriorityStream 113 | { 114 | SPDYStreamNode *currentNode; 115 | for (int priority = 0; priority < 8 && currentNode == NULL; priority++) { 116 | currentNode = _priorityHead[priority]; 117 | } 118 | 119 | if (currentNode) { 120 | return currentNode->stream; 121 | } 122 | 123 | return nil; 124 | } 125 | 126 | - (void)addStream:(SPDYStream *)stream 127 | { 128 | SPDYStreamNode *node = [[SPDYStreamNode alloc] init]; 129 | node->stream = stream; 130 | node->protocol = stream.protocol; 131 | node->streamId = stream.streamId; 132 | [self _addListNode:node]; 133 | } 134 | 135 | - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx 136 | { 137 | if (obj) { 138 | SPDYStreamNode *node = [[SPDYStreamNode alloc] init]; 139 | SPDYStream *stream = obj; 140 | node->stream = stream; 141 | node->protocol = stream.protocol; 142 | node->streamId = (SPDYStreamId)idx; 143 | 144 | [self _addListNode:node]; 145 | } else { 146 | [self removeStreamWithStreamId:(SPDYStreamId)idx]; 147 | } 148 | } 149 | 150 | - (void)_addListNode:(SPDYStreamNode *)node 151 | { 152 | // Update linked list 153 | uint8_t priority = node->stream.priority; 154 | if (_priorityHead[priority] == NULL) { 155 | _priorityHead[priority] = node; 156 | _priorityLast[priority] = node; 157 | } else { 158 | _priorityLast[priority]->next = node; 159 | node->prev = _priorityLast[priority]; 160 | _priorityLast[priority] = node; 161 | } 162 | 163 | // Update hash maps 164 | NSAssert(node->streamId != 0 || node->protocol != nil, @"cannot insert unaddressable stream"); 165 | NSAssert(CFDictionaryGetValue(_nodesByStreamId, (void *)(uintptr_t)node->streamId) == NULL, @"cannot insert stream with duplicate streamId"); 166 | if (node->streamId) { 167 | CFDictionarySetValue(_nodesByStreamId, (void *)(uintptr_t)node->streamId, (__bridge CFTypeRef)node); 168 | } 169 | if (node->protocol) { 170 | CFDictionarySetValue(_nodesByProtocol, (__bridge CFTypeRef)node->protocol, (__bridge CFTypeRef)node); 171 | } 172 | 173 | // Update counts 174 | if (node->stream.local) { 175 | _localCount += 1; 176 | } else { 177 | _remoteCount += 1; 178 | } 179 | 180 | _mutations += 1; 181 | } 182 | 183 | - (void)removeStreamWithStreamId:(SPDYStreamId)streamId 184 | { 185 | SPDYStreamNode *node = (id)CFDictionaryGetValue(_nodesByStreamId, (void *)(uintptr_t)streamId); 186 | if (node) [self _removeListNode:node]; 187 | } 188 | 189 | - (void)removeStreamForProtocol:(SPDYProtocol *)protocol 190 | { 191 | SPDYStreamNode *node = (id)CFDictionaryGetValue(_nodesByProtocol, (__bridge CFTypeRef)protocol); 192 | if (node) [self _removeListNode:node]; 193 | } 194 | 195 | - (void)_removeListNode:(SPDYStreamNode *)node 196 | { 197 | // Update linked list 198 | uint8_t priority = node->stream.priority; 199 | if (node->next != NULL) node->next->prev = node->prev; 200 | if (node->prev != NULL) node->prev->next = node->next; 201 | if (_priorityHead[priority] == node) _priorityHead[priority] = node->next; 202 | if (_priorityLast[priority] == node) _priorityLast[priority] = node->prev; 203 | 204 | // Update hash maps 205 | if (node->streamId) { 206 | CFDictionaryRemoveValue(_nodesByStreamId, (void *)(uintptr_t)node->streamId); 207 | } 208 | if (node->protocol) { 209 | CFDictionaryRemoveValue(_nodesByProtocol, (__bridge CFTypeRef)node->protocol); 210 | } 211 | 212 | // Update counts 213 | if (node->stream.local) { 214 | _localCount -= 1; 215 | } else { 216 | _remoteCount -= 1; 217 | } 218 | 219 | _mutations += 1; 220 | } 221 | 222 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len 223 | { 224 | SPDYStreamNode *currentNode = (__bridge SPDYStreamNode *)((void *)state->extra[0]); 225 | unsigned long *priority = &(state->state); 226 | 227 | for (; *priority < 8 && currentNode == NULL; *priority += 1) { 228 | currentNode = _priorityHead[*priority]; 229 | } 230 | 231 | if (currentNode == NULL) { 232 | return 0; 233 | } 234 | 235 | NSUInteger i; 236 | for (i = 0; i < len && currentNode != NULL; i++) { 237 | buffer[i] = currentNode->stream; 238 | currentNode = currentNode->next; 239 | } 240 | 241 | state->extra[0] = (unsigned long)currentNode; 242 | state->itemsPtr = buffer; 243 | state->mutationsPtr = &_mutations; 244 | return i; 245 | } 246 | 247 | - (void)removeAllStreams 248 | { 249 | // Update linked list 250 | for (int i = 0; i < 8; i++) { 251 | _priorityHead[i] = NULL; 252 | _priorityLast[i] = NULL; 253 | } 254 | 255 | // Update hash maps 256 | CFDictionaryRemoveAllValues(_nodesByStreamId); 257 | CFDictionaryRemoveAllValues(_nodesByProtocol); 258 | 259 | // Update counts 260 | _localCount = 0; 261 | _remoteCount = 0; 262 | 263 | _mutations += 1; 264 | } 265 | 266 | @end 267 | -------------------------------------------------------------------------------- /SPDY/SPDYTLSTrustEvaluator.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYTLSTrustEvaluator.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | 14 | @protocol SPDYTLSTrustEvaluator 15 | - (BOOL)evaluateServerTrust:(SecTrustRef)trust forHost:(NSString *)host; 16 | @end 17 | -------------------------------------------------------------------------------- /SPDY/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYLoggingTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYLoggingTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #import 13 | #import "SPDYCommonLogger.h" 14 | #import "SPDYProtocol.h" 15 | 16 | // Private to SPDYProtocol.m but need access to test 17 | @interface SPDYAssertionHandler : NSAssertionHandler 18 | @property (nonatomic) BOOL abortOnFailure; 19 | @end 20 | 21 | @interface SPDYLoggingTest : SenTestCase 22 | @end 23 | 24 | @implementation SPDYLoggingTest 25 | { 26 | NSString *_lastMessage; 27 | SPDYLogLevel _lastLevel; 28 | } 29 | 30 | - (void)log:(NSString *)message atLevel:(SPDYLogLevel)logLevel 31 | { 32 | NSLog(@"Got log message: %@", message); 33 | _lastMessage = message; 34 | _lastLevel = logLevel; 35 | 36 | if ([message rangeOfString:@"delay"].length != 0) { 37 | sleep(1); 38 | } 39 | } 40 | 41 | - (void)setUp 42 | { 43 | _lastMessage = nil; 44 | _lastLevel = -1; 45 | } 46 | 47 | - (void)tearDown 48 | { 49 | [SPDYProtocol setLogger:nil]; 50 | } 51 | 52 | - (BOOL)logAndWaitAtLevel:(SPDYLogLevel)level expectLog:(BOOL)expectLog 53 | { 54 | _lastMessage = nil; 55 | _lastLevel = -1; 56 | 57 | switch (level) { 58 | case SPDYLogLevelDebug: 59 | SPDY_DEBUG(@"debug %d", 1); 60 | break; 61 | case SPDYLogLevelInfo: 62 | SPDY_INFO(@"info %d", 1); 63 | break; 64 | case SPDYLogLevelWarning: 65 | SPDY_WARNING(@"warning %d", 1); 66 | break; 67 | case SPDYLogLevelError: 68 | SPDY_ERROR(@"error %d", 1); 69 | break; 70 | case SPDYLogLevelDisabled: 71 | STAssertTrue(NO, @"not a valid log level"); 72 | break; 73 | } 74 | 75 | [SPDYCommonLogger flush]; 76 | 77 | if (_lastMessage == nil && !expectLog) { 78 | return YES; 79 | } 80 | 81 | switch (level) { 82 | case SPDYLogLevelDebug: 83 | STAssertEqualObjects(_lastMessage, @"debug 1", nil); 84 | STAssertEquals(_lastLevel, SPDYLogLevelDebug, nil); 85 | break; 86 | case SPDYLogLevelInfo: 87 | STAssertEqualObjects(_lastMessage, @"info 1", nil); 88 | STAssertEquals(_lastLevel, SPDYLogLevelInfo, nil); 89 | break; 90 | case SPDYLogLevelWarning: 91 | STAssertEqualObjects(_lastMessage, @"warning 1", nil); 92 | STAssertEquals(_lastLevel, SPDYLogLevelWarning, nil); 93 | break; 94 | case SPDYLogLevelError: 95 | STAssertEqualObjects(_lastMessage, @"error 1", nil); 96 | STAssertEquals(_lastLevel, SPDYLogLevelError, nil); 97 | break; 98 | case SPDYLogLevelDisabled: 99 | break; 100 | } 101 | 102 | return NO; 103 | } 104 | 105 | - (void)testAccessors 106 | { 107 | [SPDYProtocol setLogger:self]; 108 | [SPDYProtocol setLoggerLevel:SPDYLogLevelDebug]; 109 | 110 | STAssertEquals([SPDYProtocol currentLogger], self, nil); 111 | STAssertEquals([SPDYProtocol currentLoggerLevel], SPDYLogLevelDebug, nil); 112 | 113 | [SPDYProtocol setLogger:nil]; 114 | [SPDYProtocol setLoggerLevel:SPDYLogLevelDisabled]; 115 | 116 | STAssertNil([SPDYProtocol currentLogger], nil); 117 | STAssertEquals([SPDYProtocol currentLoggerLevel], SPDYLogLevelDisabled, nil); 118 | } 119 | 120 | - (void)testLoggingAtDebugLevel 121 | { 122 | [SPDYProtocol setLogger:self]; 123 | [SPDYProtocol setLoggerLevel:SPDYLogLevelDebug]; 124 | 125 | [self logAndWaitAtLevel:SPDYLogLevelDebug expectLog:YES]; 126 | [self logAndWaitAtLevel:SPDYLogLevelInfo expectLog:YES]; 127 | [self logAndWaitAtLevel:SPDYLogLevelWarning expectLog:YES]; 128 | [self logAndWaitAtLevel:SPDYLogLevelError expectLog:YES]; 129 | } 130 | 131 | - (void)testLoggingAtInfoLevel 132 | { 133 | [SPDYProtocol setLogger:self]; 134 | [SPDYProtocol setLoggerLevel:SPDYLogLevelInfo]; 135 | 136 | [self logAndWaitAtLevel:SPDYLogLevelDebug expectLog:NO]; 137 | [self logAndWaitAtLevel:SPDYLogLevelInfo expectLog:YES]; 138 | [self logAndWaitAtLevel:SPDYLogLevelWarning expectLog:YES]; 139 | [self logAndWaitAtLevel:SPDYLogLevelError expectLog:YES]; 140 | } 141 | 142 | - (void)testLoggingAtWarningLevel 143 | { 144 | [SPDYProtocol setLogger:self]; 145 | [SPDYProtocol setLoggerLevel:SPDYLogLevelWarning]; 146 | 147 | [self logAndWaitAtLevel:SPDYLogLevelDebug expectLog:NO]; 148 | [self logAndWaitAtLevel:SPDYLogLevelInfo expectLog:NO]; 149 | [self logAndWaitAtLevel:SPDYLogLevelWarning expectLog:YES]; 150 | [self logAndWaitAtLevel:SPDYLogLevelError expectLog:YES]; 151 | } 152 | 153 | - (void)testLoggingAtErrorLevel 154 | { 155 | [SPDYProtocol setLogger:self]; 156 | [SPDYProtocol setLoggerLevel:SPDYLogLevelError]; 157 | 158 | [self logAndWaitAtLevel:SPDYLogLevelDebug expectLog:NO]; 159 | [self logAndWaitAtLevel:SPDYLogLevelInfo expectLog:NO]; 160 | [self logAndWaitAtLevel:SPDYLogLevelWarning expectLog:NO]; 161 | [self logAndWaitAtLevel:SPDYLogLevelError expectLog:YES]; 162 | } 163 | 164 | - (void)testLoggingWhenDisabled 165 | { 166 | [SPDYProtocol setLogger:self]; 167 | [SPDYProtocol setLoggerLevel:SPDYLogLevelDisabled]; 168 | 169 | [self logAndWaitAtLevel:SPDYLogLevelDebug expectLog:NO]; 170 | [self logAndWaitAtLevel:SPDYLogLevelInfo expectLog:NO]; 171 | [self logAndWaitAtLevel:SPDYLogLevelWarning expectLog:NO]; 172 | [self logAndWaitAtLevel:SPDYLogLevelError expectLog:NO]; 173 | } 174 | 175 | - (void)testLoggingWhenNil 176 | { 177 | [SPDYProtocol setLogger:nil]; 178 | [SPDYProtocol setLoggerLevel:SPDYLogLevelError]; 179 | 180 | SPDY_DEBUG(@"debug %d", 1); 181 | STAssertNil(_lastMessage, nil); 182 | 183 | SPDY_INFO(@"info %d", 1); 184 | STAssertNil(_lastMessage, nil); 185 | 186 | SPDY_WARNING(@"warning %d", 1); 187 | STAssertNil(_lastMessage, nil); 188 | 189 | SPDY_ERROR(@"error %d", 1); 190 | STAssertNil(_lastMessage, nil); 191 | } 192 | 193 | - (void)testAssertionHandler 194 | { 195 | [SPDYProtocol setLogger:self]; 196 | [SPDYProtocol setLoggerLevel:SPDYLogLevelError]; 197 | 198 | // Register SPDYProtocol's assertion handler on our thread, but disable the abort() call. 199 | SPDYAssertionHandler *assertionHandler = [[SPDYAssertionHandler alloc] init]; 200 | assertionHandler.abortOnFailure = NO; 201 | [NSThread currentThread].threadDictionary[NSAssertionHandlerKey] = assertionHandler; 202 | 203 | NSAssert(NO, @"test failing method"); 204 | STAssertNotNil(_lastMessage, nil); 205 | STAssertEquals(_lastLevel, SPDYLogLevelError, nil); 206 | 207 | _lastMessage = nil; 208 | _lastLevel = nil; 209 | NSCAssert(NO, @"test failing function"); 210 | STAssertNotNil(_lastMessage, nil); 211 | STAssertEquals(_lastLevel, SPDYLogLevelError, nil); 212 | 213 | // All done 214 | [[NSThread currentThread].threadDictionary removeObjectForKey:NSAssertionHandlerKey]; 215 | } 216 | 217 | @end 218 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMetadataTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMetadataTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #import 13 | #import "SPDYMetadata+Utils.h" 14 | #import "SPDYProtocol.h" 15 | 16 | @interface SPDYMetadataTest : SenTestCase 17 | @end 18 | 19 | @implementation SPDYMetadataTest 20 | 21 | - (void)setUp { 22 | [super setUp]; 23 | } 24 | 25 | - (void)tearDown 26 | { 27 | [super tearDown]; 28 | } 29 | 30 | - (SPDYMetadata *)createTestMetadata 31 | { 32 | SPDYMetadata *metadata = [[SPDYMetadata alloc] init]; 33 | metadata.version = @"3.2"; 34 | metadata.streamId = 1; 35 | metadata.latencyMs = 100; 36 | metadata.txBytes = 200; 37 | metadata.rxBytes = 300; 38 | metadata.cellular = YES; 39 | metadata.blockedMs = 400; 40 | metadata.connectedMs = 500; 41 | metadata.hostAddress = @"1.2.3.4"; 42 | metadata.hostPort = 1; 43 | metadata.viaProxy = YES; 44 | metadata.proxyStatus = SPDYProxyStatusManual; 45 | 46 | return metadata; 47 | } 48 | 49 | - (void)verifyTestMetadata:(SPDYMetadata *)metadata 50 | { 51 | STAssertNotNil(metadata, nil); 52 | STAssertEqualObjects(metadata.version, @"3.2", nil); 53 | STAssertEquals(metadata.streamId, (NSUInteger)1, nil); 54 | STAssertEquals(metadata.latencyMs, (NSInteger)100, nil); 55 | STAssertEquals(metadata.txBytes, (NSUInteger)200, nil); 56 | STAssertEquals(metadata.rxBytes, (NSUInteger)300, nil); 57 | STAssertEquals(metadata.cellular, YES, nil); 58 | STAssertEquals(metadata.blockedMs, (NSUInteger)400, nil); 59 | STAssertEquals(metadata.connectedMs, (NSUInteger)500, nil); 60 | STAssertEqualObjects(metadata.hostAddress, @"1.2.3.4", nil); 61 | STAssertEquals(metadata.hostPort, (NSUInteger)1, nil); 62 | STAssertEquals(metadata.viaProxy, YES, nil); 63 | STAssertEquals(metadata.proxyStatus, SPDYProxyStatusManual, nil); 64 | } 65 | 66 | #pragma mark Tests 67 | 68 | - (void)testMemberRetention 69 | { 70 | // Test all references. Note we are creating strings with initWithFormat to ensure they 71 | // are released. Static strings are not dealloc'd. 72 | SPDYMetadata *metadata = [self createTestMetadata]; 73 | NSString * __weak weakString = nil; // just an extra check to ensure test works 74 | @autoreleasepool { 75 | NSString *testString = [[NSString alloc] initWithFormat:@"foo %d", 1]; 76 | weakString = testString; 77 | 78 | metadata.hostAddress = [[NSString alloc] initWithFormat:@"%d.%d.%d.%d", 10, 11, 12, 13]; 79 | metadata.version = [[NSString alloc] initWithFormat:@"SPDY/%d.%d", 3, 1]; 80 | } 81 | 82 | STAssertNil(weakString, nil); 83 | 84 | STAssertEqualObjects(metadata.hostAddress, @"10.11.12.13", nil); 85 | STAssertEqualObjects(metadata.version, @"SPDY/3.1", nil); 86 | } 87 | 88 | - (void)testAssociatedDictionary 89 | { 90 | SPDYMetadata *originalMetadata = [self createTestMetadata]; 91 | NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init]; 92 | 93 | [SPDYMetadata setMetadata:originalMetadata forAssociatedDictionary:associatedDictionary]; 94 | SPDYMetadata *metadata = [SPDYMetadata metadataForAssociatedDictionary:associatedDictionary]; 95 | 96 | [self verifyTestMetadata:metadata]; 97 | } 98 | 99 | - (void)testAssociatedDictionaryLastOneWins 100 | { 101 | SPDYMetadata *originalMetadata1 = [self createTestMetadata]; 102 | SPDYMetadata *originalMetadata2 = [self createTestMetadata]; 103 | originalMetadata2.version = @"3.3"; 104 | NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init]; 105 | 106 | [SPDYMetadata setMetadata:originalMetadata1 forAssociatedDictionary:associatedDictionary]; 107 | [SPDYMetadata setMetadata:originalMetadata2 forAssociatedDictionary:associatedDictionary]; 108 | SPDYMetadata *metadata = [SPDYMetadata metadataForAssociatedDictionary:associatedDictionary]; 109 | 110 | // Last one wins 111 | STAssertNotNil(metadata, nil); 112 | STAssertEqualObjects(metadata.version, @"3.3", nil); 113 | STAssertEquals(metadata.streamId, (NSUInteger)1, nil); 114 | STAssertEquals(metadata.latencyMs, (NSInteger)100, nil); 115 | STAssertEquals(metadata.txBytes, (NSUInteger)200, nil); 116 | STAssertEquals(metadata.rxBytes, (NSUInteger)300, nil); 117 | } 118 | 119 | - (void)testAssociatedDictionaryWhenEmpty 120 | { 121 | NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init]; 122 | SPDYMetadata *metadata = [SPDYMetadata metadataForAssociatedDictionary:associatedDictionary]; 123 | STAssertNil(metadata, nil); 124 | } 125 | 126 | - (void)testMetadataAfterReleaseShouldNotBeNil 127 | { 128 | NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init]; 129 | SPDYMetadata * __weak weakOriginalMetadata = nil; 130 | @autoreleasepool { 131 | SPDYMetadata *originalMetadata = [self createTestMetadata]; 132 | weakOriginalMetadata = originalMetadata; 133 | [SPDYMetadata setMetadata:originalMetadata forAssociatedDictionary:associatedDictionary]; 134 | } 135 | 136 | SPDYMetadata *metadata = [SPDYMetadata metadataForAssociatedDictionary:associatedDictionary]; 137 | 138 | // Since the identifier maintains a reference, these will be alive 139 | STAssertNotNil(weakOriginalMetadata, nil); 140 | STAssertNotNil(metadata, nil); 141 | } 142 | 143 | - (void)testMetadataAfterAssociatedDictionaryDeallocShouldBeNil 144 | { 145 | SPDYMetadata * __weak weakOriginalMetadata = nil; 146 | @autoreleasepool { 147 | SPDYMetadata *originalMetadata = [self createTestMetadata]; 148 | weakOriginalMetadata = originalMetadata; 149 | NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init]; 150 | [SPDYMetadata setMetadata:originalMetadata forAssociatedDictionary:associatedDictionary]; 151 | } 152 | 153 | STAssertNil(weakOriginalMetadata, nil); 154 | } 155 | 156 | - (void)testAssociatedDictionarySameRef 157 | { 158 | NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init]; 159 | SPDYMetadata * __weak weakOriginalMetadata = nil; 160 | SPDYMetadata *metadata; 161 | @autoreleasepool { 162 | SPDYMetadata *originalMetadata = [self createTestMetadata]; 163 | weakOriginalMetadata = originalMetadata; 164 | [SPDYMetadata setMetadata:originalMetadata forAssociatedDictionary:associatedDictionary]; 165 | 166 | // Pull metadata out and keep a strong reference. To ensure this reference is the same 167 | // as the original one put in. 168 | metadata = [SPDYMetadata metadataForAssociatedDictionary:associatedDictionary]; 169 | } 170 | 171 | STAssertNotNil(weakOriginalMetadata, nil); 172 | STAssertNotNil(metadata, nil); 173 | } 174 | 175 | - (void)testAssociatedDictionaryDoesMutateOriginal 176 | { 177 | // We don't necessarily want to allow mutating the original, but this documents the behavior. 178 | // The public SPDYMetadata interface exposes all properties as readonly. 179 | NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init]; 180 | SPDYMetadata *metadata; 181 | 182 | SPDYMetadata *originalMetadata = [self createTestMetadata]; 183 | [SPDYMetadata setMetadata:originalMetadata forAssociatedDictionary:associatedDictionary]; 184 | 185 | metadata = [SPDYMetadata metadataForAssociatedDictionary:associatedDictionary]; 186 | metadata.version = @"3.3"; 187 | metadata.streamId = 2; 188 | metadata.cellular = NO; 189 | metadata.proxyStatus = SPDYProxyStatusAuto; 190 | 191 | // If not mutating 192 | //[self verifyTestMetadata:originalMetadata]; 193 | 194 | // If mutating 195 | STAssertEqualObjects(metadata.version, @"3.3", nil); 196 | STAssertEquals(metadata.streamId, (NSUInteger)2, nil); 197 | STAssertEquals(metadata.cellular, NO, nil); 198 | STAssertEquals(metadata.proxyStatus, SPDYProxyStatusAuto, nil); 199 | } 200 | 201 | @end 202 | 203 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockFrameDecoderDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockFrameDecoderDelegate.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYFrameDecoder.h" 14 | 15 | @interface SPDYMockFrameDecoderDelegate : NSObject 16 | @property (nonatomic, strong, readonly) NSArray *framesReceived; 17 | @property (nonatomic, strong, readonly) id lastFrame; 18 | @property (nonatomic, readonly) NSString *lastDelegateMessage; 19 | @property (nonatomic, readonly) NSUInteger frameCount; 20 | - (void)clear; 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockFrameDecoderDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockFrameDecoderDelegate.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import "SPDYMockFrameDecoderDelegate.h" 13 | 14 | 15 | #pragma clang diagnostic push 16 | #pragma clang diagnostic ignored "-Wprotocol" 17 | @implementation SPDYMockFrameDecoderDelegate 18 | #pragma clang diagnostic pop 19 | { 20 | NSMutableArray *_framesReceived; 21 | } 22 | 23 | - (id)init 24 | { 25 | self = [super init]; 26 | if (self) { 27 | _framesReceived = [[NSMutableArray alloc] init]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)forwardInvocation:(NSInvocation *)invocation 33 | { 34 | _lastDelegateMessage = NSStringFromSelector([invocation selector]); 35 | __unsafe_unretained SPDYFrame *frame; 36 | [invocation getArgument:&frame atIndex:2]; 37 | [_framesReceived addObject:frame]; 38 | } 39 | 40 | - (id)lastFrame 41 | { 42 | return _framesReceived.lastObject; 43 | } 44 | 45 | - (NSUInteger)frameCount 46 | { 47 | return _framesReceived.count; 48 | } 49 | 50 | - (void)clear 51 | { 52 | [_framesReceived removeAllObjects]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockFrameEncoderDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockFrameEncoderDelegate.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier on 9/19/2014. 10 | // 11 | 12 | #import "SPDYFrameEncoder.h" 13 | #import "SPDYFrameDecoder.h" 14 | 15 | // These are used by test classes to encode frames into an NSMutableData structure, which can 16 | // be provided to the spdy socket. Or, the test can take the last bytes written to the socket 17 | // and decode them into a frame. 18 | 19 | @interface SPDYMockFrameEncoderDelegate : NSObject 20 | @property (nonatomic) NSMutableData *lastEncodedData; 21 | - (void)clear; 22 | @end 23 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockFrameEncoderDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockFrameEncoderDelegate.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier on 9/19/2014. 10 | // 11 | 12 | #import "SPDYMockFrameEncoderDelegate.h" 13 | 14 | #pragma mark SPDYFrameEncoderAccumulator 15 | 16 | @implementation SPDYMockFrameEncoderDelegate 17 | 18 | - (id)init 19 | { 20 | self = [super init]; 21 | if (self) { 22 | _lastEncodedData = [NSMutableData data]; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)didEncodeData:(NSData *)data frameEncoder:(SPDYFrameEncoder *)encoder 28 | { 29 | [self.lastEncodedData appendData:data]; 30 | } 31 | 32 | - (void)didEncodeData:(NSData *)data withTag:(uint32_t)tag frameEncoder:(SPDYFrameEncoder *)encoder; 33 | { 34 | [self.lastEncodedData appendData:data]; 35 | } 36 | 37 | - (void)clear 38 | { 39 | [self.lastEncodedData setLength:0]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockOriginEndpointManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockOriginEndpointManager.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | 11 | #import "SPDYOrigin.h" 12 | #import "SPDYOriginEndpointManager.h" 13 | 14 | @interface SPDYMockOriginEndpointManager : SPDYOriginEndpointManager 15 | @property (nonatomic) NSArray *mock_proxyList; 16 | @property (nonatomic) NSString *mock_autoConfigScript; 17 | @end 18 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockOriginEndpointManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockOriginEndpointTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import "SPDYMockOriginEndpointManager.h" 13 | 14 | @interface SPDYOriginEndpointManager () 15 | - (void)_proxyExecuteAutoConfigURL:(NSURL *)pacScriptUrl; 16 | - (void)_handleExecuteCallback:(NSArray *)proxies error:(NSError *)error; 17 | @end 18 | 19 | @implementation SPDYMockOriginEndpointManager 20 | 21 | static void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) 22 | { 23 | SPDYMockOriginEndpointManager *manager = CFBridgingRelease(client); 24 | NSError *bridgedError = nil; 25 | NSArray *bridgedProxies = nil; 26 | if (error != NULL) { 27 | bridgedError = (__bridge NSError *)error; 28 | } else { 29 | bridgedProxies = (__bridge NSArray *)proxies; 30 | } 31 | [manager _handleExecuteCallback:bridgedProxies error:bridgedError]; 32 | CFRunLoopStop(CFRunLoopGetCurrent()); 33 | } 34 | 35 | - (void)_executeAutoConfigScript:(NSString *)script 36 | { 37 | NSString *originUrlString = [NSString stringWithFormat:@"%@://%@:%u", [self origin].scheme, [self origin].host, [self origin].port]; 38 | NSURL *originUrl = [NSURL URLWithString:originUrlString]; 39 | 40 | CFStreamClientContext context = {0, (void *)CFBridgingRetain(self), nil, nil, nil}; 41 | CFRunLoopSourceRef runLoopSource = CFNetworkExecuteProxyAutoConfigurationScript( 42 | (__bridge CFStringRef)script, 43 | (__bridge CFURLRef)originUrl, 44 | ResultCallback, 45 | &context); 46 | CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 47 | CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode); 48 | CFRunLoopRun(); 49 | CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode); 50 | CFRelease(runLoopSource); 51 | } 52 | 53 | #pragma mark Overrides 54 | 55 | - (NSDictionary *)_proxyGetSystemSettings 56 | { 57 | // Don't need to hook this, will hook into next layer 58 | return nil; 59 | } 60 | 61 | - (NSArray *)_proxyGetListFromSettings:(NSDictionary *)systemProxySettings 62 | { 63 | return _mock_proxyList; 64 | } 65 | 66 | - (void)_proxyExecuteAutoConfigURL:(NSURL *)pacScriptUrl 67 | { 68 | if (_mock_autoConfigScript) { 69 | [self _executeAutoConfigScript:_mock_autoConfigScript]; 70 | } else { 71 | [super _proxyExecuteAutoConfigURL:pacScriptUrl]; 72 | } 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockURLProtocolClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockURLProtocolClient.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import 13 | #import "SPDYProtocol.h" 14 | 15 | @interface SPDYMockURLProtocolClient : NSObject 16 | 17 | @property(nonatomic) int calledWasRedirectedToRequest; 18 | @property(nonatomic) int calledCachedResponseIsValid; 19 | @property(nonatomic) int calledDidReceiveResponse; 20 | @property(nonatomic) int calledDidLoadData; 21 | @property(nonatomic) int calledDidFinishLoading; 22 | @property(nonatomic) int calledDidFailWithError; 23 | @property(nonatomic) int calledDidReceiveAuthenticationChallenge; 24 | @property(nonatomic) int calledDidCancelAuthenticationChallenge; 25 | 26 | @property(nonatomic, strong) NSURLRequest *lastRedirectedRequest; 27 | @property(nonatomic, strong) NSURLResponse *lastRedirectResponse; 28 | @property(nonatomic, strong) NSCachedURLResponse *lastCachedResponse; 29 | @property(nonatomic, strong) NSURLResponse *lastResponse; 30 | @property(nonatomic) NSURLCacheStoragePolicy lastCacheStoragePolicy; 31 | @property(nonatomic, strong) NSData *lastData; 32 | @property(nonatomic, strong) NSError *lastError; 33 | @property(nonatomic, strong) NSURLAuthenticationChallenge *lastReceivedAuthenticationChallenge; 34 | @property(nonatomic, strong) NSURLAuthenticationChallenge *lastCanceledAuthenticationChallenge; 35 | @end 36 | 37 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYMockURLProtocolClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYMockURLProtocolClient.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import "SPDYMockURLProtocolClient.h" 13 | 14 | @implementation SPDYMockURLProtocolClient 15 | 16 | - (void)URLProtocol:(NSURLProtocol *)urlProtocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse 17 | { 18 | _calledWasRedirectedToRequest++; 19 | _lastRedirectedRequest = request; 20 | _lastRedirectResponse = redirectResponse; 21 | } 22 | 23 | - (void)URLProtocol:(NSURLProtocol *)urlProtocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse 24 | { 25 | _calledCachedResponseIsValid++; 26 | _lastCachedResponse = cachedResponse; 27 | } 28 | 29 | - (void)URLProtocol:(NSURLProtocol *)urlProtocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy 30 | { 31 | _calledDidReceiveResponse++; 32 | _lastResponse = response; 33 | _lastCacheStoragePolicy = policy; 34 | } 35 | 36 | - (void)URLProtocol:(NSURLProtocol *)urlProtocol didLoadData:(NSData *)data 37 | { 38 | _calledDidLoadData++; 39 | _lastData = data; 40 | } 41 | 42 | - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)urlProtocol 43 | { 44 | _calledDidFinishLoading++; 45 | } 46 | 47 | - (void)URLProtocol:(NSURLProtocol *)urlProtocol didFailWithError:(NSError *)error 48 | { 49 | _calledDidFailWithError++; 50 | _lastError = error; 51 | } 52 | 53 | - (void)URLProtocol:(NSURLProtocol *)urlProtocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 54 | { 55 | _calledDidReceiveAuthenticationChallenge++; 56 | _lastReceivedAuthenticationChallenge = challenge; 57 | } 58 | 59 | - (void)URLProtocol:(NSURLProtocol *)urlProtocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 60 | { 61 | _calledDidCancelAuthenticationChallenge++; 62 | _lastCanceledAuthenticationChallenge = challenge; 63 | } 64 | 65 | @end 66 | 67 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYOriginTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYOriginTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYOrigin.h" 14 | 15 | @interface SPDYOriginTest : SenTestCase 16 | @end 17 | 18 | @implementation SPDYOriginTest 19 | 20 | - (void)testInitEquivalency 21 | { 22 | NSError *error; 23 | NSURL *url; 24 | NSString *originStr; 25 | SPDYOrigin *o1, *o2, *o3, *o4, *o5, *o6, *o7; 26 | 27 | originStr = @"http://twitter.com"; 28 | o1 = [[SPDYOrigin alloc] initWithString:originStr error:&error]; 29 | STAssertTrue(error == nil, nil); 30 | 31 | url = [[NSURL alloc] initWithString:originStr]; 32 | o2 = [[SPDYOrigin alloc] initWithURL:url error:&error]; 33 | STAssertTrue(error == nil, nil); 34 | 35 | o3 = [[SPDYOrigin alloc] initWithScheme:@"http" 36 | host:@"twitter.com" 37 | port:0 38 | error:&error]; 39 | STAssertTrue(error == nil, nil); 40 | 41 | originStr = @"http://twitter.com:80"; 42 | o4 = [[SPDYOrigin alloc] initWithString:originStr error:&error]; 43 | STAssertTrue(error == nil, nil); 44 | 45 | url = [[NSURL alloc] initWithString:originStr]; 46 | o5 = [[SPDYOrigin alloc] initWithURL:url error:&error]; 47 | STAssertTrue(error == nil, nil); 48 | 49 | o6 = [[SPDYOrigin alloc] initWithScheme:@"http" 50 | host:@"twitter.com" 51 | port:80 52 | error:&error]; 53 | STAssertTrue(error == nil, nil); 54 | 55 | o7 = [o6 copy]; 56 | 57 | STAssertTrue([o1 isEqual:o2], nil); 58 | STAssertTrue([o1 hash] == [o2 hash], nil); 59 | 60 | STAssertTrue([o2 isEqual:o3], nil); 61 | STAssertTrue([o2 hash] == [o3 hash], nil); 62 | 63 | STAssertTrue([o3 isEqual:o4], nil); 64 | STAssertTrue([o3 hash] == [o4 hash], nil); 65 | 66 | STAssertTrue([o4 isEqual:o5], nil); 67 | STAssertTrue([o4 hash] == [o5 hash], nil); 68 | 69 | STAssertTrue([o5 isEqual:o6], nil); 70 | STAssertTrue([o5 hash] == [o6 hash], nil); 71 | 72 | STAssertFalse(o6 == o7, nil); 73 | STAssertTrue([o6 isEqual:o7], nil); 74 | STAssertTrue([o6 hash] == [o7 hash], nil); 75 | } 76 | 77 | - (void)testInitWithInvalidOrigins 78 | { 79 | NSError *error = nil; 80 | SPDYOrigin *origin = nil; 81 | 82 | NSArray *badOrigins = @[ 83 | @"http://", 84 | @"twitter.com", 85 | @"ftp://twitter.com", 86 | ]; 87 | 88 | for (NSString *originStr in badOrigins) { 89 | error = nil; 90 | origin = [[SPDYOrigin alloc] initWithString:originStr error:&error]; 91 | STAssertTrue(error != nil, nil); 92 | STAssertTrue(origin == nil, nil); 93 | } 94 | } 95 | 96 | - (void)testImmutability 97 | { 98 | NSMutableString *scheme = [[NSMutableString alloc] initWithString:@"http"]; 99 | NSMutableString *host = [[NSMutableString alloc] initWithString:@"twitter.com"]; 100 | 101 | SPDYOrigin *origin = [[SPDYOrigin alloc] initWithScheme:scheme 102 | host:host 103 | port:80 104 | error:nil]; 105 | 106 | STAssertTrue([origin.scheme isEqualToString:scheme], nil); 107 | STAssertTrue([origin.host isEqualToString:host], nil); 108 | NSUInteger hash1 = [origin hash]; 109 | 110 | [scheme appendString:@"s"]; 111 | [host appendString:@".jp"]; 112 | NSUInteger hash2 = [origin hash]; 113 | 114 | STAssertFalse([origin.scheme isEqualToString:scheme], nil); 115 | STAssertFalse([origin.host isEqualToString:host], nil); 116 | 117 | STAssertTrue(hash1 == hash2, nil); 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYProtocolTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYProtocolTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #import 13 | #import 14 | #import "NSURLRequest+SPDYURLRequest.h" 15 | #import "SPDYProtocol.h" 16 | #import "SPDYTLSTrustEvaluator.h" 17 | 18 | @interface SPDYProtocolTest : SenTestCase 19 | @end 20 | 21 | @implementation SPDYProtocolTest 22 | { 23 | NSString *_lastTLSTrustHost; 24 | } 25 | 26 | - (void)tearDown 27 | { 28 | _lastTLSTrustHost = nil; 29 | [SPDYURLConnectionProtocol unregisterAllAliases]; 30 | [SPDYURLConnectionProtocol unregisterAllOrigins]; 31 | [SPDYProtocol setTLSTrustEvaluator:nil]; 32 | } 33 | 34 | - (NSMutableURLRequest *)makeRequest:(NSString *)url 35 | { 36 | return [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; 37 | } 38 | 39 | #pragma mark SPDYTLSTrustEvaluator 40 | 41 | - (BOOL)evaluateServerTrust:(SecTrustRef)trust forHost:(NSString *)host 42 | { 43 | _lastTLSTrustHost = host; 44 | return NO; 45 | } 46 | 47 | #pragma mark Tests 48 | 49 | - (void)testURLSessionCanInitTrue 50 | { 51 | STAssertTrue([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com"]], nil); 52 | STAssertTrue([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com/foo"]], nil); 53 | STAssertTrue([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com:443/foo"]], nil); 54 | STAssertTrue([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com:8888/foo"]], nil); 55 | STAssertTrue([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"http://api.twitter.com/foo"]], nil); 56 | } 57 | 58 | - (void)testURLSessionCanInitFalse 59 | { 60 | STAssertFalse([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"ftp://api.twitter.com"]], nil); 61 | STAssertFalse([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"://api.twitter.com"]], nil); 62 | STAssertFalse([SPDYURLSessionProtocol canInitWithRequest:[self makeRequest:@"api.twitter.com"]], nil); 63 | } 64 | 65 | - (void)testURLSessionWithBypassCanInitFalse 66 | { 67 | NSMutableURLRequest *request = [self makeRequest:@"https://api.twitter.com"]; 68 | request.SPDYBypass = YES; 69 | STAssertFalse([SPDYURLSessionProtocol canInitWithRequest:request], nil); 70 | } 71 | 72 | - (void)testURLConnectionCanInitTrue 73 | { 74 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 75 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com"]], nil); 76 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com/foo"]], nil); 77 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com:443/foo"]], nil); 78 | } 79 | 80 | - (void)testURLConnectionCanInitFalse 81 | { 82 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 83 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com:8888/foo"]], nil); 84 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"http://api.twitter.com"]], nil); 85 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://twitter.com"]], nil); 86 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://foo.api.twitter.com"]], nil); 87 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://twitter.com:80"]], nil); 88 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"http://api.twitter.com:443"]], nil); 89 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"://api.twitter.com"]], nil); 90 | } 91 | 92 | - (void)testURLConnectionWithBypassCanInitFalse 93 | { 94 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 95 | NSMutableURLRequest *request = [self makeRequest:@"https://api.twitter.com"]; 96 | request.SPDYBypass = YES; 97 | STAssertFalse([SPDYURLSessionProtocol canInitWithRequest:request], nil); 98 | } 99 | 100 | - (void)testURLConnectionAliasCanInitTrue 101 | { 102 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 103 | [SPDYURLConnectionProtocol registerOrigin:@"https://1.2.3.4"]; 104 | [SPDYProtocol registerAlias:@"https://alias.twitter.com" forOrigin:@"https://api.twitter.com"]; 105 | [SPDYProtocol registerAlias:@"https://bare.twitter.com" forOrigin:@"https://1.2.3.4"]; 106 | 107 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com/foo"]], nil); 108 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://1.2.3.4/foo"]], nil); 109 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://alias.twitter.com/foo"]], nil); 110 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://bare.twitter.com/foo"]], nil); 111 | 112 | // TODO: Replace with unregisterAllAliases when available 113 | [SPDYProtocol unregisterAlias:@"https://alias.twitter.com"]; 114 | [SPDYProtocol unregisterAlias:@"https://bare.twitter.com"]; 115 | } 116 | 117 | - (void)testURLConnectionAliasToNoOriginCanInitFalse 118 | { 119 | //[SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 120 | [SPDYProtocol registerAlias:@"https://alias.twitter.com" forOrigin:@"https://api.twitter.com"]; 121 | [SPDYProtocol registerAlias:@"https://bare.twitter.com" forOrigin:@"https://1.2.3.4"]; 122 | 123 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com/foo"]], nil); 124 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://1.2.3.4/foo"]], nil); 125 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://alias.twitter.com/foo"]], nil); 126 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://bare.twitter.com/foo"]], nil); 127 | 128 | // TODO: Replace with unregisterAllAliases when available 129 | [SPDYProtocol unregisterAlias:@"https://alias.twitter.com"]; 130 | [SPDYProtocol unregisterAlias:@"https://bare.twitter.com"]; 131 | } 132 | 133 | - (void)testURLConnectionBadAliasCanInitFalse 134 | { 135 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 136 | [SPDYProtocol registerAlias:@"ftp://alias.twitter.com" forOrigin:@"https://api.twitter.com"]; // bad alias 137 | 138 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"ftp://alias.twitter.com/foo"]], nil); 139 | 140 | // TODO: Replace with unregisterAllAliases when available 141 | [SPDYProtocol unregisterAlias:@"ftp://alias.twitter.com"]; 142 | } 143 | 144 | - (void)testURLConnectionCanInitTrueAfterWeirdOrigins 145 | { 146 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com:8888"]; 147 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com:8888/foo"]], nil); 148 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com/foo"]], nil); 149 | 150 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 151 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com:8888/foo"]], nil); 152 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com/foo"]], nil); 153 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com:8889/foo"]], nil); 154 | 155 | [SPDYURLConnectionProtocol registerOrigin:@"https://www.twitter.com/foo"]; 156 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://www.twitter.com/foo"]], nil); 157 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://www.twitter.com"]], nil); 158 | } 159 | 160 | - (void)testURLConnectionCanInitFalseAfterBadOrigins 161 | { 162 | [SPDYURLConnectionProtocol registerOrigin:@"ftp://api.twitter.com"]; 163 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com/foo"]], nil); 164 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"ftp://api.twitter.com/foo"]], nil); 165 | 166 | [SPDYURLConnectionProtocol registerOrigin:@"https://"]; 167 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://"]], nil); 168 | } 169 | 170 | - (void)testURLConnectionCanInitFalseAfterUnregister 171 | { 172 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 173 | [SPDYURLConnectionProtocol registerOrigin:@"https://www.twitter.com"]; 174 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com"]], nil); 175 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://www.twitter.com"]], nil); 176 | 177 | [SPDYURLConnectionProtocol unregisterOrigin:@"https://api.twitter.com"]; 178 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://api.twitter.com"]], nil); 179 | STAssertTrue([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://www.twitter.com"]], nil); 180 | 181 | [SPDYURLConnectionProtocol unregisterAllOrigins]; 182 | STAssertFalse([SPDYURLConnectionProtocol canInitWithRequest:[self makeRequest:@"https://www.twitter.com"]], nil); 183 | } 184 | 185 | - (void)testTLSTrustEvaluatorReturnsYesWhenNotSet 186 | { 187 | STAssertTrue([SPDYProtocol evaluateServerTrust:nil forHost:@"api.twitter.com"], nil); 188 | } 189 | 190 | - (void)testTLSTrustEvaluator 191 | { 192 | [SPDYProtocol setTLSTrustEvaluator:self]; 193 | STAssertFalse([SPDYProtocol evaluateServerTrust:nil forHost:@"api.twitter.com"], nil); 194 | STAssertEqualObjects(_lastTLSTrustHost, @"api.twitter.com", nil); 195 | } 196 | 197 | - (void)testTLSTrustEvaluatorWithCertificateAlias 198 | { 199 | [SPDYProtocol setTLSTrustEvaluator:self]; 200 | [SPDYURLConnectionProtocol registerOrigin:@"https://api.twitter.com"]; 201 | [SPDYURLConnectionProtocol registerOrigin:@"https://1.2.3.4"]; 202 | [SPDYProtocol registerAlias:@"https://alias.twitter.com" forOrigin:@"https://api.twitter.com"]; 203 | [SPDYProtocol registerAlias:@"https://bare.twitter.com" forOrigin:@"https://1.2.3.4"]; 204 | 205 | STAssertFalse([SPDYProtocol evaluateServerTrust:nil forHost:@"api.twitter.com"], nil); 206 | STAssertEqualObjects(_lastTLSTrustHost, @"api.twitter.com", nil); 207 | 208 | STAssertFalse([SPDYProtocol evaluateServerTrust:nil forHost:@"1.2.3.4"], nil); 209 | STAssertEqualObjects(_lastTLSTrustHost, @"bare.twitter.com", nil); 210 | 211 | // TODO: Replace with unregisterAllAliases when available 212 | [SPDYProtocol unregisterAlias:@"https://alias.twitter.com"]; 213 | [SPDYProtocol unregisterAlias:@"https://bare.twitter.com"]; 214 | } 215 | 216 | @end 217 | 218 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYSenTestLog.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSentTestLog.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | // The entire point of this file is to flush the code coverage .gcda files. Apple has a bug 12 | // in their 7.x simulator, and possibly 8.0. At least, with Travis running 8.0, no .gcda files 13 | // are generated without the flush. It works fine locally using the 8.1 simulator. 14 | // 15 | // See: 16 | // http://www.cocoanetics.com/2013/10/xcode-coverage/ 17 | // http://stackoverflow.com/questions/19136767/generate-gcda-files-with-xcode5-ios7-simulator-and-xctest 18 | // http://stackoverflow.com/questions/18394655/xcode5-code-coverage-from-cmd-line-for-ci-builds 19 | 20 | // This is defined in the "Coverage" configuration. 21 | #if COVERAGE 22 | 23 | #import 24 | 25 | @interface SPDYSentTestLog : SenTestLog 26 | @end 27 | 28 | // GCOV Flush function 29 | extern void __gcov_flush(void); 30 | 31 | @implementation SPDYSentTestLog 32 | 33 | + (void)initialize 34 | { 35 | [[NSUserDefaults standardUserDefaults] setValue:@"SPDYSentTestLog" forKey:SenTestObserverClassKey]; 36 | 37 | [super initialize]; 38 | } 39 | 40 | + (void)testSuiteDidStop:(NSNotification *)notification 41 | { 42 | [super testSuiteDidStop:notification]; 43 | 44 | // workaround for missing flush with iOS 7 Simulator 45 | __gcov_flush(); 46 | } 47 | 48 | @end 49 | 50 | #endif -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYSettingsStoreTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSettingsStoreTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier. 10 | // 11 | 12 | #import 13 | #import 14 | #import "SPDYSettingsStore.h" 15 | #import "SPDYOrigin.h" 16 | #import "SPDYDefinitions.h" 17 | 18 | @interface SPDYSettingsStoreTest : SenTestCase 19 | @end 20 | 21 | @implementation SPDYSettingsStoreTest 22 | 23 | - (void)testSettings:(SPDYSettings *)settings 24 | { 25 | SPDY_SETTINGS_ITERATOR(i) { 26 | settings[i].set = NO; 27 | } 28 | 29 | settings[SPDY_SETTINGS_DOWNLOAD_BANDWIDTH].set = YES; 30 | settings[SPDY_SETTINGS_DOWNLOAD_BANDWIDTH].value = 1; 31 | settings[SPDY_SETTINGS_DOWNLOAD_BANDWIDTH].flags = SPDY_SETTINGS_FLAG_PERSIST_VALUE; 32 | 33 | settings[SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE].set = YES; 34 | settings[SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE].value = 2; 35 | settings[SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE].flags = SPDY_SETTINGS_FLAG_PERSIST_VALUE; 36 | 37 | settings[SPDY_SETTINGS_MAX_CONCURRENT_STREAMS].set = YES; 38 | settings[SPDY_SETTINGS_MAX_CONCURRENT_STREAMS].value = 3; 39 | settings[SPDY_SETTINGS_MAX_CONCURRENT_STREAMS].flags = 0; // not persisted 40 | } 41 | 42 | #pragma mark Tests 43 | 44 | - (void)testSettingsForWrongOrigin 45 | { 46 | SPDYOrigin *origin = [[SPDYOrigin alloc] initWithString:@"https://api.twitter.com" error:nil]; 47 | SPDYOrigin *origin2 = [[SPDYOrigin alloc] initWithString:@"http://api.twitter.com" error:nil]; 48 | 49 | SPDYSettings settings[SPDY_SETTINGS_LENGTH]; 50 | [self testSettings:settings]; 51 | 52 | [SPDYSettingsStore persistSettings:settings forOrigin:origin]; 53 | 54 | SPDYSettings *persistedSettings; 55 | persistedSettings = [SPDYSettingsStore settingsForOrigin:origin2]; // invalid origin 56 | STAssertTrue(persistedSettings == NULL, nil); 57 | } 58 | 59 | - (void)testSettingsForOrigin 60 | { 61 | SPDYOrigin *origin = [[SPDYOrigin alloc] initWithString:@"https://api.twitter.com" error:nil]; 62 | 63 | SPDYSettings settings[SPDY_SETTINGS_LENGTH]; 64 | [self testSettings:settings]; 65 | 66 | [SPDYSettingsStore persistSettings:settings forOrigin:origin]; 67 | 68 | SPDYSettings *persistedSettings; 69 | persistedSettings = [SPDYSettingsStore settingsForOrigin:origin]; 70 | STAssertTrue(persistedSettings != NULL, nil); 71 | 72 | STAssertTrue(persistedSettings[SPDY_SETTINGS_DOWNLOAD_BANDWIDTH].set, nil); 73 | STAssertTrue(persistedSettings[SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE].set, nil); 74 | STAssertFalse(persistedSettings[SPDY_SETTINGS_MAX_CONCURRENT_STREAMS].set, nil); 75 | STAssertFalse(persistedSettings[SPDY_SETTINGS_ROUND_TRIP_TIME].set, nil); 76 | 77 | STAssertEquals(persistedSettings[SPDY_SETTINGS_DOWNLOAD_BANDWIDTH].value, 1, nil); 78 | STAssertEquals(persistedSettings[SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE].value, 2, nil); 79 | } 80 | 81 | - (void)testClearSettings 82 | { 83 | SPDYOrigin *origin = [[SPDYOrigin alloc] initWithString:@"https://api.twitter.com" error:nil]; 84 | 85 | SPDYSettings settings[SPDY_SETTINGS_LENGTH]; 86 | [self testSettings:settings]; 87 | 88 | [SPDYSettingsStore persistSettings:settings forOrigin:origin]; 89 | [SPDYSettingsStore clearSettingsForOrigin:origin]; 90 | 91 | SPDYSettings *persistedSettings; 92 | persistedSettings = [SPDYSettingsStore settingsForOrigin:origin]; 93 | STAssertTrue(persistedSettings != NULL, nil); 94 | 95 | STAssertFalse(persistedSettings[SPDY_SETTINGS_DOWNLOAD_BANDWIDTH].set, nil); 96 | STAssertFalse(persistedSettings[SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE].set, nil); 97 | STAssertFalse(persistedSettings[SPDY_SETTINGS_MAX_CONCURRENT_STREAMS].set, nil); 98 | STAssertFalse(persistedSettings[SPDY_SETTINGS_ROUND_TRIP_TIME].set, nil); 99 | } 100 | 101 | - (void)testClearSettingsWhenNonePersisted 102 | { 103 | SPDYOrigin *origin = [[SPDYOrigin alloc] initWithString:@"https://api2.twitter.com" error:nil]; 104 | 105 | [SPDYSettingsStore clearSettingsForOrigin:origin]; 106 | 107 | SPDYSettings *persistedSettings; 108 | persistedSettings = [SPDYSettingsStore settingsForOrigin:origin]; 109 | STAssertTrue(persistedSettings == NULL, nil); 110 | } 111 | 112 | @end 113 | 114 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYSocket+SPDYSocketMock.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSocket+SPDYSocketMock.h 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Klemen Verdnik on 6/10/14. 10 | // 11 | 12 | #import "SPDYSocket.h" 13 | #import "SPDYSession.h" 14 | 15 | @class SPDYFrameDecoder; 16 | @class SPDYStreamManager; 17 | 18 | // TODO: remove this once the proxy fork has been merged and the socket op structures are in 19 | // their own header file that can be imported here. 20 | @interface SPDYSocketWriteOp : NSObject { 21 | @public 22 | NSData *_buffer; 23 | NSUInteger _bytesWritten; 24 | NSTimeInterval _timeout; 25 | long _tag; 26 | } 27 | 28 | - (id)initWithData:(NSData *)data timeout:(NSTimeInterval)timeout tag:(long)tag; 29 | @end 30 | 31 | // Note: these are exposed as globals only because we don't control the creation of the 32 | // SPDYSocket inside CocoaSPDY, and we cannot add ivars in a category. This is the best 33 | // I could do without a proper mocking library. Since these are only used by the unit 34 | // tests, it's a (barely) acceptable solution. 35 | extern NSError *socketMock_lastError; 36 | extern SPDYSocketWriteOp *socketMock_lastWriteOp; 37 | extern SPDYFrameDecoder *socketMock_frameDecoder; 38 | 39 | // Swizzles functions that connection/read/write data and allows the tests to call the delegate 40 | // functions inside SPDYSocket which end up calling into SPDYSession. 41 | @interface SPDYSocket (SPDYSocketMock) 42 | 43 | //@property (nonatomic) NSArray *responseStubs; 44 | 45 | + (void)performSwizzling:(BOOL)performSwizzling; 46 | - (void)setCellular:(bool)cellular; 47 | 48 | #pragma mark - SPDYSocketDelegate call forwarding 49 | - (void)performDelegateCall_socketWillDisconnectWithError:(NSError *)error; 50 | - (void)performDelegateCall_socketDidDisconnect; 51 | - (void)performDelegateCall_socketDidAcceptNewSocket:(SPDYSocket *)newSocket; 52 | - (NSRunLoop *)performDelegateCall_socketWantsRunLoopForNewSocket:(SPDYSocket *)newSocket; 53 | - (bool)performDelegateCall_socketWillConnect; 54 | - (void)performDelegateCall_socketDidConnectToHost:(NSString *)host port:(in_port_t)port; 55 | - (void)performDelegateCall_socketDidReadData:(NSData *)data withTag:(long)tag; 56 | - (void)performDelegateCall_socketDidReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; 57 | - (void)performDelegateCall_socketDidWriteDataWithTag:(long)tag; 58 | - (void)performDelegateCall_socketDidWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; 59 | - (NSTimeInterval)performDelegateCall_socketWillTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length; 60 | - (NSTimeInterval)performDelegateCall_socketWillTimeoutWriteWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length; 61 | - (bool)performDelegateCall_socketSecuredWithTrust:(SecTrustRef)trust; 62 | 63 | @end 64 | 65 | // Expose some private things in SPDYSession needed by the socket mocker. 66 | @interface SPDYSession (Test) 67 | @property (nonatomic, readonly) SPDYSocket *socket; 68 | @property (nonatomic, readonly) NSMutableData *inputBuffer; 69 | @property (nonatomic, readonly) SPDYFrameDecoder *frameDecoder; 70 | @property (nonatomic, readonly) SPDYStreamManager *activeStreams; 71 | - (void)setCellular:(bool)cellular; 72 | @end 73 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYSocket+SPDYSocketMock.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSocket+SPDYSocketMock.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Klemen Verdnik on 6/10/14. 10 | // 11 | 12 | #import "SPDYSocket+SPDYSocketMock.h" 13 | #import "SPDYFrameDecoder.h" 14 | #import "SPDYStreamManager.h" 15 | #import 16 | 17 | NSString * const kSPDYTSTResponseStubs = @"kSPDYTSTResponseStubs"; 18 | 19 | NSError *socketMock_lastError = nil; 20 | SPDYSocketWriteOp *socketMock_lastWriteOp = nil; 21 | SPDYFrameDecoder *socketMock_frameDecoder = nil; 22 | 23 | @implementation SPDYSession (Test) 24 | 25 | - (SPDYSocket *)socket 26 | { 27 | return [self valueForKey:@"_socket"]; 28 | } 29 | 30 | - (NSMutableData *)inputBuffer 31 | { 32 | return [self valueForKey:@"_inputBuffer"]; 33 | } 34 | 35 | - (SPDYFrameDecoder *)frameDecoder 36 | { 37 | return [self valueForKey:@"_frameDecoder"]; 38 | } 39 | 40 | - (SPDYStreamManager *)activeStreams 41 | { 42 | return [self valueForKey:@"_activeStreams"]; 43 | } 44 | 45 | - (void)setCellular:(bool)cellular 46 | { 47 | [self setValue:@(cellular) forKey:@"_cellular"]; 48 | } 49 | 50 | @end 51 | 52 | @implementation SPDYSocket (SPDYSocketMock) 53 | 54 | + (void)performSwizzling:(BOOL)performSwizzling 55 | { 56 | // The "+ load" method is called once, very early in the application life-cycle. 57 | // It's called even before the "main" function is called. Beware: there's no 58 | // autorelease pool at this point, so avoid Objective-C calls. 59 | Method original, swizzle; 60 | 61 | original = class_getInstanceMethod(self, @selector(connectToOrigin:withTimeout:error:)); 62 | swizzle = class_getInstanceMethod(self, @selector(swizzled_connectToOrigin:withTimeout:error:)); 63 | if (performSwizzling) { 64 | method_exchangeImplementations(original, swizzle); 65 | } else { 66 | method_exchangeImplementations(swizzle, original); 67 | } 68 | 69 | original = class_getInstanceMethod(self, @selector(readDataWithTimeout:buffer:bufferOffset:maxLength:tag:)); 70 | swizzle = class_getInstanceMethod(self, @selector(swizzled_readDataWithTimeout:buffer:bufferOffset:maxLength:tag:)); 71 | if (performSwizzling) { 72 | method_exchangeImplementations(original, swizzle); 73 | } else { 74 | method_exchangeImplementations(swizzle, original); 75 | } 76 | 77 | original = class_getInstanceMethod(self, @selector(writeData:withTimeout:tag:)); 78 | swizzle = class_getInstanceMethod(self, @selector(swizzled_writeData:withTimeout:tag:)); 79 | if (performSwizzling) { 80 | method_exchangeImplementations(original, swizzle); 81 | } else { 82 | method_exchangeImplementations(swizzle, original); 83 | } 84 | } 85 | 86 | - (void)setCellular:(bool)cellular 87 | { 88 | [self setValue:@(cellular) forKey:@"_isCellular"]; 89 | } 90 | 91 | - (bool)swizzled_connectToOrigin:(SPDYOrigin *)origin 92 | withTimeout:(NSTimeInterval)timeout 93 | error:(NSError **)pError 94 | { 95 | NSLog(@"SPDYMock: Swizzled connectToOrigin:%@ withTimeout:%f error", origin, timeout); 96 | [self setValue:@(1) forKey:@"_flags"]; // kDidStartDelegate 97 | return YES; 98 | } 99 | 100 | - (void)swizzled_readDataWithTimeout:(NSTimeInterval)timeout 101 | buffer:(NSMutableData *)buffer 102 | bufferOffset:(NSUInteger)offset 103 | maxLength:(NSUInteger)length 104 | tag:(long)tag 105 | { 106 | NSLog(@"SPDYSocketMock::readDataWithTimeout"); 107 | } 108 | 109 | - (void)swizzled_writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag 110 | { 111 | NSLog(@"SPDYSocketMock::writeData %@", data); 112 | 113 | // Simulate buffering the write op. We'll only keep the last one around. 114 | socketMock_lastWriteOp = [[SPDYSocketWriteOp alloc] initWithData:data timeout:timeout tag:tag]; 115 | 116 | if (socketMock_frameDecoder) { 117 | NSError *error = nil; 118 | [socketMock_frameDecoder decode:(uint8_t *)data.bytes length:data.length error:&error]; 119 | socketMock_lastError = error; 120 | } 121 | } 122 | 123 | #pragma mark - Response stubbing 124 | 125 | //- (NSArray *)responseStubs 126 | //{ 127 | // return objc_getAssociatedObject(self, (__bridge const void *)(kSPDYTSTResponseStubs)); 128 | //} 129 | // 130 | //- (void)setResponseStubs:(NSArray *)responseStubs 131 | //{ 132 | // objc_setAssociatedObject(self, (__bridge const void *)(kSPDYTSTResponseStubs), responseStubs, OBJC_ASSOCIATION_COPY); 133 | //} 134 | 135 | #pragma mark - SPDYSocketDelegate call forwarding 136 | 137 | - (void)performDelegateCall_socketWillDisconnectWithError:(NSError *)error 138 | { 139 | [[self delegate] socket:self willDisconnectWithError:error]; 140 | } 141 | 142 | - (void)performDelegateCall_socketDidDisconnect; 143 | { 144 | [[self delegate] socketDidDisconnect:self]; 145 | } 146 | 147 | - (void)performDelegateCall_socketDidAcceptNewSocket:(SPDYSocket *)newSocket 148 | { 149 | [[self delegate] socket:self didAcceptNewSocket:newSocket]; 150 | } 151 | 152 | - (NSRunLoop *)performDelegateCall_socketWantsRunLoopForNewSocket:(SPDYSocket *)newSocket 153 | { 154 | return [[self delegate] socket:self wantsRunLoopForNewSocket:newSocket]; 155 | } 156 | 157 | - (bool)performDelegateCall_socketWillConnect 158 | { 159 | return [[self delegate] socketWillConnect:self]; 160 | } 161 | 162 | - (void)performDelegateCall_socketDidConnectToHost:(NSString *)host port:(in_port_t)port 163 | { 164 | return [[self delegate] socket:self didConnectToHost:host port:port]; 165 | } 166 | 167 | - (void)performDelegateCall_socketDidReadData:(NSData *)data withTag:(long)tag 168 | { 169 | NSLog(@"SPDYMock: socketDidReadData:%@ tag:%ld", data, tag); 170 | return [[self delegate] socket:self didReadData:data withTag:tag]; 171 | } 172 | 173 | - (void)performDelegateCall_socketDidReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag 174 | { 175 | return [[self delegate] socket:self didReadPartialDataOfLength:partialLength tag:tag]; 176 | } 177 | 178 | - (void)performDelegateCall_socketDidWriteDataWithTag:(long)tag 179 | { 180 | [[self delegate] socket:self didWriteDataWithTag:tag]; 181 | } 182 | 183 | - (void)performDelegateCall_socketDidWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag 184 | { 185 | [[self delegate] socket:self didWritePartialDataOfLength:partialLength tag:tag]; 186 | } 187 | 188 | - (NSTimeInterval)performDelegateCall_socketWillTimeoutReadWithTag:(long)tag 189 | elapsed:(NSTimeInterval)elapsed 190 | bytesDone:(NSUInteger)length 191 | { 192 | return [[self delegate] socket:self willTimeoutReadWithTag:tag elapsed:elapsed bytesDone:length]; 193 | } 194 | 195 | - (NSTimeInterval)performDelegateCall_socketWillTimeoutWriteWithTag:(long)tag 196 | elapsed:(NSTimeInterval)elapsed 197 | bytesDone:(NSUInteger)length 198 | { 199 | return [[self delegate] socket:self willTimeoutWriteWithTag:tag elapsed:elapsed bytesDone:length]; 200 | } 201 | 202 | - (bool)performDelegateCall_socketSecuredWithTrust:(SecTrustRef)trust 203 | { 204 | return [[self delegate] socket:self securedWithTrust:trust]; 205 | } 206 | 207 | @end 208 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYSocketOpsTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYSocketOpsTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier 10 | // 11 | 12 | #import 13 | #import "SPDYSocketOps.h" 14 | #import "SPDYOrigin.h" 15 | 16 | @interface SPDYSocketOpsTest : SenTestCase 17 | @end 18 | 19 | @implementation SPDYSocketOpsTest 20 | 21 | - (void)testProxyWriteOpInit 22 | { 23 | NSError *error = nil; 24 | SPDYOrigin *origin = [[SPDYOrigin alloc] initWithString:@"https://twitter.com:443" error:&error]; 25 | SPDYSocketProxyWriteOp *op = [[SPDYSocketProxyWriteOp alloc] initWithOrigin:origin timeout:(NSTimeInterval)-1]; 26 | 27 | NSLog(@"%@", op); // ensure no crash in description 28 | STAssertTrue(op->_buffer.length > 0, nil); 29 | 30 | NSString *httpConnect = [[NSString alloc] initWithData:op->_buffer encoding:NSUTF8StringEncoding]; 31 | STAssertTrue([httpConnect hasPrefix:@"CONNECT twitter.com:443 HTTP/1.1\r\nHost: twitter.com:443\r\n"], @"actual: %@", httpConnect); 32 | } 33 | 34 | - (void)testProxyReadOpInit 35 | { 36 | SPDYSocketProxyReadOp *op = [[SPDYSocketProxyReadOp alloc] initWithTimeout:(NSTimeInterval)-1]; 37 | 38 | NSLog(@"%@", op); // ensure no crash in description 39 | STAssertFalse([op tryParseResponse], nil); 40 | STAssertFalse([op success], nil); 41 | } 42 | 43 | - (void)testProxyReadIncompleteTryParseFails 44 | { 45 | SPDYSocketProxyReadOp *op = [[SPDYSocketProxyReadOp alloc] initWithTimeout:(NSTimeInterval)-1]; 46 | NSString *responseStr = @"HTTP/1.1 200 Connection established\r\n\r\n"; 47 | NSData *responseData = [responseStr dataUsingEncoding:NSUTF8StringEncoding]; 48 | [op->_buffer setData:responseData]; 49 | 50 | // Run through all possible substring of a valid response 51 | for (NSUInteger i = 0; i < responseData.length - 1; i++) { 52 | op->_bytesRead = i; 53 | STAssertFalse([op tryParseResponse], @"failed at length %@ of %@", i, responseData.length); 54 | } 55 | 56 | op->_bytesRead = responseData.length; 57 | } 58 | 59 | - (void)testProxyReadMalformedTryParseFails 60 | { 61 | SPDYSocketProxyReadOp *op = [[SPDYSocketProxyReadOp alloc] initWithTimeout:(NSTimeInterval)-1]; 62 | // tryParseResponse is pretty forgiving actually, not much to do here 63 | NSArray *responseStrList = @[ 64 | @"", 65 | @"\r", 66 | @"\n", 67 | @"\r\n", 68 | @"\n\r", 69 | @"\r\n\r\n", 70 | @" \r\n\r\n", 71 | @" \r\n\r\n", 72 | @"\r\n \r\n", 73 | @"HTTP/1.1 200\r\n", 74 | @"HTTP/1.1 \r\n\r\n", 75 | @"HTTP/1.1 200 Connection \r\nestablished\r\n", 76 | @" HTTP/1.1 200 Connection established\r\n\r\n", 77 | @"HTTP/1.1 200 Connection established\r\n\r\n", 78 | @"200\r\n\r\n", 79 | @"\r\n\r\n", 80 | ]; 81 | 82 | for (NSString *responseStr in responseStrList) { 83 | NSData *responseData = [responseStr dataUsingEncoding:NSUTF8StringEncoding]; 84 | [op->_buffer setData:responseData]; 85 | op->_bytesRead = responseData.length; 86 | STAssertFalse([op tryParseResponse], @"response: %@", responseStr); 87 | } 88 | } 89 | 90 | - (void)testProxyReadPoorlyFormedTryParseSucceedsButSuccessFails 91 | { 92 | SPDYSocketProxyReadOp *op = [[SPDYSocketProxyReadOp alloc] initWithTimeout:(NSTimeInterval)-1]; 93 | // tryParseResponse is pretty forgiving actually, so this is easy 94 | NSArray *responseStrList = @[ 95 | @"SPDY/1.1 200 Connection established\r\n\r\n", 96 | @"1 2 3\r\n\r\n", 97 | @"HTTP/1.1 OK Connection established\r\n\r\n", 98 | @"200 HTTP/1.1 OK\r\n\r\n", 99 | @"HTTP/1.1 0 Foo\r\n\r\n", 100 | @"HTTP/1.1 -1 Connection established\r\n\r\n", 101 | @"HTTP/1.1 100 Foo\r\n\r\n", 102 | @"HTTP/1.1 300 Foo\r\n\r\n", 103 | @"HTTP/1.1 400 Foo\r\n\r\n", 104 | @"HTTP/1.1 500 Error\r\n\r\n", 105 | @"/1.1 200 Connection established\r\n\r\n", 106 | @"1.1 200 Connection established\r\n\r\n", 107 | @"GARBAGE 200 Connection established\r\n\r\n", 108 | ]; 109 | 110 | for (NSString *responseStr in responseStrList) { 111 | NSData *responseData = [responseStr dataUsingEncoding:NSUTF8StringEncoding]; 112 | [op->_buffer setData:responseData]; 113 | op->_bytesRead = responseData.length; 114 | STAssertTrue([op tryParseResponse], @"response: %@", responseStr); 115 | STAssertEquals(op->_bytesParsed, responseData.length, nil); 116 | STAssertFalse([op success], @"response: %@", responseStr); 117 | } 118 | } 119 | - (void)testProxyReadSuccessSucceeds 120 | { 121 | SPDYSocketProxyReadOp *op = [[SPDYSocketProxyReadOp alloc] initWithTimeout:(NSTimeInterval)-1]; 122 | NSArray *responseStrList = @[ 123 | @"HTTP/1.1 200 Connection established\r\n\r\n", 124 | @"HTTP/1.1 200 Blah blah foo bar\r\n\r\n", 125 | @"HTTP/1.1 200 500\r\n\r\n", 126 | @"HTTP/1.1 299 Connection established\r\n\r\n", 127 | @"HTTP/1.0 200 Connection established\r\n\r\n", 128 | @"HTTP/1.1 200 Connection established\r\nHeader: Foo\r\nHeader2: Foo Bar\r\n\r\n", 129 | @"HTTP/1.1 200.1 Connection established\r\n\r\n", 130 | @"HTTP/1.1 200 Connection established\r\n\r\n", 131 | @"HTTP/1 200 Connection established\r\n\r\n", // questionable 132 | @"HTTP/1.2 200 Connection established\r\n\r\n", // questionable 133 | ]; 134 | 135 | for (NSString *responseStr in responseStrList) { 136 | NSData *responseData = [responseStr dataUsingEncoding:NSUTF8StringEncoding]; 137 | [op->_buffer setData:responseData]; 138 | op->_bytesRead = responseData.length; 139 | STAssertTrue([op tryParseResponse], @"response: %@", responseStr); 140 | STAssertEquals(op->_bytesParsed, responseData.length, nil); 141 | STAssertTrue([op success], @"response: %@", responseStr); 142 | } 143 | } 144 | 145 | - (void)testProxyReadHasExtraData 146 | { 147 | // Currently not supported 148 | 149 | SPDYSocketProxyReadOp *op = [[SPDYSocketProxyReadOp alloc] initWithTimeout:(NSTimeInterval)-1]; 150 | NSString *responseStr = @"HTTP/1.1 200 Connection established\r\n\r\nMore data"; 151 | NSData *responseData = [responseStr dataUsingEncoding:NSUTF8StringEncoding]; 152 | [op->_buffer setData:responseData]; 153 | op->_bytesRead = responseData.length; 154 | 155 | STAssertFalse([op tryParseResponse], @"response: %@", responseStr); 156 | STAssertFalse([op success], @"response: %@", responseStr); 157 | } 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYStopwatchTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStopwatchTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Kevin Goodier on 9/19/14. 10 | // 11 | 12 | #import 13 | #import 14 | #import "SPDYStopwatch.h" 15 | 16 | @interface SPDYStopwatchTest : SenTestCase 17 | @end 18 | 19 | @implementation SPDYStopwatchTest 20 | 21 | #pragma mark Tests 22 | 23 | // Note: usleep will sleep for equal to or greater than the specified time interval, but not less. 24 | // It may sleep for much longer if a big context switch occurs, so we use a large band here 25 | // as we aren't mocking out the underlying time system call. 26 | #define INTERVAL_USEC 100000 27 | #define INTERVAL_SEC 0.1000 28 | #define LOWER_BOUND_SEC 0.0999 29 | #define UPPER_BOUND_SEC (INTERVAL_SEC * 100) 30 | 31 | - (void)testSystemTimeDoesMarchForward 32 | { 33 | SPDYTimeInterval t1 = [SPDYStopwatch currentSystemTime]; 34 | // Real sleep just to make sure currentSystemTime works 35 | usleep(INTERVAL_USEC); 36 | SPDYTimeInterval t2 = [SPDYStopwatch currentSystemTime]; 37 | STAssertTrue(t2 > t1, nil); 38 | STAssertTrue((t2 - t1) < UPPER_BOUND_SEC, nil); 39 | } 40 | 41 | - (void)testAbsoluteTimeDoesMarchForward 42 | { 43 | SPDYTimeInterval t1 = [SPDYStopwatch currentAbsoluteTime]; 44 | // Real sleep just to make sure currentAbsoluteTime works 45 | usleep(INTERVAL_USEC); 46 | SPDYTimeInterval t2 = [SPDYStopwatch currentAbsoluteTime]; 47 | STAssertTrue(t2 > t1, nil); 48 | STAssertTrue((t2 - t1) < UPPER_BOUND_SEC, nil); 49 | } 50 | 51 | - (void)testStopwatchElapsed 52 | { 53 | SPDYStopwatch *stopwatch = [[SPDYStopwatch alloc] init]; 54 | [SPDYStopwatch sleep:INTERVAL_SEC]; 55 | SPDYTimeInterval elapsed = stopwatch.elapsedSeconds; 56 | STAssertTrue(elapsed >= LOWER_BOUND_SEC, @"expect %f to be >= to %f", elapsed, LOWER_BOUND_SEC); 57 | STAssertTrue(elapsed < UPPER_BOUND_SEC, nil); 58 | } 59 | 60 | - (void)testStopwatchMultipleElapsed 61 | { 62 | SPDYStopwatch *stopwatch = [[SPDYStopwatch alloc] init]; 63 | [SPDYStopwatch sleep:INTERVAL_SEC]; 64 | SPDYTimeInterval elapsed1 = stopwatch.elapsedSeconds; 65 | [SPDYStopwatch sleep:INTERVAL_SEC]; 66 | SPDYTimeInterval elapsed2 = stopwatch.elapsedSeconds; 67 | STAssertTrue(elapsed1 >= LOWER_BOUND_SEC, @"expect %f to be >= to %f", elapsed1, LOWER_BOUND_SEC); 68 | STAssertTrue(elapsed2 >= 2 * LOWER_BOUND_SEC, @"expect %f to be >= to %f", elapsed2, 2 * LOWER_BOUND_SEC); 69 | STAssertTrue(elapsed1 < elapsed2, @"expect %f to be < %f", elapsed1, elapsed2); 70 | } 71 | 72 | - (void)testStopwatchMultipleReset 73 | { 74 | SPDYStopwatch *stopwatch = [[SPDYStopwatch alloc] init]; 75 | [SPDYStopwatch sleep:INTERVAL_SEC]; 76 | SPDYTimeInterval elapsed1 = stopwatch.elapsedSeconds; 77 | [SPDYStopwatch sleep:INTERVAL_SEC]; 78 | [stopwatch reset]; 79 | SPDYTimeInterval elapsed2 = stopwatch.elapsedSeconds; 80 | STAssertTrue(elapsed2 < elapsed1, nil); 81 | } 82 | 83 | - (void)testStopwatchStartTime 84 | { 85 | SPDYTimeInterval startTime = [SPDYStopwatch currentAbsoluteTime]; 86 | SPDYTimeInterval startSystemTime = [SPDYStopwatch currentSystemTime]; 87 | 88 | SPDYStopwatch *stopwatch = [[SPDYStopwatch alloc] init]; 89 | STAssertTrue(stopwatch.startTime >= startTime, nil); 90 | STAssertTrue(stopwatch.startSystemTime >= startSystemTime, nil); 91 | 92 | [SPDYStopwatch sleep:INTERVAL_SEC]; 93 | [stopwatch reset]; 94 | 95 | startTime += LOWER_BOUND_SEC; 96 | startSystemTime += LOWER_BOUND_SEC; 97 | STAssertTrue(stopwatch.startTime >= startTime, nil); 98 | STAssertTrue(stopwatch.startSystemTime >= startSystemTime, nil); 99 | STAssertTrue(stopwatch.startTime < (startTime + UPPER_BOUND_SEC), nil); 100 | STAssertTrue(stopwatch.startSystemTime < (startSystemTime + UPPER_BOUND_SEC), nil); 101 | } 102 | 103 | @end 104 | 105 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYStreamManagerTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStreamManagerTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYStreamManager.h" 14 | #import "SPDYStream.h" 15 | #import "SPDYProtocol.h" 16 | 17 | @interface SPDYStreamManagerTest : SenTestCase 18 | @end 19 | 20 | @interface SPDYStubbedProtocol : SPDYProtocol 21 | @end 22 | 23 | @implementation SPDYStubbedProtocol 24 | - (id)client 25 | { 26 | return nil; 27 | } 28 | @end 29 | 30 | @interface SPDYStubbedStream : SPDYStream 31 | + (void)resetTestStreamIds; 32 | - (id)initWithPriority:(uint8_t)priority; 33 | @end 34 | 35 | @implementation SPDYStubbedStream 36 | { 37 | SPDYProtocol *_retainedProtocol; 38 | } 39 | static SPDYStreamId _nextStreamId; 40 | 41 | + (void)resetTestStreamIds 42 | { 43 | _nextStreamId = 1; 44 | } 45 | 46 | - (id)initWithPriority:(uint8_t)priority 47 | { 48 | self = [super init]; 49 | if (self) { 50 | self.priority = priority; 51 | self.streamId = _nextStreamId; 52 | _nextStreamId += 1; 53 | if (self.local) { 54 | _retainedProtocol = [[SPDYStubbedProtocol alloc] init]; 55 | self.protocol = _retainedProtocol; 56 | } 57 | } 58 | return self; 59 | } 60 | 61 | - (bool)local 62 | { 63 | return self.streamId % 2 == 1; 64 | } 65 | 66 | @end 67 | 68 | @implementation SPDYStreamManagerTest 69 | { 70 | SPDYStreamManager *_manager; 71 | NSUInteger _numStreams; 72 | } 73 | 74 | - (void)setUp 75 | { 76 | [super setUp]; 77 | _manager = [[SPDYStreamManager alloc] init]; 78 | _numStreams = 100; 79 | 80 | [SPDYStubbedStream resetTestStreamIds]; 81 | for (NSUInteger i = 0; i < _numStreams; i++) { 82 | SPDYStream *stream = [[SPDYStubbedStream alloc] initWithPriority:((uint8_t)arc4random() % 8)]; 83 | _manager[stream.streamId] = stream; 84 | } 85 | } 86 | 87 | - (void)tearDown 88 | { 89 | [super tearDown]; 90 | } 91 | 92 | - (void)testFastIterationByPriority 93 | { 94 | SPDYStream *prevStream; 95 | NSUInteger streamCount = 0; 96 | NSUInteger localStreamCount = 0; 97 | NSUInteger remoteStreamCount = 0; 98 | for (SPDYStream *stream in _manager) { 99 | streamCount++; 100 | stream.local ? localStreamCount++ : remoteStreamCount++; 101 | if (prevStream) { 102 | STAssertTrue(prevStream.priority <= stream.priority, nil); 103 | } 104 | prevStream = stream; 105 | } 106 | STAssertEquals(streamCount, _numStreams, nil); 107 | STAssertEquals(streamCount, _manager.count, nil); 108 | STAssertEquals(localStreamCount, _manager.localCount, nil); 109 | STAssertEquals(remoteStreamCount, _manager.remoteCount, nil); 110 | } 111 | 112 | - (void)testSubscriptAccessors 113 | { 114 | for (SPDYStream *stream in _manager) { 115 | STAssertEquals(_manager[stream.streamId], stream, nil); 116 | if (stream.protocol) { 117 | STAssertEquals(_manager[stream.protocol], stream, nil); 118 | } 119 | } 120 | } 121 | 122 | - (void)testStreamRemoval 123 | { 124 | SPDYStream *one = _manager[4]; 125 | SPDYStream *two = _manager[5]; 126 | 127 | STAssertNotNil(one, nil); 128 | STAssertNotNil(two, nil); 129 | 130 | NSUInteger prevRemoteStreamCount = _manager.remoteCount; 131 | [_manager removeStreamWithStreamId:4]; 132 | STAssertEquals(prevRemoteStreamCount - 1, _manager.remoteCount, nil); 133 | 134 | NSUInteger prevLocalStreamCount = _manager.localCount; 135 | [_manager removeStreamForProtocol:two.protocol]; 136 | STAssertEquals(prevLocalStreamCount - 1, _manager.localCount, nil); 137 | 138 | STAssertNil(_manager[4], nil); 139 | STAssertNil(_manager[5], nil); 140 | 141 | SPDYStream *prevStream; 142 | NSUInteger streamCount = 0; 143 | for (SPDYStream *stream in _manager) { 144 | streamCount++; 145 | if (prevStream) { 146 | STAssertTrue(prevStream.priority <= stream.priority, nil); 147 | } 148 | prevStream = stream; 149 | } 150 | STAssertEquals(streamCount, _numStreams - 2, nil); 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYStreamTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPDYStreamTest.m 3 | // SPDY 4 | // 5 | // Copyright (c) 2014 Twitter, Inc. All rights reserved. 6 | // Licensed under the Apache License v2.0 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Created by Michael Schore and Jeffrey Pinner. 10 | // 11 | 12 | #import 13 | #import "SPDYStream.h" 14 | 15 | @interface SPDYStreamTest : SenTestCase 16 | @end 17 | 18 | typedef void (^SPDYAsyncTestCallback)(); 19 | 20 | @interface SPDYMockStreamDelegate : NSObject 21 | @property (nonatomic, readonly) NSData *data; 22 | @property (nonatomic, copy) SPDYAsyncTestCallback callback; 23 | @end 24 | 25 | @implementation SPDYMockStreamDelegate 26 | { 27 | NSMutableData *_data; 28 | } 29 | 30 | - (id)init 31 | { 32 | self = [super init]; 33 | if (self) { 34 | _data = [NSMutableData new]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)streamDataAvailable:(SPDYStream *)stream 40 | { 41 | [_data appendData:[stream readData:10 error:nil]]; 42 | } 43 | 44 | - (void)streamDataFinished:(SPDYStream *)stream 45 | { 46 | _callback(); 47 | } 48 | 49 | - (void)streamCanceled:(SPDYStream *)stream 50 | { 51 | // No-op 52 | } 53 | 54 | - (void)streamClosed:(SPDYStream *)stream 55 | { 56 | // No-op 57 | } 58 | 59 | @end 60 | 61 | @implementation SPDYStreamTest 62 | 63 | static const NSUInteger kTestDataLength = 128; 64 | static NSMutableData *_uploadData; 65 | static NSThread *_streamThread; 66 | 67 | + (void)setUp 68 | { 69 | _uploadData = [[NSMutableData alloc] initWithCapacity:kTestDataLength]; 70 | for (int i = 0; i < kTestDataLength; i++) { 71 | [_uploadData appendBytes:&(uint32_t){ arc4random() } length:4]; 72 | } 73 | // SecRandomCopyBytes(kSecRandomDefault, kTestDataLength, _uploadData.mutableBytes); 74 | } 75 | 76 | - (void)testStreamingWithData 77 | { 78 | NSMutableData *producedData = [[NSMutableData alloc] initWithCapacity:kTestDataLength]; 79 | SPDYStream *spdyStream = [SPDYStream new]; 80 | spdyStream.data = _uploadData; 81 | 82 | while(spdyStream.hasDataAvailable) { 83 | [producedData appendData:[spdyStream readData:10 error:nil]]; 84 | } 85 | 86 | STAssertTrue([producedData isEqualToData:_uploadData], nil); 87 | } 88 | 89 | - (void)testStreamingWithStream 90 | { 91 | SPDYMockStreamDelegate *mockDelegate = [SPDYMockStreamDelegate new]; 92 | SPDYStream *spdyStream = [SPDYStream new]; 93 | spdyStream.delegate = mockDelegate; 94 | spdyStream.dataStream = [[NSInputStream alloc] initWithData:_uploadData]; 95 | 96 | dispatch_semaphore_t main = dispatch_semaphore_create(0); 97 | dispatch_semaphore_t alt = dispatch_semaphore_create(0); 98 | mockDelegate.callback = ^{ 99 | dispatch_semaphore_signal(main); 100 | }; 101 | 102 | STAssertTrue([NSThread isMainThread], @"dispatch must occur from main thread"); 103 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 104 | STAssertFalse([NSThread isMainThread], @"stream must be scheduled off main thread"); 105 | 106 | [spdyStream startWithStreamId:1 sendWindowSize:1024 receiveWindowSize:1024]; 107 | 108 | // Run off-thread runloop 109 | while(dispatch_semaphore_wait(main, DISPATCH_TIME_NOW)) { 110 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); 111 | } 112 | dispatch_semaphore_signal(alt); 113 | }); 114 | 115 | // Run main thread runloop 116 | while(dispatch_semaphore_wait(alt, DISPATCH_TIME_NOW)) { 117 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); 118 | } 119 | 120 | STAssertTrue([mockDelegate.data isEqualToData:_uploadData], nil); 121 | } 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYUnitTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.twitter.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SPDYUnitTests/SPDYUnitTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SPDYUnitTests' target in the 'SPDYUnitTests' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /SPDYUnitTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /scripts/build-universal-framework.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | 3 | # This script is used to produce a tarball for release. It will build and assemble in 4 | # /var/tmp, then move the final tarbal to your desktop. It takes one optional input 5 | # parameter, the version, and must be run from the root CocoaSPDY directory. Examples: 6 | # 7 | # scripts/build-universal-framework.sh 1.1.0 8 | # scripts/build-universal-framework.sh (will default to 0.0.0 version) 9 | # 10 | # kgoodier, 03/2015 11 | 12 | set -e 13 | 14 | if [ -n "$1" ]; then 15 | VERSION="$1" 16 | else 17 | VERSION="0.0.0" 18 | fi 19 | 20 | CC= 21 | PRODUCT="CocoaSPDY-${VERSION}" 22 | UNIVERSAL_PATH="/var/tmp/${PRODUCT}" 23 | UNIVERSAL_BUILD_DIR="/var/tmp/${PRODUCT}-build" 24 | IOS_FRAMEWORK_PATH="${UNIVERSAL_BUILD_DIR}/Build/Products/Release-iphoneos/SPDY.framework" 25 | OSX_FRAMEWORK_PATH="${UNIVERSAL_BUILD_DIR}/Build/Products/Release/SPDY.framework" 26 | 27 | # Cleanup 28 | rm -rf "${UNIVERSAL_PATH}" 29 | rm -rf "${UNIVERSAL_BUILD_DIR}" 30 | 31 | # Build the "SPDY" scheme (frameworks for both platforms) 32 | xcodebuild -project "SPDY.xcodeproj" -configuration "Release" -scheme "SPDY" -derivedDataPath "${UNIVERSAL_BUILD_DIR}" 33 | 34 | # Prepare output dir structure 35 | mkdir -p "${UNIVERSAL_PATH}/iphoneos" 36 | mkdir -p "${UNIVERSAL_PATH}/macosx" 37 | cp -fR "${IOS_FRAMEWORK_PATH}" "${UNIVERSAL_PATH}/iphoneos/SPDY.framework" 38 | cp -fR "${OSX_FRAMEWORK_PATH}" "${UNIVERSAL_PATH}/macosx/SPDY.framework" 39 | 40 | # Create .tar.gz file 41 | pushd "/var/tmp" 42 | tar -cvzf "${PRODUCT}.tar.gz" "${PRODUCT}" 43 | popd 44 | 45 | # Cleanup 46 | rm -rf "${UNIVERSAL_PATH}" 47 | rm -rf "${UNIVERSAL_BUILD_DIR}" 48 | 49 | # Move tarball to desktop 50 | mv -f "${UNIVERSAL_PATH}.tar.gz" "${HOME}/Desktop/" 51 | echo "Created ${HOME}/Desktop/${PRODUCT}.tar.gz" 52 | 53 | --------------------------------------------------------------------------------