├── .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 | [](https://travis-ci.org/twitter/CocoaSPDY) | [](https://coveralls.io/r/twitter/CocoaSPDY?branch=master)
7 | develop | [](https://travis-ci.org/twitter/CocoaSPDY) | [](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 |
--------------------------------------------------------------------------------