pax_global_header 0000666 0000000 0000000 00000000064 13775242373 0014527 g ustar 00root root 0000000 0000000 52 comment=8ec79e8982cb65fb9740ed8ca6e1950774a5a533
ruby-http-2-0.11.0/ 0000775 0000000 0000000 00000000000 13775242373 0013703 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/.autotest 0000664 0000000 0000000 00000000724 13775242373 0015557 0 ustar 00root root 0000000 0000000 require 'autotest/growl'
Autotest.add_hook(:initialize) {|at|
at.add_exception %r{^\.git|\.yardoc}
at.add_exception %r{^./tmp}
at.add_exception %r{coverage}
at.clear_mappings
at.add_mapping(%r%^spec/.*_spec\.rb$%) { |filename, _|
filename
}
at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m|
["spec/#{m[1].split('/').last}_spec.rb"]
}
at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) {
files_matching %r%^spec/.*_spec\.rb$%
}
nil
}
ruby-http-2-0.11.0/.coveralls.yml 0000664 0000000 0000000 00000000030 13775242373 0016467 0 ustar 00root root 0000000 0000000 service_name: travis-ci
ruby-http-2-0.11.0/.gitignore 0000664 0000000 0000000 00000000262 13775242373 0015673 0 ustar 00root root 0000000 0000000 *.gem
*.rbc
.bundle
.config
.yardoc
.DS_Store
.idea
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
vendor/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
ruby-http-2-0.11.0/.gitmodules 0000664 0000000 0000000 00000000162 13775242373 0016057 0 ustar 00root root 0000000 0000000 [submodule "spec/hpack-test-case"]
path = spec/hpack-test-case
url = https://github.com/http2jp/hpack-test-case
ruby-http-2-0.11.0/.rspec 0000664 0000000 0000000 00000000100 13775242373 0015007 0 ustar 00root root 0000000 0000000 autotest
--color
--format documentation
--order rand
--warnings
ruby-http-2-0.11.0/.rubocop.yml 0000664 0000000 0000000 00000002742 13775242373 0016162 0 ustar 00root root 0000000 0000000 inherit_from: .rubocop_todo.yml
AllCops:
TargetRubyVersion: 2.1
DisplayCopNames: true
Exclude:
- 'bin/**'
- 'vendor/**/*'
- '**/huffman_statemachine.rb'
Layout/IndentHeredoc:
Exclude:
- 'lib/tasks/generate_huffman_table.rb'
- 'example/*'
Metrics/LineLength:
Max: 120
Metrics/BlockLength:
Max: 700
Layout/EndAlignment:
EnforcedStyleAlignWith: variable
Layout/CaseIndentation:
EnforcedStyle: end
Layout/IndentHash:
EnforcedStyle: consistent
Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: comma
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: comma
Layout/SpaceAroundOperators:
Enabled: false
Layout/ExtraSpacing:
Enabled: false
Layout/EmptyLinesAroundExceptionHandlingKeywords:
Enabled: false
Naming/UncommunicativeMethodParamName:
Enabled: false
Style/SignalException:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false
Style/ParallelAssignment:
Enabled: false
Style/ParenthesesAroundCondition:
Enabled: false
Style/IfInsideElse:
Enabled: false
Style/IfUnlessModifier:
Enabled: false
Style/MultilineIfModifier:
Enabled: false
Lint/EmptyWhen:
Enabled: false
Style/TrailingCommaInArguments:
Enabled: false
Style/TrailingUnderscoreVariable:
Enabled: false
Style/SymbolArray:
Enabled: false
Style/CommentedKeyword:
Enabled: false
Style/PercentLiteralDelimiters:
Enabled: false
Performance/TimesMap:
Enabled: false
Performance/RedundantBlockCall:
Enabled: false
ruby-http-2-0.11.0/.rubocop_todo.yml 0000664 0000000 0000000 00000006072 13775242373 0017207 0 ustar 00root root 0000000 0000000 # This configuration was generated by
# `rubocop --auto-gen-config`
# on 2016-06-09 10:57:54 -0400 using RuboCop version 0.40.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 24
Metrics/AbcSize:
Max: 185
# Offense count: 16
Metrics/BlockNesting:
Max: 5
# Offense count: 5
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 325
Metrics/ModuleLength:
Max: 120
# Offense count: 12
Metrics/CyclomaticComplexity:
Max: 60
# Offense count: 9
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
# URISchemes: http, https
Metrics/LineLength:
Max: 108
# Offense count: 29
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 134
# Offense count: 1
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 7
# Offense count: 10
Metrics/PerceivedComplexity:
Max: 46
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: MaxKeyValuePairs.
Performance/RedundantMerge:
Exclude:
- 'lib/http/2/server.rb'
# Offense count: 4
Style/Documentation:
Exclude:
- 'spec/**/*'
- 'test/**/*'
- 'example/helper.rb'
- 'example/upgrade_server.rb'
- 'lib/tasks/generate_huffman_table.rb'
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: braces, no_braces, context_dependent
Style/BracesAroundHashParameters:
Exclude:
- 'spec/connection_spec.rb'
- 'spec/server_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/EmptyCaseCondition:
Exclude:
- 'example/upgrade_server.rb'
# Offense count: 1
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Exclude:
- 'lib/http/2/connection.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Layout/IndentArray:
EnforcedStyle: consistent
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: symmetrical, new_line, same_line
Layout/MultilineArrayBraceLayout:
Exclude:
- 'spec/compressor_spec.rb'
# Offense count: 16
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: symmetrical, new_line, same_line
Layout/MultilineHashBraceLayout:
Exclude:
- 'spec/compressor_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/MutableConstant:
Exclude:
- 'example/upgrade_server.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/RedundantSelf:
Exclude:
- 'lib/http/2/connection.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/UnneededInterpolation:
Exclude:
- 'spec/compressor_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/ZeroLengthPredicate:
Exclude:
- 'lib/http/2/framer.rb'
ruby-http-2-0.11.0/.travis.yml 0000664 0000000 0000000 00000000327 13775242373 0016016 0 ustar 00root root 0000000 0000000 language: ruby
cache: bundler
rvm:
- 2.1
- 2.2
- 2.3
- 2.4
- 2.5
- 2.6
- 2.7
- jruby-9.2.0.0 # latest stable
- jruby-head
- rbx-2
matrix:
allow_failures:
- rvm: jruby-head
- rvm: rbx-2
ruby-http-2-0.11.0/Gemfile 0000664 0000000 0000000 00000000412 13775242373 0015173 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
gem 'rake'
gem 'yard'
group :test do
gem 'autotest-standalone'
gem 'coveralls', require: false
gem 'pry'
gem 'pry-byebug', platform: :mri
gem 'rspec', '~> 3.4.0'
gem 'rspec-autotest'
gem 'rubocop', '0.57.2'
end
gemspec
ruby-http-2-0.11.0/Guardfile 0000664 0000000 0000000 00000000676 13775242373 0015541 0 ustar 00root root 0000000 0000000 guard :process, name: 'HTTP/2 Server', command: 'ruby example/server.rb', stop_signal: 'TERM' do
watch(%r{^example/(.+)\.rb$})
watch(%r{^lib/http/2/(.+)\.rb$})
watch('Gemfile.lock')
end
def h2spec
puts 'Starting H2 Spec'
sleep 0.7
system '~/go-workspace/bin/h2spec -p 8080 -o 1 -s 4.2'
puts "\n"
end
guard :shell, name: 'H2 Spec' do
watch(%r{^example/(.+)\.rb$}) { h2spec }
watch(%r{^lib/http/2/(.+)\.rb$}) { h2spec }
end
ruby-http-2-0.11.0/Guardfile.h2spec 0000664 0000000 0000000 00000000374 13775242373 0016717 0 ustar 00root root 0000000 0000000 def h2spec
puts "Starting H2 Spec"
sleep 0.7
system '~/go-workspace/bin/h2spec -p 8080 -o 1 -s 5.1'
puts "\n"
end
guard :shell, name:'H2 Spec' do
watch(%r{^example/(.+)\.rb$}) { h2spec }
watch(%r{^lib/http/2/(.+)\.rb$}) { h2spec }
end
ruby-http-2-0.11.0/LICENSE 0000664 0000000 0000000 00000002056 13775242373 0014713 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2013 Ilya Grigorik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
ruby-http-2-0.11.0/README.md 0000664 0000000 0000000 00000026324 13775242373 0015171 0 ustar 00root root 0000000 0000000 # HTTP-2
[](http://rubygems.org/gems/http-2)
[](https://travis-ci.org/igrigorik/http-2)
[](https://coveralls.io/r/igrigorik/http-2)
[](https://github.com/igrigorik/ga-beacon)
Pure Ruby, framework and transport agnostic, implementation of HTTP/2 protocol and HPACK header compression with support for:
* [Binary framing](https://hpbn.co/http2/#binary-framing-layer) parsing and encoding
* [Stream multiplexing](https://hpbn.co/http2/#streams-messages-and-frames) and [prioritization](https://hpbn.co/http2/#stream-prioritization)
* Connection and stream [flow control](https://hpbn.co/http2/#flow-control)
* [Header compression](https://hpbn.co/http2/#header-compression) and [server push](https://hpbn.co/http2/#server-push)
* Connection and stream management
* And more... see [API docs](http://www.rubydoc.info/github/igrigorik/http-2/frames)
Protocol specifications:
* [Hypertext Transfer Protocol Version 2 (RFC 7540)](https://httpwg.github.io/specs/rfc7540.html)
* [HPACK: Header Compression for HTTP/2 (RFC 7541)](https://httpwg.github.io/specs/rfc7541.html)
## Getting started
```bash
$> gem install http-2
```
This implementation makes no assumptions as how the data is delivered: it could be a regular Ruby TCP socket, your custom eventloop, or whatever other transport you wish to use - e.g. ZeroMQ, [avian carriers](http://www.ietf.org/rfc/rfc1149.txt), etc.
Your code is responsible for feeding data into the parser, which performs all of the necessary HTTP/2 decoding, state management and the rest, and vice versa, the parser will emit bytes (encoded HTTP/2 frames) that you can then route to the destination. Roughly, this works as follows:
```ruby
require 'http/2'
socket = YourTransport.new
conn = HTTP2::Client.new
conn.on(:frame) {|bytes| socket << bytes }
while bytes = socket.read
conn << bytes
end
```
Checkout provided [client](https://github.com/igrigorik/http-2/blob/master/example/client.rb) and [server](https://github.com/igrigorik/http-2/blob/master/example/server.rb) implementations for basic examples.
### Connection lifecycle management
Depending on the role of the endpoint you must initialize either a [Client](http://www.rubydoc.info/github/igrigorik/http-2/HTTP2/Client) or a [Server](http://www.rubydoc.info/github/igrigorik/http-2/HTTP2/Server) object. Doing so picks the appropriate header compression / decompression algorithms and stream management logic. From there, you can subscribe to connection level events, or invoke appropriate APIs to allocate new streams and manage the lifecycle. For example:
```ruby
# - Server ---------------
server = HTTP2::Server.new
server.on(:stream) { |stream| ... } # process inbound stream
server.on(:frame) { |bytes| ... } # encoded HTTP/2 frames
server.ping { ... } # run liveness check, process pong response
server.goaway # send goaway frame to the client
# - Client ---------------
client = HTTP2::Client.new
client.on(:promise) { |stream| ... } # process push promise
stream = client.new_stream # allocate new stream
stream.headers({':method' => 'post', ...}, end_stream: false)
stream.data(payload, end_stream: true)
```
Events emitted by the connection object:
:promise |
client role only, fires once for each new push promise |
:stream |
server role only, fires once for each new client stream |
:frame |
fires once for every encoded HTTP/2 frame that needs to be sent to the peer |
### Stream lifecycle management
A single HTTP/2 connection can [multiplex multiple streams](https://hpbn.co/http2/#request-and-response-multiplexing) in parallel: multiple requests and responses can be in flight simultaneously and stream data can be interleaved and prioritized. Further, the specification provides a well-defined lifecycle for each stream (see below).
The good news is, all of the stream management, and state transitions, and error checking is handled by the library. All you have to do is subscribe to appropriate events (marked with ":" prefix in diagram below) and provide your application logic to handle request and response processing.
```
+--------+
PP | | PP
,--------| idle |--------.
/ | | \
v +--------+ v
+----------+ | +----------+
| | | H | |
,---|:reserved | | |:reserved |---.
| | (local) | v | (remote) | |
| +----------+ +--------+ +----------+ |
| | :active | | :active | |
| | ,-------|:active |-------. | |
| | H / ES | | ES \ H | |
| v v +--------+ v v |
| +-----------+ | +-----------+ |
| |:half_close| | |:half_close| |
| | (remote) | | | (local) | |
| +-----------+ | +-----------+ |
| | v | |
| | ES/R +--------+ ES/R | |
| `----------->| |<-----------' |
| R | :close | R |
`-------------------->| |<--------------------'
+--------+
```
For sake of example, let's take a look at a simple server implementation:
```ruby
conn = HTTP2::Server.new
# emits new streams opened by the client
conn.on(:stream) do |stream|
stream.on(:active) { } # fires when stream transitions to open state
stream.on(:close) { } # stream is closed by client and server
stream.on(:headers) { |head| ... } # header callback
stream.on(:data) { |chunk| ... } # body payload callback
# fires when client terminates its request (i.e. request finished)
stream.on(:half_close) do
# ... generate_response
# send response
stream.headers({
":status" => 200,
"content-type" => "text/plain"
})
# split response between multiple DATA frames
stream.data(response_chunk, end_stream: false)
stream.data(last_chunk)
end
end
```
Events emitted by the [Stream object](http://www.rubydoc.info/github/igrigorik/http-2/HTTP2/Stream):
:reserved |
fires exactly once when a push stream is initialized |
:active |
fires exactly once when the stream become active and is counted towards the open stream limit |
:headers |
fires once for each received header block (multi-frame blocks are reassembled before emitting this event) |
:data |
fires once for every DATA frame (no buffering) |
:half_close |
fires exactly once when the opposing peer closes its end of connection (e.g. client indicating that request is finished, or server indicating that response is finished) |
:close |
fires exactly once when both peers close the stream, or if the stream is reset |
:priority |
fires once for each received priority update (server only) |
### Prioritization
Each HTTP/2 [stream has a priority value](https://hpbn.co/http2/#stream-prioritization) that can be sent when the new stream is initialized, and optionally reprioritized later:
```ruby
client = HTTP2::Client.new
default_priority_stream = client.new_stream
custom_priority_stream = client.new_stream(priority: 42)
# sometime later: change priority value
custom_priority_stream.reprioritize(32000) # emits PRIORITY frame
```
On the opposite side, the server can optimize its stream processing order or resource allocation by accessing the stream priority value (`stream.priority`).
### Flow control
Multiplexing multiple streams over the same TCP connection introduces contention for shared bandwidth resources. Stream priorities can help determine the relative order of delivery, but priorities alone are insufficient to control how the resource allocation is performed between multiple streams. To address this, HTTP/2 provides a simple mechanism for [stream and connection flow control](https://hpbn.co/http2/#flow-control).
Connection and stream flow control is handled by the library: all streams are initialized with the default window size (64KB), and send/receive window updates are automatically processed - i.e. window is decremented on outgoing data transfers, and incremented on receipt of window frames. Similarly, if the window is exceeded, then data frames are automatically buffered until window is updated.
The only thing left is for your application to specify the logic as to when to emit window updates:
```ruby
conn.buffered_amount # check amount of buffered data
conn.window # check current window size
conn.window_update(1024) # increment connection window by 1024 bytes
stream.buffered_amount # check amount of buffered data
stream.window # check current window size
stream.window_update(2048) # increment stream window by 2048 bytes
```
### Server push
An HTTP/2 server can [send multiple replies](https://hpbn.co/http2/#server-push) to a single client request. To do so, first it emits a "push promise" frame which contains the headers of the promised resource, followed by the response to the original request, as well as promised resource payloads (which may be interleaved). A simple example is in order:
```ruby
conn = HTTP2::Server.new
conn.on(:stream) do |stream|
stream.on(:headers) { |head| ... }
stream.on(:data) { |chunk| ... }
# fires when client terminates its request (i.e. request finished)
stream.on(:half_close) do
promise_header = { ':method' => 'GET',
':authority' => 'localhost',
':scheme' => 'https',
':path' => "/other_resource" }
# initiate server push stream
push_stream = nil
stream.promise(promise_header) do |push|
push.headers({...})
push_stream = push
end
# send response
stream.headers({
":status" => 200,
"content-type" => "text/plain"
})
# split response between multiple DATA frames
stream.data(response_chunk, end_stream: false)
stream.data(last_chunk)
# now send the previously promised data
push_stream.data(push_data)
end
end
```
When a new push promise stream is sent by the server, the client is notified via the `:promise` event:
```ruby
conn = HTTP2::Client.new
conn.on(:promise) do |push|
# process push stream
end
```
The client can cancel any given push stream (via `.close`), or disable server push entirely by sending the appropriate settings frame:
```ruby
client.settings(settings_enable_push: 0)
```
### Specs
To run specs:
```ruby
rake
```
### License
(MIT License) - Copyright (c) 2013 Ilya Grigorik 
ruby-http-2-0.11.0/Rakefile 0000664 0000000 0000000 00000002220 13775242373 0015344 0 ustar 00root root 0000000 0000000 require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
require 'yard'
require 'open3'
require_relative 'lib/tasks/generate_huffman_table'
RSpec::Core::RakeTask.new(:spec) do |t|
t.exclude_pattern = './spec/hpack_test_spec.rb'
end
RSpec::Core::RakeTask.new(:hpack) do |t|
t.pattern = './spec/hpack_test_spec.rb'
end
task :h2spec do
if /darwin/ !~ RUBY_PLATFORM
abort "h2spec rake task currently only works on OSX.
Download other binaries from https://github.com/summerwind/h2spec/releases"
end
system 'ruby example/server.rb -p 9000 &', out: File::NULL
sleep 1
output = ''
Open3.popen2e('spec/h2spec/h2spec.darwin -p 9000 -o 1') do |_i, oe, _t|
oe.each do |l|
l.gsub!(/\e\[(\d+)(;\d+)*m/, '')
output << l
if l =~ /passed.*failed/
puts "\n#{l}"
break # suppress post-summary failure output
else
print '.'
end
end
end
File.write 'spec/h2spec/output/non_secure.txt', output
system 'kill `pgrep -f example/server.rb`'
end
RuboCop::RakeTask.new
YARD::Rake::YardocTask.new
task default: [:spec, :rubocop]
task all: [:default, :hpack]
ruby-http-2-0.11.0/example/ 0000775 0000000 0000000 00000000000 13775242373 0015336 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/example/Gemfile 0000664 0000000 0000000 00000000064 13775242373 0016631 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
gem 'http_parser.rb'
ruby-http-2-0.11.0/example/README.md 0000664 0000000 0000000 00000002212 13775242373 0016612 0 ustar 00root root 0000000 0000000 ## Interop
First, a quick test to ensure that we can talk to ourselves:
```bash
# Direct connection
$> ruby server.rb
$> ruby client.rb http://localhost:8080/ # GET
$> ruby client.rb http://localhost:8080/ -d 'some data' # POST
# Server push
$> ruby server.rb --push
$> ruby client.rb http://localhost:8080/ # GET
# TLS + NPN negotiation
$> ruby server.rb --secure
$> ruby client.rb https://localhost:8080/ # GET
$> ...
```
### [nghttp2](https://github.com/tatsuhiro-t/nghttp2) (HTTP/2.0 C Library)
Public test server: http://106.186.112.116 (Upgrade + Direct)
```bash
# Direct request (http-2 > nghttp2)
$> ruby client.rb http://106.186.112.116/
# TLS + NPN request (http-2 > nghttp2)
$> ruby client.rb https://106.186.112.116/
# Direct request (nghttp2 > http-2)
$> ruby server.rb
$> nghttp -vnu http://localhost:8080 # Direct request to Ruby server
```
### Twitter (Java server)
```bash
# NPN + GET request (http-2 > twitter)
$> ruby client.rb https://twitter.com/
```
For a complete list of current implementations, see [http2 wiki](https://github.com/http2/http2-spec/wiki/Implementations).
ruby-http-2-0.11.0/example/client.rb 0000664 0000000 0000000 00000005114 13775242373 0017142 0 ustar 00root root 0000000 0000000 require_relative 'helper'
options = {}
OptionParser.new do |opts|
opts.banner = 'Usage: client.rb [options]'
opts.on('-d', '--data [String]', 'HTTP payload') do |v|
options[:payload] = v
end
end.parse!
uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
tcp = TCPSocket.new(uri.host, uri.port)
sock = nil
if uri.scheme == 'https'
ctx = OpenSSL::SSL::SSLContext.new
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
# For ALPN support, Ruby >= 2.3 and OpenSSL >= 1.0.2 are required
ctx.alpn_protocols = [DRAFT]
ctx.alpn_select_cb = lambda do |protocols|
puts "ALPN protocols supported by server: #{protocols}"
DRAFT if protocols.include? DRAFT
end
sock = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
sock.sync_close = true
sock.hostname = uri.hostname
sock.connect
if sock.alpn_protocol != DRAFT
puts "Failed to negotiate #{DRAFT} via ALPN"
exit
end
else
sock = tcp
end
conn = HTTP2::Client.new
stream = conn.new_stream
log = Logger.new(stream.id)
conn.on(:frame) do |bytes|
# puts "Sending bytes: #{bytes.unpack("H*").first}"
sock.print bytes
sock.flush
end
conn.on(:frame_sent) do |frame|
puts "Sent frame: #{frame.inspect}"
end
conn.on(:frame_received) do |frame|
puts "Received frame: #{frame.inspect}"
end
conn.on(:promise) do |promise|
promise.on(:promise_headers) do |h|
log.info "promise request headers: #{h}"
end
promise.on(:headers) do |h|
log.info "promise headers: #{h}"
end
promise.on(:data) do |d|
log.info "promise data chunk: <<#{d.size}>>"
end
end
conn.on(:altsvc) do |f|
log.info "received ALTSVC #{f}"
end
stream.on(:close) do
log.info 'stream closed'
end
stream.on(:half_close) do
log.info 'closing client-end of the stream'
end
stream.on(:headers) do |h|
log.info "response headers: #{h}"
end
stream.on(:data) do |d|
log.info "response data chunk: <<#{d}>>"
end
stream.on(:altsvc) do |f|
log.info "received ALTSVC #{f}"
end
head = {
':scheme' => uri.scheme,
':method' => (options[:payload].nil? ? 'GET' : 'POST'),
':authority' => [uri.host, uri.port].join(':'),
':path' => uri.path,
'accept' => '*/*',
}
puts 'Sending HTTP 2.0 request'
if head[':method'] == 'GET'
stream.headers(head, end_stream: true)
else
stream.headers(head, end_stream: false)
stream.data(options[:payload])
end
while !sock.closed? && !sock.eof?
data = sock.read_nonblock(1024)
# puts "Received bytes: #{data.unpack("H*").first}"
begin
conn << data
rescue StandardError => e
puts "#{e.class} exception: #{e.message} - closing socket."
e.backtrace.each { |l| puts "\t" + l }
sock.close
end
end
ruby-http-2-0.11.0/example/helper.rb 0000664 0000000 0000000 00000000377 13775242373 0017151 0 ustar 00root root 0000000 0000000 $LOAD_PATH << 'lib' << '../lib'
require 'optparse'
require 'socket'
require 'openssl'
require 'http/2'
require 'uri'
DRAFT = 'h2'.freeze
class Logger
def initialize(id)
@id = id
end
def info(msg)
puts "[Stream #{@id}]: #{msg}"
end
end
ruby-http-2-0.11.0/example/keys/ 0000775 0000000 0000000 00000000000 13775242373 0016311 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/example/keys/server.crt 0000664 0000000 0000000 00000002214 13775242373 0020330 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDLjCCAhYCCQDIZ/9hq/2pXjANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTYwNjA2MTk0MzI1WhcN
MTcwNjA2MTk0MzI1WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDEwls
b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjFXrWqtRZ
EMOO/o6AxGbgDYMgg/7uxCFQJM5Z6C4II6D8V94FDyCd+J0LOK2hB+QUdFqpA1S9
6RW2MvIwdvRt03RJMgbfcUF0+w4ZItv2xrW9waCfCmLSRDZgcSATacEF6u9p2Vs+
o4J/cHacirSwjy4+m94CgkxtUFGtGcJaFqAZ6Cdj5WvQdJSiAI3x3gNC/UGA+5dL
sp8+vwWx+/TMc6nDBmoRW3GHeG/NApQSh01w3wDv0FmUaFQlA5WPya/Js+CyuYh1
miXbQJEjDnGGaJjnoyRAQpPrk72Jj+bnfOu9kxpzkuLJOsbaofRFkM+/Ar5U+bQz
uU0ErQ8Ih8MPAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBACL8HkKjW8kWLlW4TE5K
EcfsBad2Ah5ugTocJ/pLnr/YEo8uD91gECDtsuFTXul9n2M7U5jJmzbHZ63cjyC3
lb1BxJxUL7aIyaL61IMMcIJMWhC9VGnFUshMDNVBhuRkKs/QvaMD5KefKN1E9I2M
mZ72Yww0VihYwNOu3MTn8gUuy9eU6k/gTYPY7PJVh18QmR+Fs2MaaPp+bDwxiqML
0o2I6+0ZsqM3vFtcUjxjRASV5s+JkM34pTWFwUOl7TZv1YsxCKSz4f0BXDImZEvU
rwqFdlELp5WOG9LJsrszDRbf1wbFUsG1XXZpIBiWo3d6pOiIyRKrak1vKViNfYvI
W30=
-----END CERTIFICATE-----
ruby-http-2-0.11.0/example/keys/server.key 0000664 0000000 0000000 00000003217 13775242373 0020334 0 ustar 00root root 0000000 0000000 -----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAoxV61qrUWRDDjv6OgMRm4A2DIIP+7sQhUCTOWeguCCOg/Ffe
BQ8gnfidCzitoQfkFHRaqQNUvekVtjLyMHb0bdN0STIG33FBdPsOGSLb9sa1vcGg
nwpi0kQ2YHEgE2nBBervadlbPqOCf3B2nIq0sI8uPpveAoJMbVBRrRnCWhagGegn
Y+Vr0HSUogCN8d4DQv1BgPuXS7KfPr8Fsfv0zHOpwwZqEVtxh3hvzQKUEodNcN8A
79BZlGhUJQOVj8mvybPgsrmIdZol20CRIw5xhmiY56MkQEKT65O9iY/m53zrvZMa
c5LiyTrG2qH0RZDPvwK+VPm0M7lNBK0PCIfDDwIDAQABAoIBAQCZShphZsccRJ6c
bPdDX9iW5vyG9qsMgPwTGdWAOrXx3pN2PZ0pwjNVaRcsMgU6JHGlLEz/Kmtf6pQG
41I0bcuI48Yc+tHs+sadD1IMHHEHP3Yau8KfWyLSI129Pvf4Z2IQjuik5LJYaVbD
NNG4iMQYZS0Bmn6Oey0dXu62t0ywYa0qvbIDse/RmjTQSTipuvGg8/QEAeRGABv8
Nd4Esya0zuxk6hGaNp3hkjyRkeoC7RsBVJbFSnp6gSubPdXwrJyHfySKe9jvrDG3
Q/AzyHUh/6EODd5n66x0p6rq7oo9/PnLvZJY8jIGWG+aEp68RJyEgimrwll0rAWw
/buqijGRAoGBANimL8407fFirmct7BceavaeJfXPK5yWiOhVX0XlJ0phAFuaAxK3
5HVT7DD+KKV66g1jtS9FUVZGDiYFHlsdsYuHVYcRmr0h5rZr941obrDwNrM9Nf9C
0uehN5+n/FaeGoQLR3V4THoP3rlkYTlLpQnI5mKA19JukXnIiJM9ARUZAoGBAMC0
mcVsVuSKSFwURtQHHIufxL6SqC2kLTwIQ7exqejNYPCqCiif+ZWOmsTqbVGAGbMK
Ohak4oLwN5IGCl4jNQG+vWagREkx6OXSk5NYcfoNBrOm+0UoFRzoEA85s7Dy6PuD
tBucNZpt1sGauzkCSx7C8jj4ZlSwkv0XhBFfbTZnAoGBAK2wBjF+U6iq4YFM2rLq
KvzOa0Z3MdKXCOmiz//cKDTEMaI+heoyzZCWmIvqpzGLqirT3gUowH23Kk6m2eBY
nOdst0/S+Eha7nkfc9bFe8CUxHXMRAcCTs1ufYadCXtzw3RLCp4NtNpC8N+Wry9d
CtIeYz1jaCOHi0+kSoIobT65AoGAc6hxWkJp7ITqZQlucTdLdKmRheeztKEC3TMA
obGqDqWldww3SKarP431ahZhQjcmNYT/1DNmF7xhPe0OL+3llISMXJn4Ig4ogDdg
h2DgF3nV+eFQkfM6qLzHVrwFE0DXgI1NffzFV0hxSoW5tL+honbStkqv8EiCEBEb
HOovPCUCgYBpXuPARd2ycInAulVHijJmj2rmK7f41ZhVCWovYjcCWyeJyLIO7j+b
MBJZbmwpStJhEjW64nE2zZGWg2HCBbvZz5/SXIr3fp7qVXwpn1TvB/TJDf43t0oF
3caLgyQYoQCsVHKT3cU4s3wuog/DyHKh9FtRkcJrEy7h9Rrc+ModbA==
-----END RSA PRIVATE KEY-----
ruby-http-2-0.11.0/example/server.rb 0000664 0000000 0000000 00000007140 13775242373 0017173 0 ustar 00root root 0000000 0000000 require_relative 'helper'
options = { port: 8080 }
OptionParser.new do |opts|
opts.banner = 'Usage: server.rb [options]'
opts.on('-s', '--secure', 'HTTPS mode') do |v|
options[:secure] = v
end
opts.on('-p', '--port [Integer]', 'listen port') do |v|
options[:port] = v
end
opts.on('-u', '--push', 'Push message') do |_v|
options[:push] = true
end
end.parse!
puts "Starting server on port #{options[:port]}"
server = TCPServer.new(options[:port])
if options[:secure]
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
ctx.ssl_version = :TLSv1_2
ctx.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
ctx.ciphers = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers]
ctx.alpn_protocols = ['h2']
ctx.alpn_select_cb = lambda do |protocols|
raise "Protocol #{DRAFT} is required" if protocols.index(DRAFT).nil?
DRAFT
end
ctx.ecdh_curves = 'P-256'
server = OpenSSL::SSL::SSLServer.new(server, ctx)
end
loop do
sock = server.accept
puts 'New TCP connection!'
conn = HTTP2::Server.new
conn.on(:frame) do |bytes|
# puts "Writing bytes: #{bytes.unpack("H*").first}"
sock.is_a?(TCPSocket) ? sock.sendmsg(bytes) : sock.write(bytes)
end
conn.on(:frame_sent) do |frame|
puts "Sent frame: #{frame.inspect}"
end
conn.on(:frame_received) do |frame|
puts "Received frame: #{frame.inspect}"
end
conn.on(:stream) do |stream|
log = Logger.new(stream.id)
req, buffer = {}, ''
stream.on(:active) { log.info 'client opened new stream' }
stream.on(:close) { log.info 'stream closed' }
stream.on(:headers) do |h|
req = Hash[*h.flatten]
log.info "request headers: #{h}"
end
stream.on(:data) do |d|
log.info "payload chunk: <<#{d}>>"
buffer << d
end
stream.on(:half_close) do
log.info 'client closed its end of the stream'
response = nil
if req[':method'] == 'POST'
log.info "Received POST request, payload: #{buffer}"
response = "Hello HTTP 2.0! POST payload: #{buffer}"
else
log.info 'Received GET request'
response = 'Hello HTTP 2.0! GET request'
end
stream.headers({
':status' => '200',
'content-length' => response.bytesize.to_s,
'content-type' => 'text/plain',
}, end_stream: false)
if options[:push]
push_streams = []
# send 10 promises
10.times do |i|
puts 'sending push'
head = { ':method' => 'GET',
':authority' => 'localhost',
':scheme' => 'https',
':path' => "/other_resource/#{i}" }
stream.promise(head) do |push|
push.headers(':status' => '200', 'content-type' => 'text/plain', 'content-length' => '11')
push_streams << push
end
end
end
# split response into multiple DATA frames
stream.data(response.slice!(0, 5), end_stream: false)
stream.data(response)
if options[:push]
push_streams.each_with_index do |push, i|
sleep 1
push.data("push_data #{i}")
end
end
end
end
while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
data = sock.readpartial(1024)
# puts "Received bytes: #{data.unpack("H*").first}"
begin
conn << data
rescue StandardError => e
puts "#{e.class} exception: #{e.message} - closing socket."
e.backtrace.each { |l| puts "\t" + l }
sock.close
end
end
end
ruby-http-2-0.11.0/example/upgrade_client.rb 0000664 0000000 0000000 00000006161 13775242373 0020654 0 ustar 00root root 0000000 0000000 # frozen_string_literals: true
require_relative 'helper'
require 'http_parser'
OptionParser.new do |opts|
opts.banner = 'Usage: upgrade_client.rb [options]'
end.parse!
uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
sock = TCPSocket.new(uri.host, uri.port)
conn = HTTP2::Client.new
def request_header_hash
Hash.new do |hash, key|
k = key.to_s.downcase
k.tr! '_', '-'
_, value = hash.find { |header_key, _| header_key.downcase == k }
hash[key] = value if value
end
end
conn.on(:frame) do |bytes|
sock.print bytes
sock.flush
end
conn.on(:frame_sent) do |frame|
puts "Sent frame: #{frame.inspect}"
end
conn.on(:frame_received) do |frame|
puts "Received frame: #{frame.inspect}"
end
# upgrader module
class UpgradeHandler
UPGRADE_REQUEST = <>"
end
stream.on(:altsvc) do |f|
log.info "received ALTSVC #{f}"
end
@conn.on(:promise) do |promise|
promise.on(:headers) do |h|
log.info "promise headers: #{h}"
end
promise.on(:data) do |d|
log.info "promise data chunk: <<#{d.size}>>"
end
end
@conn.on(:altsvc) do |f|
log.info "received ALTSVC #{f}"
end
end
end
uh = UpgradeHandler.new(conn, sock)
puts 'Sending HTTP/1.1 upgrade request'
uh.request(uri)
while !sock.closed? && !sock.eof?
data = sock.read_nonblock(1024)
begin
if !uh.parsing && !uh.complete
uh << data
elsif uh.parsing && !uh.complete
uh << data
elsif uh.complete
conn << data
end
rescue StandardError => e
puts "#{e.class} exception: #{e.message} - closing socket."
e.backtrace.each { |l| puts "\t" + l }
conn.close
sock.close
end
end
ruby-http-2-0.11.0/example/upgrade_server.rb 0000664 0000000 0000000 00000011421 13775242373 0020677 0 ustar 00root root 0000000 0000000 # frozen_string_literals: true
require_relative 'helper'
require 'http_parser'
options = { port: 8080 }
OptionParser.new do |opts|
opts.banner = 'Usage: server.rb [options]'
opts.on('-s', '--secure', 'HTTPS mode') do |v|
options[:secure] = v
end
opts.on('-p', '--port [Integer]', 'listen port') do |v|
options[:port] = v
end
end.parse!
puts "Starting server on port #{options[:port]}"
server = TCPServer.new(options[:port])
if options[:secure]
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
ctx.npn_protocols = [DRAFT]
server = OpenSSL::SSL::SSLServer.new(server, ctx)
end
def request_header_hash
Hash.new do |hash, key|
k = key.to_s.downcase
k.tr! '_', '-'
_, value = hash.find { |header_key, _| header_key.downcase == k }
hash[key] = value if value
end
end
class UpgradeHandler
VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
UPGRADE_RESPONSE = < 'http',
':method' => @parser.http_method,
':authority' => headers['Host'],
':path' => @parser.request_url,
}.merge(headers)
@conn.upgrade(settings, request, @body)
end
def complete!
@complete = true
end
def on_headers_complete(headers)
@headers.merge! headers
end
def on_body(chunk)
@body << chunk
end
def on_message_complete
fail unless VALID_UPGRADE_METHODS.include?(@parser.http_method)
@parsing = false
complete!
end
end
loop do
sock = server.accept
puts 'New TCP connection!'
conn = HTTP2::Server.new
conn.on(:frame) do |bytes|
# puts "Writing bytes: #{bytes.unpack("H*").first}"
sock.write bytes
end
conn.on(:frame_sent) do |frame|
puts "Sent frame: #{frame.inspect}"
end
conn.on(:frame_received) do |frame|
puts "Received frame: #{frame.inspect}"
end
conn.on(:stream) do |stream|
log = Logger.new(stream.id)
req = request_header_hash
buffer = ''
stream.on(:active) { log.info 'client opened new stream' }
stream.on(:close) do
log.info 'stream closed'
end
stream.on(:headers) do |h|
req.merge! Hash[*h.flatten]
log.info "request headers: #{h}"
end
stream.on(:data) do |d|
log.info "payload chunk: <<#{d}>>"
buffer << d
end
stream.on(:half_close) do
log.info 'client closed its end of the stream'
if req['Upgrade']
log.info "Processing h2c Upgrade request: #{req}"
if req[':method'] != 'OPTIONS' # Don't respond to OPTIONS...
response = 'Hello h2c world!'
stream.headers({
':status' => '200',
'content-length' => response.bytesize.to_s,
'content-type' => 'text/plain',
}, end_stream: false)
stream.data(response)
end
else
response = nil
if req[':method'] == 'POST'
log.info "Received POST request, payload: #{buffer}"
response = "Hello HTTP 2.0! POST payload: #{buffer}"
else
log.info 'Received GET request'
response = 'Hello HTTP 2.0! GET request'
end
stream.headers({
':status' => '200',
'content-length' => response.bytesize.to_s,
'content-type' => 'text/plain',
}, end_stream: false)
# split response into multiple DATA frames
stream.data(response.slice!(0, 5), end_stream: false)
stream.data(response)
end
end
end
uh = UpgradeHandler.new(conn, sock)
while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
data = sock.readpartial(1024)
# puts "Received bytes: #{data.unpack("H*").first}"
begin
case
when !uh.parsing && !uh.complete
if data.start_with?(*UpgradeHandler::VALID_UPGRADE_METHODS)
uh << data
else
uh.complete!
conn << data
end
when uh.parsing && !uh.complete
uh << data
when uh.complete
conn << data
end
rescue StandardError => e
puts "Exception: #{e}, #{e.message} - closing socket."
puts e.backtrace.last(10).join("\n")
sock.close
end
end
end
# echo foo=bar | nghttp -d - -t 0 -vu http://127.0.0.1:8080/
# nghttp -vu http://127.0.0.1:8080/
ruby-http-2-0.11.0/http-2.gemspec 0000664 0000000 0000000 00000001530 13775242373 0016365 0 ustar 00root root 0000000 0000000 lib = File.expand_path('./lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'http/2/version'
Gem::Specification.new do |spec|
spec.name = 'http-2'
spec.version = HTTP2::VERSION
spec.authors = ['Ilya Grigorik', 'Kaoru Maeda']
spec.email = ['ilya@igvita.com']
spec.description = 'Pure-ruby HTTP 2.0 protocol implementation'
spec.summary = spec.description
spec.homepage = 'https://github.com/igrigorik/http-2'
spec.license = 'MIT'
spec.required_ruby_version = '>=2.1.0'
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ['lib']
spec.add_development_dependency 'bundler'
end
ruby-http-2-0.11.0/lib/ 0000775 0000000 0000000 00000000000 13775242373 0014451 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/lib/http/ 0000775 0000000 0000000 00000000000 13775242373 0015430 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/lib/http/2.rb 0000664 0000000 0000000 00000000525 13775242373 0016120 0 ustar 00root root 0000000 0000000 require 'http/2/version'
require 'http/2/error'
require 'http/2/emitter'
require 'http/2/buffer'
require 'http/2/flow_buffer'
require 'http/2/huffman'
require 'http/2/huffman_statemachine'
require 'http/2/compressor'
require 'http/2/framer'
require 'http/2/connection'
require 'http/2/client'
require 'http/2/server'
require 'http/2/stream'
ruby-http-2-0.11.0/lib/http/2/ 0000775 0000000 0000000 00000000000 13775242373 0015571 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/lib/http/2/buffer.rb 0000664 0000000 0000000 00000003426 13775242373 0017374 0 ustar 00root root 0000000 0000000 require 'forwardable'
module HTTP2
# Binary buffer wraps String.
#
class Buffer
extend Forwardable
def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack,
:size, :each_byte, :to_str, :to_s, :length, :inspect,
:[], :[]=, :empty?, :bytesize, :include?
UINT32 = 'N'.freeze
private_constant :UINT32
# Forces binary encoding on the string
def initialize(str = '')
str = str.dup if str.frozen?
@buffer = str.force_encoding(Encoding::BINARY)
end
# Emulate StringIO#read: slice first n bytes from the buffer.
#
# @param n [Integer] number of bytes to slice from the buffer
def read(n)
Buffer.new(@buffer.slice!(0, n))
end
# Emulate StringIO#getbyte: slice first byte from buffer.
def getbyte
read(1).ord
end
def slice!(*args)
Buffer.new(@buffer.slice!(*args))
end
def slice(*args)
Buffer.new(@buffer.slice(*args))
end
def force_encoding(*args)
@buffer = @buffer.force_encoding(*args)
end
def ==(other)
@buffer == other
end
def +(other)
@buffer += other
end
# Emulate String#getbyte: return nth byte from buffer.
def readbyte(n)
@buffer[n].ord
end
# Slice unsigned 32-bit integer from buffer.
# @return [Integer]
def read_uint32
read(4).unpack(UINT32).first
end
# Ensures that data that is added is binary encoded as well,
# otherwise this could lead to the Buffer instance changing its encoding.
[:<<, :prepend].each do |mutating_method|
define_method(mutating_method) do |string|
string = string.dup if string.frozen?
@buffer.send mutating_method, string.force_encoding(Encoding::BINARY)
self
end
end
end
end
ruby-http-2-0.11.0/lib/http/2/client.rb 0000664 0000000 0000000 00000003540 13775242373 0017376 0 ustar 00root root 0000000 0000000 module HTTP2
# HTTP 2.0 client connection class that implements appropriate header
# compression / decompression algorithms and stream management logic.
#
# Your code is responsible for driving the client object, which in turn
# performs all of the necessary HTTP 2.0 encoding / decoding, state
# management, and the rest. A simple example:
#
# @example
# socket = YourTransport.new
#
# conn = HTTP2::Client.new
# conn.on(:frame) {|bytes| socket << bytes }
#
# while bytes = socket.read
# conn << bytes
# end
#
class Client < Connection
# Initialize new HTTP 2.0 client object.
def initialize(**settings)
@stream_id = 1
@state = :waiting_connection_preface
@local_role = :client
@remote_role = :server
super
end
# Send an outgoing frame. Connection and stream flow control is managed
# by Connection class.
#
# @see Connection
# @param frame [Hash]
def send(frame)
send_connection_preface
super(frame)
end
def receive(frame)
send_connection_preface
super(frame)
end
# sends the preface and initializes the first stream in half-closed state
def upgrade
fail ProtocolError unless @stream_id == 1
send_connection_preface
new_stream(state: :half_closed_local)
end
# Emit the connection preface if not yet
def send_connection_preface
return unless @state == :waiting_connection_preface
@state = :connected
emit(:frame, CONNECTION_PREFACE_MAGIC)
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
settings(payload)
end
def self.settings_header(**settings)
frame = Framer.new.generate(type: :settings, stream: 0, payload: settings)
Base64.urlsafe_encode64(frame[9..-1])
end
end
end
ruby-http-2-0.11.0/lib/http/2/compressor.rb 0000664 0000000 0000000 00000046300 13775242373 0020315 0 ustar 00root root 0000000 0000000 module HTTP2
# Implementation of header compression for HTTP 2.0 (HPACK) format adapted
# to efficiently represent HTTP headers in the context of HTTP 2.0.
#
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
module Header
# To decompress header blocks, a decoder only needs to maintain a
# dynamic table as a decoding context.
# No other state information is needed.
class EncodingContext
include Error
# @private
# Static table
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#appendix-A
STATIC_TABLE = [
[':authority', ''],
[':method', 'GET'],
[':method', 'POST'],
[':path', '/'],
[':path', '/index.html'],
[':scheme', 'http'],
[':scheme', 'https'],
[':status', '200'],
[':status', '204'],
[':status', '206'],
[':status', '304'],
[':status', '400'],
[':status', '404'],
[':status', '500'],
['accept-charset', ''],
['accept-encoding', 'gzip, deflate'],
['accept-language', ''],
['accept-ranges', ''],
['accept', ''],
['access-control-allow-origin', ''],
['age', ''],
['allow', ''],
['authorization', ''],
['cache-control', ''],
['content-disposition', ''],
['content-encoding', ''],
['content-language', ''],
['content-length', ''],
['content-location', ''],
['content-range', ''],
['content-type', ''],
['cookie', ''],
['date', ''],
['etag', ''],
['expect', ''],
['expires', ''],
['from', ''],
['host', ''],
['if-match', ''],
['if-modified-since', ''],
['if-none-match', ''],
['if-range', ''],
['if-unmodified-since', ''],
['last-modified', ''],
['link', ''],
['location', ''],
['max-forwards', ''],
['proxy-authenticate', ''],
['proxy-authorization', ''],
['range', ''],
['referer', ''],
['refresh', ''],
['retry-after', ''],
['server', ''],
['set-cookie', ''],
['strict-transport-security', ''],
['transfer-encoding', ''],
['user-agent', ''],
['vary', ''],
['via', ''],
['www-authenticate', ''],
].each { |pair| pair.each(&:freeze).freeze }.freeze
# Current table of header key-value pairs.
attr_reader :table
# Current encoding options
#
# :table_size Integer maximum dynamic table size in bytes
# :huffman Symbol :always, :never, :shorter
# :index Symbol :all, :static, :never
attr_reader :options
# Initializes compression context with appropriate client/server
# defaults and maximum size of the dynamic table.
#
# @param options [Hash] encoding options
# :table_size Integer maximum dynamic table size in bytes
# :huffman Symbol :always, :never, :shorter
# :index Symbol :all, :static, :never
def initialize(**options)
default_options = {
huffman: :shorter,
index: :all,
table_size: 4096,
}
@table = []
@options = default_options.merge(options)
@limit = @options[:table_size]
end
# Duplicates current compression context
# @return [EncodingContext]
def dup
other = EncodingContext.new(@options)
t = @table
l = @limit
other.instance_eval do
@table = t.dup # shallow copy
@limit = l
end
other
end
# Finds an entry in current dynamic table by index.
# Note that index is zero-based in this module.
#
# If the index is greater than the last index in the static table,
# an entry in the dynamic table is dereferenced.
#
# If the index is greater than the last header index, an error is raised.
#
# @param index [Integer] zero-based index in the dynamic table.
# @return [Array] +[key, value]+
def dereference(index)
# NOTE: index is zero-based in this module.
value = STATIC_TABLE[index] || @table[index - STATIC_TABLE.size]
fail CompressionError, 'Index too large' unless value
value
end
# Header Block Processing
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-4.1
#
# @param cmd [Hash] { type:, name:, value:, index: }
# @return [Array, nil] +[name, value]+ header field that is added to the decoded header list,
# or nil if +cmd[:type]+ is +:changetablesize+
def process(cmd)
emit = nil
case cmd[:type]
when :changetablesize
if cmd[:value] > @limit
fail CompressionError, 'dynamic table size update exceed limit'
end
self.table_size = cmd[:value]
when :indexed
# Indexed Representation
# An _indexed representation_ entails the following actions:
# o The header field corresponding to the referenced entry in either
# the static table or dynamic table is added to the decoded header
# list.
idx = cmd[:name]
k, v = dereference(idx)
emit = [k, v]
when :incremental, :noindex, :neverindexed
# A _literal representation_ that is _not added_ to the dynamic table
# entails the following action:
# o The header field is added to the decoded header list.
# A _literal representation_ that is _added_ to the dynamic table
# entails the following actions:
# o The header field is added to the decoded header list.
# o The header field is inserted at the beginning of the dynamic table.
if cmd[:name].is_a? Integer
k, v = dereference(cmd[:name])
cmd = cmd.dup
cmd[:index] ||= cmd[:name]
cmd[:value] ||= v
cmd[:name] = k
end
emit = [cmd[:name], cmd[:value]]
add_to_table(emit) if cmd[:type] == :incremental
else
fail CompressionError, "Invalid type: #{cmd[:type]}"
end
emit
end
# Plan header compression according to +@options [:index]+
# :never Do not use dynamic table or static table reference at all.
# :static Use static table only.
# :all Use all of them.
#
# @param headers [Array] +[[name, value], ...]+
# @return [Array] array of commands
def encode(headers)
commands = []
# Literals commands are marked with :noindex when index is not used
noindex = [:static, :never].include?(@options[:index])
headers.each do |field, value|
# Literal header names MUST be translated to lowercase before
# encoding and transmission.
field = field.downcase
value = '/' if field == ':path' && value.empty?
cmd = addcmd(field, value)
cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
commands << cmd
process(cmd)
end
commands
end
# Emits command for a header.
# Prefer static table over dynamic table.
# Prefer exact match over name-only match.
#
# +@options [:index]+ controls whether to use the dynamic table,
# static table, or both.
# :never Do not use dynamic table or static table reference at all.
# :static Use static table only.
# :all Use all of them.
#
# @param header [Array] +[name, value]+
# @return [Hash] command
def addcmd(*header)
exact = nil
name_only = nil
if [:all, :static].include?(@options[:index])
STATIC_TABLE.each_index do |i|
if STATIC_TABLE[i] == header
exact ||= i
break
elsif STATIC_TABLE[i].first == header.first
name_only ||= i
end
end
end
if [:all].include?(@options[:index]) && !exact
@table.each_index do |i|
if @table[i] == header
exact ||= i + STATIC_TABLE.size
break
elsif @table[i].first == header.first
name_only ||= i + STATIC_TABLE.size
end
end
end
if exact
{ name: exact, type: :indexed }
elsif name_only
{ name: name_only, value: header.last, type: :incremental }
else
{ name: header.first, value: header.last, type: :incremental }
end
end
# Alter dynamic table size.
# When the size is reduced, some headers might be evicted.
def table_size=(size)
@limit = size
size_check(nil)
end
# Returns current table size in octets
# @return [Integer]
def current_table_size
@table.inject(0) { |r, (k, v)| r + k.bytesize + v.bytesize + 32 }
end
private
# Add a name-value pair to the dynamic table.
# Older entries might have been evicted so that
# the new entry fits in the dynamic table.
#
# @param cmd [Array] +[name, value]+
def add_to_table(cmd)
return unless size_check(cmd)
@table.unshift(cmd)
end
# To keep the dynamic table size lower than or equal to @limit,
# remove one or more entries at the end of the dynamic table.
#
# @param cmd [Hash]
# @return [Boolean] whether +cmd+ fits in the dynamic table.
def size_check(cmd)
cursize = current_table_size
cmdsize = cmd.nil? ? 0 : cmd[0].bytesize + cmd[1].bytesize + 32
while cursize + cmdsize > @limit
break if @table.empty?
e = @table.pop
cursize -= e[0].bytesize + e[1].bytesize + 32
end
cmdsize <= @limit
end
end
# Header representation as defined by the spec.
HEADREP = {
indexed: { prefix: 7, pattern: 0x80 },
incremental: { prefix: 6, pattern: 0x40 },
noindex: { prefix: 4, pattern: 0x00 },
neverindexed: { prefix: 4, pattern: 0x10 },
changetablesize: { prefix: 5, pattern: 0x20 },
}.each_value(&:freeze).freeze
# Predefined options set for Compressor
# http://mew.org/~kazu/material/2014-hpack.pdf
NAIVE = { index: :never, huffman: :never }.freeze
LINEAR = { index: :all, huffman: :never }.freeze
STATIC = { index: :static, huffman: :never }.freeze
SHORTER = { index: :all, huffman: :never }.freeze
NAIVEH = { index: :never, huffman: :always }.freeze
LINEARH = { index: :all, huffman: :always }.freeze
STATICH = { index: :static, huffman: :always }.freeze
SHORTERH = { index: :all, huffman: :shorter }.freeze
# Responsible for encoding header key-value pairs using HPACK algorithm.
class Compressor
# @param options [Hash] encoding options
def initialize(**options)
@cc = EncodingContext.new(**options)
end
# Set dynamic table size in EncodingContext
# @param size [Integer] new dynamic table size
def table_size=(size)
@cc.table_size = size
end
# Encodes provided value via integer representation.
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.1
#
# If I < 2^N - 1, encode I on N bits
# Else
# encode 2^N - 1 on N bits
# I = I - (2^N - 1)
# While I >= 128
# Encode (I % 128 + 128) on 8 bits
# I = I / 128
# encode (I) on 8 bits
#
# @param i [Integer] value to encode
# @param n [Integer] number of available bits
# @return [String] binary string
def integer(i, n)
limit = 2**n - 1
return [i].pack('C') if i < limit
bytes = []
bytes.push limit unless n.zero?
i -= limit
while (i >= 128)
bytes.push((i % 128) + 128)
i /= 128
end
bytes.push i
bytes.pack('C*')
end
# Encodes provided value via string literal representation.
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.2
#
# * The string length, defined as the number of bytes needed to store
# its UTF-8 representation, is represented as an integer with a seven
# bits prefix. If the string length is strictly less than 127, it is
# represented as one byte.
# * If the bit 7 of the first byte is 1, the string value is represented
# as a list of Huffman encoded octets
# (padded with bit 1's until next octet boundary).
# * If the bit 7 of the first byte is 0, the string value is
# represented as a list of UTF-8 encoded octets.
#
# +@options [:huffman]+ controls whether to use Huffman encoding:
# :never Do not use Huffman encoding
# :always Always use Huffman encoding
# :shorter Use Huffman when the result is strictly shorter
#
# @param str [String]
# @return [String] binary string
def string(str)
plain, huffman = nil, nil
unless @cc.options[:huffman] == :always
plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY)
end
unless @cc.options[:huffman] == :never
huffman = Huffman.new.encode(str)
huffman = integer(huffman.bytesize, 7) << huffman
huffman.setbyte(0, huffman.ord | 0x80)
end
case @cc.options[:huffman]
when :always
huffman
when :never
plain
else
huffman.bytesize < plain.bytesize ? huffman : plain
end
end
# Encodes header command with appropriate header representation.
#
# @param h [Hash] header command
# @param buffer [String]
# @return [Buffer]
def header(h, buffer = Buffer.new)
rep = HEADREP[h[:type]]
case h[:type]
when :indexed
buffer << integer(h[:name] + 1, rep[:prefix])
when :changetablesize
buffer << integer(h[:value], rep[:prefix])
else
if h[:name].is_a? Integer
buffer << integer(h[:name] + 1, rep[:prefix])
else
buffer << integer(0, rep[:prefix])
buffer << string(h[:name])
end
buffer << string(h[:value])
end
# set header representation pattern on first byte
fb = buffer.ord | rep[:pattern]
buffer.setbyte(0, fb)
buffer
end
# Encodes provided list of HTTP headers.
#
# @param headers [Array] +[[name, value], ...]+
# @return [Buffer]
def encode(headers)
buffer = Buffer.new
pseudo_headers, regular_headers = headers.partition { |f, _| f.start_with? ':' }
headers = [*pseudo_headers, *regular_headers]
commands = @cc.encode(headers)
commands.each do |cmd|
buffer << header(cmd)
end
buffer
end
end
# Responsible for decoding received headers and maintaining compression
# context of the opposing peer. Decompressor must be initialized with
# appropriate starting context based on local role: client or server.
#
# @example
# server_role = Decompressor.new(:request)
# client_role = Decompressor.new(:response)
class Decompressor
# @param options [Hash] decoding options. Only :table_size is effective.
def initialize(**options)
@cc = EncodingContext.new(**options)
end
# Set dynamic table size in EncodingContext
# @param size [Integer] new dynamic table size
def table_size=(size)
@cc.table_size = size
end
# Decodes integer value from provided buffer.
#
# @param buf [String]
# @param n [Integer] number of available bits
# @return [Integer]
def integer(buf, n)
limit = 2**n - 1
i = !n.zero? ? (buf.getbyte & limit) : 0
m = 0
while (byte = buf.getbyte)
i += ((byte & 127) << m)
m += 7
break if (byte & 128).zero?
end if (i == limit)
i
end
# Decodes string value from provided buffer.
#
# @param buf [String]
# @return [String] UTF-8 encoded string
# @raise [CompressionError] when input is malformed
def string(buf)
huffman = (buf.readbyte(0) & 0x80) == 0x80
len = integer(buf, 7)
str = buf.read(len)
fail CompressionError, 'string too short' unless str.bytesize == len
str = Huffman.new.decode(Buffer.new(str)) if huffman
str.force_encoding(Encoding::UTF_8)
end
# Decodes header command from provided buffer.
#
# @param buf [Buffer]
# @return [Hash] command
def header(buf)
peek = buf.readbyte(0)
header = {}
header[:type], type = HEADREP.find do |_t, desc|
mask = (peek >> desc[:prefix]) << desc[:prefix]
mask == desc[:pattern]
end
fail CompressionError unless header[:type]
header[:name] = integer(buf, type[:prefix])
case header[:type]
when :indexed
fail CompressionError if (header[:name]).zero?
header[:name] -= 1
when :changetablesize
header[:value] = header[:name]
else
if (header[:name]).zero?
header[:name] = string(buf)
else
header[:name] -= 1
end
header[:value] = string(buf)
end
header
end
# Decodes and processes header commands within provided buffer.
#
# @param buf [Buffer]
# @return [Array] +[[name, value], ...]+
def decode(buf)
list = []
decoding_pseudo_headers = true
until buf.empty?
next_header = @cc.process(header(buf))
next if next_header.nil?
is_pseudo_header = next_header.first.start_with? ':'
if !decoding_pseudo_headers && is_pseudo_header
fail ProtocolError, 'one or more pseudo headers encountered after regular headers'
end
decoding_pseudo_headers = is_pseudo_header
list << next_header
end
list
end
end
end
end
ruby-http-2-0.11.0/lib/http/2/connection.rb 0000664 0000000 0000000 00000064246 13775242373 0020271 0 ustar 00root root 0000000 0000000 module HTTP2
# Default connection and stream flow control window (64KB).
DEFAULT_FLOW_WINDOW = 65_535
# Default header table size
DEFAULT_HEADER_SIZE = 4096
# Default stream_limit
DEFAULT_MAX_CONCURRENT_STREAMS = 100
# Default values for SETTINGS frame, as defined by the spec.
SPEC_DEFAULT_CONNECTION_SETTINGS = {
settings_header_table_size: 4096,
settings_enable_push: 1, # enabled for servers
settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
settings_initial_window_size: 65_535,
settings_max_frame_size: 16_384,
settings_max_header_list_size: 2**31 - 1, # unlimited
}.freeze
DEFAULT_CONNECTION_SETTINGS = {
settings_header_table_size: 4096,
settings_enable_push: 1, # enabled for servers
settings_max_concurrent_streams: 100,
settings_initial_window_size: 65_535,
settings_max_frame_size: 16_384,
settings_max_header_list_size: 2**31 - 1, # unlimited
}.freeze
# Default stream priority (lower values are higher priority).
DEFAULT_WEIGHT = 16
# Default connection "fast-fail" preamble string as defined by the spec.
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
# Time to hold recently closed streams until purge (seconds)
RECENTLY_CLOSED_STREAMS_TTL = 15
# Connection encapsulates all of the connection, stream, flow-control,
# error management, and other processing logic required for a well-behaved
# HTTP 2.0 endpoint.
#
# Note that this class should not be used directly. Instead, you want to
# use either Client or Server class to drive the HTTP 2.0 exchange.
#
# rubocop:disable ClassLength
class Connection
include FlowBuffer
include Emitter
include Error
# Connection state (:new, :closed).
attr_reader :state
# Size of current connection flow control window (by default, set to
# infinity, but is automatically updated on receipt of peer settings).
attr_reader :local_window
attr_reader :remote_window
alias window local_window
# Current settings value for local and peer
attr_reader :local_settings
attr_reader :remote_settings
# Pending settings value
# Sent but not ack'ed settings
attr_reader :pending_settings
# Number of active streams between client and server (reserved streams
# are not counted towards the stream limit).
attr_reader :active_stream_count
# Initializes new connection object.
#
def initialize(**settings)
@local_settings = DEFAULT_CONNECTION_SETTINGS.merge(settings)
@remote_settings = SPEC_DEFAULT_CONNECTION_SETTINGS.dup
@compressor = Header::Compressor.new(**settings)
@decompressor = Header::Decompressor.new(**settings)
@active_stream_count = 0
@streams = {}
@streams_recently_closed = {}
@pending_settings = []
@framer = Framer.new
@local_window_limit = @local_settings[:settings_initial_window_size]
@local_window = @local_window_limit
@remote_window_limit = @remote_settings[:settings_initial_window_size]
@remote_window = @remote_window_limit
@recv_buffer = Buffer.new
@send_buffer = []
@continuation = []
@error = nil
@h2c_upgrade = nil
@closed_since = nil
end
def closed?
@state == :closed
end
# Allocates new stream for current connection.
#
# @param priority [Integer]
# @param window [Integer]
# @param parent [Stream]
def new_stream(**args)
fail ConnectionClosed if @state == :closed
fail StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
stream = activate_stream(id: @stream_id, **args)
@stream_id += 2
stream
end
# Sends PING frame to the peer.
#
# @param payload [String] optional payload must be 8 bytes long
# @param blk [Proc] callback to execute when PONG is received
def ping(payload, &blk)
send(type: :ping, stream: 0, payload: payload)
once(:ack, &blk) if blk
end
# Sends a GOAWAY frame indicating that the peer should stop creating
# new streams for current connection.
#
# Endpoints MAY append opaque data to the payload of any GOAWAY frame.
# Additional debug data is intended for diagnostic purposes only and
# carries no semantic value. Debug data MUST NOT be persistently stored,
# since it could contain sensitive information.
#
# @param error [Symbol]
# @param payload [String]
def goaway(error = :no_error, payload = nil)
last_stream = if (max = @streams.max)
max.first
else
0
end
send(type: :goaway, last_stream: last_stream,
error: error, payload: payload)
@state = :closed
@closed_since = Time.now
end
# Sends a WINDOW_UPDATE frame to the peer.
#
# @param increment [Integer]
def window_update(increment)
@local_window += increment
send(type: :window_update, stream: 0, increment: increment)
end
# Sends a connection SETTINGS frame to the peer.
# The values are reflected when the corresponding ACK is received.
#
# @param settings [Array or Hash]
def settings(payload)
payload = payload.to_a
connection_error if validate_settings(@local_role, payload)
@pending_settings << payload
send(type: :settings, stream: 0, payload: payload)
@pending_settings << payload
end
# Decodes incoming bytes into HTTP 2.0 frames and routes them to
# appropriate receivers: connection frames are handled directly, and
# stream frames are passed to appropriate stream objects.
#
# @param data [String] Binary encoded string
def receive(data)
@recv_buffer << data
# Upon establishment of a TCP connection and determination that
# HTTP/2.0 will be used by both peers, each endpoint MUST send a
# connection header as a final confirmation and to establish the
# initial settings for the HTTP/2.0 connection.
#
# Client connection header is 24 byte connection header followed by
# SETTINGS frame. Server connection header is SETTINGS frame only.
if @state == :waiting_magic
if @recv_buffer.size < 24
if !CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
fail HandshakeError
else
return # maybe next time
end
elsif @recv_buffer.read(24) == CONNECTION_PREFACE_MAGIC
# MAGIC is OK. Send our settings
@state = :waiting_connection_preface
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
settings(payload)
else
fail HandshakeError
end
end
while (frame = @framer.parse(@recv_buffer))
emit(:frame_received, frame)
# Header blocks MUST be transmitted as a contiguous sequence of frames
# with no interleaved frames of any other type, or from any other stream.
unless @continuation.empty?
unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
connection_error
end
@continuation << frame
return unless frame[:flags].include? :end_headers
payload = @continuation.map { |f| f[:payload] }.join
frame = @continuation.shift
@continuation.clear
frame.delete(:length)
frame[:payload] = Buffer.new(payload)
frame[:flags] << :end_headers
end
# SETTINGS frames always apply to a connection, never a single stream.
# The stream identifier for a settings frame MUST be zero. If an
# endpoint receives a SETTINGS frame whose stream identifier field is
# anything other than 0x0, the endpoint MUST respond with a connection
# error (Section 5.4.1) of type PROTOCOL_ERROR.
if connection_frame?(frame)
connection_management(frame)
else
case frame[:type]
when :headers
# When server receives even-numbered stream identifier,
# the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
connection_error if frame[:stream].even? && self.is_a?(Server)
# The last frame in a sequence of HEADERS/CONTINUATION
# frames MUST have the END_HEADERS flag set.
unless frame[:flags].include? :end_headers
@continuation << frame
return
end
# After sending a GOAWAY frame, the sender can discard frames
# for new streams. However, any frames that alter connection
# state cannot be completely ignored. For instance, HEADERS,
# PUSH_PROMISE and CONTINUATION frames MUST be minimally
# processed to ensure a consistent compression state
decode_headers(frame)
return if @state == :closed
stream = @streams[frame[:stream]]
if stream.nil?
stream = activate_stream(
id: frame[:stream],
weight: frame[:weight] || DEFAULT_WEIGHT,
dependency: frame[:dependency] || 0,
exclusive: frame[:exclusive] || false,
)
emit(:stream, stream)
end
stream << frame
when :push_promise
# The last frame in a sequence of PUSH_PROMISE/CONTINUATION
# frames MUST have the END_HEADERS flag set
unless frame[:flags].include? :end_headers
@continuation << frame
return
end
decode_headers(frame)
return if @state == :closed
# PUSH_PROMISE frames MUST be associated with an existing, peer-
# initiated stream... A receiver MUST treat the receipt of a
# PUSH_PROMISE on a stream that is neither "open" nor
# "half-closed (local)" as a connection error (Section 5.4.1) of
# type PROTOCOL_ERROR. Similarly, a receiver MUST treat the
# receipt of a PUSH_PROMISE that promises an illegal stream
# identifier (Section 5.1.1) (that is, an identifier for a stream
# that is not currently in the "idle" state) as a connection error
# (Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver
# recently sent a RST_STREAM frame to cancel the associated stream.
parent = @streams[frame[:stream]]
pid = frame[:promise_stream]
# if PUSH parent is recently closed, RST_STREAM the push
if @streams_recently_closed[frame[:stream]]
send(type: :rst_stream, stream: pid, error: :refused_stream)
return
end
connection_error(msg: 'missing parent ID') if parent.nil?
unless parent.state == :open || parent.state == :half_closed_local
# An endpoint might receive a PUSH_PROMISE frame after it sends
# RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
# The RST_STREAM does not cancel any promised stream. Therefore, if
# promised streams are not desired, a RST_STREAM can be used to
# close any of those streams.
if parent.closed == :local_rst
# We can either (a) 'resurrect' the parent, or (b) RST_STREAM
# ... sticking with (b), might need to revisit later.
send(type: :rst_stream, stream: pid, error: :refused_stream)
else
connection_error
end
end
stream = activate_stream(id: pid, parent: parent)
emit(:promise, stream)
stream << frame
else
if (stream = @streams[frame[:stream]])
stream << frame
if frame[:type] == :data
update_local_window(frame)
calculate_window_update(@local_window_limit)
end
else
case frame[:type]
# The PRIORITY frame can be sent for a stream in the "idle" or
# "closed" state. This allows for the reprioritization of a
# group of dependent streams by altering the priority of an
# unused or closed parent stream.
when :priority
stream = activate_stream(
id: frame[:stream],
weight: frame[:weight] || DEFAULT_WEIGHT,
dependency: frame[:dependency] || 0,
exclusive: frame[:exclusive] || false,
)
emit(:stream, stream)
stream << frame
# WINDOW_UPDATE can be sent by a peer that has sent a frame
# bearing the END_STREAM flag. This means that a receiver could
# receive a WINDOW_UPDATE frame on a "half-closed (remote)" or
# "closed" stream. A receiver MUST NOT treat this as an error
# (see Section 5.1).
when :window_update
process_window_update(frame)
else
# An endpoint that receives an unexpected stream identifier
# MUST respond with a connection error of type PROTOCOL_ERROR.
connection_error
end
end
end
end
end
rescue StandardError => e
raise if e.is_a?(Error::Error)
connection_error(e: e)
end
def <<(*args)
receive(*args)
end
private
# Send an outgoing frame. DATA frames are subject to connection flow
# control and may be split and / or buffered based on current window size.
# All other frames are sent immediately.
#
# @note all frames are currently delivered in FIFO order.
# @param frame [Hash]
def send(frame)
emit(:frame_sent, frame)
if frame[:type] == :data
send_data(frame, true)
else
# An endpoint can end a connection at any time. In particular, an
# endpoint MAY choose to treat a stream error as a connection error.
if frame[:type] == :rst_stream && frame[:error] == :protocol_error
goaway(frame[:error])
else
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
# RST_STREAM that are not protocol errors
frames = encode(frame)
frames.each { |f| emit(:frame, f) }
end
end
end
# Applies HTTP 2.0 binary encoding to the frame.
#
# @param frame [Hash]
# @return [Array of Buffer] encoded frame
def encode(frame)
frames = if frame[:type] == :headers || frame[:type] == :push_promise
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
else
[frame] # otherwise one frame
end
frames.map { |f| @framer.generate(f) }
end
# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
# frame addressed to stream ID = 0.
#
# @param frame [Hash]
# @return [Boolean]
def connection_frame?(frame)
(frame[:stream]).zero? ||
frame[:type] == :settings ||
frame[:type] == :ping ||
frame[:type] == :goaway
end
# Process received connection frame (stream ID = 0).
# - Handle SETTINGS updates
# - Connection flow control (WINDOW_UPDATE)
# - Emit PONG auto-reply to PING frames
# - Mark connection as closed on GOAWAY
#
# @param frame [Hash]
def connection_management(frame)
case @state
when :waiting_connection_preface
# The first frame MUST be a SETTINGS frame at the start of a connection.
@state = :connected
connection_settings(frame)
when :connected
case frame[:type]
when :settings
connection_settings(frame)
when :window_update
@remote_window += frame[:increment]
send_data(nil, true)
when :ping
if frame[:flags].include? :ack
emit(:ack, frame[:payload])
else
send(type: :ping, stream: 0,
flags: [:ack], payload: frame[:payload])
end
when :goaway
# Receivers of a GOAWAY frame MUST NOT open additional streams on
# the connection, although a new connection can be established
# for new streams.
@state = :closed
@closed_since = Time.now
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
when :altsvc
# 4. The ALTSVC HTTP/2 Frame
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
# information is invalid and MUST be ignored.
if frame[:origin] && !frame[:origin].empty?
emit(frame[:type], frame)
end
when :blocked
emit(frame[:type], frame)
else
connection_error
end
when :closed
connection_error if (Time.now - @closed_since) > 15
else
connection_error
end
end
# Validate settings parameters. See sepc Section 6.5.2.
#
# @param role [Symbol] The sender's role: :client or :server
# @return nil if no error. Exception object in case of any error.
def validate_settings(role, settings)
settings.each do |key, v|
case key
when :settings_header_table_size
# Any value is valid
when :settings_enable_push
case role
when :server
# Section 8.2
# Clients MUST reject any attempt to change the
# SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
# message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
return ProtocolError.new("invalid #{key} value") unless v.zero?
when :client
# Any value other than 0 or 1 MUST be treated as a
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
unless v.zero? || v == 1
return ProtocolError.new("invalid #{key} value")
end
end
when :settings_max_concurrent_streams
# Any value is valid
when :settings_initial_window_size
# Values above the maximum flow control window size of 2^31-1 MUST
# be treated as a connection error (Section 5.4.1) of type
# FLOW_CONTROL_ERROR.
unless v <= 0x7fffffff
return FlowControlError.new("invalid #{key} value")
end
when :settings_max_frame_size
# The initial value is 2^14 (16,384) octets. The value advertised
# by an endpoint MUST be between this initial value and the maximum
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
# Values outside this range MUST be treated as a connection error
# (Section 5.4.1) of type PROTOCOL_ERROR.
unless v >= 16_384 && v <= 16_777_215
return ProtocolError.new("invalid #{key} value")
end
when :settings_max_header_list_size
# Any value is valid
# else # ignore unknown settings
end
end
nil
end
# Update connection settings based on parameters set by the peer.
#
# @param frame [Hash]
def connection_settings(frame)
connection_error unless frame[:type] == :settings && (frame[:stream]).zero?
# Apply settings.
# side =
# local: previously sent and pended our settings should be effective
# remote: just received peer settings should immediately be effective
settings, side = if frame[:flags].include?(:ack)
# Process pending settings we have sent.
[@pending_settings.shift, :local]
else
connection_error if validate_settings(@remote_role, frame[:payload])
[frame[:payload], :remote]
end
settings.each do |key, v|
case side
when :local
@local_settings[key] = v
when :remote
@remote_settings[key] = v
end
case key
when :settings_max_concurrent_streams
# Do nothing.
# The value controls at the next attempt of stream creation.
when :settings_initial_window_size
# A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the available
# space in a flow control window to become negative. A sender MUST
# track the negative flow control window, and MUST NOT send new flow
# controlled frames until it receives WINDOW_UPDATE frames that cause
# the flow control window to become positive.
case side
when :local
@local_window = @local_window - @local_window_limit + v
@streams.each do |_id, stream|
stream.emit(:local_window, stream.local_window - @local_window_limit + v)
end
@local_window_limit = v
when :remote
@remote_window = @remote_window - @remote_window_limit + v
@streams.each do |_id, stream|
# Event name is :window, not :remote_window
stream.emit(:window, stream.remote_window - @remote_window_limit + v)
end
@remote_window_limit = v
end
when :settings_header_table_size
# Setting header table size might cause some headers evicted
case side
when :local
@compressor.table_size = v
when :remote
@decompressor.table_size = v
end
when :settings_enable_push
# nothing to do
when :settings_max_frame_size
# update framer max_frame_size
@framer.max_frame_size = v
# else # ignore unknown settings
end
end
case side
when :local
# Received a settings_ack. Notify application layer.
emit(:settings_ack, frame, @pending_settings.size)
when :remote
unless @state == :closed || @h2c_upgrade == :start
# Send ack to peer
send(type: :settings, stream: 0, payload: [], flags: [:ack])
end
end
end
# Decode headers payload and update connection decompressor state.
#
# The receiver endpoint reassembles the header block by concatenating
# the individual fragments, then decompresses the block to reconstruct
# the header set - aka, header payloads are buffered until END_HEADERS,
# or an END_PROMISE flag is seen.
#
# @param frame [Hash]
def decode_headers(frame)
if frame[:payload].is_a? Buffer
frame[:payload] = @decompressor.decode(frame[:payload])
end
rescue CompressionError => e
connection_error(:compression_error, e: e)
rescue ProtocolError => e
connection_error(:protocol_error, e: e)
rescue StandardError => e
connection_error(:internal_error, e: e)
end
# Encode headers payload and update connection compressor state.
#
# @param frame [Hash]
# @return [Array of Frame]
def encode_headers(frame)
payload = frame[:payload]
payload = @compressor.encode(payload) unless payload.is_a? Buffer
frames = []
while payload.bytesize > 0
cont = frame.dup
cont[:type] = :continuation
cont[:flags] = []
cont[:payload] = payload.slice!(0, @remote_settings[:settings_max_frame_size])
frames << cont
end
if frames.empty?
frames = [frame]
else
frames.first[:type] = frame[:type]
frames.first[:flags] = frame[:flags] - [:end_headers]
frames.last[:flags] << :end_headers
end
frames
rescue StandardError => e
connection_error(:compression_error, e: e)
nil
end
# Activates new incoming or outgoing stream and registers appropriate
# connection managemet callbacks.
#
# @param id [Integer]
# @param priority [Integer]
# @param window [Integer]
# @param parent [Stream]
def activate_stream(id: nil, **args)
connection_error(msg: 'Stream ID already exists') if @streams.key?(id)
stream = Stream.new(**{ connection: self, id: id }.merge(args))
# Streams that are in the "open" state, or either of the "half closed"
# states count toward the maximum number of streams that an endpoint is
# permitted to open.
stream.once(:active) { @active_stream_count += 1 }
stream.once(:close) do
@active_stream_count -= 1
# Store a reference to the closed stream, such that we can respond
# to any in-flight frames while close is registered on both sides.
# References to such streams will be purged whenever another stream
# is closed, with a defined RTT time window.
@streams_recently_closed[id] = Time.now.to_i
cleanup_recently_closed
end
stream.on(:promise, &method(:promise)) if self.is_a? Server
stream.on(:frame, &method(:send))
@streams[id] = stream
end
# Purge recently streams closed within defined RTT time window.
def cleanup_recently_closed
now_ts = Time.now.to_i
to_delete = []
@streams_recently_closed.each do |stream_id, ts|
# Ruby Hash enumeration is ordered, so once fresh stream is met we can stop searching.
break if now_ts - ts < RECENTLY_CLOSED_STREAMS_TTL
to_delete << stream_id
end
to_delete.each do |stream_id|
@streams.delete stream_id
@streams_recently_closed.delete stream_id
end
end
# Emit GOAWAY error indicating to peer that the connection is being
# aborted, and once sent, raise a local exception.
#
# @param error [Symbol]
# @option error [Symbol] :no_error
# @option error [Symbol] :internal_error
# @option error [Symbol] :flow_control_error
# @option error [Symbol] :stream_closed
# @option error [Symbol] :frame_too_large
# @option error [Symbol] :compression_error
# @param msg [String]
def connection_error(error = :protocol_error, msg: nil, e: nil)
goaway(error) unless @state == :closed || @state == :new
@state, @error = :closed, error
klass = error.to_s.split('_').map(&:capitalize).join
msg ||= e && e.message
backtrace = (e && e.backtrace) || []
fail Error.const_get(klass), msg, backtrace
end
alias error connection_error
def manage_state(_)
yield
end
end
# rubocop:enable ClassLength
end
ruby-http-2-0.11.0/lib/http/2/emitter.rb 0000664 0000000 0000000 00000002267 13775242373 0017576 0 ustar 00root root 0000000 0000000 module HTTP2
# Basic event emitter implementation with support for persistent and
# one-time event callbacks.
#
module Emitter
# Subscribe to all future events for specified type.
#
# @param event [Symbol]
# @param block [Proc] callback function
def add_listener(event, &block)
fail ArgumentError, 'must provide callback' unless block_given?
listeners(event.to_sym).push block
end
alias on add_listener
# Subscribe to next event (at most once) for specified type.
#
# @param event [Symbol]
# @param block [Proc] callback function
def once(event, &block)
add_listener(event) do |*args, &callback|
block.call(*args, &callback)
:delete
end
end
# Emit event with provided arguments.
#
# @param event [Symbol]
# @param args [Array] arguments to be passed to the callbacks
# @param block [Proc] callback function
def emit(event, *args, &block)
listeners(event).delete_if do |cb|
cb.call(*args, &block) == :delete
end
end
private
def listeners(event)
@listeners ||= Hash.new { |hash, key| hash[key] = [] }
@listeners[event]
end
end
end
ruby-http-2-0.11.0/lib/http/2/error.rb 0000664 0000000 0000000 00000002721 13775242373 0017251 0 ustar 00root root 0000000 0000000 module HTTP2
# Stream, connection, and compressor exceptions.
module Error
class Error < StandardError; end
# Raised if connection header is missing or invalid indicating that
# this is an invalid HTTP 2.0 request - no frames are emitted and the
# connection must be aborted.
class HandshakeError < Error; end
# Raised by stream or connection handlers, results in GOAWAY frame
# which signals termination of the current connection. You *cannot*
# recover from this exception, or any exceptions subclassed from it.
class ProtocolError < Error; end
# Raised on any header encoding / decoding exception.
#
# @see ProtocolError
class CompressionError < ProtocolError; end
# Raised on invalid flow control frame or command.
#
# @see ProtocolError
class FlowControlError < ProtocolError; end
# Raised on invalid stream processing: invalid frame type received or
# sent, or invalid command issued.
class InternalError < ProtocolError; end
#
# -- Recoverable errors -------------------------------------------------
#
# Raised if stream has been closed and new frames cannot be sent.
class StreamClosed < Error; end
# Raised if connection has been closed (or draining) and new stream
# cannot be opened.
class ConnectionClosed < Error; end
# Raised if stream limit has been reached and new stream cannot be opened.
class StreamLimitExceeded < Error; end
end
end
ruby-http-2-0.11.0/lib/http/2/flow_buffer.rb 0000664 0000000 0000000 00000006735 13775242373 0020431 0 ustar 00root root 0000000 0000000 module HTTP2
# Implementation of stream and connection DATA flow control: frames may
# be split and / or may be buffered based on current flow control window.
#
module FlowBuffer
# Amount of buffered data. Only DATA payloads are subject to flow stream
# and connection flow control.
#
# @return [Integer]
def buffered_amount
@send_buffer.map { |f| f[:length] }.reduce(:+) || 0
end
private
def update_local_window(frame)
frame_size = frame[:payload].bytesize
frame_size += frame[:padding] || 0
@local_window -= frame_size
end
def calculate_window_update(window_max_size)
# If DATA frame is received with length > 0 and
# current received window size + delta length is strictly larger than
# local window size, it throws a flow control error.
#
error(:flow_control_error) if @local_window < 0
# Send WINDOW_UPDATE if the received window size goes over
# the local window size / 2.
#
# The HTTP/2 spec mandates that every DATA frame received
# generates a WINDOW_UPDATE to send. In some cases however,
# (ex: DATA frames with short payloads),
# the noise generated by flow control frames creates enough
# congestion for this to be deemed very inefficient.
#
# This heuristic was inherited from nghttp, which delays the
# WINDOW_UPDATE until at least half the window is exhausted.
# This works because the sender doesn't need those increments
# until the receiver window is exhausted, after which he'll be
# waiting for the WINDOW_UPDATE frame.
return unless @local_window <= (window_max_size / 2)
window_update(window_max_size - @local_window)
end
# Buffers outgoing DATA frames and applies flow control logic to split
# and emit DATA frames based on current flow control window. If the
# window is large enough, the data is sent immediately. Otherwise, the
# data is buffered until the flow control window is updated.
#
# Buffered DATA frames are emitted in FIFO order.
#
# @param frame [Hash]
# @param encode [Boolean] set to true by co
def send_data(frame = nil, encode = false)
@send_buffer.push frame unless frame.nil?
# FIXME: Frames with zero length with the END_STREAM flag set (that
# is, an empty DATA frame) MAY be sent if there is no available space
# in either flow control window.
while @remote_window > 0 && !@send_buffer.empty?
frame = @send_buffer.shift
sent, frame_size = 0, frame[:payload].bytesize
if frame_size > @remote_window
payload = frame.delete(:payload)
chunk = frame.dup
# Split frame so that it fits in the window
# TODO: consider padding!
frame[:payload] = payload.slice!(0, @remote_window)
chunk[:length] = payload.bytesize
chunk[:payload] = payload
# if no longer last frame in sequence...
frame[:flags] -= [:end_stream] if frame[:flags].include? :end_stream
@send_buffer.unshift chunk
sent = @remote_window
else
sent = frame_size
end
manage_state(frame) do
frames = encode ? encode(frame) : [frame]
frames.each { |f| emit(:frame, f) }
@remote_window -= sent
end
end
end
def process_window_update(frame)
return if frame[:ignore]
@remote_window += frame[:increment]
send_data
end
end
end
ruby-http-2-0.11.0/lib/http/2/framer.rb 0000664 0000000 0000000 00000032474 13775242373 0017404 0 ustar 00root root 0000000 0000000 module HTTP2
# Performs encoding, decoding, and validation of binary HTTP/2 frames.
#
# rubocop:disable ClassLength
class Framer
include Error
# Default value of max frame size (16384 bytes)
DEFAULT_MAX_FRAME_SIZE = 2**14
# Current maximum frame size
attr_accessor :max_frame_size
# Maximum stream ID (2^31)
MAX_STREAM_ID = 0x7fffffff
# Maximum window increment value (2^31)
MAX_WINDOWINC = 0x7fffffff
# HTTP/2 frame type mapping as defined by the spec
FRAME_TYPES = {
data: 0x0,
headers: 0x1,
priority: 0x2,
rst_stream: 0x3,
settings: 0x4,
push_promise: 0x5,
ping: 0x6,
goaway: 0x7,
window_update: 0x8,
continuation: 0x9,
altsvc: 0xa,
}.freeze
FRAME_TYPES_WITH_PADDING = [:data, :headers, :push_promise].freeze
# Per frame flags as defined by the spec
FRAME_FLAGS = {
data: {
end_stream: 0,
padded: 3,
compressed: 5,
},
headers: {
end_stream: 0,
end_headers: 2,
padded: 3,
priority: 5,
},
priority: {},
rst_stream: {},
settings: { ack: 0 },
push_promise: {
end_headers: 2,
padded: 3,
},
ping: { ack: 0 },
goaway: {},
window_update: {},
continuation: { end_headers: 2 },
altsvc: {},
}.each_value(&:freeze).freeze
# Default settings as defined by the spec
DEFINED_SETTINGS = {
settings_header_table_size: 1,
settings_enable_push: 2,
settings_max_concurrent_streams: 3,
settings_initial_window_size: 4,
settings_max_frame_size: 5,
settings_max_header_list_size: 6,
}.freeze
# Default error types as defined by the spec
DEFINED_ERRORS = {
no_error: 0,
protocol_error: 1,
internal_error: 2,
flow_control_error: 3,
settings_timeout: 4,
stream_closed: 5,
frame_size_error: 6,
refused_stream: 7,
cancel: 8,
compression_error: 9,
connect_error: 10,
enhance_your_calm: 11,
inadequate_security: 12,
http_1_1_required: 13,
}.freeze
RBIT = 0x7fffffff
RBYTE = 0x0fffffff
EBIT = 0x80000000
UINT32 = 'N'.freeze
UINT16 = 'n'.freeze
UINT8 = 'C'.freeze
HEADERPACK = (UINT8 + UINT16 + UINT8 + UINT8 + UINT32).freeze
FRAME_LENGTH_HISHIFT = 16
FRAME_LENGTH_LOMASK = 0xFFFF
private_constant :RBIT, :RBYTE, :EBIT, :HEADERPACK, :UINT32, :UINT16, :UINT8
# Initializes new framer object.
#
def initialize
@max_frame_size = DEFAULT_MAX_FRAME_SIZE
end
# Generates common 9-byte frame header.
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1
#
# @param frame [Hash]
# @return [String]
def common_header(frame)
header = []
unless FRAME_TYPES[frame[:type]]
fail CompressionError, "Invalid frame type (#{frame[:type]})"
end
if frame[:length] > @max_frame_size
fail CompressionError, "Frame size is too large: #{frame[:length]}"
end
if frame[:length] < 0
fail CompressionError, "Frame size is invalid: #{frame[:length]}"
end
if frame[:stream] > MAX_STREAM_ID
fail CompressionError, "Stream ID (#{frame[:stream]}) is too large"
end
if frame[:type] == :window_update && frame[:increment] > MAX_WINDOWINC
fail CompressionError, "Window increment (#{frame[:increment]}) is too large"
end
header << (frame[:length] >> FRAME_LENGTH_HISHIFT)
header << (frame[:length] & FRAME_LENGTH_LOMASK)
header << FRAME_TYPES[frame[:type]]
header << frame[:flags].reduce(0) do |acc, f|
position = FRAME_FLAGS[frame[:type]][f]
unless position
fail CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}"
end
acc | (1 << position)
end
header << frame[:stream]
header.pack(HEADERPACK) # 8+16,8,8,32
end
# Decodes common 9-byte header.
#
# @param buf [Buffer]
def read_common_header(buf)
frame = {}
len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK)
frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
frame[:type], _ = FRAME_TYPES.find { |_t, pos| type == pos }
if frame[:type]
frame[:flags] = FRAME_FLAGS[frame[:type]].each_with_object([]) do |(name, pos), acc|
acc << name if (flags & (1 << pos)) > 0
end
end
frame[:stream] = stream & RBIT
frame
end
# Generates encoded HTTP/2 frame.
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2
#
# @param frame [Hash]
def generate(frame)
bytes = Buffer.new
length = 0
frame[:flags] ||= []
frame[:stream] ||= 0
case frame[:type]
when :data
bytes << frame[:payload]
length += frame[:payload].bytesize
when :headers
if frame[:weight] || frame[:stream_dependency] || !frame[:exclusive].nil?
unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil?
fail CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
end
frame[:flags] += [:priority] unless frame[:flags].include? :priority
end
if frame[:flags].include? :priority
bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:stream_dependency] & RBIT)].pack(UINT32)
bytes << [frame[:weight] - 1].pack(UINT8)
length += 5
end
bytes << frame[:payload]
length += frame[:payload].bytesize
when :priority
unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil?
fail CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
end
bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:stream_dependency] & RBIT)].pack(UINT32)
bytes << [frame[:weight] - 1].pack(UINT8)
length += 5
when :rst_stream
bytes << pack_error(frame[:error])
length += 4
when :settings
if (frame[:stream]).nonzero?
fail CompressionError, "Invalid stream ID (#{frame[:stream]})"
end
frame[:payload].each do |(k, v)|
if k.is_a? Integer
DEFINED_SETTINGS.value?(k) || next
else
k = DEFINED_SETTINGS[k]
fail CompressionError, "Unknown settings ID for #{k}" if k.nil?
end
bytes << [k].pack(UINT16)
bytes << [v].pack(UINT32)
length += 6
end
when :push_promise
bytes << [frame[:promise_stream] & RBIT].pack(UINT32)
bytes << frame[:payload]
length += 4 + frame[:payload].bytesize
when :ping
if frame[:payload].bytesize != 8
fail CompressionError, "Invalid payload size (#{frame[:payload].size} != 8 bytes)"
end
bytes << frame[:payload]
length += 8
when :goaway
bytes << [frame[:last_stream] & RBIT].pack(UINT32)
bytes << pack_error(frame[:error])
length += 8
if frame[:payload]
bytes << frame[:payload]
length += frame[:payload].bytesize
end
when :window_update
bytes << [frame[:increment] & RBIT].pack(UINT32)
length += 4
when :continuation
bytes << frame[:payload]
length += frame[:payload].bytesize
when :altsvc
bytes << [frame[:max_age], frame[:port]].pack(UINT32 + UINT16)
length += 6
if frame[:proto]
fail CompressionError, 'Proto too long' if frame[:proto].bytesize > 255
bytes << [frame[:proto].bytesize].pack(UINT8)
bytes << frame[:proto].force_encoding(Encoding::BINARY)
length += 1 + frame[:proto].bytesize
else
bytes << [0].pack(UINT8)
length += 1
end
if frame[:host]
fail CompressionError, 'Host too long' if frame[:host].bytesize > 255
bytes << [frame[:host].bytesize].pack(UINT8)
bytes << frame[:host].force_encoding(Encoding::BINARY)
length += 1 + frame[:host].bytesize
else
bytes << [0].pack(UINT8)
length += 1
end
if frame[:origin]
bytes << frame[:origin]
length += frame[:origin].bytesize
end
end
# Process padding.
# frame[:padding] gives number of extra octets to be added.
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.1
if frame[:padding]
unless FRAME_TYPES_WITH_PADDING.include?(frame[:type])
fail CompressionError, "Invalid padding flag for #{frame[:type]}"
end
padlen = frame[:padding]
if padlen <= 0 || padlen > 256 || padlen + length > @max_frame_size
fail CompressionError, "Invalid padding #{padlen}"
end
length += padlen
bytes.prepend([padlen -= 1].pack(UINT8))
frame[:flags] << :padded
# Padding: Padding octets that contain no application semantic value.
# Padding octets MUST be set to zero when sending and ignored when
# receiving.
bytes << "\0" * padlen
end
frame[:length] = length
bytes.prepend(common_header(frame))
end
# Decodes complete HTTP/2 frame from provided buffer. If the buffer
# does not contain enough data, no further work is performed.
#
# @param buf [Buffer]
def parse(buf)
return nil if buf.size < 9
frame = read_common_header(buf)
return nil if buf.size < 9 + frame[:length]
fail ProtocolError, 'payload too large' if frame[:length] > DEFAULT_MAX_FRAME_SIZE
buf.read(9)
payload = buf.read(frame[:length])
# Implementations MUST discard frames
# that have unknown or unsupported types.
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.5
return nil if frame[:type].nil?
# Process padding
padlen = 0
if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
padded = frame[:flags].include?(:padded)
if padded
padlen = payload.read(1).unpack(UINT8).first
frame[:padding] = padlen + 1
fail ProtocolError, 'padding too long' if padlen > payload.bytesize
payload.slice!(-padlen, padlen) if padlen > 0
frame[:length] -= frame[:padding]
frame[:flags].delete(:padded)
end
end
case frame[:type]
when :data
frame[:payload] = payload.read(frame[:length])
when :headers
if frame[:flags].include? :priority
e_sd = payload.read_uint32
frame[:stream_dependency] = e_sd & RBIT
frame[:exclusive] = (e_sd & EBIT) != 0
frame[:weight] = payload.getbyte + 1
end
frame[:payload] = payload.read(frame[:length])
when :priority
e_sd = payload.read_uint32
frame[:stream_dependency] = e_sd & RBIT
frame[:exclusive] = (e_sd & EBIT) != 0
frame[:weight] = payload.getbyte + 1
when :rst_stream
frame[:error] = unpack_error payload.read_uint32
when :settings
# NOTE: frame[:length] might not match the number of frame[:payload]
# because unknown extensions are ignored.
frame[:payload] = []
unless (frame[:length] % 6).zero?
fail ProtocolError, 'Invalid settings payload length'
end
if (frame[:stream]).nonzero?
fail ProtocolError, "Invalid stream ID (#{frame[:stream]})"
end
(frame[:length] / 6).times do
id = payload.read(2).unpack(UINT16).first
val = payload.read_uint32
# Unsupported or unrecognized settings MUST be ignored.
# Here we send it along.
name, _ = DEFINED_SETTINGS.find { |_name, v| v == id }
frame[:payload] << [name, val] if name
end
when :push_promise
frame[:promise_stream] = payload.read_uint32 & RBIT
frame[:payload] = payload.read(frame[:length])
when :ping
frame[:payload] = payload.read(frame[:length])
when :goaway
frame[:last_stream] = payload.read_uint32 & RBIT
frame[:error] = unpack_error payload.read_uint32
size = frame[:length] - 8 # for last_stream and error
frame[:payload] = payload.read(size) if size > 0
when :window_update
frame[:increment] = payload.read_uint32 & RBIT
when :continuation
frame[:payload] = payload.read(frame[:length])
when :altsvc
frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16)
len = payload.getbyte
frame[:proto] = payload.read(len) if len > 0
len = payload.getbyte
frame[:host] = payload.read(len) if len > 0
frame[:origin] = payload.read(payload.size) if payload.size > 0
# else # Unknown frame type is explicitly allowed
end
frame
end
private
def pack_error(e)
unless e.is_a? Integer
if DEFINED_ERRORS[e].nil?
fail CompressionError, "Unknown error ID for #{e}"
end
e = DEFINED_ERRORS[e]
end
[e].pack(UINT32)
end
def unpack_error(e)
name, _ = DEFINED_ERRORS.find { |_name, v| v == e }
name || error
end
end
# rubocop:enable ClassLength
end
ruby-http-2-0.11.0/lib/http/2/huffman.rb 0000664 0000000 0000000 00000017630 13775242373 0017551 0 ustar 00root root 0000000 0000000 require_relative 'error'
module HTTP2
# Implementation of huffman encoding for HPACK
#
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
module Header
# Huffman encoder/decoder
class Huffman
include Error
BITS_AT_ONCE = 4
EOS = 256
private_constant :EOS
# Encodes provided value via huffman encoding.
# Length is not encoded in this method.
#
# @param str [String]
# @return [String] binary string
def encode(str)
bitstring = str.each_byte.map { |chr| ENCODE_TABLE[chr] }.join
bitstring << '1' * ((8 - bitstring.size) % 8)
[bitstring].pack('B*')
end
# Decodes provided Huffman coded string.
#
# @param buf [Buffer]
# @return [String] binary string
# @raise [CompressionError] when Huffman coded string is malformed
def decode(buf)
emit = ''
state = 0 # start state
mask = (1 << BITS_AT_ONCE) - 1
buf.each_byte do |chr|
(8 / BITS_AT_ONCE - 1).downto(0) do |shift|
branch = (chr >> (shift * BITS_AT_ONCE)) & mask
# MACHINE[state] = [final, [transitions]]
# [final] unfinished bits so far are prefix of the EOS code.
# Each transition is [emit, next]
# [emit] character to be emitted on this transition, empty string, or EOS.
# [next] next state number.
trans = MACHINE[state][branch]
fail CompressionError, 'Huffman decode error (EOS found)' if trans.first == EOS
emit << trans.first.chr if trans.first
state = trans.last
end
end
# Check whether partial input is correctly filled
unless state <= MAX_FINAL_STATE
fail CompressionError, 'Huffman decode error (EOS invalid)'
end
emit.force_encoding(Encoding::BINARY)
end
# Huffman table as specified in
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#appendix-B
CODES = [
[0x1ff8, 13],
[0x7fffd8, 23],
[0xfffffe2, 28],
[0xfffffe3, 28],
[0xfffffe4, 28],
[0xfffffe5, 28],
[0xfffffe6, 28],
[0xfffffe7, 28],
[0xfffffe8, 28],
[0xffffea, 24],
[0x3ffffffc, 30],
[0xfffffe9, 28],
[0xfffffea, 28],
[0x3ffffffd, 30],
[0xfffffeb, 28],
[0xfffffec, 28],
[0xfffffed, 28],
[0xfffffee, 28],
[0xfffffef, 28],
[0xffffff0, 28],
[0xffffff1, 28],
[0xffffff2, 28],
[0x3ffffffe, 30],
[0xffffff3, 28],
[0xffffff4, 28],
[0xffffff5, 28],
[0xffffff6, 28],
[0xffffff7, 28],
[0xffffff8, 28],
[0xffffff9, 28],
[0xffffffa, 28],
[0xffffffb, 28],
[0x14, 6],
[0x3f8, 10],
[0x3f9, 10],
[0xffa, 12],
[0x1ff9, 13],
[0x15, 6],
[0xf8, 8],
[0x7fa, 11],
[0x3fa, 10],
[0x3fb, 10],
[0xf9, 8],
[0x7fb, 11],
[0xfa, 8],
[0x16, 6],
[0x17, 6],
[0x18, 6],
[0x0, 5],
[0x1, 5],
[0x2, 5],
[0x19, 6],
[0x1a, 6],
[0x1b, 6],
[0x1c, 6],
[0x1d, 6],
[0x1e, 6],
[0x1f, 6],
[0x5c, 7],
[0xfb, 8],
[0x7ffc, 15],
[0x20, 6],
[0xffb, 12],
[0x3fc, 10],
[0x1ffa, 13],
[0x21, 6],
[0x5d, 7],
[0x5e, 7],
[0x5f, 7],
[0x60, 7],
[0x61, 7],
[0x62, 7],
[0x63, 7],
[0x64, 7],
[0x65, 7],
[0x66, 7],
[0x67, 7],
[0x68, 7],
[0x69, 7],
[0x6a, 7],
[0x6b, 7],
[0x6c, 7],
[0x6d, 7],
[0x6e, 7],
[0x6f, 7],
[0x70, 7],
[0x71, 7],
[0x72, 7],
[0xfc, 8],
[0x73, 7],
[0xfd, 8],
[0x1ffb, 13],
[0x7fff0, 19],
[0x1ffc, 13],
[0x3ffc, 14],
[0x22, 6],
[0x7ffd, 15],
[0x3, 5],
[0x23, 6],
[0x4, 5],
[0x24, 6],
[0x5, 5],
[0x25, 6],
[0x26, 6],
[0x27, 6],
[0x6, 5],
[0x74, 7],
[0x75, 7],
[0x28, 6],
[0x29, 6],
[0x2a, 6],
[0x7, 5],
[0x2b, 6],
[0x76, 7],
[0x2c, 6],
[0x8, 5],
[0x9, 5],
[0x2d, 6],
[0x77, 7],
[0x78, 7],
[0x79, 7],
[0x7a, 7],
[0x7b, 7],
[0x7ffe, 15],
[0x7fc, 11],
[0x3ffd, 14],
[0x1ffd, 13],
[0xffffffc, 28],
[0xfffe6, 20],
[0x3fffd2, 22],
[0xfffe7, 20],
[0xfffe8, 20],
[0x3fffd3, 22],
[0x3fffd4, 22],
[0x3fffd5, 22],
[0x7fffd9, 23],
[0x3fffd6, 22],
[0x7fffda, 23],
[0x7fffdb, 23],
[0x7fffdc, 23],
[0x7fffdd, 23],
[0x7fffde, 23],
[0xffffeb, 24],
[0x7fffdf, 23],
[0xffffec, 24],
[0xffffed, 24],
[0x3fffd7, 22],
[0x7fffe0, 23],
[0xffffee, 24],
[0x7fffe1, 23],
[0x7fffe2, 23],
[0x7fffe3, 23],
[0x7fffe4, 23],
[0x1fffdc, 21],
[0x3fffd8, 22],
[0x7fffe5, 23],
[0x3fffd9, 22],
[0x7fffe6, 23],
[0x7fffe7, 23],
[0xffffef, 24],
[0x3fffda, 22],
[0x1fffdd, 21],
[0xfffe9, 20],
[0x3fffdb, 22],
[0x3fffdc, 22],
[0x7fffe8, 23],
[0x7fffe9, 23],
[0x1fffde, 21],
[0x7fffea, 23],
[0x3fffdd, 22],
[0x3fffde, 22],
[0xfffff0, 24],
[0x1fffdf, 21],
[0x3fffdf, 22],
[0x7fffeb, 23],
[0x7fffec, 23],
[0x1fffe0, 21],
[0x1fffe1, 21],
[0x3fffe0, 22],
[0x1fffe2, 21],
[0x7fffed, 23],
[0x3fffe1, 22],
[0x7fffee, 23],
[0x7fffef, 23],
[0xfffea, 20],
[0x3fffe2, 22],
[0x3fffe3, 22],
[0x3fffe4, 22],
[0x7ffff0, 23],
[0x3fffe5, 22],
[0x3fffe6, 22],
[0x7ffff1, 23],
[0x3ffffe0, 26],
[0x3ffffe1, 26],
[0xfffeb, 20],
[0x7fff1, 19],
[0x3fffe7, 22],
[0x7ffff2, 23],
[0x3fffe8, 22],
[0x1ffffec, 25],
[0x3ffffe2, 26],
[0x3ffffe3, 26],
[0x3ffffe4, 26],
[0x7ffffde, 27],
[0x7ffffdf, 27],
[0x3ffffe5, 26],
[0xfffff1, 24],
[0x1ffffed, 25],
[0x7fff2, 19],
[0x1fffe3, 21],
[0x3ffffe6, 26],
[0x7ffffe0, 27],
[0x7ffffe1, 27],
[0x3ffffe7, 26],
[0x7ffffe2, 27],
[0xfffff2, 24],
[0x1fffe4, 21],
[0x1fffe5, 21],
[0x3ffffe8, 26],
[0x3ffffe9, 26],
[0xffffffd, 28],
[0x7ffffe3, 27],
[0x7ffffe4, 27],
[0x7ffffe5, 27],
[0xfffec, 20],
[0xfffff3, 24],
[0xfffed, 20],
[0x1fffe6, 21],
[0x3fffe9, 22],
[0x1fffe7, 21],
[0x1fffe8, 21],
[0x7ffff3, 23],
[0x3fffea, 22],
[0x3fffeb, 22],
[0x1ffffee, 25],
[0x1ffffef, 25],
[0xfffff4, 24],
[0xfffff5, 24],
[0x3ffffea, 26],
[0x7ffff4, 23],
[0x3ffffeb, 26],
[0x7ffffe6, 27],
[0x3ffffec, 26],
[0x3ffffed, 26],
[0x7ffffe7, 27],
[0x7ffffe8, 27],
[0x7ffffe9, 27],
[0x7ffffea, 27],
[0x7ffffeb, 27],
[0xffffffe, 28],
[0x7ffffec, 27],
[0x7ffffed, 27],
[0x7ffffee, 27],
[0x7ffffef, 27],
[0x7fffff0, 27],
[0x3ffffee, 26],
[0x3fffffff, 30],
].each(&:freeze).freeze
ENCODE_TABLE = CODES.map { |c, l| [c].pack('N').unpack('B*').first[-l..-1] }.each(&:freeze).freeze
end
end
end
ruby-http-2-0.11.0/lib/http/2/huffman_statemachine.rb 0000664 0000000 0000000 00000130606 13775242373 0022275 0 ustar 00root root 0000000 0000000 # Machine generated Huffman decoder state machine.
# DO NOT EDIT THIS FILE.
# The following task generates this file.
# rake generate_huffman_table
module HTTP2
module Header
class Huffman
# :nodoc:
MAX_FINAL_STATE = 7
MACHINE = [
[[nil, 16], [nil, 76], [nil, 54], [nil, 36], [nil, 30], [nil, 28], [nil, 24], [nil, 221], [nil, 9], [nil, 10], [nil, 11], [nil, 12], [nil, 13], [nil, 14], [nil, 15], [nil, 1]],
[[119, 29], [119, 5], [120, 29], [120, 5], [121, 29], [121, 5], [122, 29], [122, 5], [38, 0], [42, 0], [44, 0], [59, 0], [88, 0], [90, 0], [nil, 31], [nil, 32]],
[[38, 29], [38, 5], [42, 29], [42, 5], [44, 29], [44, 5], [59, 29], [59, 5], [88, 29], [88, 5], [90, 29], [90, 5], [nil, 87], [nil, 88], [nil, 89], [nil, 90]],
[[88, 25], [88, 26], [88, 27], [88, 6], [90, 25], [90, 26], [90, 27], [90, 6], [33, 0], [34, 0], [40, 0], [41, 0], [63, 0], [nil, 84], [nil, 85], [nil, 86]],
[[33, 29], [33, 5], [34, 29], [34, 5], [40, 29], [40, 5], [41, 29], [41, 5], [63, 29], [63, 5], [39, 0], [43, 0], [124, 0], [nil, 81], [nil, 82], [nil, 83]],
[[nil, 61], [nil, 62], [nil, 63], [nil, 64], [nil, 65], [nil, 66], [nil, 67], [nil, 68], [nil, 69], [nil, 70], [nil, 71], [nil, 72], [nil, 73], [nil, 74], [nil, 75], [nil, 2]],
[[nil, 39], [nil, 40], [nil, 41], [nil, 42], [nil, 43], [nil, 44], [nil, 45], [nil, 46], [nil, 47], [nil, 48], [nil, 49], [nil, 50], [nil, 51], [nil, 52], [nil, 53], [nil, 3]],
[[85, 0], [86, 0], [87, 0], [89, 0], [106, 0], [107, 0], [113, 0], [118, 0], [119, 0], [120, 0], [121, 0], [122, 0], [nil, 33], [nil, 34], [nil, 35], [nil, 4]],
[[203, 17], [203, 18], [203, 19], [203, 20], [203, 21], [203, 22], [203, 23], [203, 7], [204, 17], [204, 18], [204, 19], [204, 20], [204, 21], [204, 22], [204, 23], [204, 7]],
[[61, 25], [61, 26], [61, 27], [61, 6], [65, 25], [65, 26], [65, 27], [65, 6], [95, 25], [95, 26], [95, 27], [95, 6], [98, 25], [98, 26], [98, 27], [98, 6]],
[[100, 25], [100, 26], [100, 27], [100, 6], [102, 25], [102, 26], [102, 27], [102, 6], [103, 25], [103, 26], [103, 27], [103, 6], [104, 25], [104, 26], [104, 27], [104, 6]],
[[108, 25], [108, 26], [108, 27], [108, 6], [109, 25], [109, 26], [109, 27], [109, 6], [110, 25], [110, 26], [110, 27], [110, 6], [112, 25], [112, 26], [112, 27], [112, 6]],
[[114, 25], [114, 26], [114, 27], [114, 6], [117, 25], [117, 26], [117, 27], [117, 6], [58, 29], [58, 5], [66, 29], [66, 5], [67, 29], [67, 5], [68, 29], [68, 5]],
[[69, 29], [69, 5], [70, 29], [70, 5], [71, 29], [71, 5], [72, 29], [72, 5], [73, 29], [73, 5], [74, 29], [74, 5], [75, 29], [75, 5], [76, 29], [76, 5]],
[[77, 29], [77, 5], [78, 29], [78, 5], [79, 29], [79, 5], [80, 29], [80, 5], [81, 29], [81, 5], [82, 29], [82, 5], [83, 29], [83, 5], [84, 29], [84, 5]],
[[85, 29], [85, 5], [86, 29], [86, 5], [87, 29], [87, 5], [89, 29], [89, 5], [106, 29], [106, 5], [107, 29], [107, 5], [113, 29], [113, 5], [118, 29], [118, 5]],
[[48, 17], [48, 18], [48, 19], [48, 20], [48, 21], [48, 22], [48, 23], [48, 7], [49, 17], [49, 18], [49, 19], [49, 20], [49, 21], [49, 22], [49, 23], [49, 7]],
[[48, 25], [48, 26], [48, 27], [48, 6], [49, 25], [49, 26], [49, 27], [49, 6], [50, 25], [50, 26], [50, 27], [50, 6], [97, 25], [97, 26], [97, 27], [97, 6]],
[[99, 25], [99, 26], [99, 27], [99, 6], [101, 25], [101, 26], [101, 27], [101, 6], [105, 25], [105, 26], [105, 27], [105, 6], [111, 25], [111, 26], [111, 27], [111, 6]],
[[115, 25], [115, 26], [115, 27], [115, 6], [116, 25], [116, 26], [116, 27], [116, 6], [32, 29], [32, 5], [37, 29], [37, 5], [45, 29], [45, 5], [46, 29], [46, 5]],
[[47, 29], [47, 5], [51, 29], [51, 5], [52, 29], [52, 5], [53, 29], [53, 5], [54, 29], [54, 5], [55, 29], [55, 5], [56, 29], [56, 5], [57, 29], [57, 5]],
[[61, 29], [61, 5], [65, 29], [65, 5], [95, 29], [95, 5], [98, 29], [98, 5], [100, 29], [100, 5], [102, 29], [102, 5], [103, 29], [103, 5], [104, 29], [104, 5]],
[[108, 29], [108, 5], [109, 29], [109, 5], [110, 29], [110, 5], [112, 29], [112, 5], [114, 29], [114, 5], [117, 29], [117, 5], [58, 0], [66, 0], [67, 0], [68, 0]],
[[69, 0], [70, 0], [71, 0], [72, 0], [73, 0], [74, 0], [75, 0], [76, 0], [77, 0], [78, 0], [79, 0], [80, 0], [81, 0], [82, 0], [83, 0], [84, 0]],
[[47, 25], [47, 26], [47, 27], [47, 6], [51, 25], [51, 26], [51, 27], [51, 6], [52, 25], [52, 26], [52, 27], [52, 6], [53, 25], [53, 26], [53, 27], [53, 6]],
[[48, 29], [48, 5], [49, 29], [49, 5], [50, 29], [50, 5], [97, 29], [97, 5], [99, 29], [99, 5], [101, 29], [101, 5], [105, 29], [105, 5], [111, 29], [111, 5]],
[[115, 29], [115, 5], [116, 29], [116, 5], [32, 0], [37, 0], [45, 0], [46, 0], [47, 0], [51, 0], [52, 0], [53, 0], [54, 0], [55, 0], [56, 0], [57, 0]],
[[61, 0], [65, 0], [95, 0], [98, 0], [100, 0], [102, 0], [103, 0], [104, 0], [108, 0], [109, 0], [110, 0], [112, 0], [114, 0], [117, 0], [nil, 37], [nil, 38]],
[[32, 25], [32, 26], [32, 27], [32, 6], [37, 25], [37, 26], [37, 27], [37, 6], [45, 25], [45, 26], [45, 27], [45, 6], [46, 25], [46, 26], [46, 27], [46, 6]],
[[48, 0], [49, 0], [50, 0], [97, 0], [99, 0], [101, 0], [105, 0], [111, 0], [115, 0], [116, 0], [nil, 55], [nil, 56], [nil, 57], [nil, 58], [nil, 59], [nil, 60]],
[[115, 17], [115, 18], [115, 19], [115, 20], [115, 21], [115, 22], [115, 23], [115, 7], [116, 17], [116, 18], [116, 19], [116, 20], [116, 21], [116, 22], [116, 23], [116, 7]],
[[33, 25], [33, 26], [33, 27], [33, 6], [34, 25], [34, 26], [34, 27], [34, 6], [40, 25], [40, 26], [40, 27], [40, 6], [41, 25], [41, 26], [41, 27], [41, 6]],
[[63, 25], [63, 26], [63, 27], [63, 6], [39, 29], [39, 5], [43, 29], [43, 5], [124, 29], [124, 5], [35, 0], [62, 0], [nil, 77], [nil, 78], [nil, 79], [nil, 80]],
[[38, 17], [38, 18], [38, 19], [38, 20], [38, 21], [38, 22], [38, 23], [38, 7], [42, 17], [42, 18], [42, 19], [42, 20], [42, 21], [42, 22], [42, 23], [42, 7]],
[[44, 17], [44, 18], [44, 19], [44, 20], [44, 21], [44, 22], [44, 23], [44, 7], [59, 17], [59, 18], [59, 19], [59, 20], [59, 21], [59, 22], [59, 23], [59, 7]],
[[88, 17], [88, 18], [88, 19], [88, 20], [88, 21], [88, 22], [88, 23], [88, 7], [90, 17], [90, 18], [90, 19], [90, 20], [90, 21], [90, 22], [90, 23], [90, 7]],
[[105, 17], [105, 18], [105, 19], [105, 20], [105, 21], [105, 22], [105, 23], [105, 7], [111, 17], [111, 18], [111, 19], [111, 20], [111, 21], [111, 22], [111, 23], [111, 7]],
[[58, 17], [58, 18], [58, 19], [58, 20], [58, 21], [58, 22], [58, 23], [58, 7], [66, 17], [66, 18], [66, 19], [66, 20], [66, 21], [66, 22], [66, 23], [66, 7]],
[[67, 17], [67, 18], [67, 19], [67, 20], [67, 21], [67, 22], [67, 23], [67, 7], [68, 17], [68, 18], [68, 19], [68, 20], [68, 21], [68, 22], [68, 23], [68, 7]],
[[69, 17], [69, 18], [69, 19], [69, 20], [69, 21], [69, 22], [69, 23], [69, 7], [70, 17], [70, 18], [70, 19], [70, 20], [70, 21], [70, 22], [70, 23], [70, 7]],
[[71, 17], [71, 18], [71, 19], [71, 20], [71, 21], [71, 22], [71, 23], [71, 7], [72, 17], [72, 18], [72, 19], [72, 20], [72, 21], [72, 22], [72, 23], [72, 7]],
[[73, 17], [73, 18], [73, 19], [73, 20], [73, 21], [73, 22], [73, 23], [73, 7], [74, 17], [74, 18], [74, 19], [74, 20], [74, 21], [74, 22], [74, 23], [74, 7]],
[[75, 17], [75, 18], [75, 19], [75, 20], [75, 21], [75, 22], [75, 23], [75, 7], [76, 17], [76, 18], [76, 19], [76, 20], [76, 21], [76, 22], [76, 23], [76, 7]],
[[77, 17], [77, 18], [77, 19], [77, 20], [77, 21], [77, 22], [77, 23], [77, 7], [78, 17], [78, 18], [78, 19], [78, 20], [78, 21], [78, 22], [78, 23], [78, 7]],
[[79, 17], [79, 18], [79, 19], [79, 20], [79, 21], [79, 22], [79, 23], [79, 7], [80, 17], [80, 18], [80, 19], [80, 20], [80, 21], [80, 22], [80, 23], [80, 7]],
[[81, 17], [81, 18], [81, 19], [81, 20], [81, 21], [81, 22], [81, 23], [81, 7], [82, 17], [82, 18], [82, 19], [82, 20], [82, 21], [82, 22], [82, 23], [82, 7]],
[[83, 17], [83, 18], [83, 19], [83, 20], [83, 21], [83, 22], [83, 23], [83, 7], [84, 17], [84, 18], [84, 19], [84, 20], [84, 21], [84, 22], [84, 23], [84, 7]],
[[85, 17], [85, 18], [85, 19], [85, 20], [85, 21], [85, 22], [85, 23], [85, 7], [86, 17], [86, 18], [86, 19], [86, 20], [86, 21], [86, 22], [86, 23], [86, 7]],
[[87, 17], [87, 18], [87, 19], [87, 20], [87, 21], [87, 22], [87, 23], [87, 7], [89, 17], [89, 18], [89, 19], [89, 20], [89, 21], [89, 22], [89, 23], [89, 7]],
[[106, 17], [106, 18], [106, 19], [106, 20], [106, 21], [106, 22], [106, 23], [106, 7], [107, 17], [107, 18], [107, 19], [107, 20], [107, 21], [107, 22], [107, 23], [107, 7]],
[[113, 17], [113, 18], [113, 19], [113, 20], [113, 21], [113, 22], [113, 23], [113, 7], [118, 17], [118, 18], [118, 19], [118, 20], [118, 21], [118, 22], [118, 23], [118, 7]],
[[119, 17], [119, 18], [119, 19], [119, 20], [119, 21], [119, 22], [119, 23], [119, 7], [120, 17], [120, 18], [120, 19], [120, 20], [120, 21], [120, 22], [120, 23], [120, 7]],
[[121, 17], [121, 18], [121, 19], [121, 20], [121, 21], [121, 22], [121, 23], [121, 7], [122, 17], [122, 18], [122, 19], [122, 20], [122, 21], [122, 22], [122, 23], [122, 7]],
[[38, 25], [38, 26], [38, 27], [38, 6], [42, 25], [42, 26], [42, 27], [42, 6], [44, 25], [44, 26], [44, 27], [44, 6], [59, 25], [59, 26], [59, 27], [59, 6]],
[[99, 17], [99, 18], [99, 19], [99, 20], [99, 21], [99, 22], [99, 23], [99, 7], [101, 17], [101, 18], [101, 19], [101, 20], [101, 21], [101, 22], [101, 23], [101, 7]],
[[32, 17], [32, 18], [32, 19], [32, 20], [32, 21], [32, 22], [32, 23], [32, 7], [37, 17], [37, 18], [37, 19], [37, 20], [37, 21], [37, 22], [37, 23], [37, 7]],
[[45, 17], [45, 18], [45, 19], [45, 20], [45, 21], [45, 22], [45, 23], [45, 7], [46, 17], [46, 18], [46, 19], [46, 20], [46, 21], [46, 22], [46, 23], [46, 7]],
[[47, 17], [47, 18], [47, 19], [47, 20], [47, 21], [47, 22], [47, 23], [47, 7], [51, 17], [51, 18], [51, 19], [51, 20], [51, 21], [51, 22], [51, 23], [51, 7]],
[[52, 17], [52, 18], [52, 19], [52, 20], [52, 21], [52, 22], [52, 23], [52, 7], [53, 17], [53, 18], [53, 19], [53, 20], [53, 21], [53, 22], [53, 23], [53, 7]],
[[54, 17], [54, 18], [54, 19], [54, 20], [54, 21], [54, 22], [54, 23], [54, 7], [55, 17], [55, 18], [55, 19], [55, 20], [55, 21], [55, 22], [55, 23], [55, 7]],
[[56, 17], [56, 18], [56, 19], [56, 20], [56, 21], [56, 22], [56, 23], [56, 7], [57, 17], [57, 18], [57, 19], [57, 20], [57, 21], [57, 22], [57, 23], [57, 7]],
[[61, 17], [61, 18], [61, 19], [61, 20], [61, 21], [61, 22], [61, 23], [61, 7], [65, 17], [65, 18], [65, 19], [65, 20], [65, 21], [65, 22], [65, 23], [65, 7]],
[[95, 17], [95, 18], [95, 19], [95, 20], [95, 21], [95, 22], [95, 23], [95, 7], [98, 17], [98, 18], [98, 19], [98, 20], [98, 21], [98, 22], [98, 23], [98, 7]],
[[100, 17], [100, 18], [100, 19], [100, 20], [100, 21], [100, 22], [100, 23], [100, 7], [102, 17], [102, 18], [102, 19], [102, 20], [102, 21], [102, 22], [102, 23], [102, 7]],
[[103, 17], [103, 18], [103, 19], [103, 20], [103, 21], [103, 22], [103, 23], [103, 7], [104, 17], [104, 18], [104, 19], [104, 20], [104, 21], [104, 22], [104, 23], [104, 7]],
[[108, 17], [108, 18], [108, 19], [108, 20], [108, 21], [108, 22], [108, 23], [108, 7], [109, 17], [109, 18], [109, 19], [109, 20], [109, 21], [109, 22], [109, 23], [109, 7]],
[[110, 17], [110, 18], [110, 19], [110, 20], [110, 21], [110, 22], [110, 23], [110, 7], [112, 17], [112, 18], [112, 19], [112, 20], [112, 21], [112, 22], [112, 23], [112, 7]],
[[114, 17], [114, 18], [114, 19], [114, 20], [114, 21], [114, 22], [114, 23], [114, 7], [117, 17], [117, 18], [117, 19], [117, 20], [117, 21], [117, 22], [117, 23], [117, 7]],
[[58, 25], [58, 26], [58, 27], [58, 6], [66, 25], [66, 26], [66, 27], [66, 6], [67, 25], [67, 26], [67, 27], [67, 6], [68, 25], [68, 26], [68, 27], [68, 6]],
[[69, 25], [69, 26], [69, 27], [69, 6], [70, 25], [70, 26], [70, 27], [70, 6], [71, 25], [71, 26], [71, 27], [71, 6], [72, 25], [72, 26], [72, 27], [72, 6]],
[[73, 25], [73, 26], [73, 27], [73, 6], [74, 25], [74, 26], [74, 27], [74, 6], [75, 25], [75, 26], [75, 27], [75, 6], [76, 25], [76, 26], [76, 27], [76, 6]],
[[77, 25], [77, 26], [77, 27], [77, 6], [78, 25], [78, 26], [78, 27], [78, 6], [79, 25], [79, 26], [79, 27], [79, 6], [80, 25], [80, 26], [80, 27], [80, 6]],
[[81, 25], [81, 26], [81, 27], [81, 6], [82, 25], [82, 26], [82, 27], [82, 6], [83, 25], [83, 26], [83, 27], [83, 6], [84, 25], [84, 26], [84, 27], [84, 6]],
[[85, 25], [85, 26], [85, 27], [85, 6], [86, 25], [86, 26], [86, 27], [86, 6], [87, 25], [87, 26], [87, 27], [87, 6], [89, 25], [89, 26], [89, 27], [89, 6]],
[[106, 25], [106, 26], [106, 27], [106, 6], [107, 25], [107, 26], [107, 27], [107, 6], [113, 25], [113, 26], [113, 27], [113, 6], [118, 25], [118, 26], [118, 27], [118, 6]],
[[119, 25], [119, 26], [119, 27], [119, 6], [120, 25], [120, 26], [120, 27], [120, 6], [121, 25], [121, 26], [121, 27], [121, 6], [122, 25], [122, 26], [122, 27], [122, 6]],
[[50, 17], [50, 18], [50, 19], [50, 20], [50, 21], [50, 22], [50, 23], [50, 7], [97, 17], [97, 18], [97, 19], [97, 20], [97, 21], [97, 22], [97, 23], [97, 7]],
[[0, 17], [0, 18], [0, 19], [0, 20], [0, 21], [0, 22], [0, 23], [0, 7], [36, 17], [36, 18], [36, 19], [36, 20], [36, 21], [36, 22], [36, 23], [36, 7]],
[[64, 17], [64, 18], [64, 19], [64, 20], [64, 21], [64, 22], [64, 23], [64, 7], [91, 17], [91, 18], [91, 19], [91, 20], [91, 21], [91, 22], [91, 23], [91, 7]],
[[93, 17], [93, 18], [93, 19], [93, 20], [93, 21], [93, 22], [93, 23], [93, 7], [126, 17], [126, 18], [126, 19], [126, 20], [126, 21], [126, 22], [126, 23], [126, 7]],
[[94, 25], [94, 26], [94, 27], [94, 6], [125, 25], [125, 26], [125, 27], [125, 6], [60, 29], [60, 5], [96, 29], [96, 5], [123, 29], [123, 5], [nil, 91], [nil, 92]],
[[35, 17], [35, 18], [35, 19], [35, 20], [35, 21], [35, 22], [35, 23], [35, 7], [62, 17], [62, 18], [62, 19], [62, 20], [62, 21], [62, 22], [62, 23], [62, 7]],
[[0, 25], [0, 26], [0, 27], [0, 6], [36, 25], [36, 26], [36, 27], [36, 6], [64, 25], [64, 26], [64, 27], [64, 6], [91, 25], [91, 26], [91, 27], [91, 6]],
[[93, 25], [93, 26], [93, 27], [93, 6], [126, 25], [126, 26], [126, 27], [126, 6], [94, 29], [94, 5], [125, 29], [125, 5], [60, 0], [96, 0], [123, 0], [nil, 93]],
[[39, 17], [39, 18], [39, 19], [39, 20], [39, 21], [39, 22], [39, 23], [39, 7], [43, 17], [43, 18], [43, 19], [43, 20], [43, 21], [43, 22], [43, 23], [43, 7]],
[[124, 17], [124, 18], [124, 19], [124, 20], [124, 21], [124, 22], [124, 23], [124, 7], [35, 25], [35, 26], [35, 27], [35, 6], [62, 25], [62, 26], [62, 27], [62, 6]],
[[0, 29], [0, 5], [36, 29], [36, 5], [64, 29], [64, 5], [91, 29], [91, 5], [93, 29], [93, 5], [126, 29], [126, 5], [94, 0], [125, 0], [nil, 94], [nil, 95]],
[[33, 17], [33, 18], [33, 19], [33, 20], [33, 21], [33, 22], [33, 23], [33, 7], [34, 17], [34, 18], [34, 19], [34, 20], [34, 21], [34, 22], [34, 23], [34, 7]],
[[40, 17], [40, 18], [40, 19], [40, 20], [40, 21], [40, 22], [40, 23], [40, 7], [41, 17], [41, 18], [41, 19], [41, 20], [41, 21], [41, 22], [41, 23], [41, 7]],
[[63, 17], [63, 18], [63, 19], [63, 20], [63, 21], [63, 22], [63, 23], [63, 7], [39, 25], [39, 26], [39, 27], [39, 6], [43, 25], [43, 26], [43, 27], [43, 6]],
[[124, 25], [124, 26], [124, 27], [124, 6], [35, 29], [35, 5], [62, 29], [62, 5], [0, 0], [36, 0], [64, 0], [91, 0], [93, 0], [126, 0], [nil, 96], [nil, 97]],
[[92, 29], [92, 5], [195, 29], [195, 5], [208, 29], [208, 5], [128, 0], [130, 0], [131, 0], [162, 0], [184, 0], [194, 0], [224, 0], [226, 0], [nil, 98], [nil, 99]],
[[nil, 100], [nil, 101], [nil, 102], [nil, 103], [nil, 104], [nil, 105], [nil, 106], [nil, 107], [nil, 108], [nil, 109], [nil, 110], [nil, 111], [nil, 112], [nil, 113], [nil, 114], [nil, 115]],
[[92, 0], [195, 0], [208, 0], [nil, 116], [nil, 117], [nil, 118], [nil, 119], [nil, 120], [nil, 121], [nil, 122], [nil, 123], [nil, 124], [nil, 125], [nil, 126], [nil, 127], [nil, 128]],
[[60, 17], [60, 18], [60, 19], [60, 20], [60, 21], [60, 22], [60, 23], [60, 7], [96, 17], [96, 18], [96, 19], [96, 20], [96, 21], [96, 22], [96, 23], [96, 7]],
[[123, 17], [123, 18], [123, 19], [123, 20], [123, 21], [123, 22], [123, 23], [123, 7], [nil, 129], [nil, 130], [nil, 131], [nil, 132], [nil, 133], [nil, 134], [nil, 135], [nil, 136]],
[[94, 17], [94, 18], [94, 19], [94, 20], [94, 21], [94, 22], [94, 23], [94, 7], [125, 17], [125, 18], [125, 19], [125, 20], [125, 21], [125, 22], [125, 23], [125, 7]],
[[60, 25], [60, 26], [60, 27], [60, 6], [96, 25], [96, 26], [96, 27], [96, 6], [123, 25], [123, 26], [123, 27], [123, 6], [nil, 137], [nil, 138], [nil, 139], [nil, 140]],
[[153, 17], [153, 18], [153, 19], [153, 20], [153, 21], [153, 22], [153, 23], [153, 7], [161, 17], [161, 18], [161, 19], [161, 20], [161, 21], [161, 22], [161, 23], [161, 7]],
[[167, 17], [167, 18], [167, 19], [167, 20], [167, 21], [167, 22], [167, 23], [167, 7], [172, 17], [172, 18], [172, 19], [172, 20], [172, 21], [172, 22], [172, 23], [172, 7]],
[[176, 17], [176, 18], [176, 19], [176, 20], [176, 21], [176, 22], [176, 23], [176, 7], [177, 17], [177, 18], [177, 19], [177, 20], [177, 21], [177, 22], [177, 23], [177, 7]],
[[179, 17], [179, 18], [179, 19], [179, 20], [179, 21], [179, 22], [179, 23], [179, 7], [209, 17], [209, 18], [209, 19], [209, 20], [209, 21], [209, 22], [209, 23], [209, 7]],
[[216, 17], [216, 18], [216, 19], [216, 20], [216, 21], [216, 22], [216, 23], [216, 7], [217, 17], [217, 18], [217, 19], [217, 20], [217, 21], [217, 22], [217, 23], [217, 7]],
[[227, 17], [227, 18], [227, 19], [227, 20], [227, 21], [227, 22], [227, 23], [227, 7], [229, 17], [229, 18], [229, 19], [229, 20], [229, 21], [229, 22], [229, 23], [229, 7]],
[[230, 17], [230, 18], [230, 19], [230, 20], [230, 21], [230, 22], [230, 23], [230, 7], [129, 25], [129, 26], [129, 27], [129, 6], [132, 25], [132, 26], [132, 27], [132, 6]],
[[133, 25], [133, 26], [133, 27], [133, 6], [134, 25], [134, 26], [134, 27], [134, 6], [136, 25], [136, 26], [136, 27], [136, 6], [146, 25], [146, 26], [146, 27], [146, 6]],
[[154, 25], [154, 26], [154, 27], [154, 6], [156, 25], [156, 26], [156, 27], [156, 6], [160, 25], [160, 26], [160, 27], [160, 6], [163, 25], [163, 26], [163, 27], [163, 6]],
[[164, 25], [164, 26], [164, 27], [164, 6], [169, 25], [169, 26], [169, 27], [169, 6], [170, 25], [170, 26], [170, 27], [170, 6], [173, 25], [173, 26], [173, 27], [173, 6]],
[[178, 25], [178, 26], [178, 27], [178, 6], [181, 25], [181, 26], [181, 27], [181, 6], [185, 25], [185, 26], [185, 27], [185, 6], [186, 25], [186, 26], [186, 27], [186, 6]],
[[187, 25], [187, 26], [187, 27], [187, 6], [189, 25], [189, 26], [189, 27], [189, 6], [190, 25], [190, 26], [190, 27], [190, 6], [196, 25], [196, 26], [196, 27], [196, 6]],
[[198, 25], [198, 26], [198, 27], [198, 6], [228, 25], [228, 26], [228, 27], [228, 6], [232, 25], [232, 26], [232, 27], [232, 6], [233, 25], [233, 26], [233, 27], [233, 6]],
[[1, 29], [1, 5], [135, 29], [135, 5], [137, 29], [137, 5], [138, 29], [138, 5], [139, 29], [139, 5], [140, 29], [140, 5], [141, 29], [141, 5], [143, 29], [143, 5]],
[[147, 29], [147, 5], [149, 29], [149, 5], [150, 29], [150, 5], [151, 29], [151, 5], [152, 29], [152, 5], [155, 29], [155, 5], [157, 29], [157, 5], [158, 29], [158, 5]],
[[165, 29], [165, 5], [166, 29], [166, 5], [168, 29], [168, 5], [174, 29], [174, 5], [175, 29], [175, 5], [180, 29], [180, 5], [182, 29], [182, 5], [183, 29], [183, 5]],
[[188, 29], [188, 5], [191, 29], [191, 5], [197, 29], [197, 5], [231, 29], [231, 5], [239, 29], [239, 5], [9, 0], [142, 0], [144, 0], [145, 0], [148, 0], [159, 0]],
[[171, 0], [206, 0], [215, 0], [225, 0], [236, 0], [237, 0], [nil, 141], [nil, 142], [nil, 143], [nil, 144], [nil, 145], [nil, 146], [nil, 147], [nil, 148], [nil, 149], [nil, 150]],
[[128, 17], [128, 18], [128, 19], [128, 20], [128, 21], [128, 22], [128, 23], [128, 7], [130, 17], [130, 18], [130, 19], [130, 20], [130, 21], [130, 22], [130, 23], [130, 7]],
[[131, 17], [131, 18], [131, 19], [131, 20], [131, 21], [131, 22], [131, 23], [131, 7], [162, 17], [162, 18], [162, 19], [162, 20], [162, 21], [162, 22], [162, 23], [162, 7]],
[[184, 17], [184, 18], [184, 19], [184, 20], [184, 21], [184, 22], [184, 23], [184, 7], [194, 17], [194, 18], [194, 19], [194, 20], [194, 21], [194, 22], [194, 23], [194, 7]],
[[224, 17], [224, 18], [224, 19], [224, 20], [224, 21], [224, 22], [224, 23], [224, 7], [226, 17], [226, 18], [226, 19], [226, 20], [226, 21], [226, 22], [226, 23], [226, 7]],
[[153, 25], [153, 26], [153, 27], [153, 6], [161, 25], [161, 26], [161, 27], [161, 6], [167, 25], [167, 26], [167, 27], [167, 6], [172, 25], [172, 26], [172, 27], [172, 6]],
[[176, 25], [176, 26], [176, 27], [176, 6], [177, 25], [177, 26], [177, 27], [177, 6], [179, 25], [179, 26], [179, 27], [179, 6], [209, 25], [209, 26], [209, 27], [209, 6]],
[[216, 25], [216, 26], [216, 27], [216, 6], [217, 25], [217, 26], [217, 27], [217, 6], [227, 25], [227, 26], [227, 27], [227, 6], [229, 25], [229, 26], [229, 27], [229, 6]],
[[230, 25], [230, 26], [230, 27], [230, 6], [129, 29], [129, 5], [132, 29], [132, 5], [133, 29], [133, 5], [134, 29], [134, 5], [136, 29], [136, 5], [146, 29], [146, 5]],
[[154, 29], [154, 5], [156, 29], [156, 5], [160, 29], [160, 5], [163, 29], [163, 5], [164, 29], [164, 5], [169, 29], [169, 5], [170, 29], [170, 5], [173, 29], [173, 5]],
[[178, 29], [178, 5], [181, 29], [181, 5], [185, 29], [185, 5], [186, 29], [186, 5], [187, 29], [187, 5], [189, 29], [189, 5], [190, 29], [190, 5], [196, 29], [196, 5]],
[[198, 29], [198, 5], [228, 29], [228, 5], [232, 29], [232, 5], [233, 29], [233, 5], [1, 0], [135, 0], [137, 0], [138, 0], [139, 0], [140, 0], [141, 0], [143, 0]],
[[147, 0], [149, 0], [150, 0], [151, 0], [152, 0], [155, 0], [157, 0], [158, 0], [165, 0], [166, 0], [168, 0], [174, 0], [175, 0], [180, 0], [182, 0], [183, 0]],
[[188, 0], [191, 0], [197, 0], [231, 0], [239, 0], [nil, 151], [nil, 152], [nil, 153], [nil, 154], [nil, 155], [nil, 156], [nil, 157], [nil, 158], [nil, 159], [nil, 160], [nil, 161]],
[[92, 17], [92, 18], [92, 19], [92, 20], [92, 21], [92, 22], [92, 23], [92, 7], [195, 17], [195, 18], [195, 19], [195, 20], [195, 21], [195, 22], [195, 23], [195, 7]],
[[208, 17], [208, 18], [208, 19], [208, 20], [208, 21], [208, 22], [208, 23], [208, 7], [128, 25], [128, 26], [128, 27], [128, 6], [130, 25], [130, 26], [130, 27], [130, 6]],
[[131, 25], [131, 26], [131, 27], [131, 6], [162, 25], [162, 26], [162, 27], [162, 6], [184, 25], [184, 26], [184, 27], [184, 6], [194, 25], [194, 26], [194, 27], [194, 6]],
[[224, 25], [224, 26], [224, 27], [224, 6], [226, 25], [226, 26], [226, 27], [226, 6], [153, 29], [153, 5], [161, 29], [161, 5], [167, 29], [167, 5], [172, 29], [172, 5]],
[[176, 29], [176, 5], [177, 29], [177, 5], [179, 29], [179, 5], [209, 29], [209, 5], [216, 29], [216, 5], [217, 29], [217, 5], [227, 29], [227, 5], [229, 29], [229, 5]],
[[230, 29], [230, 5], [129, 0], [132, 0], [133, 0], [134, 0], [136, 0], [146, 0], [154, 0], [156, 0], [160, 0], [163, 0], [164, 0], [169, 0], [170, 0], [173, 0]],
[[178, 0], [181, 0], [185, 0], [186, 0], [187, 0], [189, 0], [190, 0], [196, 0], [198, 0], [228, 0], [232, 0], [233, 0], [nil, 162], [nil, 163], [nil, 164], [nil, 165]],
[[nil, 166], [nil, 167], [nil, 168], [nil, 169], [nil, 170], [nil, 171], [nil, 172], [nil, 173], [nil, 174], [nil, 175], [nil, 176], [nil, 177], [nil, 178], [nil, 179], [nil, 180], [nil, 181]],
[[92, 25], [92, 26], [92, 27], [92, 6], [195, 25], [195, 26], [195, 27], [195, 6], [208, 25], [208, 26], [208, 27], [208, 6], [128, 29], [128, 5], [130, 29], [130, 5]],
[[131, 29], [131, 5], [162, 29], [162, 5], [184, 29], [184, 5], [194, 29], [194, 5], [224, 29], [224, 5], [226, 29], [226, 5], [153, 0], [161, 0], [167, 0], [172, 0]],
[[176, 0], [177, 0], [179, 0], [209, 0], [216, 0], [217, 0], [227, 0], [229, 0], [230, 0], [nil, 182], [nil, 183], [nil, 184], [nil, 185], [nil, 186], [nil, 187], [nil, 188]],
[[nil, 189], [nil, 190], [nil, 191], [nil, 192], [nil, 193], [nil, 194], [nil, 195], [nil, 196], [nil, 197], [nil, 198], [nil, 199], [nil, 200], [nil, 201], [nil, 202], [nil, 203], [nil, 204]],
[[199, 17], [199, 18], [199, 19], [199, 20], [199, 21], [199, 22], [199, 23], [199, 7], [207, 17], [207, 18], [207, 19], [207, 20], [207, 21], [207, 22], [207, 23], [207, 7]],
[[234, 17], [234, 18], [234, 19], [234, 20], [234, 21], [234, 22], [234, 23], [234, 7], [235, 17], [235, 18], [235, 19], [235, 20], [235, 21], [235, 22], [235, 23], [235, 7]],
[[192, 25], [192, 26], [192, 27], [192, 6], [193, 25], [193, 26], [193, 27], [193, 6], [200, 25], [200, 26], [200, 27], [200, 6], [201, 25], [201, 26], [201, 27], [201, 6]],
[[202, 25], [202, 26], [202, 27], [202, 6], [205, 25], [205, 26], [205, 27], [205, 6], [210, 25], [210, 26], [210, 27], [210, 6], [213, 25], [213, 26], [213, 27], [213, 6]],
[[218, 25], [218, 26], [218, 27], [218, 6], [219, 25], [219, 26], [219, 27], [219, 6], [238, 25], [238, 26], [238, 27], [238, 6], [240, 25], [240, 26], [240, 27], [240, 6]],
[[242, 25], [242, 26], [242, 27], [242, 6], [243, 25], [243, 26], [243, 27], [243, 6], [255, 25], [255, 26], [255, 27], [255, 6], [203, 29], [203, 5], [204, 29], [204, 5]],
[[211, 29], [211, 5], [212, 29], [212, 5], [214, 29], [214, 5], [221, 29], [221, 5], [222, 29], [222, 5], [223, 29], [223, 5], [241, 29], [241, 5], [244, 29], [244, 5]],
[[245, 29], [245, 5], [246, 29], [246, 5], [247, 29], [247, 5], [248, 29], [248, 5], [250, 29], [250, 5], [251, 29], [251, 5], [252, 29], [252, 5], [253, 29], [253, 5]],
[[254, 29], [254, 5], [2, 0], [3, 0], [4, 0], [5, 0], [6, 0], [7, 0], [8, 0], [11, 0], [12, 0], [14, 0], [15, 0], [16, 0], [17, 0], [18, 0]],
[[19, 0], [20, 0], [21, 0], [23, 0], [24, 0], [25, 0], [26, 0], [27, 0], [28, 0], [29, 0], [30, 0], [31, 0], [127, 0], [220, 0], [249, 0], [nil, 205]],
[[9, 17], [9, 18], [9, 19], [9, 20], [9, 21], [9, 22], [9, 23], [9, 7], [142, 17], [142, 18], [142, 19], [142, 20], [142, 21], [142, 22], [142, 23], [142, 7]],
[[144, 17], [144, 18], [144, 19], [144, 20], [144, 21], [144, 22], [144, 23], [144, 7], [145, 17], [145, 18], [145, 19], [145, 20], [145, 21], [145, 22], [145, 23], [145, 7]],
[[148, 17], [148, 18], [148, 19], [148, 20], [148, 21], [148, 22], [148, 23], [148, 7], [159, 17], [159, 18], [159, 19], [159, 20], [159, 21], [159, 22], [159, 23], [159, 7]],
[[171, 17], [171, 18], [171, 19], [171, 20], [171, 21], [171, 22], [171, 23], [171, 7], [206, 17], [206, 18], [206, 19], [206, 20], [206, 21], [206, 22], [206, 23], [206, 7]],
[[215, 17], [215, 18], [215, 19], [215, 20], [215, 21], [215, 22], [215, 23], [215, 7], [225, 17], [225, 18], [225, 19], [225, 20], [225, 21], [225, 22], [225, 23], [225, 7]],
[[236, 17], [236, 18], [236, 19], [236, 20], [236, 21], [236, 22], [236, 23], [236, 7], [237, 17], [237, 18], [237, 19], [237, 20], [237, 21], [237, 22], [237, 23], [237, 7]],
[[199, 25], [199, 26], [199, 27], [199, 6], [207, 25], [207, 26], [207, 27], [207, 6], [234, 25], [234, 26], [234, 27], [234, 6], [235, 25], [235, 26], [235, 27], [235, 6]],
[[192, 29], [192, 5], [193, 29], [193, 5], [200, 29], [200, 5], [201, 29], [201, 5], [202, 29], [202, 5], [205, 29], [205, 5], [210, 29], [210, 5], [213, 29], [213, 5]],
[[218, 29], [218, 5], [219, 29], [219, 5], [238, 29], [238, 5], [240, 29], [240, 5], [242, 29], [242, 5], [243, 29], [243, 5], [255, 29], [255, 5], [203, 0], [204, 0]],
[[211, 0], [212, 0], [214, 0], [221, 0], [222, 0], [223, 0], [241, 0], [244, 0], [245, 0], [246, 0], [247, 0], [248, 0], [250, 0], [251, 0], [252, 0], [253, 0]],
[[254, 0], [nil, 206], [nil, 207], [nil, 208], [nil, 209], [nil, 210], [nil, 211], [nil, 212], [nil, 213], [nil, 214], [nil, 215], [nil, 216], [nil, 217], [nil, 218], [nil, 219], [nil, 220]],
[[1, 17], [1, 18], [1, 19], [1, 20], [1, 21], [1, 22], [1, 23], [1, 7], [135, 17], [135, 18], [135, 19], [135, 20], [135, 21], [135, 22], [135, 23], [135, 7]],
[[137, 17], [137, 18], [137, 19], [137, 20], [137, 21], [137, 22], [137, 23], [137, 7], [138, 17], [138, 18], [138, 19], [138, 20], [138, 21], [138, 22], [138, 23], [138, 7]],
[[139, 17], [139, 18], [139, 19], [139, 20], [139, 21], [139, 22], [139, 23], [139, 7], [140, 17], [140, 18], [140, 19], [140, 20], [140, 21], [140, 22], [140, 23], [140, 7]],
[[141, 17], [141, 18], [141, 19], [141, 20], [141, 21], [141, 22], [141, 23], [141, 7], [143, 17], [143, 18], [143, 19], [143, 20], [143, 21], [143, 22], [143, 23], [143, 7]],
[[147, 17], [147, 18], [147, 19], [147, 20], [147, 21], [147, 22], [147, 23], [147, 7], [149, 17], [149, 18], [149, 19], [149, 20], [149, 21], [149, 22], [149, 23], [149, 7]],
[[150, 17], [150, 18], [150, 19], [150, 20], [150, 21], [150, 22], [150, 23], [150, 7], [151, 17], [151, 18], [151, 19], [151, 20], [151, 21], [151, 22], [151, 23], [151, 7]],
[[152, 17], [152, 18], [152, 19], [152, 20], [152, 21], [152, 22], [152, 23], [152, 7], [155, 17], [155, 18], [155, 19], [155, 20], [155, 21], [155, 22], [155, 23], [155, 7]],
[[157, 17], [157, 18], [157, 19], [157, 20], [157, 21], [157, 22], [157, 23], [157, 7], [158, 17], [158, 18], [158, 19], [158, 20], [158, 21], [158, 22], [158, 23], [158, 7]],
[[165, 17], [165, 18], [165, 19], [165, 20], [165, 21], [165, 22], [165, 23], [165, 7], [166, 17], [166, 18], [166, 19], [166, 20], [166, 21], [166, 22], [166, 23], [166, 7]],
[[168, 17], [168, 18], [168, 19], [168, 20], [168, 21], [168, 22], [168, 23], [168, 7], [174, 17], [174, 18], [174, 19], [174, 20], [174, 21], [174, 22], [174, 23], [174, 7]],
[[175, 17], [175, 18], [175, 19], [175, 20], [175, 21], [175, 22], [175, 23], [175, 7], [180, 17], [180, 18], [180, 19], [180, 20], [180, 21], [180, 22], [180, 23], [180, 7]],
[[182, 17], [182, 18], [182, 19], [182, 20], [182, 21], [182, 22], [182, 23], [182, 7], [183, 17], [183, 18], [183, 19], [183, 20], [183, 21], [183, 22], [183, 23], [183, 7]],
[[188, 17], [188, 18], [188, 19], [188, 20], [188, 21], [188, 22], [188, 23], [188, 7], [191, 17], [191, 18], [191, 19], [191, 20], [191, 21], [191, 22], [191, 23], [191, 7]],
[[197, 17], [197, 18], [197, 19], [197, 20], [197, 21], [197, 22], [197, 23], [197, 7], [231, 17], [231, 18], [231, 19], [231, 20], [231, 21], [231, 22], [231, 23], [231, 7]],
[[239, 17], [239, 18], [239, 19], [239, 20], [239, 21], [239, 22], [239, 23], [239, 7], [9, 25], [9, 26], [9, 27], [9, 6], [142, 25], [142, 26], [142, 27], [142, 6]],
[[144, 25], [144, 26], [144, 27], [144, 6], [145, 25], [145, 26], [145, 27], [145, 6], [148, 25], [148, 26], [148, 27], [148, 6], [159, 25], [159, 26], [159, 27], [159, 6]],
[[171, 25], [171, 26], [171, 27], [171, 6], [206, 25], [206, 26], [206, 27], [206, 6], [215, 25], [215, 26], [215, 27], [215, 6], [225, 25], [225, 26], [225, 27], [225, 6]],
[[236, 25], [236, 26], [236, 27], [236, 6], [237, 25], [237, 26], [237, 27], [237, 6], [199, 29], [199, 5], [207, 29], [207, 5], [234, 29], [234, 5], [235, 29], [235, 5]],
[[192, 0], [193, 0], [200, 0], [201, 0], [202, 0], [205, 0], [210, 0], [213, 0], [218, 0], [219, 0], [238, 0], [240, 0], [242, 0], [243, 0], [255, 0], [nil, 8]],
[[nil, 222], [nil, 223], [nil, 224], [nil, 225], [nil, 226], [nil, 227], [nil, 228], [nil, 229], [nil, 230], [nil, 231], [nil, 232], [nil, 233], [nil, 234], [nil, 235], [nil, 236], [nil, 237]],
[[129, 17], [129, 18], [129, 19], [129, 20], [129, 21], [129, 22], [129, 23], [129, 7], [132, 17], [132, 18], [132, 19], [132, 20], [132, 21], [132, 22], [132, 23], [132, 7]],
[[133, 17], [133, 18], [133, 19], [133, 20], [133, 21], [133, 22], [133, 23], [133, 7], [134, 17], [134, 18], [134, 19], [134, 20], [134, 21], [134, 22], [134, 23], [134, 7]],
[[136, 17], [136, 18], [136, 19], [136, 20], [136, 21], [136, 22], [136, 23], [136, 7], [146, 17], [146, 18], [146, 19], [146, 20], [146, 21], [146, 22], [146, 23], [146, 7]],
[[154, 17], [154, 18], [154, 19], [154, 20], [154, 21], [154, 22], [154, 23], [154, 7], [156, 17], [156, 18], [156, 19], [156, 20], [156, 21], [156, 22], [156, 23], [156, 7]],
[[160, 17], [160, 18], [160, 19], [160, 20], [160, 21], [160, 22], [160, 23], [160, 7], [163, 17], [163, 18], [163, 19], [163, 20], [163, 21], [163, 22], [163, 23], [163, 7]],
[[164, 17], [164, 18], [164, 19], [164, 20], [164, 21], [164, 22], [164, 23], [164, 7], [169, 17], [169, 18], [169, 19], [169, 20], [169, 21], [169, 22], [169, 23], [169, 7]],
[[170, 17], [170, 18], [170, 19], [170, 20], [170, 21], [170, 22], [170, 23], [170, 7], [173, 17], [173, 18], [173, 19], [173, 20], [173, 21], [173, 22], [173, 23], [173, 7]],
[[178, 17], [178, 18], [178, 19], [178, 20], [178, 21], [178, 22], [178, 23], [178, 7], [181, 17], [181, 18], [181, 19], [181, 20], [181, 21], [181, 22], [181, 23], [181, 7]],
[[185, 17], [185, 18], [185, 19], [185, 20], [185, 21], [185, 22], [185, 23], [185, 7], [186, 17], [186, 18], [186, 19], [186, 20], [186, 21], [186, 22], [186, 23], [186, 7]],
[[187, 17], [187, 18], [187, 19], [187, 20], [187, 21], [187, 22], [187, 23], [187, 7], [189, 17], [189, 18], [189, 19], [189, 20], [189, 21], [189, 22], [189, 23], [189, 7]],
[[190, 17], [190, 18], [190, 19], [190, 20], [190, 21], [190, 22], [190, 23], [190, 7], [196, 17], [196, 18], [196, 19], [196, 20], [196, 21], [196, 22], [196, 23], [196, 7]],
[[198, 17], [198, 18], [198, 19], [198, 20], [198, 21], [198, 22], [198, 23], [198, 7], [228, 17], [228, 18], [228, 19], [228, 20], [228, 21], [228, 22], [228, 23], [228, 7]],
[[232, 17], [232, 18], [232, 19], [232, 20], [232, 21], [232, 22], [232, 23], [232, 7], [233, 17], [233, 18], [233, 19], [233, 20], [233, 21], [233, 22], [233, 23], [233, 7]],
[[1, 25], [1, 26], [1, 27], [1, 6], [135, 25], [135, 26], [135, 27], [135, 6], [137, 25], [137, 26], [137, 27], [137, 6], [138, 25], [138, 26], [138, 27], [138, 6]],
[[139, 25], [139, 26], [139, 27], [139, 6], [140, 25], [140, 26], [140, 27], [140, 6], [141, 25], [141, 26], [141, 27], [141, 6], [143, 25], [143, 26], [143, 27], [143, 6]],
[[147, 25], [147, 26], [147, 27], [147, 6], [149, 25], [149, 26], [149, 27], [149, 6], [150, 25], [150, 26], [150, 27], [150, 6], [151, 25], [151, 26], [151, 27], [151, 6]],
[[152, 25], [152, 26], [152, 27], [152, 6], [155, 25], [155, 26], [155, 27], [155, 6], [157, 25], [157, 26], [157, 27], [157, 6], [158, 25], [158, 26], [158, 27], [158, 6]],
[[165, 25], [165, 26], [165, 27], [165, 6], [166, 25], [166, 26], [166, 27], [166, 6], [168, 25], [168, 26], [168, 27], [168, 6], [174, 25], [174, 26], [174, 27], [174, 6]],
[[175, 25], [175, 26], [175, 27], [175, 6], [180, 25], [180, 26], [180, 27], [180, 6], [182, 25], [182, 26], [182, 27], [182, 6], [183, 25], [183, 26], [183, 27], [183, 6]],
[[188, 25], [188, 26], [188, 27], [188, 6], [191, 25], [191, 26], [191, 27], [191, 6], [197, 25], [197, 26], [197, 27], [197, 6], [231, 25], [231, 26], [231, 27], [231, 6]],
[[239, 25], [239, 26], [239, 27], [239, 6], [9, 29], [9, 5], [142, 29], [142, 5], [144, 29], [144, 5], [145, 29], [145, 5], [148, 29], [148, 5], [159, 29], [159, 5]],
[[171, 29], [171, 5], [206, 29], [206, 5], [215, 29], [215, 5], [225, 29], [225, 5], [236, 29], [236, 5], [237, 29], [237, 5], [199, 0], [207, 0], [234, 0], [235, 0]],
[[nil, 238], [nil, 239], [nil, 240], [nil, 241], [nil, 242], [nil, 243], [nil, 244], [nil, 245], [nil, 246], [nil, 247], [nil, 248], [nil, 249], [nil, 250], [nil, 251], [nil, 252], [nil, 253]],
[[10, 25], [10, 26], [10, 27], [10, 6], [13, 25], [13, 26], [13, 27], [13, 6], [22, 25], [22, 26], [22, 27], [22, 6], [256, 25], [256, 26], [256, 27], [256, 6]],
[[2, 17], [2, 18], [2, 19], [2, 20], [2, 21], [2, 22], [2, 23], [2, 7], [3, 17], [3, 18], [3, 19], [3, 20], [3, 21], [3, 22], [3, 23], [3, 7]],
[[4, 17], [4, 18], [4, 19], [4, 20], [4, 21], [4, 22], [4, 23], [4, 7], [5, 17], [5, 18], [5, 19], [5, 20], [5, 21], [5, 22], [5, 23], [5, 7]],
[[6, 17], [6, 18], [6, 19], [6, 20], [6, 21], [6, 22], [6, 23], [6, 7], [7, 17], [7, 18], [7, 19], [7, 20], [7, 21], [7, 22], [7, 23], [7, 7]],
[[8, 17], [8, 18], [8, 19], [8, 20], [8, 21], [8, 22], [8, 23], [8, 7], [11, 17], [11, 18], [11, 19], [11, 20], [11, 21], [11, 22], [11, 23], [11, 7]],
[[12, 17], [12, 18], [12, 19], [12, 20], [12, 21], [12, 22], [12, 23], [12, 7], [14, 17], [14, 18], [14, 19], [14, 20], [14, 21], [14, 22], [14, 23], [14, 7]],
[[15, 17], [15, 18], [15, 19], [15, 20], [15, 21], [15, 22], [15, 23], [15, 7], [16, 17], [16, 18], [16, 19], [16, 20], [16, 21], [16, 22], [16, 23], [16, 7]],
[[17, 17], [17, 18], [17, 19], [17, 20], [17, 21], [17, 22], [17, 23], [17, 7], [18, 17], [18, 18], [18, 19], [18, 20], [18, 21], [18, 22], [18, 23], [18, 7]],
[[19, 17], [19, 18], [19, 19], [19, 20], [19, 21], [19, 22], [19, 23], [19, 7], [20, 17], [20, 18], [20, 19], [20, 20], [20, 21], [20, 22], [20, 23], [20, 7]],
[[21, 17], [21, 18], [21, 19], [21, 20], [21, 21], [21, 22], [21, 23], [21, 7], [23, 17], [23, 18], [23, 19], [23, 20], [23, 21], [23, 22], [23, 23], [23, 7]],
[[24, 17], [24, 18], [24, 19], [24, 20], [24, 21], [24, 22], [24, 23], [24, 7], [25, 17], [25, 18], [25, 19], [25, 20], [25, 21], [25, 22], [25, 23], [25, 7]],
[[26, 17], [26, 18], [26, 19], [26, 20], [26, 21], [26, 22], [26, 23], [26, 7], [27, 17], [27, 18], [27, 19], [27, 20], [27, 21], [27, 22], [27, 23], [27, 7]],
[[28, 17], [28, 18], [28, 19], [28, 20], [28, 21], [28, 22], [28, 23], [28, 7], [29, 17], [29, 18], [29, 19], [29, 20], [29, 21], [29, 22], [29, 23], [29, 7]],
[[30, 17], [30, 18], [30, 19], [30, 20], [30, 21], [30, 22], [30, 23], [30, 7], [31, 17], [31, 18], [31, 19], [31, 20], [31, 21], [31, 22], [31, 23], [31, 7]],
[[127, 17], [127, 18], [127, 19], [127, 20], [127, 21], [127, 22], [127, 23], [127, 7], [220, 17], [220, 18], [220, 19], [220, 20], [220, 21], [220, 22], [220, 23], [220, 7]],
[[249, 17], [249, 18], [249, 19], [249, 20], [249, 21], [249, 22], [249, 23], [249, 7], [10, 29], [10, 5], [13, 29], [13, 5], [22, 29], [22, 5], [256, 29], [256, 5]],
[[54, 25], [54, 26], [54, 27], [54, 6], [55, 25], [55, 26], [55, 27], [55, 6], [56, 25], [56, 26], [56, 27], [56, 6], [57, 25], [57, 26], [57, 27], [57, 6]],
[[211, 17], [211, 18], [211, 19], [211, 20], [211, 21], [211, 22], [211, 23], [211, 7], [212, 17], [212, 18], [212, 19], [212, 20], [212, 21], [212, 22], [212, 23], [212, 7]],
[[214, 17], [214, 18], [214, 19], [214, 20], [214, 21], [214, 22], [214, 23], [214, 7], [221, 17], [221, 18], [221, 19], [221, 20], [221, 21], [221, 22], [221, 23], [221, 7]],
[[222, 17], [222, 18], [222, 19], [222, 20], [222, 21], [222, 22], [222, 23], [222, 7], [223, 17], [223, 18], [223, 19], [223, 20], [223, 21], [223, 22], [223, 23], [223, 7]],
[[241, 17], [241, 18], [241, 19], [241, 20], [241, 21], [241, 22], [241, 23], [241, 7], [244, 17], [244, 18], [244, 19], [244, 20], [244, 21], [244, 22], [244, 23], [244, 7]],
[[245, 17], [245, 18], [245, 19], [245, 20], [245, 21], [245, 22], [245, 23], [245, 7], [246, 17], [246, 18], [246, 19], [246, 20], [246, 21], [246, 22], [246, 23], [246, 7]],
[[247, 17], [247, 18], [247, 19], [247, 20], [247, 21], [247, 22], [247, 23], [247, 7], [248, 17], [248, 18], [248, 19], [248, 20], [248, 21], [248, 22], [248, 23], [248, 7]],
[[250, 17], [250, 18], [250, 19], [250, 20], [250, 21], [250, 22], [250, 23], [250, 7], [251, 17], [251, 18], [251, 19], [251, 20], [251, 21], [251, 22], [251, 23], [251, 7]],
[[252, 17], [252, 18], [252, 19], [252, 20], [252, 21], [252, 22], [252, 23], [252, 7], [253, 17], [253, 18], [253, 19], [253, 20], [253, 21], [253, 22], [253, 23], [253, 7]],
[[254, 17], [254, 18], [254, 19], [254, 20], [254, 21], [254, 22], [254, 23], [254, 7], [2, 25], [2, 26], [2, 27], [2, 6], [3, 25], [3, 26], [3, 27], [3, 6]],
[[4, 25], [4, 26], [4, 27], [4, 6], [5, 25], [5, 26], [5, 27], [5, 6], [6, 25], [6, 26], [6, 27], [6, 6], [7, 25], [7, 26], [7, 27], [7, 6]],
[[8, 25], [8, 26], [8, 27], [8, 6], [11, 25], [11, 26], [11, 27], [11, 6], [12, 25], [12, 26], [12, 27], [12, 6], [14, 25], [14, 26], [14, 27], [14, 6]],
[[15, 25], [15, 26], [15, 27], [15, 6], [16, 25], [16, 26], [16, 27], [16, 6], [17, 25], [17, 26], [17, 27], [17, 6], [18, 25], [18, 26], [18, 27], [18, 6]],
[[19, 25], [19, 26], [19, 27], [19, 6], [20, 25], [20, 26], [20, 27], [20, 6], [21, 25], [21, 26], [21, 27], [21, 6], [23, 25], [23, 26], [23, 27], [23, 6]],
[[24, 25], [24, 26], [24, 27], [24, 6], [25, 25], [25, 26], [25, 27], [25, 6], [26, 25], [26, 26], [26, 27], [26, 6], [27, 25], [27, 26], [27, 27], [27, 6]],
[[28, 25], [28, 26], [28, 27], [28, 6], [29, 25], [29, 26], [29, 27], [29, 6], [30, 25], [30, 26], [30, 27], [30, 6], [31, 25], [31, 26], [31, 27], [31, 6]],
[[127, 25], [127, 26], [127, 27], [127, 6], [220, 25], [220, 26], [220, 27], [220, 6], [249, 25], [249, 26], [249, 27], [249, 6], [10, 0], [13, 0], [22, 0], [256, 0]],
[[192, 17], [192, 18], [192, 19], [192, 20], [192, 21], [192, 22], [192, 23], [192, 7], [193, 17], [193, 18], [193, 19], [193, 20], [193, 21], [193, 22], [193, 23], [193, 7]],
[[200, 17], [200, 18], [200, 19], [200, 20], [200, 21], [200, 22], [200, 23], [200, 7], [201, 17], [201, 18], [201, 19], [201, 20], [201, 21], [201, 22], [201, 23], [201, 7]],
[[202, 17], [202, 18], [202, 19], [202, 20], [202, 21], [202, 22], [202, 23], [202, 7], [205, 17], [205, 18], [205, 19], [205, 20], [205, 21], [205, 22], [205, 23], [205, 7]],
[[210, 17], [210, 18], [210, 19], [210, 20], [210, 21], [210, 22], [210, 23], [210, 7], [213, 17], [213, 18], [213, 19], [213, 20], [213, 21], [213, 22], [213, 23], [213, 7]],
[[218, 17], [218, 18], [218, 19], [218, 20], [218, 21], [218, 22], [218, 23], [218, 7], [219, 17], [219, 18], [219, 19], [219, 20], [219, 21], [219, 22], [219, 23], [219, 7]],
[[238, 17], [238, 18], [238, 19], [238, 20], [238, 21], [238, 22], [238, 23], [238, 7], [240, 17], [240, 18], [240, 19], [240, 20], [240, 21], [240, 22], [240, 23], [240, 7]],
[[242, 17], [242, 18], [242, 19], [242, 20], [242, 21], [242, 22], [242, 23], [242, 7], [243, 17], [243, 18], [243, 19], [243, 20], [243, 21], [243, 22], [243, 23], [243, 7]],
[[255, 17], [255, 18], [255, 19], [255, 20], [255, 21], [255, 22], [255, 23], [255, 7], [203, 25], [203, 26], [203, 27], [203, 6], [204, 25], [204, 26], [204, 27], [204, 6]],
[[211, 25], [211, 26], [211, 27], [211, 6], [212, 25], [212, 26], [212, 27], [212, 6], [214, 25], [214, 26], [214, 27], [214, 6], [221, 25], [221, 26], [221, 27], [221, 6]],
[[222, 25], [222, 26], [222, 27], [222, 6], [223, 25], [223, 26], [223, 27], [223, 6], [241, 25], [241, 26], [241, 27], [241, 6], [244, 25], [244, 26], [244, 27], [244, 6]],
[[245, 25], [245, 26], [245, 27], [245, 6], [246, 25], [246, 26], [246, 27], [246, 6], [247, 25], [247, 26], [247, 27], [247, 6], [248, 25], [248, 26], [248, 27], [248, 6]],
[[250, 25], [250, 26], [250, 27], [250, 6], [251, 25], [251, 26], [251, 27], [251, 6], [252, 25], [252, 26], [252, 27], [252, 6], [253, 25], [253, 26], [253, 27], [253, 6]],
[[254, 25], [254, 26], [254, 27], [254, 6], [2, 29], [2, 5], [3, 29], [3, 5], [4, 29], [4, 5], [5, 29], [5, 5], [6, 29], [6, 5], [7, 29], [7, 5]],
[[8, 29], [8, 5], [11, 29], [11, 5], [12, 29], [12, 5], [14, 29], [14, 5], [15, 29], [15, 5], [16, 29], [16, 5], [17, 29], [17, 5], [18, 29], [18, 5]],
[[19, 29], [19, 5], [20, 29], [20, 5], [21, 29], [21, 5], [23, 29], [23, 5], [24, 29], [24, 5], [25, 29], [25, 5], [26, 29], [26, 5], [27, 29], [27, 5]],
[[28, 29], [28, 5], [29, 29], [29, 5], [30, 29], [30, 5], [31, 29], [31, 5], [127, 29], [127, 5], [220, 29], [220, 5], [249, 29], [249, 5], [nil, 254], [nil, 255]],
[[10, 17], [10, 18], [10, 19], [10, 20], [10, 21], [10, 22], [10, 23], [10, 7], [13, 17], [13, 18], [13, 19], [13, 20], [13, 21], [13, 22], [13, 23], [13, 7]],
[[22, 17], [22, 18], [22, 19], [22, 20], [22, 21], [22, 22], [22, 23], [22, 7], [256, 17], [256, 18], [256, 19], [256, 20], [256, 21], [256, 22], [256, 23], [256, 7]],
].each { |arr| arr.each { |subarr| subarr.each(&:freeze) }.freeze }.freeze
end
end
end
ruby-http-2-0.11.0/lib/http/2/server.rb 0000664 0000000 0000000 00000010050 13775242373 0017420 0 ustar 00root root 0000000 0000000 require 'base64'
module HTTP2
# HTTP 2.0 server connection class that implements appropriate header
# compression / decompression algorithms and stream management logic.
#
# Your code is responsible for feeding request data to the server object,
# which in turn performs all of the necessary HTTP 2.0 decoding / encoding,
# state management, and the rest. A simple example:
#
# @example
# socket = YourTransport.new
#
# conn = HTTP2::Server.new
# conn.on(:stream) do |stream|
# ...
# end
#
# while bytes = socket.read
# conn << bytes
# end
#
class Server < Connection
# Initialize new HTTP 2.0 server object.
def initialize(**settings)
@stream_id = 2
@state = :waiting_magic
@local_role = :server
@remote_role = :client
super
end
# GET / HTTP/1.1
# Host: server.example.com
# Connection: Upgrade, HTTP2-Settings
# Upgrade: h2c
# HTTP2-Settings:
#
# Requests that contain a payload body MUST be sent in their entirety
# before the client can send HTTP/2 frames. This means that a large
# request can block the use of the connection until it is completely sent.
#
# If concurrency of an initial request with subsequent requests is
# important, an OPTIONS request can be used to perform the upgrade to
# HTTP/2, at the cost of an additional round trip.
#
# HTTP/1.1 101 Switching Protocols
# Connection: Upgrade
# Upgrade: h2c
#
# [ HTTP/2 connection ...
#
# - The first HTTP/2 frame sent by the server MUST be a server
# connection preface (Section 3.5) consisting of a SETTINGS frame.
# - Upon receiving the 101 response, the client MUST send a connection
# preface (Section 3.5), which includes a SETTINGS frame.
#
# The HTTP/1.1 request that is sent prior to upgrade is assigned a stream
# identifier of 1 (see Section 5.1.1) with default priority values
# (Section 5.3.5). Stream 1 is implicitly "half-closed" from the client
# toward the server (see Section 5.1), since the request is completed as
# an HTTP/1.1 request. After commencing the HTTP/2 connection, stream 1
# is used for the response.
#
def upgrade(settings, headers, body)
@h2c_upgrade = :start
# Pretend that we've received the preface
# - puts us into :waiting_connection_preface state
# - emits a SETTINGS frame to the client
receive(CONNECTION_PREFACE_MAGIC)
# Process received HTTP2-Settings payload
buf = HTTP2::Buffer.new Base64.urlsafe_decode64(settings.to_s)
header = @framer.common_header(
length: buf.bytesize,
type: :settings,
stream: 0,
flags: [],
)
buf.prepend(header)
receive(buf)
# Activate stream (id: 1) with on HTTP/1.1 request parameters
stream = activate_stream(id: 1)
emit(:stream, stream)
headers_frame = {
type: :headers,
stream: 1,
weight: DEFAULT_WEIGHT,
dependency: 0,
exclusive: false,
payload: headers,
}
if body.empty?
headers_frame.merge!(flags: [:end_stream])
stream << headers_frame
else
stream << headers_frame
stream << { type: :data, stream: 1, payload: body, flags: [:end_stream] }
end
# Mark h2c upgrade as finished
@h2c_upgrade = :finished
# Transition back to :waiting_magic and wait for client's preface
@state = :waiting_magic
end
private
# Handle locally initiated server-push event emitted by the stream.
#
# @param args [Array]
# @param callback [Proc]
def promise(*args, &callback)
parent, headers, flags = *args
promise = new_stream(parent: parent)
promise.send(
type: :push_promise,
flags: flags,
stream: parent.id,
promise_stream: promise.id,
payload: headers.to_a,
)
callback.call(promise)
end
end
end
ruby-http-2-0.11.0/lib/http/2/stream.rb 0000664 0000000 0000000 00000057567 13775242373 0017435 0 ustar 00root root 0000000 0000000 module HTTP2
# A single HTTP 2.0 connection can multiplex multiple streams in parallel:
# multiple requests and responses can be in flight simultaneously and stream
# data can be interleaved and prioritized.
#
# This class encapsulates all of the state, transition, flow-control, and
# error management as defined by the HTTP 2.0 specification. All you have
# to do is subscribe to appropriate events (marked with ":" prefix in
# diagram below) and provide your application logic to handle request
# and response processing.
#
# +--------+
# PP | | PP
# ,--------| idle |--------.
# / | | \
# v +--------+ v
# +----------+ | +----------+
# | | | H | |
# ,---|:reserved | | |:reserved |---.
# | | (local) | v | (remote) | |
# | +----------+ +--------+ +----------+ |
# | | :active | | :active | |
# | | ,-------|:active |-------. | |
# | | H / ES | | ES \ H | |
# | v v +--------+ v v |
# | +-----------+ | +-----------+ |
# | |:half_close| | |:half_close| |
# | | (remote) | | | (local) | |
# | +-----------+ | +-----------+ |
# | | v | |
# | | ES/R +--------+ ES/R | |
# | `----------->| |<-----------' |
# | R | :close | R |
# `-------------------->| |<--------------------'
# +--------+
class Stream
include FlowBuffer
include Emitter
include Error
# Stream ID (odd for client initiated streams, even otherwise).
attr_reader :id
# Stream state as defined by HTTP 2.0.
attr_reader :state
# Request parent stream of push stream.
attr_reader :parent
# Stream priority as set by initiator.
attr_reader :weight
attr_reader :dependency
# Size of current stream flow control window.
attr_reader :local_window
attr_reader :remote_window
alias window local_window
# Reason why connection was closed.
attr_reader :closed
# Initializes new stream.
#
# Note that you should never have to call this directly. To create a new
# client initiated stream, use Connection#new_stream. Similarly, Connection
# will emit new stream objects, when new stream frames are received.
#
# @param id [Integer]
# @param weight [Integer]
# @param dependency [Integer]
# @param exclusive [Boolean]
# @param window [Integer]
# @param parent [Stream]
# @param state [Symbol]
def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil, state: :idle)
@connection = connection
@id = id
@weight = weight
@dependency = dependency
process_priority(weight: weight, stream_dependency: dependency, exclusive: exclusive)
@local_window_max_size = connection.local_settings[:settings_initial_window_size]
@local_window = connection.local_settings[:settings_initial_window_size]
@remote_window = connection.remote_settings[:settings_initial_window_size]
@parent = parent
@state = state
@error = false
@closed = false
@send_buffer = []
on(:window) { |v| @remote_window = v }
on(:local_window) { |v| @local_window_max_size = @local_window = v }
end
def closed?
@state == :closed
end
# Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
#
# @param frame [Hash]
def receive(frame)
transition(frame, false)
case frame[:type]
when :data
update_local_window(frame)
# Emit DATA frame
emit(:data, frame[:payload]) unless frame[:ignore]
calculate_window_update(@local_window_max_size)
when :headers
emit(:headers, frame[:payload]) unless frame[:ignore]
when :push_promise
emit(:promise_headers, frame[:payload]) unless frame[:ignore]
when :priority
process_priority(frame)
when :window_update
process_window_update(frame)
when :altsvc
# 4. The ALTSVC HTTP/2 Frame
# An ALTSVC frame on a
# stream other than stream 0 containing non-empty "Origin" information
# is invalid and MUST be ignored.
if !frame[:origin] || frame[:origin].empty?
emit(frame[:type], frame)
end
when :blocked
emit(frame[:type], frame)
end
complete_transition(frame)
end
alias << receive
# Processes outgoing HTTP 2.0 frames. Data frames may be automatically
# split and buffered based on maximum frame size and current stream flow
# control window size.
#
# @param frame [Hash]
def send(frame)
process_priority(frame) if frame[:type] == :priority
case frame[:type]
when :data
# @remote_window is maintained in send_data
send_data(frame)
when :window_update
manage_state(frame) do
@local_window += frame[:increment]
emit(:frame, frame)
end
else
manage_state(frame) do
emit(:frame, frame)
end
end
end
# Sends a HEADERS frame containing HTTP response headers.
# All pseudo-header fields MUST appear in the header block before regular header fields.
#
# @param headers [Array or Hash] Array of key-value pairs or Hash
# @param end_headers [Boolean] indicates that no more headers will be sent
# @param end_stream [Boolean] indicates that no payload will be sent
def headers(headers, end_headers: true, end_stream: false)
flags = []
flags << :end_headers if end_headers
flags << :end_stream if end_stream
send(type: :headers, flags: flags, payload: headers)
end
def promise(headers, end_headers: true, &block)
fail ArgumentError, 'must provide callback' unless block_given?
flags = end_headers ? [:end_headers] : []
emit(:promise, self, headers, flags, &block)
end
# Sends a PRIORITY frame with new stream priority value (can only be
# performed by the client).
#
# @param weight [Integer] new stream weight value
# @param dependency [Integer] new stream dependency stream
def reprioritize(weight: 16, dependency: 0, exclusive: false)
stream_error if @id.even?
send(type: :priority, weight: weight, stream_dependency: dependency, exclusive: exclusive)
end
# Sends DATA frame containing response payload.
#
# @param payload [String]
# @param end_stream [Boolean] indicates last response DATA frame
def data(payload, end_stream: true)
# Split data according to each frame is smaller enough
# TODO: consider padding?
max_size = @connection.remote_settings[:settings_max_frame_size]
if payload.bytesize > max_size
payload = chunk_data(payload, max_size) do |chunk|
send(type: :data, flags: [], payload: chunk)
end
end
flags = []
flags << :end_stream if end_stream
send(type: :data, flags: flags, payload: payload)
end
# Chunk data into max_size, yield each chunk, then return final chunk
#
def chunk_data(payload, max_size)
total = payload.bytesize
cursor = 0
while (total - cursor) > max_size
yield payload.byteslice(cursor, max_size)
cursor += max_size
end
payload.byteslice(cursor, total - cursor)
end
# Sends a RST_STREAM frame which closes current stream - this does not
# close the underlying connection.
#
# @param error [:Symbol] optional reason why stream was closed
def close(error = :stream_closed)
send(type: :rst_stream, error: error)
end
# Sends a RST_STREAM indicating that the stream is no longer needed.
def cancel
send(type: :rst_stream, error: :cancel)
end
# Sends a RST_STREAM indicating that the stream has been refused prior
# to performing any application processing.
def refuse
send(type: :rst_stream, error: :refused_stream)
end
# Sends a WINDOW_UPDATE frame to the peer.
#
# @param increment [Integer]
def window_update(increment)
# emit stream-level WINDOW_UPDATE unless stream is closed
return if @state == :closed || @state == :remote_closed
send(type: :window_update, increment: increment)
end
private
# HTTP 2.0 Stream States
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1
#
# +--------+
# send PP | | recv PP
# ,--------| idle |--------.
# / | | \
# v +--------+ v
# +----------+ | +----------+
# | | | send H/ | |
# ,-----| reserved | | recv H | reserved |-----.
# | | (local) | | | (remote) | |
# | +----------+ v +----------+ |
# | | +--------+ | |
# | | recv ES | | send ES | |
# | send H | ,-------| open |-------. | recv H |
# | | / | | \ | |
# | v v +--------+ v v |
# | +----------+ | +----------+ |
# | | half | | | half | |
# | | closed | | send R/ | closed | |
# | | (remote) | | recv R | (local) | |
# | +----------+ | +----------+ |
# | | | | |
# | | send ES/ | recv ES/ | |
# | | send R/ v send R/ | |
# | | recv R +--------+ recv R | |
# | send R/ `----------->| |<-----------' send R/ |
# | recv R | closed | recv R |
# `---------------------->| |<----------------------'
# +--------+
#
def transition(frame, sending)
case @state
# All streams start in the "idle" state. In this state, no frames
# have been exchanged.
# The following transitions are valid from this state:
# * Sending or receiving a HEADERS frame causes the stream to
# become "open". The stream identifier is selected as described
# in Section 5.1.1. The same HEADERS frame can also cause a
# stream to immediately become "half closed".
# * Sending a PUSH_PROMISE frame reserves an idle stream for later
# use. The stream state for the reserved stream transitions to
# "reserved (local)".
# * Receiving a PUSH_PROMISE frame reserves an idle stream for
# later use. The stream state for the reserved stream
# transitions to "reserved (remote)".
# Receiving any frames other than HEADERS, PUSH_PROMISE or PRIORITY
# on a stream in this state MUST be treated as a connection error
# (Section 5.4.1) of type PROTOCOL_ERROR.
when :idle
if sending
case frame[:type]
when :push_promise then event(:reserved_local)
when :headers
if end_stream?(frame)
event(:half_closed_local)
else
event(:open)
end
when :rst_stream then event(:local_rst)
when :priority then process_priority(frame)
else stream_error
end
else
case frame[:type]
when :push_promise then event(:reserved_remote)
when :headers
if end_stream?(frame)
event(:half_closed_remote)
else
event(:open)
end
when :priority then process_priority(frame)
else stream_error(:protocol_error)
end
end
# A stream in the "reserved (local)" state is one that has been
# promised by sending a PUSH_PROMISE frame. A PUSH_PROMISE frame
# reserves an idle stream by associating the stream with an open
# stream that was initiated by the remote peer (see Section 8.2).
# In this state, only the following transitions are possible:
# * The endpoint can send a HEADERS frame. This causes the stream
# to open in a "half closed (remote)" state.
# * Either endpoint can send a RST_STREAM frame to cause the stream
# to become "closed". This releases the stream reservation.
# An endpoint MUST NOT send any type of frame other than HEADERS,
# RST_STREAM, or PRIORITY in this state.
# A PRIORITY or WINDOW_UPDATE frame MAY be received in this state.
# Receiving any type of frame other than RST_STREAM, PRIORITY or
# WINDOW_UPDATE on a stream in this state MUST be treated as a
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
when :reserved_local
@state = if sending
case frame[:type]
when :headers then event(:half_closed_remote)
when :rst_stream then event(:local_rst)
else stream_error
end
else
case frame[:type]
when :rst_stream then event(:remote_rst)
when :priority, :window_update then @state
else stream_error
end
end
# A stream in the "reserved (remote)" state has been reserved by a
# remote peer.
# In this state, only the following transitions are possible:
# * Receiving a HEADERS frame causes the stream to transition to
# "half closed (local)".
# * Either endpoint can send a RST_STREAM frame to cause the stream
# to become "closed". This releases the stream reservation.
# An endpoint MAY send a PRIORITY frame in this state to
# reprioritize the reserved stream. An endpoint MUST NOT send any
# type of frame other than RST_STREAM, WINDOW_UPDATE, or PRIORITY in
# this state.
# Receiving any type of frame other than HEADERS, RST_STREAM or
# PRIORITY on a stream in this state MUST be treated as a connection
# error (Section 5.4.1) of type PROTOCOL_ERROR.
when :reserved_remote
@state = if sending
case frame[:type]
when :rst_stream then event(:local_rst)
when :priority, :window_update then @state
else stream_error
end
else
case frame[:type]
when :headers then event(:half_closed_local)
when :rst_stream then event(:remote_rst)
else stream_error
end
end
# A stream in the "open" state may be used by both peers to send
# frames of any type. In this state, sending peers observe
# advertised stream level flow control limits (Section 5.2).
# From this state either endpoint can send a frame with an
# END_STREAM flag set, which causes the stream to transition into
# one of the "half closed" states: an endpoint sending an END_STREAM
# flag causes the stream state to become "half closed (local)"; an
# endpoint receiving an END_STREAM flag causes the stream state to
# become "half closed (remote)".
# Either endpoint can send a RST_STREAM frame from this state,
# causing it to transition immediately to "closed".
when :open
if sending
case frame[:type]
when :data, :headers, :continuation
event(:half_closed_local) if end_stream?(frame)
when :rst_stream then event(:local_rst)
end
else
case frame[:type]
when :data, :headers, :continuation
event(:half_closed_remote) if end_stream?(frame)
when :rst_stream then event(:remote_rst)
end
end
# A stream that is in the "half closed (local)" state cannot be used
# for sending frames. Only WINDOW_UPDATE, PRIORITY and RST_STREAM
# frames can be sent in this state.
# A stream transitions from this state to "closed" when a frame that
# contains an END_STREAM flag is received, or when either peer sends
# a RST_STREAM frame.
# A receiver can ignore WINDOW_UPDATE frames in this state, which
# might arrive for a short period after a frame bearing the
# END_STREAM flag is sent.
# PRIORITY frames received in this state are used to reprioritize
# streams that depend on the current stream.
when :half_closed_local
if sending
case frame[:type]
when :rst_stream
event(:local_rst)
when :priority
process_priority(frame)
when :window_update
# nop here
else
stream_error
end
else
case frame[:type]
when :data, :headers, :continuation
event(:remote_closed) if end_stream?(frame)
when :rst_stream then event(:remote_rst)
when :priority
process_priority(frame)
when :window_update
# nop here
end
end
# A stream that is "half closed (remote)" is no longer being used by
# the peer to send frames. In this state, an endpoint is no longer
# obligated to maintain a receiver flow control window if it
# performs flow control.
# If an endpoint receives additional frames for a stream that is in
# this state, other than WINDOW_UPDATE, PRIORITY or RST_STREAM, it
# MUST respond with a stream error (Section 5.4.2) of type
# STREAM_CLOSED.
# A stream that is "half closed (remote)" can be used by the
# endpoint to send frames of any type. In this state, the endpoint
# continues to observe advertised stream level flow control limits
# (Section 5.2).
# A stream can transition from this state to "closed" by sending a
# frame that contains an END_STREAM flag, or when either peer sends
# a RST_STREAM frame.
when :half_closed_remote
if sending
case frame[:type]
when :data, :headers, :continuation
event(:local_closed) if end_stream?(frame)
when :rst_stream then event(:local_rst)
end
else
case frame[:type]
when :rst_stream then event(:remote_rst)
when :priority
process_priority(frame)
when :window_update
# nop
else
stream_error(:stream_closed)
end
end
# The "closed" state is the terminal state.
# An endpoint MUST NOT send frames other than PRIORITY on a closed
# stream. An endpoint that receives any frame other than PRIORITY
# after receiving a RST_STREAM MUST treat that as a stream error
# (Section 5.4.2) of type STREAM_CLOSED. Similarly, an endpoint
# that receives any frames after receiving a frame with the
# END_STREAM flag set MUST treat that as a connection error
# (Section 5.4.1) of type STREAM_CLOSED, unless the frame is
# permitted as described below.
# WINDOW_UPDATE or RST_STREAM frames can be received in this state
# for a short period after a DATA or HEADERS frame containing an
# END_STREAM flag is sent. Until the remote peer receives and
# processes RST_STREAM or the frame bearing the END_STREAM flag, it
# might send frames of these types. Endpoints MUST ignore
# WINDOW_UPDATE or RST_STREAM frames received in this state, though
# endpoints MAY choose to treat frames that arrive a significant
# time after sending END_STREAM as a connection error
# (Section 5.4.1) of type PROTOCOL_ERROR.
# PRIORITY frames can be sent on closed streams to prioritize
# streams that are dependent on the closed stream. Endpoints SHOULD
# process PRIORITY frames, though they can be ignored if the stream
# has been removed from the dependency tree (see Section 5.3.4).
# If this state is reached as a result of sending a RST_STREAM
# frame, the peer that receives the RST_STREAM might have already
# sent - or enqueued for sending - frames on the stream that cannot
# be withdrawn. An endpoint MUST ignore frames that it receives on
# closed streams after it has sent a RST_STREAM frame. An endpoint
# MAY choose to limit the period over which it ignores frames and
# treat frames that arrive after this time as being in error.
# Flow controlled frames (i.e., DATA) received after sending
# RST_STREAM are counted toward the connection flow control window.
# Even though these frames might be ignored, because they are sent
# before the sender receives the RST_STREAM, the sender will
# consider the frames to count against the flow control window.
# An endpoint might receive a PUSH_PROMISE frame after it sends
# RST_STREAM. PUSH_PROMISE causes a stream to become "reserved"
# even if the associated stream has been reset. Therefore, a
# RST_STREAM is needed to close an unwanted promised stream.
when :closed
if sending
case frame[:type]
when :rst_stream then # ignore
when :priority then
process_priority(frame)
else
stream_error(:stream_closed) unless (frame[:type] == :rst_stream)
end
else
if frame[:type] == :priority
process_priority(frame)
else
case @closed
when :remote_rst, :remote_closed
case frame[:type]
when :rst_stream, :window_update # nop here
else
stream_error(:stream_closed)
end
when :local_rst, :local_closed
frame[:ignore] = true if frame[:type] != :window_update
end
end
end
end
end
def event(newstate)
case newstate
when :open
@state = newstate
emit(:active)
when :reserved_local, :reserved_remote
@state = newstate
emit(:reserved)
when :half_closed_local, :half_closed_remote
@closed = newstate
emit(:active) unless @state == :open
@state = :half_closing
when :local_closed, :remote_closed, :local_rst, :remote_rst
@closed = newstate
@state = :closing
end
@state
end
def complete_transition(frame)
case @state
when :closing
@state = :closed
emit(:close, frame[:error])
when :half_closing
@state = @closed
emit(:half_close)
end
end
def process_priority(frame)
@weight = frame[:weight]
@dependency = frame[:stream_dependency]
emit(
:priority,
weight: frame[:weight],
dependency: frame[:stream_dependency],
exclusive: frame[:exclusive],
)
# TODO: implement dependency tree housekeeping
# Latest draft defines a fairly complex priority control.
# See https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.3
# We currently have no prioritization among streams.
# We should add code here.
end
def end_stream?(frame)
case frame[:type]
when :data, :headers, :continuation
frame[:flags].include?(:end_stream)
else false
end
end
def stream_error(error = :internal_error, msg: nil)
@error = error
close(error) if @state != :closed
klass = error.to_s.split('_').map(&:capitalize).join
fail Error.const_get(klass), msg
end
alias error stream_error
def manage_state(frame)
transition(frame, true)
frame[:stream] ||= @id
yield
complete_transition(frame)
end
end
end
ruby-http-2-0.11.0/lib/http/2/version.rb 0000664 0000000 0000000 00000000055 13775242373 0017603 0 ustar 00root root 0000000 0000000 module HTTP2
VERSION = '0.11.0'.freeze
end
ruby-http-2-0.11.0/lib/tasks/ 0000775 0000000 0000000 00000000000 13775242373 0015576 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/lib/tasks/generate_huffman_table.rb 0000664 0000000 0000000 00000010101 13775242373 0022561 0 ustar 00root root 0000000 0000000 desc 'Generate Huffman precompiled table in huffman_statemachine.rb'
task :generate_table do
HuffmanTable::Node.generate_state_table
end
require_relative '../http/2/huffman'
# @private
module HuffmanTable
BITS_AT_ONCE = HTTP2::Header::Huffman::BITS_AT_ONCE
EOS = 256
class Node
attr_accessor :next, :emit, :final, :depth
attr_accessor :transitions
attr_accessor :id
@@id = 0 # rubocop:disable Style/ClassVars
def initialize(depth)
@next = [nil, nil]
@id = @@id
@@id += 1 # rubocop:disable Style/ClassVars
@final = false
@depth = depth
end
def add(code, len, chr)
self.final = true if chr == EOS && @depth <= 7
if len.zero?
@emit = chr
else
bit = (code & (1 << (len - 1))).zero? ? 0 : 1
node = @next[bit] ||= Node.new(@depth + 1)
node.add(code, len - 1, chr)
end
end
class Transition
attr_accessor :emit, :node
def initialize(emit, node)
@emit = emit
@node = node
end
end
def self.generate_tree
@root = new(0)
HTTP2::Header::Huffman::CODES.each_with_index do |c, chr|
code, len = c
@root.add(code, len, chr)
end
puts "#{@@id} nodes"
@root
end
def self.generate_machine
generate_tree
togo = Set[@root]
@states = Set[@root]
until togo.empty?
node = togo.first
togo.delete(node)
next if node.transitions
node.transitions = Array[1 << BITS_AT_ONCE]
(1 << BITS_AT_ONCE).times do |input|
n = node
emit = ''
(BITS_AT_ONCE - 1).downto(0) do |i|
bit = (input & (1 << i)).zero? ? 0 : 1
n = n.next[bit]
next unless n.emit
if n.emit == EOS
emit = EOS # cause error on decoding
else
emit << n.emit.chr(Encoding::BINARY) unless emit == EOS
end
n = @root
end
node.transitions[input] = Transition.new(emit, n)
togo << n
@states << n
end
end
puts "#{@states.size} states"
@root
end
def self.generate_state_table
generate_machine
state_id = {}
id_state = {}
state_id[@root] = 0
id_state[0] = @root
max_final = 0
id = 1
(@states - [@root]).sort_by { |s| s.final ? 0 : 1 }.each do |s|
state_id[s] = id
id_state[id] = s
max_final = id if s.final
id += 1
end
File.open(File.expand_path('../http/2/huffman_statemachine.rb', File.dirname(__FILE__)), 'w') do |f|
f.print < 1
emit = bytes.first
end
"[#{emit.inspect}, #{state_id.fetch(transition.node)}]"
end.join(', ')
f.print(string)
f.print "],\n"
end
f.print <> 4), b & 0xf] }
until nibbles.empty?
nb = nibbles.shift
t = n.transitions[nb]
emit << t.emit
n = t.node
end
unless n.final && nibbles.all? { |x| x == 0xf }
puts "len = #{emit.size} n.final = #{n.final} nibbles = #{nibbles}"
end
emit
end
end
end
ruby-http-2-0.11.0/spec/ 0000775 0000000 0000000 00000000000 13775242373 0014635 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/spec/buffer_spec.rb 0000664 0000000 0000000 00000001254 13775242373 0017447 0 ustar 00root root 0000000 0000000 require 'helper'
RSpec.describe HTTP2::Buffer do
let(:b) { Buffer.new('émalgré') }
it 'should force 8-bit encoding' do
expect(b.encoding.to_s).to eq 'ASCII-8BIT'
end
it 'should force 8-bit encoding when adding data' do
b << 'émalgré'
expect(b.encoding.to_s).to eq 'ASCII-8BIT'
b.prepend('émalgré')
expect(b.encoding.to_s).to eq 'ASCII-8BIT'
end
it 'should return bytesize of the buffer' do
expect(b.size).to eq 9
end
it 'should read single byte at a time' do
9.times { expect(b.read(1)).not_to be_nil }
end
it 'should unpack an unsigned 32-bit int' do
expect(Buffer.new([256].pack('N')).read_uint32).to eq 256
end
end
ruby-http-2-0.11.0/spec/client_spec.rb 0000664 0000000 0000000 00000012420 13775242373 0017451 0 ustar 00root root 0000000 0000000 require 'helper'
RSpec.describe HTTP2::Client do
include FrameHelpers
before(:each) do
@client = Client.new
end
let(:f) { Framer.new }
context 'initialization and settings' do
it 'should return odd stream IDs' do
expect(@client.new_stream.id).not_to be_even
end
it 'should emit connection header and SETTINGS on new client connection' do
frames = []
@client.on(:frame) { |bytes| frames << bytes }
@client.ping('12345678')
expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
expect(f.parse(frames[1])[:type]).to eq :settings
end
it 'should initialize client with custom connection settings' do
frames = []
@client = Client.new(settings_max_concurrent_streams: 200)
@client.on(:frame) { |bytes| frames << bytes }
@client.ping('12345678')
frame = f.parse(frames[1])
expect(frame[:type]).to eq :settings
expect(frame[:payload]).to include([:settings_max_concurrent_streams, 200])
end
it 'should initialize client when receiving server settings before sending ack' do
frames = []
@client.on(:frame) { |bytes| frames << bytes }
@client << f.generate(settings_frame)
expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
expect(f.parse(frames[1])[:type]).to eq :settings
ack_frame = f.parse(frames[2])
expect(ack_frame[:type]).to eq :settings
expect(ack_frame[:flags]).to include(:ack)
end
end
context 'upgrade' do
it 'fails when client has already created streams' do
@client.new_stream
expect { @client.upgrade }.to raise_error(HTTP2::Error::ProtocolError)
end
it 'sends the preface' do
expect(@client).to receive(:send_connection_preface)
@client.upgrade
end
it 'initializes the first stream in the half-closed state' do
stream = @client.upgrade
expect(stream.state).to be(:half_closed_local)
end
end
context 'push' do
it 'should disallow client initiated push' do
expect do
@client.promise({}) {}
end.to raise_error(NoMethodError)
end
it 'should raise error on PUSH_PROMISE against stream 0' do
expect do
@client << set_stream_id(f.generate(push_promise_frame), 0)
end.to raise_error(ProtocolError)
end
it 'should raise error on PUSH_PROMISE against bogus stream' do
expect do
@client << set_stream_id(f.generate(push_promise_frame), 31_415)
end.to raise_error(ProtocolError)
end
it 'should raise error on PUSH_PROMISE against non-idle stream' do
expect do
s = @client.new_stream
s.send headers_frame
@client << set_stream_id(f.generate(push_promise_frame), s.id)
@client << set_stream_id(f.generate(push_promise_frame), s.id)
end.to raise_error(ProtocolError)
end
it 'should emit stream object for received PUSH_PROMISE' do
s = @client.new_stream
s.send headers_frame
promise = nil
@client.on(:promise) { |stream| promise = stream }
@client << set_stream_id(f.generate(push_promise_frame), s.id)
expect(promise.id).to eq 2
expect(promise.state).to eq :reserved_remote
end
it 'should emit promise headers for received PUSH_PROMISE' do
header = nil
s = @client.new_stream
s.send headers_frame
@client.on(:promise) do |stream|
stream.on(:promise_headers) do |h|
header = h
end
end
@client << set_stream_id(f.generate(push_promise_frame), s.id)
expect(header).to be_a(Array)
# expect(header).to eq([%w(a b)])
end
it 'should auto RST_STREAM promises against locally-RST stream' do
s = @client.new_stream
s.send headers_frame
s.close
allow(@client).to receive(:send)
expect(@client).to receive(:send) do |frame|
expect(frame[:type]).to eq :rst_stream
expect(frame[:stream]).to eq 2
end
@client << set_stream_id(f.generate(push_promise_frame), s.id)
end
end
context 'alt-svc' do
context 'received in the connection' do
it 'should emit :altsvc when receiving one' do
@client << f.generate(settings_frame)
frame = nil
@client.on(:altsvc) do |f|
frame = f
end
@client << f.generate(altsvc_frame)
expect(frame).to be_a(Hash)
end
it 'should not emit :altsvc when the frame when contains no host' do
@client << f.generate(settings_frame)
frame = nil
@client.on(:altsvc) do |f|
frame = f
end
@client << f.generate(altsvc_frame.merge(origin: nil))
expect(frame).to be_nil
end
end
context 'received in a stream' do
it 'should emit :altsvc' do
s = @client.new_stream
s.send headers_frame
s.close
frame = nil
s.on(:altsvc) { |f| frame = f }
@client << set_stream_id(f.generate(altsvc_frame.merge(origin: nil)), s.id)
expect(frame).to be_a(Hash)
end
it 'should not emit :alt_svc when the frame when contains a origin' do
s = @client.new_stream
s.send headers_frame
s.close
frame = nil
s.on(:altsvc) { |f| frame = f }
@client << set_stream_id(f.generate(altsvc_frame), s.id)
expect(frame).to be_nil
end
end
end
end
ruby-http-2-0.11.0/spec/compressor_spec.rb 0000664 0000000 0000000 00000054105 13775242373 0020375 0 ustar 00root root 0000000 0000000 require 'helper'
RSpec.describe HTTP2::Header do
let(:c) { Compressor.new }
let(:d) { Decompressor.new }
context 'literal representation' do
context 'integer' do
it 'should encode 10 using a 5-bit prefix' do
buf = c.integer(10, 5)
expect(buf).to eq [10].pack('C')
expect(d.integer(Buffer.new(buf), 5)).to eq 10
end
it 'should encode 10 using a 0-bit prefix' do
buf = c.integer(10, 0)
expect(buf).to eq [10].pack('C')
expect(d.integer(Buffer.new(buf), 0)).to eq 10
end
it 'should encode 1337 using a 5-bit prefix' do
buf = c.integer(1337, 5)
expect(buf).to eq [31, 128 + 26, 10].pack('C*')
expect(d.integer(Buffer.new(buf), 5)).to eq 1337
end
it 'should encode 1337 using a 0-bit prefix' do
buf = c.integer(1337, 0)
expect(buf).to eq [128 + 57, 10].pack('C*')
expect(d.integer(Buffer.new(buf), 0)).to eq 1337
end
end
context 'string' do
[
['with huffman', :always, 0x80],
['without huffman', :never, 0],
].each do |desc, option, msb|
let(:trailer) { 'trailer' }
[
['ascii codepoints', 'abcdefghij'],
['utf-8 codepoints', 'éáűőúöüó€'],
['long utf-8 strings', 'éáűőúöüó€' * 100],
].each do |datatype, plain|
it "should handle #{datatype} #{desc}" do
# NOTE: don't put this new in before{} because of test case shuffling
@c = Compressor.new(huffman: option)
str = @c.string(plain)
expect(str.getbyte(0) & 0x80).to eq msb
buf = Buffer.new(str + trailer)
expect(d.string(buf)).to eq plain
expect(buf).to eq trailer
end
end
end
context 'choosing shorter representation' do
[['日本語', :plain],
['200', :huffman],
['xq', :plain], # prefer plain if equal size
].each do |string, choice|
before { @c = Compressor.new(huffman: :shorter) }
it "should return #{choice} representation" do
wire = @c.string(string)
expect(wire.getbyte(0) & 0x80).to eq(choice == :plain ? 0 : 0x80)
end
end
end
end
end
context 'header representation' do
it 'should handle indexed representation' do
h = { name: 10, type: :indexed }
wire = c.header(h)
expect(wire.readbyte(0) & 0x80).to eq 0x80
expect(wire.readbyte(0) & 0x7f).to eq h[:name] + 1
expect(d.header(wire)).to eq h
end
it 'should raise when decoding indexed representation with index zero' do
h = { name: 10, type: :indexed }
wire = c.header(h)
wire[0] = 0x80.chr(Encoding::BINARY)
expect { d.header(wire) }.to raise_error CompressionError
end
context 'literal w/o indexing representation' do
it 'should handle indexed header' do
h = { name: 10, value: 'my-value', type: :noindex }
wire = c.header(h)
expect(wire.readbyte(0) & 0xf0).to eq 0x0
expect(wire.readbyte(0) & 0x0f).to eq h[:name] + 1
expect(d.header(wire)).to eq h
end
it 'should handle literal header' do
h = { name: 'x-custom', value: 'my-value', type: :noindex }
wire = c.header(h)
expect(wire.readbyte(0) & 0xf0).to eq 0x0
expect(wire.readbyte(0) & 0x0f).to eq 0
expect(d.header(wire)).to eq h
end
end
context 'literal w/ incremental indexing' do
it 'should handle indexed header' do
h = { name: 10, value: 'my-value', type: :incremental }
wire = c.header(h)
expect(wire.readbyte(0) & 0xc0).to eq 0x40
expect(wire.readbyte(0) & 0x3f).to eq h[:name] + 1
expect(d.header(wire)).to eq h
end
it 'should handle literal header' do
h = { name: 'x-custom', value: 'my-value', type: :incremental }
wire = c.header(h)
expect(wire.readbyte(0) & 0xc0).to eq 0x40
expect(wire.readbyte(0) & 0x3f).to eq 0
expect(d.header(wire)).to eq h
end
end
context 'literal never indexed' do
it 'should handle indexed header' do
h = { name: 10, value: 'my-value', type: :neverindexed }
wire = c.header(h)
expect(wire.readbyte(0) & 0xf0).to eq 0x10
expect(wire.readbyte(0) & 0x0f).to eq h[:name] + 1
expect(d.header(wire)).to eq h
end
it 'should handle literal header' do
h = { name: 'x-custom', value: 'my-value', type: :neverindexed }
wire = c.header(h)
expect(wire.readbyte(0) & 0xf0).to eq 0x10
expect(wire.readbyte(0) & 0x0f).to eq 0
expect(d.header(wire)).to eq h
end
end
end
context 'shared compression context' do
before(:each) { @cc = EncodingContext.new }
it 'should be initialized with empty headers' do
cc = EncodingContext.new
expect(cc.table).to be_empty
end
context 'processing' do
[
['no indexing', :noindex],
['never indexed', :neverindexed],
].each do |desc, type|
context "#{desc}" do
it 'should process indexed header with literal value' do
original_table = @cc.table.dup
emit = @cc.process(name: 4, value: '/path', type: type)
expect(emit).to eq [':path', '/path']
expect(@cc.table).to eq original_table
end
it 'should process literal header with literal value' do
original_table = @cc.table.dup
emit = @cc.process(name: 'x-custom', value: 'random', type: type)
expect(emit).to eq ['x-custom', 'random']
expect(@cc.table).to eq original_table
end
end
end
context 'incremental indexing' do
it 'should process indexed header with literal value' do
original_table = @cc.table.dup
emit = @cc.process(name: 4, value: '/path', type: :incremental)
expect(emit).to eq [':path', '/path']
expect(@cc.table - original_table).to eq [[':path', '/path']]
end
it 'should process literal header with literal value' do
original_table = @cc.table.dup
@cc.process(name: 'x-custom', value: 'random', type: :incremental)
expect(@cc.table - original_table).to eq [['x-custom', 'random']]
end
end
context 'size bounds' do
it 'should drop headers from end of table' do
cc = EncodingContext.new(table_size: 2048)
cc.instance_eval do
add_to_table(['test1', '1' * 1024])
add_to_table(['test2', '2' * 500])
end
original_table = cc.table.dup
original_size = original_table.join.bytesize + original_table.size * 32
cc.process(name: 'x-custom',
value: 'a' * (2048 - original_size),
type: :incremental)
expect(cc.table.first[0]).to eq 'x-custom'
expect(cc.table.size).to eq original_table.size # number of entries
end
end
it 'should clear table if entry exceeds table size' do
cc = EncodingContext.new(table_size: 2048)
cc.instance_eval do
add_to_table(['test1', '1' * 1024])
add_to_table(['test2', '2' * 500])
end
h = { name: 'x-custom', value: 'a', index: 0, type: :incremental }
e = { name: 'large', value: 'a' * 2048, index: 0 }
cc.process(h)
cc.process(e.merge(type: :incremental))
expect(cc.table).to be_empty
end
it 'should shrink table if set smaller size' do
cc = EncodingContext.new(table_size: 2048)
cc.instance_eval do
add_to_table(['test1', '1' * 1024])
add_to_table(['test2', '2' * 500])
end
cc.process(type: :changetablesize, value: 1500)
expect(cc.table.size).to be 1
expect(cc.table.first[0]).to eq 'test2'
end
it 'should reject table size update if exceed limit' do
cc = EncodingContext.new(table_size: 4096)
expect { cc.process(type: :changetablesize, value: 150_000_000) }.to raise_error(CompressionError)
end
end
context 'encode' do
it 'downcases the field' do
expect(EncodingContext.new.encode([['Content-Length', '5']]))
.to eq(EncodingContext.new.encode([['content-length', '5']]))
end
it 'fills :path if empty' do
expect(EncodingContext.new.encode([[':path', '']]))
.to eq(EncodingContext.new.encode([[':path', '/']]))
end
end
end
spec_examples = [
{ title: 'D.3. Request Examples without Huffman',
type: :request,
table_size: 4096,
huffman: :never,
streams: [
{ wire: "8286 8441 0f77 7777 2e65 7861 6d70 6c65
2e63 6f6d",
emitted: [
[':method', 'GET'],
[':scheme', 'http'],
[':path', '/'],
[':authority', 'www.example.com'],
],
table: [
[':authority', 'www.example.com'],
],
table_size: 57,
},
{ wire: '8286 84be 5808 6e6f 2d63 6163 6865',
emitted: [
[':method', 'GET'],
[':scheme', 'http'],
[':path', '/'],
[':authority', 'www.example.com'],
['cache-control', 'no-cache'],
],
table: [
['cache-control', 'no-cache'],
[':authority', 'www.example.com'],
],
table_size: 110,
},
{ wire: "8287 85bf 400a 6375 7374 6f6d 2d6b 6579
0c63 7573 746f 6d2d 7661 6c75 65",
emitted: [
[':method', 'GET'],
[':scheme', 'https'],
[':path', '/index.html'],
[':authority', 'www.example.com'],
['custom-key', 'custom-value'],
],
table: [
['custom-key', 'custom-value'],
['cache-control', 'no-cache'],
[':authority', 'www.example.com'],
],
table_size: 164,
},
],
},
{ title: 'D.4. Request Examples with Huffman',
type: :request,
table_size: 4096,
huffman: :always,
streams: [
{ wire: '8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff',
emitted: [
[':method', 'GET'],
[':scheme', 'http'],
[':path', '/'],
[':authority', 'www.example.com'],
],
table: [
[':authority', 'www.example.com'],
],
table_size: 57,
},
{ wire: '8286 84be 5886 a8eb 1064 9cbf',
emitted: [
[':method', 'GET'],
[':scheme', 'http'],
[':path', '/'],
[':authority', 'www.example.com'],
['cache-control', 'no-cache'],
],
table: [
['cache-control', 'no-cache'],
[':authority', 'www.example.com'],
],
table_size: 110,
},
{ wire: "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
a849 e95b b8e8 b4bf",
emitted: [
[':method', 'GET'],
[':scheme', 'https'],
[':path', '/index.html'],
[':authority', 'www.example.com'],
['custom-key', 'custom-value'],
],
table: [
['custom-key', 'custom-value'],
['cache-control', 'no-cache'],
[':authority', 'www.example.com'],
],
table_size: 164,
},
],
},
{ title: 'D.4.a. Request Examples with Huffman - Client Handling of Improperly Ordered Headers',
type: :request,
table_size: 4096,
huffman: :always,
streams: [
{ wire: '8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff',
emitted: [
[':method', 'GET'],
[':scheme', 'http'],
[':path', '/'],
[':authority', 'www.example.com'],
],
table: [
[':authority', 'www.example.com'],
],
table_size: 57,
},
{ wire: '8286 84be 5886 a8eb 1064 9cbf',
emitted: [
[':method', 'GET'],
[':scheme', 'http'],
['cache-control', 'no-cache'],
[':path', '/'],
[':authority', 'www.example.com'],
],
table: [
['cache-control', 'no-cache'],
[':authority', 'www.example.com'],
],
table_size: 110,
},
{ wire: "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
a849 e95b b8e8 b4bf",
emitted: [
[':method', 'GET'],
[':scheme', 'https'],
['custom-key', 'custom-value'],
[':path', '/index.html'],
[':authority', 'www.example.com'],
],
table: [
['custom-key', 'custom-value'],
['cache-control', 'no-cache'],
[':authority', 'www.example.com'],
],
table_size: 164,
},
],
},
{ title: 'D.4.b. Request Examples with Huffman - Server Handling of Improperly Ordered Headers',
type: :request,
bypass_encoder: true,
table_size: 4096,
huffman: :always,
streams: [
{ wire: '8286408825a849e95ba97d7f8925a849e95bb8e8b4bf84418cf1e3c2e5f23a6ba0ab90f4ff',
emitted: [
[':method', 'GET'],
[':scheme', 'http'],
['custom-key', 'custom-value'],
[':path', '/'],
[':authority', 'www.example.com'],
],
table: [
['custom-key', 'custom-value'],
[':authority', 'www.example.com'],
],
table_size: 111,
has_bad_headers: true,
},
],
},
{ title: 'D.5. Response Examples without Huffman',
type: :response,
table_size: 256,
huffman: :never,
streams: [
{ wire: "4803 3330 3258 0770 7269 7661 7465 611d
4d6f 6e2c 2032 3120 4f63 7420 3230 3133
2032 303a 3133 3a32 3120 474d 546e 1768
7474 7073 3a2f 2f77 7777 2e65 7861 6d70
6c65 2e63 6f6d",
emitted: [
[':status', '302'],
['cache-control', 'private'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['location', 'https://www.example.com'],
],
table: [
['location', 'https://www.example.com'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['cache-control', 'private'],
[':status', '302'],
],
table_size: 222,
},
{ wire: '4803 3330 37c1 c0bf',
emitted: [
[':status', '307'],
['cache-control', 'private'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['location', 'https://www.example.com'],
],
table: [
[':status', '307'],
['location', 'https://www.example.com'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['cache-control', 'private'],
],
table_size: 222,
},
{ wire: "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420
3230 3133 2032 303a 3133 3a32 3220 474d
54c0 5a04 677a 6970 7738 666f 6f3d 4153
444a 4b48 514b 425a 584f 5157 454f 5049
5541 5851 5745 4f49 553b 206d 6178 2d61
6765 3d33 3630 303b 2076 6572 7369 6f6e
3d31",
emitted: [
[':status', '200'],
['cache-control', 'private'],
['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
['location', 'https://www.example.com'],
['content-encoding', 'gzip'],
['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
],
table: [
['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
['content-encoding', 'gzip'],
['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
],
table_size: 215,
},
],
},
{ title: 'D.6. Response Examples with Huffman',
type: :response,
table_size: 256,
huffman: :always,
streams: [
{ wire: "4882 6402 5885 aec3 771a 4b61 96d0 7abe
9410 54d4 44a8 2005 9504 0b81 66e0 82a6
2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8
e9ae 82ae 43d3",
emitted: [
[':status', '302'],
['cache-control', 'private'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['location', 'https://www.example.com'],
],
table: [
['location', 'https://www.example.com'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['cache-control', 'private'],
[':status', '302'],
],
table_size: 222,
},
{ wire: '4883 640e ffc1 c0bf',
emitted: [
[':status', '307'],
['cache-control', 'private'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['location', 'https://www.example.com'],
],
table: [
[':status', '307'],
['location', 'https://www.example.com'],
['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
['cache-control', 'private'],
],
table_size: 222,
},
{ wire: "88c1 6196 d07a be94 1054 d444 a820 0595
040b 8166 e084 a62d 1bff c05a 839b d9ab
77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b
3960 d5af 2708 7f36 72c1 ab27 0fb5 291f
9587 3160 65c0 03ed 4ee5 b106 3d50 07",
emitted: [
[':status', '200'],
['cache-control', 'private'],
['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
['location', 'https://www.example.com'],
['content-encoding', 'gzip'],
['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
],
table: [
['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
['content-encoding', 'gzip'],
['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
],
table_size: 215,
},
],
},
{ title: 'D.6.a. Response Examples with Huffman - dynamic table size updates should not trigger exceptions',
type: :response,
table_size: 4096,
huffman: :always,
bypass_encoder: true,
streams: [
{ wire: '2088 7689 aa63 55e5 80ae 16d7 17',
emitted: [
[':status', '200'],
['server', 'nginx/1.15.2'],
],
table: [],
table_size: 0,
},
],
},
]
context 'decode' do
spec_examples.each do |ex|
context "spec example #{ex[:title]}" do
ex[:streams].size.times do |nth|
context "request #{nth + 1}" do
before { @dc = Decompressor.new(table_size: ex[:table_size]) }
before do
(0...nth).each do |i|
bytes = [ex[:streams][i][:wire].delete(" \n")].pack('H*')
if ex[:streams][i][:has_bad_headers]
expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
else
@dc.decode(HTTP2::Buffer.new(bytes))
end
end
end
if ex[:streams][nth][:has_bad_headers]
it 'should raise CompressionError' do
bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
end
else
subject do
bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
@emitted = @dc.decode(HTTP2::Buffer.new(bytes))
end
it 'should emit expected headers' do
subject
# partitioned compare
pseudo_headers, headers = ex[:streams][nth][:emitted].partition { |f, _| f.start_with? ':' }
partitioned_headers = pseudo_headers + headers
expect(@emitted).to eq partitioned_headers
end
it 'should update header table' do
subject
expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
end
it 'should compute header table size' do
subject
expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
end
end
end
end
end
end
end
context 'encode' do
spec_examples.each do |ex|
next if ex[:bypass_encoder]
context "spec example #{ex[:title]}" do
ex[:streams].size.times do |nth|
context "request #{nth + 1}" do
before do
@cc = Compressor.new(table_size: ex[:table_size],
huffman: ex[:huffman])
end
before do
(0...nth).each do |i|
if ex[:streams][i][:has_bad_headers]
@cc.encode(ex[:streams][i][:emitted], ensure_proper_ordering: false)
else
@cc.encode(ex[:streams][i][:emitted])
end
end
end
subject do
if ex[:streams][nth][:has_bad_headers]
@cc.encode(ex[:streams][nth][:emitted], ensure_proper_ordering: false)
else
@cc.encode(ex[:streams][nth][:emitted])
end
end
it 'should emit expected bytes on wire' do
puts subject.unpack('H*').first
expect(subject.unpack('H*').first).to eq ex[:streams][nth][:wire].delete(" \n")
end
unless ex[:streams][nth][:has_bad_headers]
it 'should update header table' do
subject
expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
end
it 'should compute header table size' do
subject
expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
end
end
end
end
end
end
end
end
ruby-http-2-0.11.0/spec/connection_spec.rb 0000664 0000000 0000000 00000053244 13775242373 0020343 0 ustar 00root root 0000000 0000000 require 'helper'
RSpec.describe HTTP2::Connection do
include FrameHelpers
before(:each) do
@conn = Client.new
end
let(:f) { Framer.new }
context 'initialization and settings' do
it 'should raise error if first frame is not settings' do
(frame_types - [settings_frame]).each do |frame|
expect { @conn << frame }.to raise_error(ProtocolError)
expect(@conn).to be_closed
end
end
it 'should not raise error if first frame is SETTINGS' do
expect { @conn << f.generate(settings_frame) }.to_not raise_error
expect(@conn.state).to eq :connected
expect(@conn).to_not be_closed
end
it 'should raise error if SETTINGS stream != 0' do
frame = set_stream_id(f.generate(settings_frame), 0x1)
expect { @conn << frame }.to raise_error(ProtocolError)
end
end
context 'settings synchronization' do
it 'should reflect outgoing settings when ack is received' do
expect(@conn.local_settings[:settings_header_table_size]).to eq 4096
@conn.settings(settings_header_table_size: 256)
expect(@conn.local_settings[:settings_header_table_size]).to eq 4096
ack = { type: :settings, stream: 0, payload: [], flags: [:ack] }
@conn << f.generate(ack)
expect(@conn.local_settings[:settings_header_table_size]).to eq 256
end
it 'should reflect incoming settings when SETTINGS is received' do
expect(@conn.remote_settings[:settings_header_table_size]).to eq 4096
settings = settings_frame
settings[:payload] = [[:settings_header_table_size, 256]]
@conn << f.generate(settings)
expect(@conn.remote_settings[:settings_header_table_size]).to eq 256
end
it 'should reflect settings_max_frame_size recevied from peer' do
settings = settings_frame
settings[:payload] = [[:settings_max_frame_size, 16_385]]
@conn << f.generate(settings)
frame = {
length: 16_385,
type: :data,
flags: [:end_stream],
stream: 1,
payload: 'a' * 16_385,
}
expect { @conn.send(frame) }.not_to raise_error(CompressionError)
end
it 'should send SETTINGS ACK when SETTINGS is received' do
settings = settings_frame
settings[:payload] = [[:settings_header_table_size, 256]]
# We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
frames = []
expect(@conn).to receive(:send).twice do |frame|
frames << frame
end
@conn.send_connection_preface
@conn << f.generate(settings)
frame = frames.last
expect(frame[:type]).to eq :settings
expect(frame[:flags]).to eq [:ack]
expect(frame[:payload]).to eq []
end
end
context 'stream management' do
it 'should initialize to default stream limit (100)' do
expect(@conn.local_settings[:settings_max_concurrent_streams]).to eq 100
end
it 'should change stream limit to received SETTINGS value' do
@conn << f.generate(settings_frame)
expect(@conn.remote_settings[:settings_max_concurrent_streams]).to eq 10
end
it 'should count open streams against stream limit' do
s = @conn.new_stream
expect(@conn.active_stream_count).to eq 0
s.receive headers_frame
expect(@conn.active_stream_count).to eq 1
end
it 'should not count reserved streams against stream limit' do
s1 = @conn.new_stream
s1.receive push_promise_frame
expect(@conn.active_stream_count).to eq 0
s2 = @conn.new_stream
s2.send push_promise_frame
expect(@conn.active_stream_count).to eq 0
# transition to half closed
s1.receive headers_frame
s2.send headers_frame
expect(@conn.active_stream_count).to eq 2
# transition to closed
s1.receive data_frame
s2.send data_frame
expect(@conn.active_stream_count).to eq 0
expect(s1).to be_closed
expect(s2).to be_closed
end
it 'should not exceed stream limit set by peer' do
@conn << f.generate(settings_frame)
expect do
10.times do
s = @conn.new_stream
s.send headers_frame
end
end.to_not raise_error
expect { @conn.new_stream }.to raise_error(StreamLimitExceeded)
end
it 'should initialize stream with HEADERS priority value' do
@conn << f.generate(settings_frame)
stream, headers = nil, headers_frame
headers[:weight] = 20
headers[:stream_dependency] = 0
headers[:exclusive] = false
@conn.on(:stream) { |s| stream = s }
@conn << f.generate(headers)
expect(stream.weight).to eq 20
end
it 'should initialize idle stream on PRIORITY frame' do
@conn << f.generate(settings_frame)
stream = nil
@conn.on(:stream) { |s| stream = s }
@conn << f.generate(priority_frame)
expect(stream.state).to eq :idle
end
end
context 'cleanup_recently_closed' do
it 'should cleanup old connections' do
now_ts = Time.now.to_i
stream_ids = Array.new(4) { @conn.new_stream.id }
expect(@conn.instance_variable_get('@streams').size).to eq(4)
# Assume that the first 3 streams were closed in different time
recently_closed = stream_ids[0, 3].zip([now_ts - 100, now_ts - 50, now_ts - 5]).to_h
@conn.instance_variable_set('@streams_recently_closed', recently_closed)
# Cleanup should delete streams that were closed earlier than 15s ago
@conn.__send__(:cleanup_recently_closed)
expect(@conn.instance_variable_get('@streams').size).to eq(2)
expect(@conn.instance_variable_get('@streams_recently_closed')).to eq(stream_ids[2] => now_ts - 5)
end
end
context 'Headers pre/post processing' do
it 'should not concatenate multiple occurences of a header field with the same name' do
input = [
['Content-Type', 'text/html'],
['Cache-Control', 'max-age=60, private'],
['Cache-Control', 'must-revalidate'],
]
expected = [
['content-type', 'text/html'],
['cache-control', 'max-age=60, private'],
['cache-control', 'must-revalidate'],
]
headers = []
@conn.on(:frame) do |bytes|
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
end
stream = @conn.new_stream
stream.headers(input)
expect(headers.size).to eq 1
emitted = Decompressor.new.decode(headers.first[:payload])
expect(emitted).to match_array(expected)
end
it 'should not split zero-concatenated header field values' do
input = [*RESPONSE_HEADERS,
['cache-control', "max-age=60, private\0must-revalidate"],
['content-type', 'text/html'],
['cookie', "a=b\0c=d; e=f"]]
expected = [*RESPONSE_HEADERS,
['cache-control', "max-age=60, private\0must-revalidate"],
['content-type', 'text/html'],
['cookie', "a=b\0c=d; e=f"]]
result = nil
@conn.on(:stream) do |stream|
stream.on(:headers) { |h| result = h }
end
srv = Server.new
srv.on(:frame) { |bytes| @conn << bytes }
stream = srv.new_stream
stream.headers(input)
expect(result).to eq expected
end
end
context 'flow control' do
it 'should initialize to default flow window' do
expect(@conn.remote_window).to eq DEFAULT_FLOW_WINDOW
end
it 'should update connection and stream windows on SETTINGS' do
settings, data = settings_frame, data_frame
settings[:payload] = [[:settings_initial_window_size, 1024]]
data[:payload] = 'x' * 2048
stream = @conn.new_stream
stream.send headers_frame
stream.send data
expect(stream.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
expect(@conn.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
@conn << f.generate(settings)
expect(@conn.remote_window).to eq(-1024)
expect(stream.remote_window).to eq(-1024)
end
it 'should initialize streams with window specified by peer' do
settings = settings_frame
settings[:payload] = [[:settings_initial_window_size, 1024]]
@conn << f.generate(settings)
expect(@conn.new_stream.remote_window).to eq 1024
end
it 'should observe connection flow control' do
settings, data = settings_frame, data_frame
settings[:payload] = [[:settings_initial_window_size, 1000]]
@conn << f.generate(settings)
s1 = @conn.new_stream
s2 = @conn.new_stream
s1.send headers_frame
s1.send data.merge(payload: 'x' * 900)
expect(@conn.remote_window).to eq 100
s2.send headers_frame
s2.send data.merge(payload: 'x' * 200)
expect(@conn.remote_window).to eq 0
expect(@conn.buffered_amount).to eq 100
@conn << f.generate(window_update_frame.merge(stream: 0, increment: 1000))
expect(@conn.buffered_amount).to eq 0
expect(@conn.remote_window).to eq 900
end
it 'should update window when data received is over half of the maximum local window size' do
settings, data = settings_frame, data_frame
conn = Client.new(settings_initial_window_size: 500)
conn.receive f.generate(settings)
s1 = conn.new_stream
s2 = conn.new_stream
s1.send headers_frame
s2.send headers_frame
expect(conn).to receive(:send) do |frame|
expect(frame[:type]).to eq :window_update
expect(frame[:stream]).to eq 0
expect(frame[:increment]).to eq 400
end
conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s1.id))
conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s2.id))
expect(s1.local_window).to eq 300
expect(s2.local_window).to eq 300
expect(conn.local_window).to eq 500
end
end
context 'framing' do
it 'should buffer incomplete frames' do
settings = settings_frame
settings[:payload] = [[:settings_initial_window_size, 1000]]
@conn << f.generate(settings)
frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
@conn << frame
expect(@conn.remote_window).to eq 2000
@conn << frame.slice!(0, 1)
@conn << frame
expect(@conn.remote_window).to eq 3000
end
it 'should decompress header blocks regardless of stream state' do
req_headers = [
['content-length', '20'],
['x-my-header', 'first'],
]
cc = Compressor.new
headers = headers_frame
headers[:payload] = cc.encode(req_headers)
@conn << f.generate(settings_frame)
@conn.on(:stream) do |stream|
expect(stream).to receive(:<<) do |frame|
expect(frame[:payload]).to eq req_headers
end
end
@conn << f.generate(headers)
end
it 'should decode non-contiguous header blocks' do
req_headers = [
['content-length', '15'],
['x-my-header', 'first'],
]
cc = Compressor.new
h1, h2 = headers_frame, continuation_frame
# Header block fragment might not complete for decompression
payload = cc.encode(req_headers)
h1[:payload] = payload.slice!(0, payload.size / 2) # first half
h1[:stream] = 5
h1[:flags] = []
h2[:payload] = payload # the remaining
h2[:stream] = 5
@conn << f.generate(settings_frame)
@conn.on(:stream) do |stream|
expect(stream).to receive(:<<) do |frame|
expect(frame[:payload]).to eq req_headers
end
end
@conn << f.generate(h1)
@conn << f.generate(h2)
end
it 'should require that split header blocks are a contiguous sequence' do
headers = headers_frame
headers[:flags] = []
@conn << f.generate(settings_frame)
@conn << f.generate(headers)
(frame_types - [continuation_frame]).each do |frame|
expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
end
end
it 'should raise compression error on encode of invalid frame' do
@conn << f.generate(settings_frame)
stream = @conn.new_stream
expect do
stream.headers({ 'name' => Float::INFINITY })
end.to raise_error(CompressionError)
end
it 'should raise connection error on decode of invalid frame' do
@conn << f.generate(settings_frame)
frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
# Connection errors emit protocol error frames
expect { @conn << frame }.to raise_error(ProtocolError)
end
it 'should emit encoded frames via on(:frame)' do
bytes = nil
@conn.on(:frame) { |d| bytes = d }
@conn.settings(settings_max_concurrent_streams: 10,
settings_initial_window_size: 0x7fffffff)
expect(bytes).to eq f.generate(settings_frame)
end
it 'should compress stream headers' do
@conn.on(:frame) do |bytes|
expect(bytes).not_to include('get')
expect(bytes).not_to include('http')
expect(bytes).not_to include('www.example.org') # should be huffman encoded
end
stream = @conn.new_stream
stream.headers({ ':method' => 'get',
':scheme' => 'http',
':authority' => 'www.example.org',
':path' => '/resource' })
end
it 'should generate CONTINUATION if HEADERS is too long' do
headers = []
@conn.on(:frame) do |bytes|
# bytes[3]: frame's type field
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
end
stream = @conn.new_stream
stream.headers({
':method' => 'get',
':scheme' => 'http',
':authority' => 'www.example.org',
':path' => '/resource',
'custom' => 'q' * 44_000,
}, end_stream: true)
expect(headers.size).to eq 3
expect(headers[0][:type]).to eq :headers
expect(headers[1][:type]).to eq :continuation
expect(headers[2][:type]).to eq :continuation
expect(headers[0][:flags]).to eq [:end_stream]
expect(headers[1][:flags]).to eq []
expect(headers[2][:flags]).to eq [:end_headers]
end
it 'should not generate CONTINUATION if HEADERS fits exactly in a frame' do
headers = []
@conn.on(:frame) do |bytes|
# bytes[3]: frame's type field
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
end
stream = @conn.new_stream
stream.headers({
':method' => 'get',
':scheme' => 'http',
':authority' => 'www.example.org',
':path' => '/resource',
'custom' => 'q' * 18_682, # this number should be updated when Huffman table is changed
}, end_stream: true)
expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
expect(headers.size).to eq 1
expect(headers[0][:type]).to eq :headers
expect(headers[0][:flags]).to include(:end_headers)
expect(headers[0][:flags]).to include(:end_stream)
end
it 'should not generate CONTINUATION if HEADERS fits exactly in a frame' do
headers = []
@conn.on(:frame) do |bytes|
# bytes[3]: frame's type field
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
end
stream = @conn.new_stream
stream.headers({
':method' => 'get',
':scheme' => 'http',
':authority' => 'www.example.org',
':path' => '/resource',
'custom' => 'q' * 18_682, # this number should be updated when Huffman table is changed
}, end_stream: true)
expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
expect(headers.size).to eq 1
expect(headers[0][:type]).to eq :headers
expect(headers[0][:flags]).to include(:end_headers)
expect(headers[0][:flags]).to include(:end_stream)
end
it 'should generate CONTINUATION if HEADERS exceed the max payload by one byte' do
headers = []
@conn.on(:frame) do |bytes|
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
end
stream = @conn.new_stream
stream.headers({
':method' => 'get',
':scheme' => 'http',
':authority' => 'www.example.org',
':path' => '/resource',
'custom' => 'q' * 18_683, # this number should be updated when Huffman table is changed
}, end_stream: true)
expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
expect(headers[1][:length]).to eq 1
expect(headers.size).to eq 2
expect(headers[0][:type]).to eq :headers
expect(headers[1][:type]).to eq :continuation
expect(headers[0][:flags]).to eq [:end_stream]
expect(headers[1][:flags]).to eq [:end_headers]
end
end
context 'connection management' do
it 'should raise error on invalid connection header' do
srv = Server.new
expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
srv = Server.new
expect do
srv << CONNECTION_PREFACE_MAGIC
srv << f.generate(settings_frame)
end.to_not raise_error
end
it 'should respond to PING frames' do
@conn << f.generate(settings_frame)
expect(@conn).to receive(:send) do |frame|
expect(frame[:type]).to eq :ping
expect(frame[:flags]).to eq [:ack]
expect(frame[:payload]).to eq '12345678'
end
@conn << f.generate(ping_frame)
end
it 'should fire callback on PONG' do
@conn << f.generate(settings_frame)
pong = nil
@conn.ping('12345678') { |d| pong = d }
@conn << f.generate(pong_frame)
expect(pong).to eq '12345678'
end
it 'should fire callback on receipt of GOAWAY' do
last_stream, payload, error = nil
@conn << f.generate(settings_frame)
@conn.on(:goaway) do |s, e, p|
last_stream = s
error = e
payload = p
end
@conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
expect(last_stream).to eq 17
expect(error).to eq :no_error
expect(payload).to eq 'test'
expect(@conn).to be_closed
end
it 'should raise error when opening new stream after sending GOAWAY' do
@conn.goaway
expect(@conn).to be_closed
expect { @conn.new_stream }.to raise_error(ConnectionClosed)
end
it 'should raise error when opening new stream after receiving GOAWAY' do
@conn << f.generate(settings_frame)
@conn << f.generate(goaway_frame)
expect { @conn.new_stream }.to raise_error(ConnectionClosed)
end
it 'should not raise error when receiving connection management frames immediately after emitting goaway' do
@conn.goaway
expect(@conn).to be_closed
expect { @conn << f.generate(settings_frame) }.not_to raise_error(ProtocolError)
expect { @conn << f.generate(ping_frame) }.not_to raise_error(ProtocolError)
expect { @conn << f.generate(goaway_frame) }.not_to raise_error(ProtocolError)
end
it 'should process connection management frames after GOAWAY' do
@conn << f.generate(settings_frame)
@conn << f.generate(headers_frame)
@conn << f.generate(goaway_frame)
@conn << f.generate(headers_frame.merge(stream: 7))
@conn << f.generate(push_promise_frame)
expect(@conn.active_stream_count).to eq 1
end
it 'should raise error on frame for invalid stream ID' do
@conn << f.generate(settings_frame)
expect do
@conn << f.generate(data_frame.merge(stream: 31))
end.to raise_error(ProtocolError)
end
it 'should not raise an error on frame for a closed stream ID' do
srv = Server.new
srv << CONNECTION_PREFACE_MAGIC
stream = srv.new_stream
stream.send headers_frame
stream.send data_frame
stream.close
expect do
srv << f.generate(rst_stream_frame.merge(stream: stream.id))
end.to_not raise_error
end
it 'should send GOAWAY frame on connection error' do
stream = @conn.new_stream
expect(@conn).to receive(:encode) do |frame|
expect(frame[:type]).to eq :settings
[frame]
end
expect(@conn).to receive(:encode) do |frame|
expect(frame[:type]).to eq :goaway
expect(frame[:last_stream]).to eq stream.id
expect(frame[:error]).to eq :protocol_error
[frame]
end
expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
end
end
context 'API' do
it '.settings should emit SETTINGS frames' do
expect(@conn).to receive(:send) do |frame|
expect(frame[:type]).to eq :settings
expect(frame[:payload]).to eq([
[:settings_max_concurrent_streams, 10],
[:settings_initial_window_size, 0x7fffffff],
])
expect(frame[:stream]).to eq 0
end
@conn.settings(settings_max_concurrent_streams: 10,
settings_initial_window_size: 0x7fffffff)
end
it '.ping should generate PING frames' do
expect(@conn).to receive(:send) do |frame|
expect(frame[:type]).to eq :ping
expect(frame[:payload]).to eq 'somedata'
end
@conn.ping('somedata')
end
it '.goaway should generate GOAWAY frame with last processed stream ID' do
@conn << f.generate(settings_frame)
@conn << f.generate(headers_frame.merge(stream: 17))
expect(@conn).to receive(:send) do |frame|
expect(frame[:type]).to eq :goaway
expect(frame[:last_stream]).to eq 17
expect(frame[:error]).to eq :internal_error
expect(frame[:payload]).to eq 'payload'
end
@conn.goaway(:internal_error, 'payload')
end
it '.window_update should emit WINDOW_UPDATE frames' do
expect(@conn).to receive(:send) do |frame|
expect(frame[:type]).to eq :window_update
expect(frame[:increment]).to eq 20
expect(frame[:stream]).to eq 0
end
@conn.window_update(20)
end
end
end
ruby-http-2-0.11.0/spec/emitter_spec.rb 0000664 0000000 0000000 00000002103 13775242373 0017641 0 ustar 00root root 0000000 0000000 require 'helper'
RSpec.describe HTTP2::Emitter do
class Worker
include Emitter
end
before(:each) do
@w = Worker.new
@cnt = 0
end
it 'should raise error on missing callback' do
expect { @w.on(:a) {} }.to_not raise_error
expect { @w.on(:a) }.to raise_error
end
it 'should allow multiple callbacks on single event' do
@w.on(:a) { @cnt += 1 }
@w.on(:a) { @cnt += 1 }
@w.emit(:a)
expect(@cnt).to eq 2
end
it 'should execute callback with optional args' do
args = nil
@w.on(:a) { |a| args = a }
@w.emit(:a, 123)
expect(args).to eq 123
end
it 'should pass emitted callbacks to listeners' do
@w.on(:a) { |&block| block.call }
@w.once(:a) { |&block| block.call }
@w.emit(:a) { @cnt += 1 }
expect(@cnt).to eq 2
end
it 'should allow events with no callbacks' do
expect { @w.emit(:missing) }.to_not raise_error
end
it 'should execute callback exactly once' do
@w.on(:a) { @cnt += 1 }
@w.once(:a) { @cnt += 1 }
@w.emit(:a)
@w.emit(:a)
expect(@cnt).to eq 3
end
end
ruby-http-2-0.11.0/spec/framer_spec.rb 0000664 0000000 0000000 00000033524 13775242373 0017457 0 ustar 00root root 0000000 0000000 require 'helper'
RSpec.describe HTTP2::Framer do
let(:f) { Framer.new }
context 'common header' do
let(:frame) do
{
length: 4,
type: :headers,
flags: [:end_stream, :end_headers],
stream: 15,
}
end
let(:bytes) { [0, 0x04, 0x01, 0x5, 0x0000000F].pack('CnCCN') }
it 'should generate common 9 byte header' do
expect(f.common_header(frame)).to eq bytes
end
it 'should parse common 9 byte header' do
expect(f.read_common_header(Buffer.new(bytes))).to eq frame
end
it 'should generate a large frame' do
f = Framer.new
f.max_frame_size = 2**24 - 1
frame = {
length: 2**18 + 2**16 + 17,
type: :headers,
flags: [:end_stream, :end_headers],
stream: 15,
}
bytes = [5, 17, 0x01, 0x5, 0x0000000F].pack('CnCCN')
expect(f.common_header(frame)).to eq bytes
expect(f.read_common_header(Buffer.new(bytes))).to eq frame
end
it 'should raise exception on invalid frame type when sending' do
expect do
frame[:type] = :bogus
f.common_header(frame)
end.to raise_error(CompressionError, /invalid.*type/i)
end
it 'should raise exception on invalid stream ID' do
expect do
frame[:stream] = Framer::MAX_STREAM_ID + 1
f.common_header(frame)
end.to raise_error(CompressionError, /stream/i)
end
it 'should raise exception on invalid frame flag' do
expect do
frame[:flags] = [:bogus]
f.common_header(frame)
end.to raise_error(CompressionError, /frame flag/)
end
it 'should raise exception on invalid frame size' do
expect do
frame[:length] = 2**24
f.common_header(frame)
end.to raise_error(CompressionError, /too large/)
end
end
context 'DATA' do
it 'should generate and parse bytes' do
frame = {
length: 4,
type: :data,
flags: [:end_stream],
stream: 1,
payload: 'text',
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0x4, 0x0, 0x1, 0x1, *'text'.bytes].pack('CnCCNC*')
expect(f.parse(bytes)).to eq frame
end
end
context 'HEADERS' do
it 'should generate and parse bytes' do
frame = {
length: 12,
type: :headers,
flags: [:end_stream, :end_headers],
stream: 1,
payload: 'header-block',
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0xc, 0x1, 0x5, 0x1, *'header-block'.bytes].pack('CnCCNC*')
expect(f.parse(bytes)).to eq frame
end
it 'should carry an optional stream priority' do
frame = {
length: 16,
type: :headers,
flags: [:end_headers],
stream: 1,
stream_dependency: 15,
weight: 12,
exclusive: false,
payload: 'header-block',
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0x11, 0x1, 0x24, 0x1, 0xf, 0xb, *'header-block'.bytes].pack('CnCCNNCC*')
expect(f.parse(bytes)).to eq frame
end
end
context 'PRIORITY' do
it 'should generate and parse bytes' do
frame = {
length: 5,
type: :priority,
stream: 1,
stream_dependency: 15,
weight: 12,
exclusive: true,
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0x5, 0x2, 0x0, 0x1, 0x8000000f, 0xb].pack('CnCCNNC')
expect(f.parse(bytes)).to eq frame
end
end
context 'RST_STREAM' do
it 'should generate and parse bytes' do
frame = {
length: 4,
type: :rst_stream,
stream: 1,
error: :stream_closed,
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0x4, 0x3, 0x0, 0x1, 0x5].pack('CnCCNN')
expect(f.parse(bytes)).to eq frame
end
end
context 'SETTINGS' do
let(:frame) do
{
type: :settings,
flags: [],
stream: 0,
payload: [
[:settings_max_concurrent_streams, 10],
[:settings_header_table_size, 2048],
],
}
end
it 'should generate and parse bytes' do
bytes = f.generate(frame)
expect(bytes).to eq [0, 12, 0x4, 0x0, 0x0, 3, 10, 1, 2048].pack('CnCCNnNnN')
parsed = f.parse(bytes)
parsed.delete(:length)
frame.delete(:length)
expect(parsed).to eq frame
end
it 'should generate settings when id is given as an integer' do
frame[:payload][1][0] = 1
bytes = f.generate(frame)
expect(bytes).to eq [0, 12, 0x4, 0x0, 0x0, 3, 10, 1, 2048].pack('CnCCNnNnN')
end
it 'should ignore custom settings when sending' do
frame[:payload] = [
[:settings_max_concurrent_streams, 10],
[:settings_initial_window_size, 20],
[55, 30],
]
buf = f.generate(frame)
frame[:payload].slice!(2) # cut off the extension
frame[:length] = 12 # frame length should be computed WITHOUT extensions
expect(f.parse(buf)).to eq frame
end
it 'should ignore custom settings when receiving' do
frame[:payload] = [
[:settings_max_concurrent_streams, 10],
[:settings_initial_window_size, 20],
]
buf = f.generate(frame)
buf.setbyte(2, 18) # add 6 to the frame length
buf << "\x00\x37\x00\x00\x00\x1e"
parsed = f.parse(buf)
parsed.delete(:length)
frame.delete(:length)
expect(parsed).to eq frame
end
it 'should raise exception on sending invalid stream ID' do
expect do
frame[:stream] = 1
f.generate(frame)
end.to raise_error(CompressionError, /Invalid stream ID/)
end
it 'should raise exception on receiving invalid stream ID' do
expect do
buf = f.generate(frame)
buf.setbyte(8, 1)
f.parse(buf)
end.to raise_error(ProtocolError, /Invalid stream ID/)
end
it 'should raise exception on sending invalid setting' do
expect do
frame[:payload] = [[:random, 23]]
f.generate(frame)
end.to raise_error(CompressionError, /Unknown settings ID/)
end
it 'should raise exception on receiving invalid payload length' do
expect do
buf = f.generate(frame)
buf.setbyte(2, 11) # change payload length
f.parse(buf)
end.to raise_error(ProtocolError, /Invalid settings payload length/)
end
end
context 'PUSH_PROMISE' do
it 'should generate and parse bytes' do
frame = {
length: 11,
type: :push_promise,
flags: [:end_headers],
stream: 1,
promise_stream: 2,
payload: 'headers',
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0xb, 0x5, 0x4, 0x1, 0x2, *'headers'.bytes].pack('CnCCNNC*')
expect(f.parse(bytes)).to eq frame
end
end
context 'PING' do
let(:frame) do
{
length: 8,
stream: 1,
type: :ping,
flags: [:ack],
payload: '12345678',
}
end
it 'should generate and parse bytes' do
bytes = f.generate(frame)
expect(bytes).to eq [0, 0x8, 0x6, 0x1, 0x1, *'12345678'.bytes].pack('CnCCNC*')
expect(f.parse(bytes)).to eq frame
end
it 'should raise exception on invalid payload' do
expect do
frame[:payload] = '1234'
f.generate(frame)
end.to raise_error(CompressionError, /Invalid payload size/)
end
end
context 'GOAWAY' do
let(:frame) do
{
length: 13,
stream: 1,
type: :goaway,
last_stream: 2,
error: :no_error,
payload: 'debug',
}
end
it 'should generate and parse bytes' do
bytes = f.generate(frame)
expect(bytes).to eq [0, 0xd, 0x7, 0x0, 0x1, 0x2, 0x0, *'debug'.bytes].pack('CnCCNNNC*')
expect(f.parse(bytes)).to eq frame
end
it 'should treat debug payload as optional' do
frame.delete :payload
frame[:length] = 0x8
bytes = f.generate(frame)
expect(bytes).to eq [0, 0x8, 0x7, 0x0, 0x1, 0x2, 0x0].pack('CnCCNNN')
expect(f.parse(bytes)).to eq frame
end
end
context 'WINDOW_UPDATE' do
it 'should generate and parse bytes' do
frame = {
length: 4,
type: :window_update,
increment: 10,
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0x4, 0x8, 0x0, 0x0, 0xa].pack('CnCCNN')
expect(f.parse(bytes)).to eq frame
end
end
context 'CONTINUATION' do
it 'should generate and parse bytes' do
frame = {
length: 12,
type: :continuation,
stream: 1,
flags: [:end_headers],
payload: 'header-block',
}
bytes = f.generate(frame)
expect(bytes).to eq [0, 0xc, 0x9, 0x4, 0x1, *'header-block'.bytes].pack('CnCCNC*')
expect(f.parse(bytes)).to eq frame
end
end
context 'ALTSVC' do
it 'should generate and parse bytes' do
frame = {
length: 44,
type: :altsvc,
stream: 1,
max_age: 1_402_290_402, # 4
port: 8080, # 2
proto: 'h2-13', # 1 + 5
host: 'www.example.com', # 1 + 15
origin: 'www.example.com', # 15
}
bytes = f.generate(frame)
expected = [0, 43, 0xa, 0, 1, 1_402_290_402, 8080].pack('CnCCNNn')
expected << [5, *'h2-13'.bytes].pack('CC*')
expected << [15, *'www.example.com'.bytes].pack('CC*')
expected << [*'www.example.com'.bytes].pack('C*')
expect(bytes).to eq expected
expect(f.parse(bytes)).to eq frame
end
end
context 'Padding' do
[:data, :headers, :push_promise].each do |type|
[1, 256].each do |padlen|
context "generating #{type} frame padded #{padlen}" do
before do
@frame = {
length: 12,
type: type,
stream: 1,
payload: 'example data',
}
@frame[:promise_stream] = 2 if type == :push_promise
@normal = f.generate(@frame)
@padded = f.generate(@frame.merge(padding: padlen))
end
it 'should generate a frame with padding' do
expect(@padded.bytesize).to eq @normal.bytesize + padlen
end
it 'should fill padded octets with zero' do
trailer_len = padlen - 1
expect(@padded[-trailer_len, trailer_len]).to match(/\A\0*\z/)
end
it 'should parse a frame with padding' do
expect(f.parse(Buffer.new(@padded))).to eq \
f.parse(Buffer.new(@normal)).merge(padding: padlen)
end
it 'should preserve payload' do
expect(f.parse(Buffer.new(@padded))[:payload]).to eq @frame[:payload]
end
end
end
end
context 'generating with invalid padding length' do
before do
@frame = {
length: 12,
type: :data,
stream: 1,
payload: 'example data',
}
end
[0, 257, 1334].each do |padlen|
it "should raise error on trying to generate data frame padded with invalid #{padlen}" do
expect do
f.generate(@frame.merge(padding: padlen))
end.to raise_error(CompressionError, /padding/i)
end
end
it 'should raise error when adding a padding would make frame too large' do
@frame[:payload] = 'q' * (f.max_frame_size - 200)
@frame[:length] = @frame[:payload].size
@frame[:padding] = 210 # would exceed 4096
expect do
f.generate(@frame)
end.to raise_error(CompressionError, /padding/i)
end
end
context 'parsing frames with invalid paddings' do
before do
@frame = {
length: 12,
type: :data,
stream: 1,
payload: 'example data',
}
@padlen = 123
@padded = f.generate(@frame.merge(padding: @padlen))
end
it 'should raise exception when the given padding is longer than the payload' do
@padded.setbyte(9, 240)
expect { f.parse(Buffer.new(@padded)) }.to raise_error(ProtocolError, /padding/)
end
end
end
it 'should determine frame length' do
frames = [
[{ type: :data, stream: 1, flags: [:end_stream], payload: 'abc' }, 3],
[{ type: :headers, stream: 1, payload: 'abc' }, 3],
[{ type: :priority, stream: 3, stream_dependency: 30, exclusive: false, weight: 1 }, 5],
[{ type: :rst_stream, stream: 3, error: 100 }, 4],
[{ type: :settings, payload: [[:settings_max_concurrent_streams, 10]] }, 6],
[{ type: :push_promise, promise_stream: 5, payload: 'abc' }, 7],
[{ type: :ping, payload: 'blob' * 2 }, 8],
[{ type: :goaway, last_stream: 5, error: 20, payload: 'blob' }, 12],
[{ type: :window_update, stream: 1, increment: 1024 }, 4],
[{ type: :continuation, stream: 1, payload: 'abc' }, 3],
]
frames.each do |(frame, size)|
bytes = f.generate(frame)
expect(bytes.slice(1, 2).unpack('n').first).to eq size
expect(bytes.readbyte(0)).to eq 0
end
end
it 'should parse single frame at a time' do
frames = [
{ type: :headers, stream: 1, payload: 'headers' },
{ type: :data, stream: 1, flags: [:end_stream], payload: 'abc' },
]
buf = f.generate(frames[0]) << f.generate(frames[1])
expect(f.parse(buf)).to eq frames[0]
expect(f.parse(buf)).to eq frames[1]
end
it 'should process full frames only' do
frame = { type: :headers, stream: 1, payload: 'headers' }
bytes = f.generate(frame)
expect(f.parse(bytes[0...-1])).to be_nil
expect(f.parse(bytes)).to eq frame
expect(bytes).to be_empty
end
it 'should ignore unknown extension frames' do
frame = { type: :headers, stream: 1, payload: 'headers' }
bytes = f.generate(frame)
bytes = Buffer.new(bytes + bytes) # Two HEADERS frames in bytes
bytes.setbyte(3, 42) # Make the first unknown type 42
expect(f.parse(bytes)).to be_nil # first frame should be ignored
expect(f.parse(bytes)).to eq frame # should generate only one HEADERS
expect(bytes).to be_empty
end
end
ruby-http-2-0.11.0/spec/h2spec/ 0000775 0000000 0000000 00000000000 13775242373 0016021 5 ustar 00root root 0000000 0000000 ruby-http-2-0.11.0/spec/h2spec/h2spec.darwin 0000775 0000000 0000000 00026646054 13775242373 0020445 0 ustar 00root root 0000000 0000000 h
H __PAGEZERO x __TEXT `8 `8 __text __TEXT ` __rodata __TEXT j
p __typelink __TEXT * # * __itablink __TEXT + * __gosymtab __TEXT + + __gopclntab __TEXT + sQ
+ __symbol_stub1 __TEXT h8 X8 __DATA p8 `8 __nl_symbol_ptr __DATA p8 h `8 * __noptrdata __DATA q8 u a8 __data __DATA @9 o @9 __bss __DATA W: __noptrbss __DATA :<