pax_global_header00006660000000000000000000000064140036500430014505gustar00rootroot0000000000000052 comment=3d767261618d386a9404c86e0cb46f9c84ab826c ruby-protocol-http2-0.14.2/000077500000000000000000000000001400365004300154505ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/.editorconfig000066400000000000000000000000641400365004300201250ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-protocol-http2-0.14.2/.github/000077500000000000000000000000001400365004300170105ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/.github/workflows/000077500000000000000000000000001400365004300210455ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/.github/workflows/development.yml000066400000000000000000000015601400365004300241140ustar00rootroot00000000000000name: Development on: [push, pull_request] jobs: test: runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} strategy: matrix: os: - ubuntu - macos ruby: - 2.5 - 2.6 - 2.7 experimental: [false] env: [""] include: - os: ubuntu ruby: truffleruby experimental: true - os: ubuntu ruby: jruby experimental: true - os: ubuntu ruby: head experimental: true steps: - uses: actions/checkout@v2 - uses: ioquatix/setup-ruby@master with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: ${{matrix.env}} bundle exec rspec ruby-protocol-http2-0.14.2/.gitignore000066400000000000000000000002321400365004300174350ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /fuzz/*/output/ # rspec failure tracking .rspec_status /gems.locked .covered.db ruby-protocol-http2-0.14.2/.rspec000066400000000000000000000000671400365004300165700ustar00rootroot00000000000000--format documentation --warnings --require spec_helperruby-protocol-http2-0.14.2/README.md000066400000000000000000000062001400365004300167250ustar00rootroot00000000000000# Protocol::HTTP2 Provides a low-level implementation of the HTTP/2 protocol. [![Development Status](https://github.com/socketry/protocol-http2/workflows/Development/badge.svg)](https://github.com/socketry/protocol-http2/actions?workflow=Development) ## Installation Add this line to your application's Gemfile: ``` ruby gem 'protocol-http2' ``` And then execute: $ bundle Or install it yourself as: $ gem install protocol-http2 ## Usage Here is a basic HTTP/2 client: ``` ruby require 'async' require 'async/io/stream' require 'async/http/endpoint' require 'protocol/http2/client' Async do endpoint = Async::HTTP::Endpoint.parse("https://www.google.com/search?q=kittens") peer = endpoint.connect puts "Connected to #{peer.inspect}" # IO Buffering... stream = Async::IO::Stream.new(peer) framer = Protocol::HTTP2::Framer.new(stream) client = Protocol::HTTP2::Client.new(framer) puts "Sending connection preface..." client.send_connection_preface puts "Creating stream..." stream = client.create_stream headers = [ [":scheme", endpoint.scheme], [":method", "GET"], [":authority", "www.google.com"], [":path", endpoint.path], ["accept", "*/*"], ] puts "Sending request on stream id=#{stream.id} state=#{stream.state}..." stream.send_headers(nil, headers, Protocol::HTTP2::END_STREAM) puts "Waiting for response..." $count = 0 def stream.process_headers(frame) headers = super puts "Got response headers: #{headers} (#{frame.end_stream?})" end def stream.receive_data(frame) data = super $count += data.scan(/kittens/).count puts "Got response data: #{data.bytesize}" end until stream.closed? frame = client.read_frame end puts "Got #{$count} kittens!" puts "Closing client..." client.close end ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## License Released under the MIT license. Copyright, 2019, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). 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-protocol-http2-0.14.2/examples/000077500000000000000000000000001400365004300172665ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/examples/http2/000077500000000000000000000000001400365004300203275ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/examples/http2/request.rb000066400000000000000000000025421400365004300223470ustar00rootroot00000000000000 $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require 'async' require 'async/io/stream' require 'async/http/endpoint' require 'protocol/http2/client' Async do endpoint = Async::HTTP::Endpoint.parse("https://www.google.com/search?q=kittens") peer = endpoint.connect puts "Connected to #{peer.inspect}" # IO Buffering... stream = Async::IO::Stream.new(peer) framer = Protocol::HTTP2::Framer.new(stream) client = Protocol::HTTP2::Client.new(framer) puts "Sending connection preface..." client.send_connection_preface puts "Creating stream..." stream = client.create_stream headers = [ [":scheme", endpoint.scheme], [":method", "GET"], [":authority", "www.google.com"], [":path", endpoint.path], ["accept", "*/*"], ] puts "Sending request on stream id=#{stream.id} state=#{stream.state}..." stream.send_headers(nil, headers, Protocol::HTTP2::END_STREAM) puts "Waiting for response..." $count = 0 def stream.process_headers(frame) headers = super puts "Got response headers: #{headers} (#{frame.end_stream?})" end def stream.process_data(frame) if data = super $count += data.scan(/kittens/).size puts "Got response data: #{data.bytesize}" end end until stream.closed? frame = client.read_frame end puts "Got #{$count} kittens!" puts "Closing client..." client.close end puts "Exiting." ruby-protocol-http2-0.14.2/examples/http2/requests.rb000066400000000000000000000025531400365004300225340ustar00rootroot00000000000000 $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require 'async' require 'async/io/stream' require 'async/http/endpoint' require 'protocol/http2/client' queries = ["apple", "orange", "teapot", "async"] Async do endpoint = Async::HTTP::Endpoint.parse("https://www.google.com") peer = endpoint.connect stream = Async::IO::Stream.new(peer) framer = Protocol::HTTP2::Framer.new(stream) client = Protocol::HTTP2::Client.new(framer) puts "Sending connection preface..." client.send_connection_preface puts "Creating stream..." streams = queries.collect do |keyword| client.create_stream.tap do |stream| headers = [ [":scheme", endpoint.scheme], [":method", "GET"], [":authority", "www.google.com"], [":path", "/search?q=#{keyword}"], ["accept", "*/*"], ] puts "Sending request on stream id=#{stream.id} state=#{stream.state}..." stream.send_headers(nil, headers, Protocol::HTTP2::END_STREAM) def stream.process_headers(frame) headers = super puts "Stream #{self.id}: Got response headers: #{headers} (#{frame.end_stream?})" end def stream.receive_data(frame) data = super puts "Stream #{self.id}: Got response data: #{data.bytesize}" end end end until streams.all?{|stream| stream.closed?} frame = client.read_frame end puts "Closing client..." client.close end puts "Exiting." ruby-protocol-http2-0.14.2/fuzz/000077500000000000000000000000001400365004300164465ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/fuzz/framer/000077500000000000000000000000001400365004300177225ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/fuzz/framer/bake.rb000066400000000000000000000005221400365004300211500ustar00rootroot00000000000000 # Run the fuzz test. def run system("AFL_SKIP_BIN_CHECK=1 afl-fuzz -i input/ -o output/ -m 100 -- ruby script.rb") end def generate require_relative '../../lib/protocol/http2/framer' framer = Protocol::HTTP2::Framer.new($stdout) frame = Protocol::HTTP2::DataFrame.new frame.pack("Hello World") framer.write_frame(frame) end ruby-protocol-http2-0.14.2/fuzz/framer/input/000077500000000000000000000000001400365004300210615ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/fuzz/framer/input/data.txt000066400000000000000000000000241400365004300225270ustar00rootroot00000000000000 Hello Worldruby-protocol-http2-0.14.2/fuzz/framer/script.rb000077500000000000000000000007061400365004300215610ustar00rootroot00000000000000#!/usr/bin/env ruby require 'socket' require_relative '../../lib/protocol/http2/framer' def test framer = Protocol::HTTP2::Framer.new($stdin) while frame = framer.read_frame pp frame end rescue EOFError # Ignore. end if ENV["_"] =~ /afl/ require 'kisaten' Kisaten.crash_at [Exception], [EOFError, Protocol::HTTP2::FrameSizeError, Protocol::HTTP2::ProtocolError], Signal.list['USR1'] while Kisaten.loop 10_000 test end else test end ruby-protocol-http2-0.14.2/gems.rb000066400000000000000000000001731400365004300167310ustar00rootroot00000000000000source "https://rubygems.org" gemspec group :maintenance, optional: true do gem "bake-modernize" gem "bake-bundler" endruby-protocol-http2-0.14.2/lib/000077500000000000000000000000001400365004300162165ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/lib/protocol/000077500000000000000000000000001400365004300200575ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/lib/protocol/http2.rb000066400000000000000000000022621400365004300214470ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # 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. require_relative 'http2/version' require_relative 'http2/connection' ruby-protocol-http2-0.14.2/lib/protocol/http2/000077500000000000000000000000001400365004300211205ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/lib/protocol/http2/client.rb000066400000000000000000000046421400365004300227310ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection' module Protocol module HTTP2 class Client < Connection def initialize(framer) super(framer, 1) end def local_stream_id?(id) id.odd? end def remote_stream_id?(id) id.even? end def valid_remote_stream_id?(stream_id) stream_id.even? end def send_connection_preface(settings = []) if @state == :new @framer.write_connection_preface send_settings(settings) yield if block_given? read_frame do |frame| raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}" unless frame.is_a? SettingsFrame end else raise ProtocolError, "Cannot send connection preface in state #{@state}" end end def create_push_promise_stream raise ProtocolError, "Cannot create push promises from client!" end def receive_push_promise(frame) if frame.stream_id == 0 raise ProtocolError, "Cannot receive headers for stream 0!" end if stream = @streams[frame.stream_id] # This is almost certainly invalid: promised_stream, request_headers = stream.receive_push_promise(frame) if stream.closed? @streams.delete(stream.id) end return promised_stream, request_headers end end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/connection.rb000066400000000000000000000364701400365004300236160ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'framer' require_relative 'dependency' require_relative 'flow_controlled' require 'protocol/hpack' module Protocol module HTTP2 class Connection include FlowControlled def initialize(framer, local_stream_id) super() @state = :new # Hash(Integer, Stream) @streams = {} # Hash(Integer, Dependency) @dependency = Dependency.new(self, 0) @dependencies = {0 => @dependency} @framer = framer # The next stream id to use: @local_stream_id = local_stream_id # The biggest remote stream id seen thus far: @remote_stream_id = 0 @local_settings = PendingSettings.new @remote_settings = Settings.new @decoder = HPACK::Context.new @encoder = HPACK::Context.new @local_window = LocalWindow.new @remote_window = Window.new end def id 0 end def [] id if id.zero? self else @streams[id] end end # The size of a frame payload is limited by the maximum size that a receiver advertises in the SETTINGS_MAX_FRAME_SIZE setting. def maximum_frame_size @remote_settings.maximum_frame_size end def maximum_concurrent_streams [@local_settings.maximum_concurrent_streams, @remote_settings.maximum_concurrent_streams].min end attr :framer # Connection state (:new, :open, :closed). attr_accessor :state # Current settings value for local and peer attr_accessor :local_settings attr_accessor :remote_settings # Our window for receiving data. When we receive data, it reduces this window. # If the window gets too small, we must send a window update. attr :local_window # Our window for sending data. When we send data, it reduces this window. attr :remote_window # The highest stream_id that has been successfully accepted by this connection. attr :remote_stream_id # Whether the connection is effectively or actually closed. def closed? @state == :closed || @framer.nil? end def delete(id) @streams.delete(id) @dependencies[id]&.delete! end # Close the underlying framer and all streams. def close(error = nil) # The underlying socket may already be closed by this point. @streams.each_value{|stream| stream.close(error)} @streams.clear if @framer @framer.close @framer = nil end end def encode_headers(headers, buffer = String.new.b) HPACK::Compressor.new(buffer, @encoder, table_size_limit: @remote_settings.header_table_size).encode(headers) end def decode_headers(data) HPACK::Decompressor.new(data, @decoder, table_size_limit: @local_settings.header_table_size).decode end # Streams are identified with an unsigned 31-bit integer. Streams initiated by a client MUST use odd-numbered stream identifiers; those initiated by the server MUST use even-numbered stream identifiers. A stream identifier of zero (0x0) is used for connection control messages; the stream identifier of zero cannot be used to establish a new stream. def next_stream_id id = @local_stream_id @local_stream_id += 2 return id end attr :streams attr :dependencies attr :dependency # 6.8. GOAWAY # There is an inherent race condition between an endpoint starting new streams and the remote sending a GOAWAY frame. To deal with this case, the GOAWAY contains the stream identifier of the last peer-initiated stream that was or might be processed on the sending endpoint in this connection. For instance, if the server sends a GOAWAY frame, the identified stream is the highest-numbered stream initiated by the client. # Once sent, the sender will ignore frames sent on streams initiated by the receiver if the stream has an identifier higher than the included last stream identifier. Receivers of a GOAWAY frame MUST NOT open additional streams on the connection, although a new connection can be established for new streams. def ignore_frame?(frame) if self.closed? # puts "ignore_frame? #{frame.stream_id} -> #{valid_remote_stream_id?(frame.stream_id)} > #{@remote_stream_id}" if valid_remote_stream_id?(frame.stream_id) return frame.stream_id > @remote_stream_id end end end # Reads one frame from the network and processes. Processing the frame updates the state of the connection and related streams. If the frame triggers an error, e.g. a protocol error, the connection will typically emit a goaway frame and re-raise the exception. You should continue processing frames until the underlying connection is closed. def read_frame frame = @framer.read_frame(@local_settings.maximum_frame_size) # puts "#{self.class} #{@state} read_frame: class=#{frame.class} stream_id=#{frame.stream_id} flags=#{frame.flags} length=#{frame.length} (remote_stream_id=#{@remote_stream_id})" # puts "Windows: local_window=#{@local_window.inspect}; remote_window=#{@remote_window.inspect}" return if ignore_frame?(frame) yield frame if block_given? frame.apply(self) return frame rescue GoawayError => error # Go directly to jail. Do not pass go, do not collect $200. raise rescue ProtocolError => error send_goaway(error.code || PROTOCOL_ERROR, error.message) raise rescue HPACK::Error => error send_goaway(COMPRESSION_ERROR, error.message) raise end def send_settings(changes) @local_settings.append(changes) frame = SettingsFrame.new frame.pack(changes) write_frame(frame) end # Transition the connection into the closed state. def close! @state = :closed return self end # Tell the remote end that the connection is being shut down. If the `error_code` is 0, this is a graceful shutdown. The other end of the connection should not make any new streams, but existing streams may be completed. def send_goaway(error_code = 0, message = "") frame = GoawayFrame.new frame.pack @remote_stream_id, error_code, message write_frame(frame) ensure self.close! end def receive_goaway(frame) # We capture the last stream that was processed. @remote_stream_id, error_code, message = frame.unpack self.close! if error_code != 0 # Shut down immediately. raise GoawayError.new(message, error_code) end end def write_frame(frame) @framer.write_frame(frame) end def write_frames yield @framer end def update_local_settings(changes) capacity = @local_settings.initial_window_size @streams.each_value do |stream| stream.local_window.capacity = capacity end @local_window.desired = capacity end def update_remote_settings(changes) capacity = @remote_settings.initial_window_size @streams.each_value do |stream| stream.remote_window.capacity = capacity end end # In addition to changing the flow-control window for streams that are not yet active, a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows (that is, streams in the "open" or "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value. # # @return [Boolean] whether the frame was an acknowledgement def process_settings(frame) if frame.acknowledgement? # The remote end has confirmed the settings have been received: changes = @local_settings.acknowledge update_local_settings(changes) return true else # The remote end is updating the settings, we reply with acknowledgement: reply = frame.acknowledge write_frame(reply) changes = frame.unpack @remote_settings.update(changes) update_remote_settings(changes) return false end end def open! @state = :open return self end def receive_settings(frame) if @state == :new # We transition to :open when we receive acknowledgement of first settings frame: open! if process_settings(frame) elsif @state != :closed process_settings(frame) else raise ProtocolError, "Cannot receive settings in state #{@state}" end end def send_ping(data) if @state != :closed frame = PingFrame.new frame.pack data write_frame(frame) else raise ProtocolError, "Cannot send ping in state #{@state}" end end def receive_ping(frame) if @state != :closed if frame.stream_id != 0 raise ProtocolError, "Ping received for non-zero stream!" end unless frame.acknowledgement? reply = frame.acknowledge write_frame(reply) end else raise ProtocolError, "Cannot receive ping in state #{@state}" end end def receive_data(frame) update_local_window(frame) if stream = @streams[frame.stream_id] stream.receive_data(frame) elsif closed_stream_id?(frame.stream_id) # This can occur if one end sent a stream reset, while the other end was sending a data frame. It's mostly harmless. else raise ProtocolError, "Cannot receive data for stream id #{frame.stream_id}" end end def valid_remote_stream_id? false end # Accept an incoming stream from the other side of the connnection. # On the server side, we accept requests. def accept_stream(stream_id, &block) unless valid_remote_stream_id?(stream_id) raise ProtocolError, "Invalid stream id: #{stream_id}" end create_stream(stream_id, &block) end # Accept an incoming push promise from the other side of the connection. # On the client side, we accept push promise streams. # On the server side, existing streams create push promise streams. def accept_push_promise_stream(stream_id, &block) accept_stream(stream_id, &block) end # Create a stream, defaults to an outgoing stream. # On the client side, we create requests. # @return [Stream] the created stream. def create_stream(id = next_stream_id, &block) if block_given? return yield(self, id) else return Stream.create(self, id) end end def create_push_promise_stream(&block) create_stream(&block) end # On the server side, starts a new request. def receive_headers(frame) stream_id = frame.stream_id if stream_id.zero? raise ProtocolError, "Cannot receive headers for stream 0!" end if stream = @streams[stream_id] stream.receive_headers(frame) else if stream_id <= @remote_stream_id raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!" end if @streams.size < self.maximum_concurrent_streams stream = accept_stream(stream_id) @remote_stream_id = stream_id stream.receive_headers(frame) else raise ProtocolError, "Exceeded maximum concurrent streams" end end end def send_priority(stream_id, priority) frame = PriorityFrame.new(stream_id) frame.pack(priority) write_frame(frame) end def idle_stream_id?(id) if id.even? # Server-initiated streams are even. if @local_stream_id.even? id >= @local_stream_id else id > @remote_stream_id end elsif id.odd? # Client-initiated streams are odd. if @local_stream_id.odd? id >= @local_stream_id else id > @remote_stream_id end end end # Sets the priority for an incoming stream. def receive_priority(frame) if dependency = @dependencies[frame.stream_id] dependency.receive_priority(frame) elsif idle_stream_id?(frame.stream_id) Dependency.create(self, frame.stream_id, frame.unpack) end end def receive_push_promise(frame) raise ProtocolError, "Unable to receive push promise!" end def client_stream_id?(id) id.odd? end def server_stream_id?(id) id.even? end def closed_stream_id?(id) if id.zero? # The connection "stream id" can never be closed: false elsif id.even? # Server-initiated streams are even. if @local_stream_id.even? id < @local_stream_id else id <= @remote_stream_id end elsif id.odd? # Client-initiated streams are odd. if @local_stream_id.odd? id < @local_stream_id else id <= @remote_stream_id end end end def receive_reset_stream(frame) if frame.connection? raise ProtocolError, "Cannot reset connection!" elsif stream = @streams[frame.stream_id] stream.receive_reset_stream(frame) elsif closed_stream_id?(frame.stream_id) # Ignore. else raise StreamClosed, "Cannot reset stream #{frame.stream_id}" end end # Traverse active streams in order of priority and allow them to consume the available flow-control window. # @param amount [Integer] the amount of data to write. Defaults to the current window capacity. def consume_window(size = self.available_size) # Return if there is no window to consume: return unless size > 0 # Console.logger.debug(self) do |buffer| # @dependencies.each do |id, dependency| # buffer.puts "- #{dependency}" # end # # buffer.puts # # @dependency.print_hierarchy(buffer) # end @dependency.consume_window(size) end def receive_window_update(frame) if frame.connection? super self.consume_window elsif stream = @streams[frame.stream_id] begin stream.receive_window_update(frame) rescue ProtocolError => error stream.send_reset_stream(error.code) end elsif closed_stream_id?(frame.stream_id) # Ignore. else # Receiving any frame other than HEADERS or PRIORITY on a stream in this state (idle) MUST be treated as a connection error of type PROTOCOL_ERROR. raise ProtocolError, "Cannot update window of idle stream #{frame.stream_id}" end end def receive_continuation(frame) raise ProtocolError, "Received unexpected continuation: #{frame.class}" end def receive_frame(frame) # ignore. end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/continuation_frame.rb000066400000000000000000000070141400365004300253330ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' module Protocol module HTTP2 module Continued def initialize(*) super @continuation = nil end def end_headers? flag_set?(END_HEADERS) end def read(stream, maximum_frame_size) super unless end_headers? continuation = ContinuationFrame.new continuation.read_header(stream) # We validate the frame type here: unless continuation.valid_type? raise ProtocolError, "Invalid frame type: #{@type}!" end if continuation.stream_id != @stream_id raise ProtocolError, "Invalid stream id: #{continuation.stream_id} for continuation of stream id: #{@stream_id}!" end continuation.read(stream, maximum_frame_size) @continuation = continuation end end def write(stream) super if continuation = self.continuation continuation.write(stream) end end attr_accessor :continuation def pack(data, **options) maximum_size = options[:maximum_size] if maximum_size and data.bytesize > maximum_size clear_flags(END_HEADERS) super(data.byteslice(0, maximum_size), **options) remainder = data.byteslice(maximum_size, data.bytesize-maximum_size) @continuation = ContinuationFrame.new @continuation.pack(remainder, maximum_size: maximum_size) else set_flags(END_HEADERS) super data, **options @continuation = nil end end def unpack if @continuation.nil? super else super + @continuation.unpack end end end # The CONTINUATION frame is used to continue a sequence of header block fragments. Any number of CONTINUATION frames can be sent, as long as the preceding frame is on the same stream and is a HEADERS, PUSH_PROMISE, or CONTINUATION frame without the END_HEADERS flag set. # # +---------------------------------------------------------------+ # | Header Block Fragment (*) ... # +---------------------------------------------------------------+ # class ContinuationFrame < Frame include Continued TYPE = 0x9 # This is only invoked if the continuation is received out of the normal flow. def apply(connection) connection.receive_continuation(self) end def inspect "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length}b>" end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/data_frame.rb000066400000000000000000000045401400365004300235330ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # Copyright, 2013, by 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. require_relative 'frame' require_relative 'padded' module Protocol module HTTP2 # DATA frames convey arbitrary, variable-length sequences of octets associated with a stream. One or more DATA frames are used, for instance, to carry HTTP request or response payloads. # # DATA frames MAY also contain padding. Padding can be added to DATA frames to obscure the size of messages. # # +---------------+ # |Pad Length? (8)| # +---------------+-----------------------------------------------+ # | Data (*) ... # +---------------------------------------------------------------+ # | Padding (*) ... # +---------------------------------------------------------------+ # class DataFrame < Frame include Padded TYPE = 0x0 def end_stream? flag_set?(END_STREAM) end def pack(data, *arguments, **options) if data super else @length = 0 set_flags(END_STREAM) end end def apply(connection) connection.receive_data(self) end def inspect "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length}b>" end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/dependency.rb000066400000000000000000000142141400365004300235650ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. module Protocol module HTTP2 DEFAULT_WEIGHT = 16 class Dependency def self.create(connection, id, priority = nil) weight = DEFAULT_WEIGHT exclusive = false if priority if parent = connection.dependencies[priority.stream_dependency] exclusive = priority.exclusive end weight = priority.weight end if parent.nil? parent = connection.dependency end dependency = self.new(connection, id, weight) connection.dependencies[id] = dependency if exclusive parent.exclusive_child(dependency) else parent.add_child(dependency) end return dependency end def initialize(connection, id, weight = DEFAULT_WEIGHT) @connection = connection @id = id @parent = nil @children = nil @weight = weight # Cache of any associated stream: @stream = nil # Cache of children for window allocation: @total_weight = 0 @ordered_children = nil end def <=> other @weight <=> other.weight end # The connection this stream belongs to. attr :connection # Stream ID (odd for client initiated streams, even otherwise). attr :id # The parent dependency. attr_accessor :parent # The dependent children. attr_accessor :children # The weight of the stream relative to other siblings. attr_accessor :weight def stream @stream ||= @connection.streams[@id] end def clear_cache! @ordered_children = nil end def delete! @connection.dependencies.delete(@id) @parent.remove_child(self) @children&.each do |id, child| parent.add_child(child) end @connection = nil @parent = nil @children = nil end def add_child(dependency) @children ||= {} @children[dependency.id] = dependency dependency.parent = self self.clear_cache! end def remove_child(dependency) @children&.delete(dependency.id) self.clear_cache! end # An exclusive flag allows for the insertion of a new level of dependencies. The exclusive flag causes the stream to become the sole dependency of its parent stream, causing other dependencies to become dependent on the exclusive stream. # @param parent [Dependency] the dependency which will be inserted, taking control of all current children. def exclusive_child(parent) parent.children = @children @children&.each_value do |child| child.parent = parent end parent.clear_cache! @children = {parent.id => parent} self.clear_cache! parent.parent = self end def process_priority(priority) dependent_id = priority.stream_dependency if dependent_id == @id raise ProtocolError, "Stream priority for stream id #{@id} cannot depend on itself!" end @weight = priority.weight # We essentially ignore `dependent_id` if the dependency does not exist: if parent = @connection.dependencies[dependent_id] if priority.exclusive @parent.remove_child(self) parent.exclusive_child(self) elsif !@parent.equal?(parent) @parent.remove_child(self) parent.add_child(self) end end end # Change the priority of the stream both locally and remotely. def priority= priority send_priority(priority) process_priority(priority) end # The current local priority of the stream. def priority(exclusive = false) Priority.new(exclusive, @parent.id, @weight) end def send_priority(priority) @connection.send_priority(@id, priority) end def receive_priority(frame) self.process_priority(frame.unpack) end def total_weight self.orderd_children return @total_weight end def ordered_children unless @ordered_children if @children and !@children.empty? @ordered_children = @children.values.sort @total_weight = @ordered_children.sum(&:weight) end end return @ordered_children end # Traverse active streams in order of priority and allow them to consume the available flow-control window. # @param amount [Integer] the amount of data to write. Defaults to the current window capacity. def consume_window(size) # If there is an associated stream, give it priority: if stream = self.stream return if stream.window_updated(size) end # Otherwise, allow the dependent children to use up the available window: self.ordered_children&.each do |child| # Compute the proportional allocation: allocated = (child.weight * size) / @total_weight child.consume_window(allocated) if allocated > 0 end end def to_s "\#<#{self.class} id=#{@id} parent id=#{@parent&.id} weight=#{@weight} #{@children&.size || 0} children>" end def print_hierarchy(buffer, indent: 0) buffer.puts "#{" " * indent}#{self}" @children&.each_value do |child| child.print_hierarchy(buffer, indent: indent+1) end end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/error.rb000066400000000000000000000101321400365004300225730ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # 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. require 'protocol/http/error' module Protocol module HTTP2 # Status codes as defined by . class Error < HTTP::Error # The associated condition is not a result of an error. For example, a GOAWAY might include this code to indicate graceful shutdown of a connection. NO_ERROR = 0x0 # The endpoint detected an unspecific protocol error. This error is for use when a more specific error code is not available. PROTOCOL_ERROR = 0x1 # The endpoint encountered an unexpected internal error. INTERNAL_ERROR = 0x2 # The endpoint detected that its peer violated the flow-control protocol. FLOW_CONTROL_ERROR = 0x3 # The endpoint sent a SETTINGS frame but did not receive a response in a timely manner. SETTINGS_TIMEOUT = 0x4 # The endpoint received a frame after a stream was half-closed. STREAM_CLOSED = 0x5 # The endpoint received a frame with an invalid size. FRAME_SIZE_ERROR = 0x6 # The endpoint refused the stream prior to performing any application processing. REFUSED_STREAM = 0x7 # Used by the endpoint to indicate that the stream is no longer needed. CANCEL = 0x8 # The endpoint is unable to maintain the header compression context for the connection. COMPRESSION_ERROR = 0x9 # The connection established in response to a CONNECT request was reset or abnormally closed. CONNECT_ERROR = 0xA # The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load. ENHANCE_YOUR_CALM = 0xB # The underlying transport has properties that do not meet minimum security requirements. INADEQUATE_SECURITY = 0xC # The endpoint requires that HTTP/1.1 be used instead of HTTP/2. HTTP_1_1_REQUIRED = 0xD 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 def initialize(message, code = PROTOCOL_ERROR) super(message) @code = code end attr :code end class StreamError < ProtocolError end class StreamClosed < StreamError def initialize(message) super message, STREAM_CLOSED end end class HeaderError < StreamClosed def initialize(message) super(message) end end class GoawayError < ProtocolError end # When the frame payload does not match expectations. class FrameSizeError < ProtocolError def initialize(message) super message, FRAME_SIZE_ERROR end end # Raised on invalid flow control frame or command. class FlowControlError < ProtocolError def initialize(message) super message, FLOW_CONTROL_ERROR end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/flow_controlled.rb000066400000000000000000000072261400365004300246500ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'window_update_frame' module Protocol module HTTP2 module FlowControlled def available_size @remote_window.available end # This could be negative if the window has been overused due to a change in initial window size. def available_frame_size(maximum_frame_size = self.maximum_frame_size) available_size = self.available_size # puts "available_size=#{available_size} maximum_frame_size=#{maximum_frame_size}" if available_size < maximum_frame_size return available_size else return maximum_frame_size end end # Keep track of the amount of data sent, and fail if is too much. def consume_remote_window(frame) amount = frame.length # 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. if amount.zero? and frame.end_stream? # It's okay, we can send. No need to consume, it's empty anyway. elsif amount >= 0 and amount <= @remote_window.available @remote_window.consume(amount) else raise FlowControlError, "Trying to send #{frame.length} bytes, exceeded window size: #{@remote_window.available} (#{@remote_window})" end end def update_local_window(frame) consume_local_window(frame) request_window_update end def consume_local_window(frame) # For flow-control calculations, the 9-octet frame header is not counted. amount = frame.length @local_window.consume(amount) end def request_window_update if @local_window.limited? self.send_window_update(@local_window.wanted) end end # Notify the remote end that we are prepared to receive more data: def send_window_update(window_increment) frame = WindowUpdateFrame.new(self.id) frame.pack window_increment write_frame(frame) @local_window.expand(window_increment) end def receive_window_update(frame) amount = frame.unpack # Async.logger.info(self) {"expanding remote_window=#{@remote_window} by #{amount}"} if amount != 0 @remote_window.expand(amount) else raise ProtocolError, "Invalid window size increment: #{amount}!" end # puts "expanded remote_window=#{@remote_window} by #{amount}" end # The window has been expanded by the given amount. # @param size [Integer] the maximum amount of data to send. # @return [Boolean] whether the window update was used or not. def window_updated(size) return false end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/frame.rb000066400000000000000000000142211400365004300225370ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # Copyright, 2013, by 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. require_relative 'error' module Protocol module HTTP2 END_STREAM = 0x1 END_HEADERS = 0x4 PADDED = 0x8 PRIORITY = 0x20 MAXIMUM_ALLOWED_WINDOW_SIZE = 0x7FFFFFFF MINIMUM_ALLOWED_FRAME_SIZE = 0x4000 MAXIMUM_ALLOWED_FRAME_SIZE = 0xFFFFFF class Frame include Comparable # Stream Identifier cannot be bigger than this: # https://http2.github.stream/http2-spec/#rfc.section.4.1 VALID_STREAM_ID = 0..0x7fffffff # The absolute maximum bounds for the length field: VALID_LENGTH = 0..0xffffff # Used for generating 24-bit frame length: LENGTH_HISHIFT = 16 LENGTH_LOMASK = 0xFFFF # @param length [Integer] the length of the payload, or nil if the header has not been read yet. def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil) @length = length @type = type @flags = flags @stream_id = stream_id @payload = payload end def valid_type? @type == self.class::TYPE end def <=> other to_ary <=> other.to_ary end def to_ary [@length, @type, @flags, @stream_id, @payload] end # The generic frame header uses the following binary representation: # # +-----------------------------------------------+ # | Length (24) | # +---------------+---------------+---------------+ # | Type (8) | Flags (8) | # +-+-------------+---------------+-------------------------------+ # |R| Stream Identifier (31) | # +=+=============================================================+ # | Frame Payload (0...) ... # +---------------------------------------------------------------+ attr_accessor :length attr_accessor :type attr_accessor :flags attr_accessor :stream_id attr_accessor :payload def unpack @payload end def pack(payload, maximum_size: nil) @payload = payload @length = payload.bytesize if maximum_size and @length > maximum_size raise ProtocolError, "Frame length #{@length} bigger than maximum allowed: #{maximum_size}" end end def set_flags(mask) @flags |= mask end def clear_flags(mask) @flags &= ~mask end def flag_set?(mask) @flags & mask != 0 end # Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any # frame addressed to stream ID = 0. # # @return [Boolean] def connection? @stream_id.zero? end HEADER_FORMAT = 'CnCCN'.freeze STREAM_ID_MASK = 0x7fffffff # Generates common 9-byte frame header. # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1 # # @return [String] def header unless VALID_LENGTH.include? @length raise ProtocolError, "Invalid frame size: #{@length.inspect}" end unless VALID_STREAM_ID.include? @stream_id raise ProtocolError, "Invalid stream identifier: #{@stream_id.inspect}" end [ # These are guaranteed correct due to the length check above. @length >> LENGTH_HISHIFT, @length & LENGTH_LOMASK, @type, @flags, @stream_id ].pack(HEADER_FORMAT) end # Decodes common 9-byte header. # # @param buffer [String] def self.parse_header(buffer) length_hi, length_lo, type, flags, stream_id = buffer.unpack(HEADER_FORMAT) length = (length_hi << LENGTH_HISHIFT) | length_lo stream_id = stream_id & STREAM_ID_MASK # puts "parse_header: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id}" return length, type, flags, stream_id end def read_header(stream) if buffer = stream.read(9) and buffer.bytesize == 9 @length, @type, @flags, @stream_id = Frame.parse_header(buffer) # puts "read_header: #{@length} #{@type} #{@flags} #{@stream_id}" else raise EOFError, "Could not read frame header!" end end def read_payload(stream) if payload = stream.read(@length) and payload.bytesize == @length @payload = payload else raise EOFError, "Could not read frame payload!" end end def read(stream, maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE) read_header(stream) unless @length if @length > maximum_frame_size raise FrameSizeError, "#{self.class} (type=#{@type}) frame length #{@length} exceeds maximum frame size #{maximum_frame_size}!" end read_payload(stream) end def write_header(stream) stream.write self.header end def write_payload(stream) stream.write(@payload) if @payload end def write(stream) if @payload and @length != @payload.bytesize raise ProtocolError, "Invalid payload size: #{@length} != #{@payload.bytesize}" end self.write_header(stream) self.write_payload(stream) end def apply(connection) connection.receive_frame(self) end def inspect "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{self.unpack}>" end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/framer.rb000066400000000000000000000072611400365004300227270ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # Copyright, 2013, by 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. require_relative 'error' require_relative 'data_frame' require_relative 'headers_frame' require_relative 'priority_frame' require_relative 'reset_stream_frame' require_relative 'settings_frame' require_relative 'push_promise_frame' require_relative 'ping_frame' require_relative 'goaway_frame' require_relative 'window_update_frame' require_relative 'continuation_frame' module Protocol module HTTP2 # HTTP/2 frame type mapping as defined by the spec FRAMES = [ DataFrame, HeadersFrame, PriorityFrame, ResetStreamFrame, SettingsFrame, PushPromiseFrame, PingFrame, GoawayFrame, WindowUpdateFrame, ContinuationFrame, ].freeze # 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 class Framer def initialize(stream, frames = FRAMES) @stream = stream @frames = frames end def close @stream.close end def closed? @stream.closed? end def write_connection_preface @stream.write(CONNECTION_PREFACE_MAGIC) end def read_connection_preface string = @stream.read(CONNECTION_PREFACE_MAGIC.bytesize) unless string == CONNECTION_PREFACE_MAGIC raise HandshakeError, "Invalid connection preface: #{string.inspect}" end return string end # @return [Frame] the frame that has been read from the underlying IO. # @raise if the underlying IO fails for some reason. def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE) # Read the header: length, type, flags, stream_id = read_header # Async.logger.debug(self) {"read_frame: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id} -> klass=#{@frames[type].inspect}"} # Allocate the frame: klass = @frames[type] || Frame frame = klass.new(stream_id, flags, type, length) # Read the payload: frame.read(@stream, maximum_frame_size) # Async.logger.debug(self, name: "read") {frame.inspect} return frame end def write_frame(frame) # Async.logger.debug(self, name: "write") {frame.inspect} frame.write(@stream) # Don't call @stream.flush here because it can cause significant contention if there is a semaphore around this method. # @stream.flush return frame end def read_header if buffer = @stream.read(9) if buffer.bytesize == 9 return Frame.parse_header(buffer) end end raise EOFError, "Could not read frame header!" end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/goaway_frame.rb000066400000000000000000000046411400365004300241130ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' module Protocol module HTTP2 # The GOAWAY frame is used to initiate shutdown of a connection or to signal serious error conditions. GOAWAY allows an endpoint to gracefully stop accepting new streams while still finishing processing of previously established streams. This enables administrative actions, like server maintenance. # # +-+-------------------------------------------------------------+ # |R| Last-Stream-ID (31) | # +-+-------------------------------------------------------------+ # | Error Code (32) | # +---------------------------------------------------------------+ # | Additional Debug Data (*) | # +---------------------------------------------------------------+ # class GoawayFrame < Frame TYPE = 0x7 FORMAT = "NN" def connection? true end def unpack data = super last_stream_id, error_code = data.unpack(FORMAT) return last_stream_id, error_code, data.slice(8, data.bytesize-8) end def pack(last_stream_id, error_code, data) super [last_stream_id, error_code].pack(FORMAT) + data end def apply(connection) connection.receive_goaway(self) end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/headers_frame.rb000066400000000000000000000056401400365004300242370ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' require_relative 'padded' require_relative 'continuation_frame' require_relative 'priority_frame' module Protocol module HTTP2 # The HEADERS frame is used to open a stream, and additionally carries a header block fragment. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state. # # +---------------+ # |Pad Length? (8)| # +-+-------------+-----------------------------------------------+ # |E| Stream Dependency? (31) | # +-+-------------+-----------------------------------------------+ # | Weight? (8) | # +-+-------------+-----------------------------------------------+ # | Header Block Fragment (*) ... # +---------------------------------------------------------------+ # | Padding (*) ... # +---------------------------------------------------------------+ # class HeadersFrame < Frame include Continued, Padded TYPE = 0x1 def priority? flag_set?(PRIORITY) end def end_stream? flag_set?(END_STREAM) end def unpack data = super if priority? priority = Priority.unpack(data) data = data.byteslice(5, data.bytesize - 5) end return priority, data end def pack(priority, data, *arguments, **options) buffer = String.new.b if priority buffer << priority.pack set_flags(PRIORITY) else clear_flags(PRIORITY) end buffer << data super(buffer, *arguments, **options) end def apply(connection) connection.receive_headers(self) end def inspect "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length}b>" end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/padded.rb000066400000000000000000000046661400365004300227020ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # Copyright, 2013, by 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. require_relative 'frame' module Protocol module HTTP2 # Certain frames can have padding: # https://http2.github.io/http2-spec/#padding # # +---------------+ # |Pad Length? (8)| # +---------------+-----------------------------------------------+ # | Data (*) ... # +---------------------------------------------------------------+ # | Padding (*) ... # +---------------------------------------------------------------+ # module Padded def padded? flag_set?(PADDED) end def pack(data, padding_size: nil, maximum_size: nil) if padding_size set_flags(PADDED) buffer = String.new.b buffer << padding_size.chr buffer << data if padding_size > 1 buffer << "\0" * (padding_size - 1) end super buffer else clear_flags(PADDED) super data end end def unpack if padded? padding_size = @payload[0].ord data_size = (@payload.bytesize - 1) - padding_size if data_size < 0 raise ProtocolError, "Invalid padding length: #{padding_size}" end return @payload.byteslice(1, data_size) else return @payload end end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/ping_frame.rb000066400000000000000000000046401400365004300235600ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' module Protocol module HTTP2 ACKNOWLEDGEMENT = 0x1 module Acknowledgement def acknowledgement? flag_set?(ACKNOWLEDGEMENT) end def acknowledge frame = self.class.new frame.length = 0 frame.set_flags(ACKNOWLEDGEMENT) return frame end end # The PING frame is a mechanism for measuring a minimal round-trip time from the sender, as well as determining whether an idle connection is still functional. PING frames can be sent from any endpoint. # # +---------------------------------------------------------------+ # | | # | Opaque Data (64) | # | | # +---------------------------------------------------------------+ # class PingFrame < Frame TYPE = 0x6 include Acknowledgement def connection? true end def apply(connection) connection.receive_ping(self) end def acknowledge frame = super frame.pack self.unpack return frame end def read_payload(stream) super if @length != 8 raise FrameSizeError, "Invalid frame length: #{@length} != 8!" end end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/priority_frame.rb000066400000000000000000000066551400365004300245140ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' module Protocol module HTTP2 VALID_WEIGHT = (1..256) # Stream Dependency: A 31-bit stream identifier for the stream that # this stream depends on (see Section 5.3). This field is only # present if the PRIORITY flag is set. class Priority < Struct.new(:exclusive, :stream_dependency, :weight) FORMAT = "NC".freeze EXCLUSIVE = 1 << 31 # All streams are initially assigned a non-exclusive dependency on stream 0x0. Pushed streams (Section 8.2) initially depend on their associated stream. In both cases, streams are assigned a default weight of 16. def self.default(stream_dependency = 0, weight = 16) self.new(false, stream_dependency, weight) end def self.unpack(data) stream_dependency, weight = data.unpack(FORMAT) # Weight: An unsigned 8-bit integer representing a priority weight for the stream (see Section 5.3). Add one to the value to obtain a weight between 1 and 256. This field is only present if the PRIORITY flag is set. return self.new(stream_dependency & EXCLUSIVE != 0, stream_dependency & ~EXCLUSIVE, weight + 1) end def pack if exclusive stream_dependency = self.stream_dependency | EXCLUSIVE else stream_dependency = self.stream_dependency end return [stream_dependency, self.weight - 1].pack(FORMAT) end def weight= value if VALID_WEIGHT.include?(value) super else raise ArgumentError, "Weight #{value} must be between 1-256!" end end end # The PRIORITY frame specifies the sender-advised priority of a stream. It can be sent in any stream state, including idle or closed streams. # # +-+-------------------------------------------------------------+ # |E| Stream Dependency (31) | # +-+-------------+-----------------------------------------------+ # | Weight (8) | # +-+-------------+ # class PriorityFrame < Frame TYPE = 0x2 def priority Priority.unpack(@payload) end def pack priority super priority.pack end def unpack Priority.unpack(super) end def apply(connection) connection.receive_priority(self) end def read_payload(stream) super if @length != 5 raise FrameSizeError, "Invalid frame length" end end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/push_promise_frame.rb000066400000000000000000000050101400365004300253300ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' require_relative 'padded' require_relative 'continuation_frame' module Protocol module HTTP2 # The PUSH_PROMISE frame is used to notify the peer endpoint in advance of streams the sender intends to initiate. The PUSH_PROMISE frame includes the unsigned 31-bit identifier of the stream the endpoint plans to create along with a set of headers that provide additional context for the stream. # # +---------------+ # |Pad Length? (8)| # +-+-------------+-----------------------------------------------+ # |R| Promised Stream ID (31) | # +-+-----------------------------+-------------------------------+ # | Header Block Fragment (*) ... # +---------------------------------------------------------------+ # | Padding (*) ... # +---------------------------------------------------------------+ # class PushPromiseFrame < Frame include Continued, Padded TYPE = 0x5 FORMAT = "N".freeze def unpack data = super stream_id = data.unpack1(FORMAT) return stream_id, data.byteslice(4, data.bytesize - 4) end def pack(stream_id, data, *arguments, **options) super([stream_id].pack(FORMAT) + data, *arguments, **options) end def apply(connection) connection.receive_push_promise(self) end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/reset_stream_frame.rb000066400000000000000000000044601400365004300253200ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' module Protocol module HTTP2 NO_ERROR = 0 PROTOCOL_ERROR = 1 INTERNAL_ERROR = 2 FLOW_CONTROL_ERROR = 3 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 # The RST_STREAM frame allows for immediate termination of a stream. RST_STREAM is sent to request cancellation of a stream or to indicate that an error condition has occurred. # # +---------------------------------------------------------------+ # | Error Code (32) | # +---------------------------------------------------------------+ # class ResetStreamFrame < Frame TYPE = 0x3 FORMAT = "N".freeze def unpack @payload.unpack1(FORMAT) end def pack(error_code = NO_ERROR) @payload = [error_code].pack(FORMAT) @length = @payload.bytesize end def apply(connection) connection.receive_reset_stream(self) end def read_payload(stream) super if @length != 4 raise FrameSizeError, "Invalid frame length" end end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/server.rb000066400000000000000000000040311400365004300227510ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection' module Protocol module HTTP2 class Server < Connection def initialize(framer) super(framer, 2) end def local_stream_id?(id) id.even? end def remote_stream_id?(id) id.odd? end def valid_remote_stream_id?(stream_id) stream_id.odd? end def read_connection_preface(settings = []) if @state == :new @framer.read_connection_preface send_settings(settings) read_frame do |frame| raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}" unless frame.is_a? SettingsFrame end else raise ProtocolError, "Cannot send connection preface in state #{@state}" end end def accept_push_promise_stream(stream_id, &block) raise ProtocolError, "Cannot accept push promises on server!" end def enable_push? @remote_settings.enable_push? end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/settings_frame.rb000066400000000000000000000174321400365004300244660ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'ping_frame' module Protocol module HTTP2 class Settings HEADER_TABLE_SIZE = 0x1 ENABLE_PUSH = 0x2 MAXIMUM_CONCURRENT_STREAMS = 0x3 INITIAL_WINDOW_SIZE = 0x4 MAXIMUM_FRAME_SIZE = 0x5 MAXIMUM_HEADER_LIST_SIZE = 0x6 ENABLE_CONNECT_PROTOCOL = 0x8 # Allows the sender to inform the remote endpoint of the maximum size of the header compression table used to decode header blocks, in octets. attr_accessor :header_table_size # This setting can be used to disable server push. An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0. attr :enable_push def enable_push= value if value == 0 or value == 1 @enable_push = value else raise ProtocolError, "Invalid value for enable_push: #{value}" end end def enable_push? @enable_push == 1 end # Indicates the maximum number of concurrent streams that the sender will allow. attr_accessor :maximum_concurrent_streams # Indicates the sender's initial window size (in octets) for stream-level flow control. attr :initial_window_size def initial_window_size= value if value <= MAXIMUM_ALLOWED_WINDOW_SIZE @initial_window_size = value else # An endpoint MUST treat a change to SETTINGS_INITIAL_WINDOW_SIZE that causes any flow-control window to exceed the maximum size as a connection error of type FLOW_CONTROL_ERROR. raise FlowControlError, "Invalid value for initial_window_size: #{value} > #{MAXIMUM_ALLOWED_WINDOW_SIZE}" end end # Indicates the size of the largest frame payload that the sender is willing to receive, in octets. attr :maximum_frame_size def maximum_frame_size= value if value > MAXIMUM_ALLOWED_FRAME_SIZE raise ProtocolError, "Invalid value for maximum_frame_size: #{value} > #{MAXIMUM_ALLOWED_FRAME_SIZE}" elsif value < MINIMUM_ALLOWED_FRAME_SIZE raise ProtocolError, "Invalid value for maximum_frame_size: #{value} < #{MINIMUM_ALLOWED_FRAME_SIZE}" else @maximum_frame_size = value end end # This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets. attr_accessor :maximum_header_list_size attr :enable_connect_protocol def enable_connect_protocol= value if value == 0 or value == 1 @enable_connect_protocol = value else raise ProtocolError, "Invalid value for enable_connect_protocol: #{value}" end end def enable_connect_protocol? @enable_connect_protocol == 1 end def initialize # These limits are taken from the RFC: # https://tools.ietf.org/html/rfc7540#section-6.5.2 @header_table_size = 4096 @enable_push = 1 @maximum_concurrent_streams = 0xFFFFFFFF @initial_window_size = 0xFFFF # 2**16 - 1 @maximum_frame_size = 0x4000 # 2**14 @maximum_header_list_size = 0xFFFFFFFF @enable_connect_protocol = 0 end ASSIGN = [ nil, :header_table_size=, :enable_push=, :maximum_concurrent_streams=, :initial_window_size=, :maximum_frame_size=, :maximum_header_list_size=, nil, nil, :enable_connect_protocol=, ] def update(changes) changes.each do |key, value| if name = ASSIGN[key] self.send(name, value) end end end def difference(other) changes = [] if @header_table_size != other.header_table_size changes << [HEADER_TABLE_SIZE, @header_table_size] end if @enable_push != other.enable_push changes << [ENABLE_PUSH, @enable_push ? 1 : 0] end if @maximum_concurrent_streams != other.maximum_concurrent_streams changes << [MAXIMUM_CONCURRENT_STREAMS, @maximum_concurrent_streams] end if @initial_window_size != other.initial_window_size changes << [INITIAL_WINDOW_SIZE, @initial_window_size] end if @maximum_frame_size != other.maximum_frame_size changes << [MAXIMUM_FRAME_SIZE, @maximum_frame_size] end if @maximum_header_list_size != other.maximum_header_list_size changes << [MAXIMUM_HEADER_LIST_SIZE, @maximum_header_list_size] end if @enable_connect_protocol != other.enable_connect_protocol changes << [ENABLE_CONNECT_PROTOCOL, @enable_connect_protocol] end return changes end end class PendingSettings def initialize(current = Settings.new) @current = current @pending = current.dup @queue = [] end attr :current attr :pending def append(changes) @queue << changes @pending.update(changes) end def acknowledge if changes = @queue.shift @current.update(changes) return changes else raise ProtocolError, "Cannot acknowledge settings, no changes pending" end end def header_table_size @current.header_table_size end def enable_push @current.enable_push end def maximum_concurrent_streams @current.maximum_concurrent_streams end def initial_window_size @current.initial_window_size end def maximum_frame_size @current.maximum_frame_size end def maximum_header_list_size @current.maximum_header_list_size end end # The SETTINGS frame conveys configuration parameters that affect how endpoints communicate, such as preferences and constraints on peer behavior. The SETTINGS frame is also used to acknowledge the receipt of those parameters. Individually, a SETTINGS parameter can also be referred to as a "setting". # # +-------------------------------+ # | Identifier (16) | # +-------------------------------+-------------------------------+ # | Value (32) | # +---------------------------------------------------------------+ # class SettingsFrame < Frame TYPE = 0x4 FORMAT = "nN".freeze include Acknowledgement def connection? true end def unpack if buffer = super # TODO String#each_slice, or #each_unpack would be nice. buffer.scan(/....../m).map{|s| s.unpack(FORMAT)} else [] end end def pack(settings = []) super(settings.map{|s| s.pack(FORMAT)}.join) end def apply(connection) connection.receive_settings(self) end def read_payload(stream) super if @stream_id != 0 raise ProtocolError, "Settings apply to connection only, but stream_id was given" end if acknowledgement? and @length != 0 raise FrameSizeError, "Settings acknowledgement must not contain payload: #{@payload.inspect}" end if (@length % 6) != 0 raise FrameSizeError, "Invalid frame length" end end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/stream.rb000066400000000000000000000331501400365004300227420ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection' require_relative 'dependency' module Protocol 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. # # +--------+ # 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 | # `----------------------->| |<----------------------' # +--------+ # # send: endpoint sends this frame # recv: endpoint receives this frame # # H: HEADERS frame (with implied CONTINUATIONs) # PP: PUSH_PROMISE frame (with implied CONTINUATIONs) # ES: END_STREAM flag # R: RST_STREAM frame # # State transition methods use a trailing "!". class Stream include FlowControlled def self.create(connection, id) stream = self.new(connection, id) connection.streams[id] = stream return stream end def initialize(connection, id, state = :idle) @connection = connection @id = id @state = state @local_window = Window.new(@connection.local_settings.initial_window_size) @remote_window = Window.new(@connection.remote_settings.initial_window_size) @dependency = Dependency.create(@connection, @id) end # The connection this stream belongs to. attr :connection # Stream ID (odd for client initiated streams, even otherwise). attr :id # Stream state, e.g. `idle`, `closed`. attr_accessor :state attr :dependency attr :local_window attr :remote_window def weight @dependency.weight end def priority @dependency.priority end def priority= priority @dependency.priority = priority end def parent=(stream) @dependency.parent = stream.dependency end def maximum_frame_size @connection.available_frame_size end def write_frame(frame) @connection.write_frame(frame) end def active? @state != :closed && @state != :idle end def closed? @state == :closed end # Transition directly to closed state. Do not pass go, do not collect $200. # This method should only be used by `Connection#close`. def close(error = nil) unless closed? @state = :closed self.closed(error) end end def send_headers? @state == :idle or @state == :reserved_local or @state == :open or @state == :half_closed_remote end private def write_headers(priority, headers, flags = 0) frame = HeadersFrame.new(@id, flags) @connection.write_frames do |framer| data = @connection.encode_headers(headers) frame.pack(priority, data, maximum_size: @connection.maximum_frame_size) framer.write_frame(frame) end return frame end # The HEADERS frame is used to open a stream, and additionally carries a header block fragment. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state. def send_headers(*arguments) if @state == :idle frame = write_headers(*arguments) if frame.end_stream? @state = :half_closed_local else open! end elsif @state == :reserved_local frame = write_headers(*arguments) @state = :half_closed_remote elsif @state == :open frame = write_headers(*arguments) if frame.end_stream? @state = :half_closed_local end elsif @state == :half_closed_remote frame = write_headers(*arguments) if frame.end_stream? close! end else raise ProtocolError, "Cannot send headers in state: #{@state}" end end def consume_remote_window(frame) super @connection.consume_remote_window(frame) end private def write_data(data, flags = 0, **options) frame = DataFrame.new(@id, flags) frame.pack(data, **options) # This might fail if the data payload was too big: consume_remote_window(frame) write_frame(frame) return frame end def send_data(*arguments, **options) if @state == :open frame = write_data(*arguments, **options) if frame.end_stream? @state = :half_closed_local end elsif @state == :half_closed_remote frame = write_data(*arguments, **options) if frame.end_stream? close! end else raise ProtocolError, "Cannot send data in state: #{@state}" end end # The stream has been opened. def opened(error = nil) end def open! if @state == :idle @state = :open else raise ProtocolError, "Cannot open stream in state: #{@state}" end self.opened return self end # The stream has been closed. If closed due to a stream reset, the error will be set. def closed(error = nil) end # Transition the stream into the closed state. # @param error_code [Integer] the error code if the stream was closed due to a stream reset. def close!(error_code = nil) @state = :closed @connection.delete(@id) if error_code error = StreamError.new("Stream closed!", error_code) end self.closed(error) return self end def send_reset_stream(error_code = 0) if @state != :idle and @state != :closed frame = ResetStreamFrame.new(@id) frame.pack(error_code) write_frame(frame) close! else raise ProtocolError, "Cannot send reset stream (#{error_code}) in state: #{@state}" end end def process_headers(frame) # Receiving request headers: priority, data = frame.unpack if priority @dependency.process_priority(priority) end @connection.decode_headers(data) end protected def ignore_headers(frame) # Async.logger.warn(self) {"Received headers in state: #{@state}!"} end def receive_headers(frame) if @state == :idle if frame.end_stream? @state = :half_closed_remote else open! end process_headers(frame) elsif @state == :reserved_remote process_headers(frame) @state = :half_closed_local elsif @state == :open process_headers(frame) if frame.end_stream? @state = :half_closed_remote end elsif @state == :half_closed_local process_headers(frame) if frame.end_stream? close! end elsif self.closed? ignore_headers(frame) else self.send_reset_stream(Error::STREAM_CLOSED) end end # @return [String] the data that was received. def process_data(frame) frame.unpack end def ignore_data(frame) # Async.logger.warn(self) {"Received headers in state: #{@state}!"} end # DATA frames are subject to flow control and can only be sent when a stream is in the "open" or "half-closed (remote)" state. The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present. If a DATA frame is received whose stream is not in "open" or "half-closed (local)" state, the recipient MUST respond with a stream error of type STREAM_CLOSED. def receive_data(frame) if @state == :open update_local_window(frame) if frame.end_stream? @state = :half_closed_remote end process_data(frame) elsif @state == :half_closed_local update_local_window(frame) process_data(frame) if frame.end_stream? close! end elsif self.closed? ignore_data(frame) else # If a DATA frame is received whose stream is not in "open" or "half-closed (local)" state, the recipient MUST respond with a stream error (Section 5.4.2) of type STREAM_CLOSED. self.send_reset_stream(Error::STREAM_CLOSED) end end def receive_reset_stream(frame) if @state == :idle # If a RST_STREAM frame identifying an idle stream is received, the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. raise ProtocolError, "Cannot receive reset stream in state: #{@state}!" else error_code = frame.unpack close!(error_code) return error_code end end # A normal request is client request -> server response -> client. # A push promise is server request -> client -> server response -> client. # The server generates the same set of headers as if the client was sending a request, and sends these to the client. The client can reject the request by resetting the (new) stream. Otherwise, the server will start sending a response as if the client had send the request. private def write_push_promise(stream_id, headers, flags = 0, **options) frame = PushPromiseFrame.new(@id, flags) @connection.write_frames do |framer| data = @connection.encode_headers(headers) frame.pack(stream_id, data, maximum_size: @connection.maximum_frame_size) framer.write_frame(frame) end return frame end def reserved_local! if @state == :idle @state = :reserved_local else raise ProtocolError, "Cannot reserve stream in state: #{@state}" end end def reserved_remote! if @state == :idle @state = :reserved_remote else raise ProtocolError, "Cannot reserve stream in state: #{@state}" end end # Override this function to implement your own push promise logic. def create_push_promise_stream(headers) @connection.create_push_promise_stream end # Server push is semantically equivalent to a server responding to a request; however, in this case, that request is also sent by the server, as a PUSH_PROMISE frame. # @param headers [Hash] contains a complete set of request header fields that the server attributes to the request. def send_push_promise(headers) if @state == :open or @state == :half_closed_remote promised_stream = self.create_push_promise_stream(headers) promised_stream.reserved_local! write_push_promise(promised_stream.id, headers) return promised_stream else raise ProtocolError, "Cannot send push promise in state: #{@state}" end end # Override this function to implement your own push promise logic. def accept_push_promise_stream(stream_id, headers) @connection.accept_push_promise_stream(stream_id) end def receive_push_promise(frame) promised_stream_id, data = frame.unpack headers = @connection.decode_headers(data) stream = self.accept_push_promise_stream(promised_stream_id, headers) stream.parent = self stream.reserved_remote! return stream, headers end def inspect "\#<#{self.class} id=#{@id} state=#{@state}>" end def to_s inspect end end end end ruby-protocol-http2-0.14.2/lib/protocol/http2/version.rb000066400000000000000000000022511400365004300231320ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # 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. module Protocol module HTTP2 VERSION = "0.14.2" end end ruby-protocol-http2-0.14.2/lib/protocol/http2/window_update_frame.rb000066400000000000000000000074421400365004300254770ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'frame' module Protocol module HTTP2 class Window # @param capacity [Integer] The initial window size, typically from the settings. def initialize(capacity = 0xFFFF) # This is the main field required: @available = capacity # These two fields are primarily used for efficiently sending window updates: @used = 0 @capacity = capacity end # The window is completely full? def full? @available <= 0 end attr :used attr :capacity # When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value. def capacity= value difference = value - @capacity @available += difference @capacity = value end def consume(amount) @available -= amount @used += amount end attr :available def available? @available > 0 end def expand(amount) # puts "expand(#{amount}) @available=#{@available}" @available += amount @used -= amount if @available > MAXIMUM_ALLOWED_WINDOW_SIZE raise FlowControlError, "Expanding window by #{amount} caused overflow: #{@available} > #{MAXIMUM_ALLOWED_WINDOW_SIZE}!" end end def wanted @used end def limited? @available < (@capacity / 2) end def to_s "\#" end end # This is a window which efficiently maintains a desired capacity. class LocalWindow < Window def initialize(capacity = 0xFFFF, desired: nil) super(capacity) @desired = desired end attr_accessor :desired def wanted if @desired # We must send an update which allows at least @desired bytes to be sent. (@desired - @capacity) + @used else @used end end def limited? @available < ((@desired || @capacity) / 2) end end # The WINDOW_UPDATE frame is used to implement flow control. # # +-+-------------------------------------------------------------+ # |R| Window Size Increment (31) | # +-+-------------------------------------------------------------+ # class WindowUpdateFrame < Frame TYPE = 0x8 FORMAT = "N" def pack(window_size_increment) super [window_size_increment].pack(FORMAT) end def unpack super.unpack1(FORMAT) end def read_payload(stream) super if @length != 4 raise FrameSizeError, "Invalid frame length: #{@length} != 4!" end end def apply(connection) connection.receive_window_update(self) end end end end ruby-protocol-http2-0.14.2/protocol-http2.gemspec000066400000000000000000000012751400365004300217220ustar00rootroot00000000000000 require_relative "lib/protocol/http2/version" Gem::Specification.new do |spec| spec.name = "protocol-http2" spec.version = Protocol::HTTP2::VERSION spec.summary = "A low level implementation of the HTTP/2 protocol." spec.authors = ["Samuel Williams"] spec.license = "MIT" spec.homepage = "https://github.com/socketry/protocol-http2" spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 2.5" spec.add_dependency "protocol-hpack", "~> 1.4" spec.add_dependency "protocol-http", "~> 0.18" spec.add_development_dependency "bundler" spec.add_development_dependency "covered" spec.add_development_dependency "rspec", "~> 3.0" end ruby-protocol-http2-0.14.2/spec/000077500000000000000000000000001400365004300164025ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/spec/protocol/000077500000000000000000000000001400365004300202435ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/spec/protocol/http2/000077500000000000000000000000001400365004300213045ustar00rootroot00000000000000ruby-protocol-http2-0.14.2/spec/protocol/http2/client_spec.rb000066400000000000000000000043101400365004300241170ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection_context' RSpec.describe Protocol::HTTP2::Client do include_context Protocol::HTTP2::Connection let(:framer) {server.framer} let(:settings) do [[Protocol::HTTP2::Settings::HEADER_TABLE_SIZE, 1024]] end it "should start in new state" do expect(client.state).to eq :new end it "should send connection preface followed by settings frame" do client.send_connection_preface(settings) do expect(framer.read_connection_preface).to eq Protocol::HTTP2::CONNECTION_PREFACE_MAGIC client_settings_frame = framer.read_frame expect(client_settings_frame).to be_kind_of Protocol::HTTP2::SettingsFrame expect(client_settings_frame.unpack).to eq settings # Fake (empty) server settings: server_settings_frame = Protocol::HTTP2::SettingsFrame.new server_settings_frame.pack framer.write_frame(server_settings_frame) framer.write_frame(client_settings_frame.acknowledge) end expect(client.state).to eq :new client.read_frame expect(client.state).to eq :open expect(client.local_settings.header_table_size).to eq 1024 end end ruby-protocol-http2-0.14.2/spec/protocol/http2/connection_context.rb000066400000000000000000000030141400365004300255320ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/client' require 'protocol/http2/server' require 'protocol/http2/stream' require 'socket' RSpec.shared_context Protocol::HTTP2::Connection do let(:sockets) {Socket.pair(Socket::PF_UNIX, Socket::SOCK_STREAM)} let(:client) {Protocol::HTTP2::Client.new(Protocol::HTTP2::Framer.new(sockets.first))} let(:server) {Protocol::HTTP2::Server.new(Protocol::HTTP2::Framer.new(sockets.last))} end ruby-protocol-http2-0.14.2/spec/protocol/http2/connection_spec.rb000066400000000000000000000134521400365004300250070ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection_context' RSpec.describe Protocol::HTTP2::Connection do include_context Protocol::HTTP2::Connection it "can negotiate connection" do first_server_frame = nil first_client_frame = client.send_connection_preface do first_server_frame = server.read_connection_preface([]) end expect(first_client_frame).to be_kind_of Protocol::HTTP2::SettingsFrame expect(first_client_frame).to_not be_acknowledgement expect(first_server_frame).to be_kind_of Protocol::HTTP2::SettingsFrame expect(first_server_frame).to_not be_acknowledgement frame = client.read_frame expect(frame).to be_kind_of Protocol::HTTP2::SettingsFrame expect(frame).to be_acknowledgement frame = server.read_frame expect(frame).to be_kind_of Protocol::HTTP2::SettingsFrame expect(frame).to be_acknowledgement expect(client.state).to eq :open expect(server.state).to eq :open end context Protocol::HTTP2::PingFrame do before do client.open! server.open! end it "can send ping and receive pong" do expect(server).to receive(:receive_ping).once.and_call_original client.send_ping("12345678") server.read_frame expect(client).to receive(:receive_ping).once.and_call_original client.read_frame end end context Protocol::HTTP2::Stream do before do client.open! server.open! end let(:request_data) {"Hello World!"} let(:stream) {client.create_stream} let(:request_headers) {[[':method', 'GET'], [':path', '/'], [':authority', 'localhost']]} let(:response_headers) {[[':status', '200']]} it "can create new stream and send response" do stream.send_headers(nil, request_headers) expect(stream.id).to eq 1 expect(server).to receive(:receive_headers).once.and_wrap_original do |method, frame| headers = method.call(frame) expect(headers).to be == request_headers end server.read_frame expect(server.streams).to_not be_empty expect(server.streams[1].state).to be :open stream.send_data(request_data, Protocol::HTTP2::END_STREAM) expect(stream.state).to eq :half_closed_local expect(server).to receive(:receive_data).and_call_original data_frame = server.read_frame expect(data_frame.unpack).to be == request_data expect(server.streams[1].state).to be :half_closed_remote server.streams[1].send_headers(nil, response_headers, Protocol::HTTP2::END_STREAM) expect(stream).to receive(:process_headers).once.and_wrap_original do |method, frame| headers = method.call(frame) expect(headers).to be == response_headers end client.read_frame expect(stream.state).to eq :closed expect(stream.remote_window.used).to eq data_frame.length end it "client can handle graceful shutdown" do stream.send_headers(nil, request_headers, Protocol::HTTP2::END_STREAM) # Establish request stream on server: server.read_frame # Graceful shutdown server.send_goaway(0) expect(client.read_frame).to be_a Protocol::HTTP2::GoawayFrame expect(client.remote_stream_id).to be == 1 expect(client).to be_closed expect(server.streams[1].state).to eq :half_closed_remote server.streams[1].send_headers(nil, response_headers, Protocol::HTTP2::END_STREAM) client.read_frame expect(stream.state).to eq :closed end it "client can handle non-graceful shutdown" do stream.send_headers(nil, request_headers, Protocol::HTTP2::END_STREAM) # Establish request stream on server: server.read_frame # Send connection error to client: server.send_goaway(1, "Bugger off!") expect(stream).to receive(:close).and_call_original expect do client.read_frame end.to raise_error(Protocol::HTTP2::GoawayError) client.close expect(client.remote_stream_id).to be == 1 expect(client).to be_closed end it "can stream data" do stream.send_headers(nil, request_headers) stream.send_data("A") stream.send_data("B") stream.send_data("C") stream.send_data("", Protocol::HTTP2::END_STREAM) frame = server.read_frame expect(frame).to be_a(Protocol::HTTP2::HeadersFrame) expect(stream.available_frame_size).to be >= 3 frame = server.read_frame expect(frame).to be_a(Protocol::HTTP2::DataFrame) expect(frame.unpack).to be == "A" frame = server.read_frame expect(frame).to be_a(Protocol::HTTP2::DataFrame) expect(frame.unpack).to be == "B" frame = server.read_frame expect(frame).to be_a(Protocol::HTTP2::DataFrame) expect(frame.unpack).to be == "C" frame = server.read_frame expect(frame).to be_a(Protocol::HTTP2::DataFrame) expect(frame.unpack).to be == "" expect(frame).to be_end_stream end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/continuation_frame_spec.rb000066400000000000000000000036161400365004300265350ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/continuation_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::ContinuationFrame do let(:data) {"Hello World!"} it_behaves_like Protocol::HTTP2::Frame do before do subject.pack data end end describe '#pack' do it "packs data" do subject.pack data expect(subject.length).to be == data.bytesize end it "packs data over multiple continuation frames" do subject.pack data, maximum_size: 6 expect(subject.continuation).to_not be_nil end end describe '#unpack' do it "unpacks data" do subject.pack data expect(subject.unpack).to be == data end it "unpacks data over multiple continuations" do subject.pack data, maximum_size: 2 expect(subject.unpack).to be == data end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/data_frame_spec.rb000066400000000000000000000046411400365004300247330ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/data_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::DataFrame do it_behaves_like Protocol::HTTP2::Frame do before do subject.pack "Hello World!" end end context 'wire representation' do let(:stream) {StringIO.new} let(:payload) {'Hello World!'} let(:data) do [0, 12, 0x0, 0x1, 0x1].pack('CnCCNC*') + payload end it "should write frame to buffer" do subject.set_flags(Protocol::HTTP2::END_STREAM) subject.stream_id = 1 subject.payload = payload subject.length = payload.bytesize subject.write(stream) expect(stream.string).to be == data end it "should read frame from buffer" do stream.write(data) stream.seek(0) subject.read(stream) expect(subject.length).to be == payload.bytesize expect(subject.flags).to be == Protocol::HTTP2::END_STREAM expect(subject.stream_id).to be == 1 expect(subject.payload).to be == payload end end describe '#pack' do it "adds appropriate padding" do subject.pack "Hello World!", padding_size: 4 expect(subject.length).to be == 16 expect(subject.payload[0].ord).to be == 4 end end describe '#unpack' do it "removes padding" do subject.pack "Hello World!" expect(subject.unpack).to be == "Hello World!" end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/dependency_spec.rb000066400000000000000000000103621400365004300247630ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection_context' RSpec.describe Protocol::HTTP2::Stream do include_context Protocol::HTTP2::Connection before do client.open! server.open! end it "can set the priority of a stream" do stream = client.create_stream priority = stream.priority expect(priority.weight).to be 16 priority.weight = 32 stream.priority = priority expect(server.read_frame).to be_a Protocol::HTTP2::PriorityFrame expect(server.dependencies[stream.id].weight).to be == 32 end it "can make an exclusive stream" do a, b, c, d = 4.times.collect {client.create_stream} # a # / # b begin a.priority = a.priority server.read_frame priority = b.priority priority.stream_dependency = a.id b.priority = priority server.read_frame end expect(a.dependency.children).to be == {b.id => b.dependency} expect(server.dependencies).to include(a.id) expect(server.dependencies[a.id].children).to be == {b.id => server.dependencies[b.id]} # a # / \ # b c begin priority = c.priority priority.stream_dependency = a.id c.priority = priority server.read_frame end expect(a.dependency.children).to be == {b.id => b.dependency, c.id => c.dependency} expect(server.dependencies[a.id].children).to be == {b.id => server.dependencies[b.id], c.id => server.dependencies[c.id]} # a # | # d # / \ # b c begin priority = d.priority priority.stream_dependency = a.id priority.exclusive = true d.priority = priority server.read_frame end expect(a.dependency.children).to be == {d.id => d.dependency} expect(d.dependency.children).to be == {b.id => b.dependency, c.id => c.dependency} expect(server.dependencies[a.id].children).to be == {d.id => server.dependencies[d.id]} expect(server.dependencies[d.id].children).to be == {b.id => server.dependencies[b.id], c.id => server.dependencies[c.id]} end it "correctly allocates window" do parent = client.create_stream children = 2.times.collect {client.create_stream} children.each do |child| priority = child.priority priority.stream_dependency = parent.id child.priority = priority end 2.times {server.read_frame} # Now we have this prioritization on the server: # a # / \ # b c expect(server.dependencies[children[0].id]).to receive(:consume_window).with(0xFFFF / 2).once expect(server.dependencies[children[1].id]).to receive(:consume_window).with(0xFFFF / 2).once server.consume_window end it "correctly allocates window" do streams = 4.times.collect{client.create_stream} top = streams.first top.send_headers(nil, []) top.send_reset_stream 2.times {server.read_frame} # The dependency has been recycled: expect(server.dependencies).to_not include(top.id) bottom = streams.last priority = bottom.priority priority.stream_dependency = top.id bottom.send_headers(priority, []) 1.times {server.read_frame} expect(server.dependencies).to include(bottom.id) dependency = server.dependencies[bottom.id] expect(dependency.parent).to be == server.dependency end end ruby-protocol-http2-0.14.2/spec/protocol/http2/frame_examples.rb000066400000000000000000000030501400365004300246170ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/framer' RSpec.shared_examples Protocol::HTTP2::Frame do let(:stream) {StringIO.new} it "can write frame" do subject.write(stream) expect(stream.string).to_not be_empty end let(:framer) {Protocol::HTTP2::Framer.new(stream, {described_class::TYPE => described_class})} it "can read frame using framer" do subject.write(stream) stream.seek(0) expect(framer.read_frame).to be == subject end endruby-protocol-http2-0.14.2/spec/protocol/http2/frame_spec.rb000066400000000000000000000022741400365004300237420ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/frame' RSpec.describe Protocol::HTTP2::Frame do end ruby-protocol-http2-0.14.2/spec/protocol/http2/goaway_frame_spec.rb000066400000000000000000000031741400365004300253110ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/goaway_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::GoawayFrame do let(:data) {"Hikikomori desu!"} it_behaves_like Protocol::HTTP2::Frame do before do subject.pack 1, 2, data end end describe '#pack' do it "packs data" do subject.pack 3, 2, data expect(subject.length).to be == 8+data.bytesize end end describe '#unpack' do it "unpacks data" do subject.pack 3, 2, data expect(subject.unpack).to be == [3, 2, data] end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/headers_frame_spec.rb000066400000000000000000000060731400365004300254360ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/headers_frame' require_relative 'connection_context' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::HeadersFrame do let(:priority) {Protocol::HTTP2::Priority.new(true, 42, 7)} let(:data) {"Hello World!"} it_behaves_like Protocol::HTTP2::Frame do before do subject.set_flags(Protocol::HTTP2::END_HEADERS) subject.pack priority, data end end describe '#pack' do it "adds appropriate padding" do subject.pack nil, data expect(subject.length).to be == 12 expect(subject).to_not be_priority end it "packs priority with no padding" do subject.pack priority, data expect(priority.pack.size).to be == 5 expect(subject.length).to be == (5 + data.bytesize) end end describe '#unpack' do it "removes padding" do subject.pack nil, data expect(subject.unpack).to be == [nil, data] end end describe '#continuation' do it "generates chain of frames" do subject.pack nil, "Hello World", maximum_size: 8 expect(subject.length).to eq 8 expect(subject.continuation).to_not be_nil expect(subject.continuation.length).to eq 3 end end context "client/server connection" do include_context Protocol::HTTP2::Connection before do client.open! server.open! # We force this to something low so we can exceed it without hitting the socket buffer: server.local_settings.current.instance_variable_set(:@maximum_frame_size, 128) end let(:stream) {client.create_stream} it "rejects headers frame that exceeds maximum frame size" do subject.stream_id = stream.id subject.pack nil, "\0" * (server.local_settings.maximum_frame_size + 1) client.write_frame(subject) expect do server.read_frame end.to raise_error(Protocol::HTTP2::FrameSizeError) expect(client).to receive(:receive_goaway).once.and_call_original expect do client.read_frame end.to raise_error(Protocol::HTTP2::GoawayError) end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/ping_frame_spec.rb000066400000000000000000000031101400365004300247450ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/ping_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::PingFrame do let(:data) {"PingPong"} it_behaves_like Protocol::HTTP2::Frame do before do subject.pack data end end describe '#pack' do it "packs data" do subject.pack data expect(subject.length).to be == 8 end end describe '#unpack' do it "unpacks data" do subject.pack data expect(subject.unpack).to be == data end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/priority_frame_spec.rb000066400000000000000000000032141400365004300256760ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/priority_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::PriorityFrame do let(:priority) {Protocol::HTTP2::Priority.new(true, 42, 7)} it_behaves_like Protocol::HTTP2::Frame do before do subject.pack priority end end describe '#pack' do it "packs priority" do subject.pack priority expect(subject.length).to be == 5 end end describe '#unpack' do it "unpacks priority" do subject.pack priority expect(subject.unpack).to be == priority end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/push_promise_frame_spec.rb000066400000000000000000000057331400365004300265420ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/push_promise_frame' require_relative 'connection_context' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::PushPromiseFrame do let(:stream_id) {5} let(:data) {"Hello World!"} it_behaves_like Protocol::HTTP2::Frame do before do subject.set_flags(Protocol::HTTP2::END_HEADERS) subject.pack stream_id, data end end describe '#pack' do it "packs stream_id and data with padding" do subject.pack stream_id, data expect(subject.padded?).to be_falsey expect(subject.length).to be == 16 end end describe '#unpack' do it "unpacks stream_id and data" do subject.pack stream_id, data expect(subject.unpack).to be == [stream_id, data] end end context "client/server connection" do include_context Protocol::HTTP2::Connection before do client.open! server.open! end let(:stream) {client.create_stream} let(:request_headers) do [[':method', 'GET'], [':authority', 'Earth'], [':path', '/index.html']] end let(:push_promise_headers) do [[':method', 'GET'], [':authority', 'Earth'], [':path', '/index.css']] end it "sends push promise to client" do # Client sends a request: stream.send_headers(nil, request_headers) # Server receives request: expect(server.read_frame).to be_kind_of Protocol::HTTP2::HeadersFrame # Get the request stream on the server: server_stream = server.streams[stream.id] # Push a promise back through the stream: promised_stream = server_stream.send_push_promise(push_promise_headers) expect(client).to receive(:receive_push_promise).and_wrap_original do |m, *args| stream, headers = m.call(*args) expect(stream.id).to be == promised_stream.id expect(headers).to be == push_promise_headers end expect(client.read_frame).to be_kind_of Protocol::HTTP2::PushPromiseFrame end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/reset_stream_frame_spec.rb000066400000000000000000000031531400365004300265140ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/reset_stream_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::ResetStreamFrame do let(:error) {Protocol::HTTP2::INTERNAL_ERROR} it_behaves_like Protocol::HTTP2::Frame do before do subject.pack error end end describe '#pack' do it "packs error" do subject.pack error expect(subject.length).to be == 4 end end describe '#unpack' do it "unpacks error" do subject.pack error expect(subject.unpack).to be == error end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/server_spec.rb000066400000000000000000000050701400365004300241530ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection_context' RSpec.describe Protocol::HTTP2::Client do include_context Protocol::HTTP2::Connection let(:framer) {client.framer} let(:client_settings) do [[Protocol::HTTP2::Settings::HEADER_TABLE_SIZE, 1024]] end let(:server_settings) do [[Protocol::HTTP2::Settings::HEADER_TABLE_SIZE, 2048]] end it "should start in new state" do expect(server.state).to eq :new end it "should receive connection preface followed by settings frame" do # The client must write the connection preface followed immediately by the first settings frame: framer.write_connection_preface settings_frame = Protocol::HTTP2::SettingsFrame.new settings_frame.pack(client_settings) framer.write_frame(settings_frame) expect(server.state).to eq :new # The server should read the preface and settings... server.read_connection_preface(server_settings) expect(server.remote_settings.header_table_size).to eq 1024 # The server immediately sends its own settings frame... frame = framer.read_frame expect(frame).to be_kind_of Protocol::HTTP2::SettingsFrame expect(frame.unpack).to eq server_settings # And then it acknowledges the client settings: frame = framer.read_frame expect(frame).to be_kind_of Protocol::HTTP2::SettingsFrame expect(frame).to be_acknowledgement # We reply with acknolwedgement: framer.write_frame(frame.acknowledge) server.read_frame expect(server.state).to eq :open end end ruby-protocol-http2-0.14.2/spec/protocol/http2/settings_frame_spec.rb000066400000000000000000000032451400365004300256610ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/settings_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::SettingsFrame do let(:settings) {[ [3, 10], [5, 1048576], [4, 2147483647], [8, 1] ]} it_behaves_like Protocol::HTTP2::Frame do before do subject.pack settings end end describe '#pack' do it "packs settings" do subject.pack settings expect(subject.length).to be == 6*settings.size end end describe '#unpack' do it "unpacks settings" do subject.pack settings expect(subject.unpack).to be == settings end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/stream_spec.rb000066400000000000000000000066571400365004300241540ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection_context' RSpec.describe Protocol::HTTP2::Stream do include_context Protocol::HTTP2::Connection before do client.open! server.open! end context "idle stream" do let(:stream) {client.create_stream} it "can send headers" do stream.send_headers(nil, [["foo", "bar"]]) server_stream = server.create_stream(stream.id) expect(server_stream).to receive(:receive_headers) server.read_frame end it "cannot send data" do expect do stream.send_data("Hello World!") end.to raise_error(Protocol::HTTP2::ProtocolError, /Cannot send data in state: idle/) end it "cannot send reset stream" do expect do stream.send_reset_stream end.to raise_error(Protocol::HTTP2::ProtocolError, /Cannot send reset stream/) end it "cannot receive data" do stream.open! stream.send_data("Hello World!") expect do server.read_frame end.to raise_error(Protocol::HTTP2::ProtocolError, /Cannot receive data/) end it "cannot receive stream reset" do stream.open! stream.send_reset_stream expect do server.read_frame end.to raise_error(Protocol::HTTP2::ProtocolError, /Cannot reset stream/) end end context "open stream" do let(:stream) {client.create_stream.open!} it "can send data" do stream.send_data("Hello World!") server_stream = server.create_stream(stream.id) expect(server_stream).to receive(:receive_data) server.read_frame end it "can send reset stream" do stream.send_reset_stream server_stream = server.create_stream(stream.id) expect(server_stream).to receive(:receive_reset_stream) server.read_frame end end context "closed stream" do let(:stream) {client.create_stream.close!} it "cannot send reset stream" do expect do stream.send_reset_stream end.to raise_error(Protocol::HTTP2::ProtocolError, /Cannot send reset stream/) end it "ignores headers" do expect(stream).to receive(:ignore_headers) stream.receive_headers(double) end it "ignores data" do expect(stream).to receive(:ignore_data) stream.receive_data(double) end it "ignores reset stream" do server_stream = server.create_stream(stream.id) server_stream.open! server_stream.send_reset_stream client.read_frame end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/window_spec.rb000066400000000000000000000075741400365004300241670ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require_relative 'connection_context' RSpec.describe Protocol::HTTP2::Window do include_context Protocol::HTTP2::Connection let(:framer) {client.framer} let(:settings) do [[Protocol::HTTP2::Settings::INITIAL_WINDOW_SIZE, 200]] end let(:headers) do [[':method', 'GET'], [':authority', 'Earth']] end let(:stream) do client.create_stream end before do client.send_connection_preface do server.read_connection_preface(settings) end client.read_frame until client.state == :open server.read_frame until server.state == :open stream.send_headers(nil, headers) expect(server.read_frame).to be_kind_of Protocol::HTTP2::HeadersFrame end it "should assign capacity according to settings" do expect(client.remote_settings.initial_window_size).to eq 200 expect(server.local_settings.initial_window_size).to eq 200 expect(client.remote_window.capacity).to eq 0xFFFF expect(server.local_window.capacity).to eq 0xFFFF expect(client.local_window.capacity).to eq 0xFFFF expect(server.remote_window.capacity).to eq 0xFFFF expect(client.local_settings.initial_window_size).to eq 0xFFFF expect(server.remote_settings.initial_window_size).to eq 0xFFFF end it "should send window update after exhausting half of the available window" do # Write 60 bytes of data. stream.send_data("*" * 60) expect(stream.remote_window.used).to eq 60 expect(client.remote_window.used).to eq 60 # puts "Server #{server} #{server.remote_window.inspect} reading frame..." expect(server.read_frame).to be_kind_of Protocol::HTTP2::DataFrame expect(server.local_window.used).to eq 60 # Write another 60 bytes which passes the 50% threshold. stream.send_data("*" * 60) # The server receives a data frame... expect(server.read_frame).to be_kind_of Protocol::HTTP2::DataFrame # ...and must respond with a window update: frame = client.read_frame expect(frame).to be_kind_of Protocol::HTTP2::WindowUpdateFrame expect(frame.unpack).to eq 120 end describe '#receive_window_update' do it "should be invoked when window update is received" do # Write 200 bytes of data (client -> server) which exhausts server local window stream.send_data("*" * 200) expect(server.read_frame).to be_kind_of Protocol::HTTP2::DataFrame expect(server.local_window.used).to eq 200 expect(client.remote_window.used).to eq 200 # Window update was sent, and used data was zeroed: server_stream = server.streams[stream.id] expect(server_stream.local_window.used).to eq 0 # ...and must respond with a window update for the stream: expect(stream).to receive(:receive_window_update).once frame = client.read_frame expect(frame).to be_kind_of Protocol::HTTP2::WindowUpdateFrame expect(frame.unpack).to eq 200 end end end ruby-protocol-http2-0.14.2/spec/protocol/http2/window_update_frame_spec.rb000066400000000000000000000032501400365004300266660ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # # 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. require 'protocol/http2/window_update_frame' require_relative 'frame_examples' RSpec.describe Protocol::HTTP2::WindowUpdateFrame do let(:window_size_increment) {1024} it_behaves_like Protocol::HTTP2::Frame do before do subject.pack window_size_increment end end describe '#pack' do it "packs data" do subject.pack window_size_increment expect(subject.length).to be == 4 end end describe '#unpack' do it "unpacks data" do subject.pack window_size_increment expect(subject.unpack).to be == window_size_increment end end end ruby-protocol-http2-0.14.2/spec/protocol/http2_spec.rb000066400000000000000000000023471400365004300226510ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # 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. RSpec.describe Protocol::HTTP2 do it "has a version number" do expect(Protocol::HTTP2::VERSION).not_to be nil end end ruby-protocol-http2-0.14.2/spec/spec_helper.rb000066400000000000000000000026761400365004300212330ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # 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. require "covered/rspec" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! config.expect_with :rspec do |c| c.syntax = :expect end end