protocol-http2 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/protocol/http2/client.rb +1 -1
- data/lib/protocol/http2/connection.rb +62 -35
- data/lib/protocol/http2/continuation_frame.rb +4 -0
- data/lib/protocol/http2/data_frame.rb +4 -0
- data/lib/protocol/http2/error.rb +7 -1
- data/lib/protocol/http2/flow_control.rb +40 -8
- data/lib/protocol/http2/frame.rb +4 -0
- data/lib/protocol/http2/framer.rb +7 -3
- data/lib/protocol/http2/headers_frame.rb +4 -0
- data/lib/protocol/http2/priority_frame.rb +18 -2
- data/lib/protocol/http2/server.rb +1 -1
- data/lib/protocol/http2/settings_frame.rb +7 -2
- data/lib/protocol/http2/stream.rb +133 -42
- data/lib/protocol/http2/version.rb +1 -1
- data/lib/protocol/http2/window_update_frame.rb +2 -0
- data/protocol-http2.gemspec +1 -1
- metadata +5 -5
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 49e091bc1a6d2b2bdee4176a13648d306f81042ebc6d25cf0a44a921d33d0950
         | 
| 4 | 
            +
              data.tar.gz: 5ecd3a8454c2a130247c624abdd067d87b38e63c09e9ed88fe2214e52461ac6c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ca124b2c3232e5329b4dd19c29f1b503fb490a03d94b1501f03de0ca1f478fbed8a99c38318002eda6b7f2df484be0b8ab9b1ed0c38a4388a7e63ed2065a5d2f
         | 
| 7 | 
            +
              data.tar.gz: e766f4a80429e0ddc22edc2700ce4d20054d4fc07de7d81677cb1b89351019c5bf565a5ff51ce224002d0bb3e9184cbdda97807c2d59013d34c67fab31421443
         | 
| @@ -40,7 +40,7 @@ module Protocol | |
| 40 40 | 
             
            					yield if block_given?
         | 
| 41 41 |  | 
| 42 42 | 
             
            					read_frame do |frame|
         | 
| 43 | 
            -
            						raise ProtocolError, "First frame must be SettingsFrame, but got #{frame.class}" unless frame.is_a? SettingsFrame
         | 
| 43 | 
            +
            						raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}" unless frame.is_a? SettingsFrame
         | 
| 44 44 | 
             
            					end
         | 
| 45 45 | 
             
            				else
         | 
| 46 46 | 
             
            					raise ProtocolError, "Cannot send connection preface in state #{@state}"
         | 
| @@ -29,8 +29,11 @@ module Protocol | |
| 29 29 | 
             
            			include FlowControl
         | 
| 30 30 |  | 
| 31 31 | 
             
            			def initialize(framer, local_stream_id)
         | 
| 32 | 
            +
            				super()
         | 
| 33 | 
            +
            				
         | 
| 32 34 | 
             
            				@state = :new
         | 
| 33 35 | 
             
            				@streams = {}
         | 
| 36 | 
            +
            				@children = {}
         | 
| 34 37 |  | 
| 35 38 | 
             
            				@framer = framer
         | 
| 36 39 | 
             
            				@local_stream_id = local_stream_id
         | 
| @@ -50,6 +53,18 @@ module Protocol | |
| 50 53 | 
             
            				0
         | 
| 51 54 | 
             
            			end
         | 
| 52 55 |  | 
| 56 | 
            +
            			def parent
         | 
| 57 | 
            +
            				nil
         | 
| 58 | 
            +
            			end
         | 
| 59 | 
            +
            			
         | 
| 60 | 
            +
            			def [] id
         | 
| 61 | 
            +
            				if id.zero?
         | 
| 62 | 
            +
            					self
         | 
| 63 | 
            +
            				else
         | 
| 64 | 
            +
            					@streams[id]
         | 
| 65 | 
            +
            				end
         | 
| 66 | 
            +
            			end
         | 
| 67 | 
            +
            			
         | 
| 53 68 | 
             
            			# The size of a frame payload is limited by the maximum size that a receiver advertises in the SETTINGS_MAX_FRAME_SIZE setting.
         | 
| 54 69 | 
             
            			def maximum_frame_size
         | 
| 55 70 | 
             
            				@remote_settings.maximum_frame_size
         | 
| @@ -83,6 +98,7 @@ module Protocol | |
| 83 98 | 
             
            			end
         | 
| 84 99 |  | 
| 85 100 | 
             
            			def active_streams
         | 
| 101 | 
            +
            				# TODO inefficient
         | 
| 86 102 | 
             
            				@streams.each_value.select(&:active?)
         | 
| 87 103 | 
             
            			end
         | 
| 88 104 |  | 
| @@ -100,7 +116,7 @@ module Protocol | |
| 100 116 | 
             
            			end
         | 
| 101 117 |  | 
| 102 118 | 
             
            			def decode_headers(data)
         | 
| 103 | 
            -
            				HPACK::Decompressor.new(data, @decoder).decode
         | 
| 119 | 
            +
            				HPACK::Decompressor.new(data, @decoder, table_size_limit: @local_settings.header_table_size).decode
         | 
| 104 120 | 
             
            			end
         | 
| 105 121 |  | 
| 106 122 | 
             
            			# 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.
         | 
| @@ -113,13 +129,34 @@ module Protocol | |
| 113 129 | 
             
            			end
         | 
| 114 130 |  | 
| 115 131 | 
             
            			attr :streams
         | 
| 132 | 
            +
            			attr :children
         | 
| 133 | 
            +
            			
         | 
| 134 | 
            +
            			def add_child(stream)
         | 
| 135 | 
            +
            				@children[stream.id] = stream
         | 
| 136 | 
            +
            			end
         | 
| 137 | 
            +
            			
         | 
| 138 | 
            +
            			def remove_child(stream)
         | 
| 139 | 
            +
            				@children.delete(stream.id)
         | 
| 140 | 
            +
            			end
         | 
| 141 | 
            +
            			
         | 
| 142 | 
            +
            			def exclusive_child(stream)
         | 
| 143 | 
            +
            				stream.children = @children
         | 
| 144 | 
            +
            				
         | 
| 145 | 
            +
            				@children.each do |child|
         | 
| 146 | 
            +
            					child.dependent_id = stream.id
         | 
| 147 | 
            +
            				end
         | 
| 148 | 
            +
            				
         | 
| 149 | 
            +
            				@children = {stream.id => stream}
         | 
| 150 | 
            +
            				
         | 
| 151 | 
            +
            				stream.dependent_id = 0
         | 
| 152 | 
            +
            			end
         | 
| 116 153 |  | 
| 117 154 | 
             
            			# 6.8. GOAWAY
         | 
| 118 155 | 
             
            			# 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.
         | 
| 119 156 | 
             
            			# 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.
         | 
| 120 157 | 
             
            			def ignore_frame?(frame)
         | 
| 121 158 | 
             
            				if self.closed?
         | 
| 122 | 
            -
            					puts "ignore_frame? #{frame.stream_id} -> #{valid_remote_stream_id?(frame.stream_id)} > #{@remote_stream_id}"
         | 
| 159 | 
            +
            					# puts "ignore_frame? #{frame.stream_id} -> #{valid_remote_stream_id?(frame.stream_id)} > #{@remote_stream_id}"
         | 
| 123 160 | 
             
            					if valid_remote_stream_id?(frame.stream_id)
         | 
| 124 161 | 
             
            						return frame.stream_id > @remote_stream_id
         | 
| 125 162 | 
             
            					end
         | 
| @@ -140,8 +177,6 @@ module Protocol | |
| 140 177 | 
             
            				return frame
         | 
| 141 178 | 
             
            			rescue GoawayError => error
         | 
| 142 179 | 
             
            				# Go directly to jail. Do not pass go, do not collect $200.
         | 
| 143 | 
            -
            				self.close(error)
         | 
| 144 | 
            -
            				
         | 
| 145 180 | 
             
            				raise
         | 
| 146 181 | 
             
            			rescue ProtocolError => error
         | 
| 147 182 | 
             
            				send_goaway(error.code || PROTOCOL_ERROR, error.message)
         | 
| @@ -150,10 +185,6 @@ module Protocol | |
| 150 185 | 
             
            			rescue HPACK::CompressionError => error
         | 
| 151 186 | 
             
            				send_goaway(COMPRESSION_ERROR, error.message)
         | 
| 152 187 |  | 
| 153 | 
            -
            				raise
         | 
| 154 | 
            -
            			rescue
         | 
| 155 | 
            -
            				send_goaway(PROTOCOL_ERROR, $!.message)
         | 
| 156 | 
            -
            				
         | 
| 157 188 | 
             
            				raise
         | 
| 158 189 | 
             
            			end
         | 
| 159 190 |  | 
| @@ -179,7 +210,7 @@ module Protocol | |
| 179 210 | 
             
            				frame.pack @remote_stream_id, error_code, message
         | 
| 180 211 |  | 
| 181 212 | 
             
            				write_frame(frame)
         | 
| 182 | 
            -
             | 
| 213 | 
            +
            			ensure
         | 
| 183 214 | 
             
            				self.close!
         | 
| 184 215 | 
             
            			end
         | 
| 185 216 |  | 
| @@ -217,6 +248,8 @@ module Protocol | |
| 217 248 | 
             
            				@streams.each_value do |stream|
         | 
| 218 249 | 
             
            					stream.local_window.capacity = capacity
         | 
| 219 250 | 
             
            				end
         | 
| 251 | 
            +
            				
         | 
| 252 | 
            +
            				@decoder.table_size = @local_settings.header_table_size
         | 
| 220 253 | 
             
            			end
         | 
| 221 254 |  | 
| 222 255 | 
             
            			def update_remote_settings(changes)
         | 
| @@ -225,6 +258,8 @@ module Protocol | |
| 225 258 | 
             
            				@streams.each_value do |stream|
         | 
| 226 259 | 
             
            					stream.remote_window.capacity = capacity
         | 
| 227 260 | 
             
            				end
         | 
| 261 | 
            +
            				
         | 
| 262 | 
            +
            				@encoder.table_size = @remote_settings.header_table_size
         | 
| 228 263 | 
             
            			end
         | 
| 229 264 |  | 
| 230 265 | 
             
            			# 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.
         | 
| @@ -304,15 +339,9 @@ module Protocol | |
| 304 339 | 
             
            			# On the server side, we accept requests.
         | 
| 305 340 | 
             
            			def accept_stream(stream_id, &block)
         | 
| 306 341 | 
             
            				unless valid_remote_stream_id?(stream_id)
         | 
| 307 | 
            -
            					raise ProtocolError, "Invalid  | 
| 342 | 
            +
            					raise ProtocolError, "Invalid stream id: #{stream_id}"
         | 
| 308 343 | 
             
            				end
         | 
| 309 344 |  | 
| 310 | 
            -
            				if stream_id <= @remote_stream_id
         | 
| 311 | 
            -
            					raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!"
         | 
| 312 | 
            -
            				end
         | 
| 313 | 
            -
            				
         | 
| 314 | 
            -
            				@remote_stream_id = stream_id
         | 
| 315 | 
            -
            				
         | 
| 316 345 | 
             
            				create_stream(stream_id, &block)
         | 
| 317 346 | 
             
            			end
         | 
| 318 347 |  | 
| @@ -326,17 +355,11 @@ module Protocol | |
| 326 355 | 
             
            			# Create a stream, defaults to an outgoing stream.
         | 
| 327 356 | 
             
            			# On the client side, we create requests.
         | 
| 328 357 | 
             
            			# @return [Stream] the created stream.
         | 
| 329 | 
            -
            			def create_stream( | 
| 358 | 
            +
            			def create_stream(id = next_stream_id, &block)
         | 
| 330 359 | 
             
            				if block_given?
         | 
| 331 | 
            -
            					yield( | 
| 360 | 
            +
            					return yield(self, id)
         | 
| 332 361 | 
             
            				else
         | 
| 333 | 
            -
            					return Stream. | 
| 334 | 
            -
            				end
         | 
| 335 | 
            -
            				
         | 
| 336 | 
            -
            				if stream = @streams[stream_id]
         | 
| 337 | 
            -
            					return stream
         | 
| 338 | 
            -
            				else
         | 
| 339 | 
            -
            					raise ProtocolError, "Stream creation failed!"
         | 
| 362 | 
            +
            					return Stream.create(self, id)
         | 
| 340 363 | 
             
            				end
         | 
| 341 364 | 
             
            			end
         | 
| 342 365 |  | 
| @@ -346,15 +369,23 @@ module Protocol | |
| 346 369 |  | 
| 347 370 | 
             
            			# On the server side, starts a new request.
         | 
| 348 371 | 
             
            			def receive_headers(frame)
         | 
| 349 | 
            -
            				 | 
| 372 | 
            +
            				stream_id = frame.stream_id
         | 
| 373 | 
            +
            				
         | 
| 374 | 
            +
            				if stream_id.zero?
         | 
| 350 375 | 
             
            					raise ProtocolError, "Cannot receive headers for stream 0!"
         | 
| 351 376 | 
             
            				end
         | 
| 352 377 |  | 
| 353 | 
            -
            				if stream = @streams[ | 
| 378 | 
            +
            				if stream = @streams[stream_id]
         | 
| 354 379 | 
             
            					stream.receive_headers(frame)
         | 
| 355 380 | 
             
            				else
         | 
| 381 | 
            +
            					if stream_id <= @remote_stream_id
         | 
| 382 | 
            +
            						raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!"
         | 
| 383 | 
            +
            					end
         | 
| 384 | 
            +
            					
         | 
| 356 385 | 
             
            					if self.active_streams.count < self.maximum_concurrent_streams
         | 
| 357 | 
            -
            						stream = accept_stream( | 
| 386 | 
            +
            						stream = accept_stream(stream_id)
         | 
| 387 | 
            +
            						@remote_stream_id = stream_id
         | 
| 388 | 
            +
            						
         | 
| 358 389 | 
             
            						stream.receive_headers(frame)
         | 
| 359 390 | 
             
            					else
         | 
| 360 391 | 
             
            						raise ProtocolError, "Exceeded maximum concurrent streams"
         | 
| @@ -367,6 +398,7 @@ module Protocol | |
| 367 398 | 
             
            				if stream = @streams[frame.stream_id]
         | 
| 368 399 | 
             
            					stream.receive_priority(frame)
         | 
| 369 400 | 
             
            				else
         | 
| 401 | 
            +
            					# Stream doesn't exist yet.
         | 
| 370 402 | 
             
            					stream = accept_stream(frame.stream_id)
         | 
| 371 403 | 
             
            					stream.receive_priority(frame)
         | 
| 372 404 | 
             
            				end
         | 
| @@ -387,6 +419,8 @@ module Protocol | |
| 387 419 | 
             
            			def receive_window_update(frame)
         | 
| 388 420 | 
             
            				if frame.connection?
         | 
| 389 421 | 
             
            					super
         | 
| 422 | 
            +
            					
         | 
| 423 | 
            +
            					self.consume_window
         | 
| 390 424 | 
             
            				elsif stream = @streams[frame.stream_id]
         | 
| 391 425 | 
             
            					begin
         | 
| 392 426 | 
             
            						stream.receive_window_update(frame)
         | 
| @@ -399,13 +433,6 @@ module Protocol | |
| 399 433 | 
             
            				end
         | 
| 400 434 | 
             
            			end
         | 
| 401 435 |  | 
| 402 | 
            -
            			def window_updated
         | 
| 403 | 
            -
            				# This is very inefficient, but workable.
         | 
| 404 | 
            -
            				@streams.each_value do |stream|
         | 
| 405 | 
            -
            					stream.window_updated unless stream.closed?
         | 
| 406 | 
            -
            				end
         | 
| 407 | 
            -
            			end
         | 
| 408 | 
            -
            			
         | 
| 409 436 | 
             
            			def receive_continuation(frame)
         | 
| 410 437 | 
             
            				raise ProtocolError, "Received unexpected continuation: #{frame.class}"
         | 
| 411 438 | 
             
            			end
         | 
    
        data/lib/protocol/http2/error.rb
    CHANGED
    
    | @@ -86,7 +86,13 @@ module Protocol | |
| 86 86 | 
             
            			attr :code
         | 
| 87 87 | 
             
            		end
         | 
| 88 88 |  | 
| 89 | 
            -
            		class  | 
| 89 | 
            +
            		class HeaderError < ProtocolError
         | 
| 90 | 
            +
            		end
         | 
| 91 | 
            +
            		
         | 
| 92 | 
            +
            		class StreamError < ProtocolError
         | 
| 93 | 
            +
            		end
         | 
| 94 | 
            +
            		
         | 
| 95 | 
            +
            		class StreamClosed < StreamError
         | 
| 90 96 | 
             
            			def initialize(message)
         | 
| 91 97 | 
             
            				super message, STREAM_CLOSED
         | 
| 92 98 | 
             
            			end
         | 
| @@ -23,9 +23,13 @@ require_relative 'window_update_frame' | |
| 23 23 | 
             
            module Protocol
         | 
| 24 24 | 
             
            	module HTTP2
         | 
| 25 25 | 
             
            		module FlowControl
         | 
| 26 | 
            -
            			def  | 
| 27 | 
            -
            				 | 
| 28 | 
            -
             | 
| 26 | 
            +
            			def available_size
         | 
| 27 | 
            +
            				@remote_window.available
         | 
| 28 | 
            +
            			end
         | 
| 29 | 
            +
            			
         | 
| 30 | 
            +
            			# This could be negative if the window has been overused due to a change in initial window size.
         | 
| 31 | 
            +
            			def available_frame_size(maximum_frame_size = self.maximum_frame_size)
         | 
| 32 | 
            +
            				available_size = self.available_size
         | 
| 29 33 |  | 
| 30 34 | 
             
            				# puts "available_size=#{available_size} maximum_frame_size=#{maximum_frame_size}"
         | 
| 31 35 |  | 
| @@ -71,10 +75,9 @@ module Protocol | |
| 71 75 | 
             
            			end
         | 
| 72 76 |  | 
| 73 77 | 
             
            			def receive_window_update(frame)
         | 
| 74 | 
            -
            				was_full = @remote_window.full?
         | 
| 75 | 
            -
            				
         | 
| 76 78 | 
             
            				amount = frame.unpack
         | 
| 77 | 
            -
            				 | 
| 79 | 
            +
            				
         | 
| 80 | 
            +
            				# puts "expanding remote_window=#{@remote_window} by #{amount}"
         | 
| 78 81 |  | 
| 79 82 | 
             
            				if amount != 0
         | 
| 80 83 | 
             
            					@remote_window.expand(amount)
         | 
| @@ -82,10 +85,39 @@ module Protocol | |
| 82 85 | 
             
            					raise ProtocolError, "Invalid window size increment: #{amount}!"
         | 
| 83 86 | 
             
            				end
         | 
| 84 87 |  | 
| 85 | 
            -
            				 | 
| 88 | 
            +
            				# puts "expanded remote_window=#{@remote_window} by #{amount}"
         | 
| 86 89 | 
             
            			end
         | 
| 87 90 |  | 
| 88 | 
            -
            			 | 
| 91 | 
            +
            			# The window has been expanded by the given amount.
         | 
| 92 | 
            +
            			# @param size [Integer] the maximum amount of data to send.
         | 
| 93 | 
            +
            			# @return [Boolean] whether the window update was used or not.
         | 
| 94 | 
            +
            			def window_updated(size = self.available_size)
         | 
| 95 | 
            +
            				return false
         | 
| 96 | 
            +
            			end
         | 
| 97 | 
            +
            			
         | 
| 98 | 
            +
            			# Traverse active streams in order of priority and allow them to consume the available flow-control window.
         | 
| 99 | 
            +
            			# @param amount [Integer] the amount of data to write. Defaults to the current window capacity.
         | 
| 100 | 
            +
            			def consume_window(size = self.available_size)
         | 
| 101 | 
            +
            				# Don't consume more than the available window size:
         | 
| 102 | 
            +
            				size = [self.available_size, size].min
         | 
| 103 | 
            +
            				# puts "consume_window(#{size}) local_window=#{@local_window} remote_window=#{@remote_window}"
         | 
| 104 | 
            +
            				
         | 
| 105 | 
            +
            				# Return if there is no window to consume:
         | 
| 106 | 
            +
            				return unless size > 0
         | 
| 107 | 
            +
            				
         | 
| 108 | 
            +
            				# Allow the current flow-controlled instance to use up the window:
         | 
| 109 | 
            +
            				if !self.window_updated(size) and children = self.children
         | 
| 110 | 
            +
            					children = children.values.sort_by(&:weight)
         | 
| 111 | 
            +
            					
         | 
| 112 | 
            +
            					# This must always be at least >= `children.count`, since stream weight can't be 0.
         | 
| 113 | 
            +
            					total = children.sum(&:weight)
         | 
| 114 | 
            +
            					
         | 
| 115 | 
            +
            					children.each do |child|
         | 
| 116 | 
            +
            						# Compute the proportional allocation:
         | 
| 117 | 
            +
            						allocated = (child.weight * size) / total
         | 
| 118 | 
            +
            						child.consume_window(allocated)
         | 
| 119 | 
            +
            					end
         | 
| 120 | 
            +
            				end
         | 
| 89 121 | 
             
            			end
         | 
| 90 122 | 
             
            		end
         | 
| 91 123 | 
             
            	end
         | 
    
        data/lib/protocol/http2/frame.rb
    CHANGED
    
    
| @@ -52,6 +52,8 @@ module Protocol | |
| 52 52 | 
             
            		CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
         | 
| 53 53 |  | 
| 54 54 | 
             
            		class Framer
         | 
| 55 | 
            +
            			# DEBUG = !!ENV['FRAMER_DEBUG']
         | 
| 56 | 
            +
            			
         | 
| 55 57 | 
             
            			def initialize(stream, frames = FRAMES)
         | 
| 56 58 | 
             
            				@stream = stream
         | 
| 57 59 | 
             
            				@frames = frames
         | 
| @@ -79,6 +81,8 @@ module Protocol | |
| 79 81 | 
             
            				return string
         | 
| 80 82 | 
             
            			end
         | 
| 81 83 |  | 
| 84 | 
            +
            			# @return [Frame] the frame that has been read from the underlying IO.
         | 
| 85 | 
            +
            			# @raise if the underlying IO fails for some reason.
         | 
| 82 86 | 
             
            			def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
         | 
| 83 87 | 
             
            				# Read the header:
         | 
| 84 88 | 
             
            				length, type, flags, stream_id = read_header
         | 
| @@ -86,18 +90,18 @@ module Protocol | |
| 86 90 |  | 
| 87 91 | 
             
            				# Allocate the frame:
         | 
| 88 92 | 
             
            				klass = @frames[type] || Frame
         | 
| 89 | 
            -
            				# puts "read_frame #{klass} id=#{stream_id} length=#{length} flags=#{flags}"
         | 
| 90 | 
            -
            				
         | 
| 91 93 | 
             
            				frame = klass.new(stream_id, flags, type, length)
         | 
| 92 94 |  | 
| 93 95 | 
             
            				# Read the payload:
         | 
| 94 96 | 
             
            				frame.read(@stream, maximum_frame_size)
         | 
| 95 97 |  | 
| 98 | 
            +
            				# DEBUG and puts "read_frame: #{frame.inspect}"
         | 
| 99 | 
            +
            				
         | 
| 96 100 | 
             
            				return frame
         | 
| 97 101 | 
             
            			end
         | 
| 98 102 |  | 
| 99 103 | 
             
            			def write_frame(frame)
         | 
| 100 | 
            -
            				# puts " | 
| 104 | 
            +
            				# DEBUG and puts "write_frame: #{frame.inspect}"
         | 
| 101 105 | 
             
            				frame.write(@stream)
         | 
| 102 106 |  | 
| 103 107 | 
             
            				@stream.flush
         | 
| @@ -22,6 +22,8 @@ require_relative 'frame' | |
| 22 22 |  | 
| 23 23 | 
             
            module Protocol
         | 
| 24 24 | 
             
            	module HTTP2
         | 
| 25 | 
            +
            		VALID_WEIGHT = (1..256)
         | 
| 26 | 
            +
            		
         | 
| 25 27 | 
             
            		# Stream Dependency:  A 31-bit stream identifier for the stream that
         | 
| 26 28 | 
             
            		# this stream depends on (see Section 5.3).  This field is only
         | 
| 27 29 | 
             
            		# present if the PRIORITY flag is set.
         | 
| @@ -29,10 +31,16 @@ module Protocol | |
| 29 31 | 
             
            			FORMAT = "NC".freeze
         | 
| 30 32 | 
             
            			EXCLUSIVE = 1 << 31
         | 
| 31 33 |  | 
| 34 | 
            +
            			# 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.
         | 
| 35 | 
            +
            			def self.default(stream_dependency = 0, weight = 16)
         | 
| 36 | 
            +
            				self.new(false, stream_dependency, weight)
         | 
| 37 | 
            +
            			end
         | 
| 38 | 
            +
            			
         | 
| 32 39 | 
             
            			def self.unpack(data)
         | 
| 33 40 | 
             
            				stream_dependency, weight = data.unpack(FORMAT)
         | 
| 34 41 |  | 
| 35 | 
            -
            				 | 
| 42 | 
            +
            				# 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.
         | 
| 43 | 
            +
            				return self.new(stream_dependency & EXCLUSIVE != 0, stream_dependency & ~EXCLUSIVE, weight + 1)
         | 
| 36 44 | 
             
            			end
         | 
| 37 45 |  | 
| 38 46 | 
             
            			def pack
         | 
| @@ -40,7 +48,15 @@ module Protocol | |
| 40 48 | 
             
            					stream_dependency = self.stream_dependency | EXCLUSIVE
         | 
| 41 49 | 
             
            				end
         | 
| 42 50 |  | 
| 43 | 
            -
            				return [stream_dependency, self.weight].pack(FORMAT)
         | 
| 51 | 
            +
            				return [stream_dependency, self.weight - 1].pack(FORMAT)
         | 
| 52 | 
            +
            			end
         | 
| 53 | 
            +
            			
         | 
| 54 | 
            +
            			def weight= value
         | 
| 55 | 
            +
            				if VALID_WEIGHT.include?(value)
         | 
| 56 | 
            +
            					super
         | 
| 57 | 
            +
            				else
         | 
| 58 | 
            +
            					raise ArgumentError, "Weight #{value} must be between 1-256!"
         | 
| 59 | 
            +
            				end
         | 
| 44 60 | 
             
            			end
         | 
| 45 61 | 
             
            		end
         | 
| 46 62 |  | 
| @@ -38,7 +38,7 @@ module Protocol | |
| 38 38 | 
             
            					send_settings(settings)
         | 
| 39 39 |  | 
| 40 40 | 
             
            					read_frame do |frame|
         | 
| 41 | 
            -
            						raise ProtocolError, "First frame must be SettingsFrame, but got #{frame.class}" unless frame.is_a? SettingsFrame
         | 
| 41 | 
            +
            						raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}" unless frame.is_a? SettingsFrame
         | 
| 42 42 | 
             
            					end
         | 
| 43 43 | 
             
            				else
         | 
| 44 44 | 
             
            					raise ProtocolError, "Cannot send connection preface in state #{@state}"
         | 
| @@ -231,11 +231,16 @@ module Protocol | |
| 231 231 | 
             
            			end
         | 
| 232 232 |  | 
| 233 233 | 
             
            			def unpack
         | 
| 234 | 
            -
            				super | 
| 234 | 
            +
            				if buffer = super
         | 
| 235 | 
            +
            					# TODO String#each_slice, or #each_unpack would be nice.
         | 
| 236 | 
            +
            					buffer.scan(/....../m).map{|s| s.unpack(FORMAT)}
         | 
| 237 | 
            +
            				else
         | 
| 238 | 
            +
            					[]
         | 
| 239 | 
            +
            				end
         | 
| 235 240 | 
             
            			end
         | 
| 236 241 |  | 
| 237 242 | 
             
            			def pack(settings = [])
         | 
| 238 | 
            -
            				super | 
| 243 | 
            +
            				super(settings.map{|s| s.pack(FORMAT)}.join)
         | 
| 239 244 | 
             
            			end
         | 
| 240 245 |  | 
| 241 246 | 
             
            			def apply(connection)
         | 
| @@ -74,44 +74,136 @@ module Protocol | |
| 74 74 | 
             
            		class Stream
         | 
| 75 75 | 
             
            			include FlowControl
         | 
| 76 76 |  | 
| 77 | 
            -
            			def  | 
| 77 | 
            +
            			def self.create(connection, id = connection.next_stream_id)
         | 
| 78 | 
            +
            				local_window = Window.new(connection.local_settings.initial_window_size)
         | 
| 79 | 
            +
            				remote_window = Window.new(connection.remote_settings.initial_window_size)
         | 
| 80 | 
            +
            				
         | 
| 81 | 
            +
            				stream = self.new(connection, id, local_window, remote_window)
         | 
| 82 | 
            +
            				
         | 
| 83 | 
            +
            				# Create new stream:
         | 
| 84 | 
            +
            				connection.streams[id] = stream
         | 
| 85 | 
            +
            				stream.parent.add_child(stream)
         | 
| 86 | 
            +
            				
         | 
| 87 | 
            +
            				return stream
         | 
| 88 | 
            +
            			end
         | 
| 89 | 
            +
            			
         | 
| 90 | 
            +
            			def self.replace(stream)
         | 
| 91 | 
            +
            				connection = stream.connection
         | 
| 92 | 
            +
            				stream.parent.remove_child(stream)
         | 
| 93 | 
            +
            				
         | 
| 94 | 
            +
            				stream = self.new(
         | 
| 95 | 
            +
            					connection, stream.id, stream.local_window, stream.remote_window,
         | 
| 96 | 
            +
            					stream.state, stream.dependent_id, stream.weight, stream.children
         | 
| 97 | 
            +
            				)
         | 
| 98 | 
            +
            				
         | 
| 99 | 
            +
            				# Replace existing stream:
         | 
| 100 | 
            +
            				connection.streams[stream.id] = stream
         | 
| 101 | 
            +
            				stream.parent.add_child(stream)
         | 
| 102 | 
            +
            				
         | 
| 103 | 
            +
            				return stream
         | 
| 104 | 
            +
            			end
         | 
| 105 | 
            +
            			
         | 
| 106 | 
            +
            			def self.accept(connection, id)
         | 
| 107 | 
            +
            				if stream = connection.streams[id]
         | 
| 108 | 
            +
            					self.replace(stream)
         | 
| 109 | 
            +
            				else
         | 
| 110 | 
            +
            					self.create(connection, id)
         | 
| 111 | 
            +
            				end
         | 
| 112 | 
            +
            			end
         | 
| 113 | 
            +
            			
         | 
| 114 | 
            +
            			def initialize(connection, id, local_window, remote_window, state = :idle, dependent_id = 0, weight = 16, children = nil)
         | 
| 78 115 | 
             
            				@connection = connection
         | 
| 79 116 | 
             
            				@id = id
         | 
| 80 117 |  | 
| 81 | 
            -
            				@ | 
| 118 | 
            +
            				@local_window = local_window
         | 
| 119 | 
            +
            				@remote_window = remote_window
         | 
| 82 120 |  | 
| 83 | 
            -
            				@ | 
| 84 | 
            -
            				@local_window = Window.new(connection.local_settings.initial_window_size)
         | 
| 85 | 
            -
            				@remote_window = Window.new(connection.remote_settings.initial_window_size)
         | 
| 121 | 
            +
            				@state = state
         | 
| 86 122 |  | 
| 87 | 
            -
            				 | 
| 88 | 
            -
            				@ | 
| 123 | 
            +
            				# Stream priority:
         | 
| 124 | 
            +
            				@dependent_id = dependent_id
         | 
| 125 | 
            +
            				@weight = weight
         | 
| 89 126 |  | 
| 90 | 
            -
            				 | 
| 127 | 
            +
            				# A cache of streams that have child.dependent_id = self.id
         | 
| 128 | 
            +
            				@children = children
         | 
| 91 129 | 
             
            			end
         | 
| 92 130 |  | 
| 93 | 
            -
            			#  | 
| 94 | 
            -
            			 | 
| 95 | 
            -
            			 | 
| 131 | 
            +
            			# Cache of dependent children.
         | 
| 132 | 
            +
            			attr_accessor :children
         | 
| 133 | 
            +
            			
         | 
| 134 | 
            +
            			# The connection this stream belongs to.
         | 
| 135 | 
            +
            			attr :connection
         | 
| 96 136 |  | 
| 97 137 | 
             
            			# Stream ID (odd for client initiated streams, even otherwise).
         | 
| 98 138 | 
             
            			attr :id
         | 
| 99 139 |  | 
| 100 | 
            -
            			# Stream state  | 
| 101 | 
            -
            			 | 
| 140 | 
            +
            			# Stream state, e.g. `idle`, `closed`.
         | 
| 141 | 
            +
            			attr_accessor :state
         | 
| 142 | 
            +
            			
         | 
| 143 | 
            +
            			# The stream id that this stream depends on, according to the priority.
         | 
| 144 | 
            +
            			attr_accessor :dependent_id
         | 
| 102 145 |  | 
| 103 | 
            -
            			 | 
| 104 | 
            -
            			 | 
| 146 | 
            +
            			# The weight of the stream relative to other siblings.
         | 
| 147 | 
            +
            			attr_accessor :weight
         | 
| 105 148 |  | 
| 106 149 | 
             
            			attr :local_window
         | 
| 107 150 | 
             
            			attr :remote_window
         | 
| 108 151 |  | 
| 152 | 
            +
            			def add_child(stream)
         | 
| 153 | 
            +
            				@children ||= {}
         | 
| 154 | 
            +
            				@children[stream.id] = stream
         | 
| 155 | 
            +
            			end
         | 
| 156 | 
            +
            			
         | 
| 157 | 
            +
            			def remove_child(stream)
         | 
| 158 | 
            +
            				@children.delete(stream.id)
         | 
| 159 | 
            +
            			end
         | 
| 160 | 
            +
            			
         | 
| 161 | 
            +
            			def exclusive_child(stream)
         | 
| 162 | 
            +
            				stream.children = @children
         | 
| 163 | 
            +
            				
         | 
| 164 | 
            +
            				@children.each do |child|
         | 
| 165 | 
            +
            					child.dependent_id = stream.id
         | 
| 166 | 
            +
            				end
         | 
| 167 | 
            +
            				
         | 
| 168 | 
            +
            				@children = {stream.id => stream}
         | 
| 169 | 
            +
            				
         | 
| 170 | 
            +
            				stream.dependent_id = @id
         | 
| 171 | 
            +
            			end
         | 
| 172 | 
            +
            			
         | 
| 173 | 
            +
            			def parent(id = @dependent_id)
         | 
| 174 | 
            +
            				@connection[id] || @connection.accept_stream(id)
         | 
| 175 | 
            +
            			end
         | 
| 176 | 
            +
            			
         | 
| 177 | 
            +
            			def parent= stream
         | 
| 178 | 
            +
            				self.parent&.remove_child(self)
         | 
| 179 | 
            +
            				
         | 
| 180 | 
            +
            				@dependent_id = stream.id
         | 
| 181 | 
            +
            				
         | 
| 182 | 
            +
            				stream.add_child(self)
         | 
| 183 | 
            +
            			end
         | 
| 184 | 
            +
            			
         | 
| 109 185 | 
             
            			def priority= priority
         | 
| 110 | 
            -
            				 | 
| 186 | 
            +
            				dependent_id = priority.stream_dependency
         | 
| 187 | 
            +
            				
         | 
| 188 | 
            +
            				if dependent_id == @id
         | 
| 111 189 | 
             
            					raise ProtocolError, "Stream priority for stream id #{@id} cannot depend on itself!"
         | 
| 112 190 | 
             
            				end
         | 
| 113 191 |  | 
| 114 | 
            -
            				 | 
| 192 | 
            +
            				if priority.exclusive
         | 
| 193 | 
            +
            					self.parent&.remove_child(self)
         | 
| 194 | 
            +
            					
         | 
| 195 | 
            +
            					self.parent(dependent_id).exclusive_child(self)
         | 
| 196 | 
            +
            				elsif dependent_id != @dependent_id
         | 
| 197 | 
            +
            					self.parent&.remove_child(self)
         | 
| 198 | 
            +
            					
         | 
| 199 | 
            +
            					@dependent_id = dependent_id
         | 
| 200 | 
            +
            					
         | 
| 201 | 
            +
            					self.parent.add_child(self)
         | 
| 202 | 
            +
            				end
         | 
| 203 | 
            +
            			end
         | 
| 204 | 
            +
            			
         | 
| 205 | 
            +
            			# The stream is being closed because the connection is being closed.
         | 
| 206 | 
            +
            			def close(error = nil)
         | 
| 115 207 | 
             
            			end
         | 
| 116 208 |  | 
| 117 209 | 
             
            			def maximum_frame_size
         | 
| @@ -134,19 +226,6 @@ module Protocol | |
| 134 226 | 
             
            				@state == :idle or @state == :reserved_local or @state == :open or @state == :half_closed_remote
         | 
| 135 227 | 
             
            			end
         | 
| 136 228 |  | 
| 137 | 
            -
            			def send_failure(status, reason)
         | 
| 138 | 
            -
            				if send_headers?
         | 
| 139 | 
            -
            					send_headers(nil, [
         | 
| 140 | 
            -
            						[':status', status.to_s],
         | 
| 141 | 
            -
            						['reason', reason]
         | 
| 142 | 
            -
            					], END_STREAM)
         | 
| 143 | 
            -
            				else
         | 
| 144 | 
            -
            					send_reset_stream(PROTOCOL_ERROR)
         | 
| 145 | 
            -
            				end
         | 
| 146 | 
            -
            				
         | 
| 147 | 
            -
            				return nil
         | 
| 148 | 
            -
            			end
         | 
| 149 | 
            -
            			
         | 
| 150 229 | 
             
            			private def write_headers(priority, headers, flags = 0)
         | 
| 151 230 | 
             
            				data = @connection.encode_headers(headers)
         | 
| 152 231 |  | 
| @@ -201,7 +280,6 @@ module Protocol | |
| 201 280 |  | 
| 202 281 | 
             
            				# This might fail if the data payload was too big:
         | 
| 203 282 | 
             
            				consume_remote_window(frame)
         | 
| 204 | 
            -
            				
         | 
| 205 283 | 
             
            				write_frame(frame)
         | 
| 206 284 |  | 
| 207 285 | 
             
            				return frame
         | 
| @@ -226,10 +304,15 @@ module Protocol | |
| 226 304 | 
             
            			end
         | 
| 227 305 |  | 
| 228 306 | 
             
            			# Transition the stream into the closed state.
         | 
| 229 | 
            -
            			 | 
| 307 | 
            +
            			# @param error_code [Integer] the error code if the stream was closed due to a stream reset.
         | 
| 308 | 
            +
            			def close!(error_code = nil)
         | 
| 230 309 | 
             
            				@state = :closed
         | 
| 231 310 |  | 
| 232 | 
            -
            				 | 
| 311 | 
            +
            				if error_code
         | 
| 312 | 
            +
            					error = StreamError.new("Stream closed!", error_code)
         | 
| 313 | 
            +
            				end
         | 
| 314 | 
            +
            				
         | 
| 315 | 
            +
            				self.close(error)
         | 
| 233 316 | 
             
            			end
         | 
| 234 317 |  | 
| 235 318 | 
             
            			def send_reset_stream(error_code = 0)
         | 
| @@ -264,28 +347,33 @@ module Protocol | |
| 264 347 | 
             
            						@state = :open
         | 
| 265 348 | 
             
            					end
         | 
| 266 349 |  | 
| 267 | 
            -
            					 | 
| 350 | 
            +
            					return process_headers(frame)
         | 
| 268 351 | 
             
            				elsif @state == :reserved_remote
         | 
| 269 352 | 
             
            					@state = :half_closed_local
         | 
| 270 353 |  | 
| 271 | 
            -
            					 | 
| 354 | 
            +
            					return process_headers(frame)
         | 
| 272 355 | 
             
            				elsif @state == :open
         | 
| 273 356 | 
             
            					if frame.end_stream?
         | 
| 274 357 | 
             
            						@state = :half_closed_remote
         | 
| 275 358 | 
             
            					end
         | 
| 276 359 |  | 
| 277 | 
            -
            					 | 
| 360 | 
            +
            					return process_headers(frame)
         | 
| 278 361 | 
             
            				elsif @state == :half_closed_local
         | 
| 279 362 | 
             
            					if frame.end_stream?
         | 
| 280 363 | 
             
            						close!
         | 
| 281 364 | 
             
            					end
         | 
| 282 365 |  | 
| 283 | 
            -
            					 | 
| 366 | 
            +
            					return process_headers(frame)
         | 
| 284 367 | 
             
            				else
         | 
| 285 368 | 
             
            					raise ProtocolError, "Cannot receive headers in state: #{@state}"
         | 
| 286 369 | 
             
            				end
         | 
| 287 370 | 
             
            			end
         | 
| 288 371 |  | 
| 372 | 
            +
            			# @return [String] the data that was received.
         | 
| 373 | 
            +
            			def process_data(frame)
         | 
| 374 | 
            +
            				frame.unpack
         | 
| 375 | 
            +
            			end
         | 
| 376 | 
            +
            			
         | 
| 289 377 | 
             
            			# 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.
         | 
| 290 378 | 
             
            			def receive_data(frame)
         | 
| 291 379 | 
             
            				if @state == :open
         | 
| @@ -295,15 +383,15 @@ module Protocol | |
| 295 383 | 
             
            						@state = :half_closed_remote
         | 
| 296 384 | 
             
            					end
         | 
| 297 385 |  | 
| 298 | 
            -
            					 | 
| 386 | 
            +
            					process_data(frame)
         | 
| 299 387 | 
             
            				elsif @state == :half_closed_local
         | 
| 300 388 | 
             
            					consume_local_window(frame)
         | 
| 301 389 |  | 
| 390 | 
            +
            					process_data(frame)
         | 
| 391 | 
            +
            					
         | 
| 302 392 | 
             
            					if frame.end_stream?
         | 
| 303 393 | 
             
            						close!
         | 
| 304 394 | 
             
            					end
         | 
| 305 | 
            -
            					
         | 
| 306 | 
            -
            					@data = frame.unpack
         | 
| 307 395 | 
             
            				else
         | 
| 308 396 | 
             
            					raise ProtocolError, "Cannot receive data in state: #{@state}"
         | 
| 309 397 | 
             
            				end
         | 
| @@ -315,9 +403,11 @@ module Protocol | |
| 315 403 |  | 
| 316 404 | 
             
            			def receive_reset_stream(frame)
         | 
| 317 405 | 
             
            				if @state != :idle and @state != :closed
         | 
| 318 | 
            -
            					 | 
| 406 | 
            +
            					error_code = frame.unpack
         | 
| 407 | 
            +
            					
         | 
| 408 | 
            +
            					close!(error_code)
         | 
| 319 409 |  | 
| 320 | 
            -
            					return  | 
| 410 | 
            +
            					return error_code
         | 
| 321 411 | 
             
            				else
         | 
| 322 412 | 
             
            					raise ProtocolError, "Cannot reset stream in state: #{@state}"
         | 
| 323 413 | 
             
            				end
         | 
| @@ -383,6 +473,7 @@ module Protocol | |
| 383 473 | 
             
            				headers = @connection.decode_headers(data)
         | 
| 384 474 |  | 
| 385 475 | 
             
            				stream = self.accept_push_promise_stream(promised_stream_id, headers)
         | 
| 476 | 
            +
            				stream.parent = self
         | 
| 386 477 | 
             
            				stream.reserved_remote!
         | 
| 387 478 |  | 
| 388 479 | 
             
            				return stream, headers
         | 
| @@ -49,6 +49,7 @@ module Protocol | |
| 49 49 | 
             
            			def capacity= value
         | 
| 50 50 | 
             
            				difference = value - @capacity
         | 
| 51 51 | 
             
            				@available += difference
         | 
| 52 | 
            +
            				@capacity = value
         | 
| 52 53 | 
             
            			end
         | 
| 53 54 |  | 
| 54 55 | 
             
            			def consume(amount)
         | 
| @@ -63,6 +64,7 @@ module Protocol | |
| 63 64 | 
             
            			end
         | 
| 64 65 |  | 
| 65 66 | 
             
            			def expand(amount)
         | 
| 67 | 
            +
            				# puts "expand(#{amount}) @available=#{@available}"
         | 
| 66 68 | 
             
            				@available += amount
         | 
| 67 69 | 
             
            				@used -= amount
         | 
| 68 70 |  | 
    
        data/protocol-http2.gemspec
    CHANGED
    
    | @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| | |
| 18 18 | 
             
            	spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 19 19 | 
             
            	spec.require_paths = ["lib"]
         | 
| 20 20 |  | 
| 21 | 
            -
            	spec.add_dependency "protocol-hpack", "~> 1. | 
| 21 | 
            +
            	spec.add_dependency "protocol-hpack", "~> 1.2"
         | 
| 22 22 | 
             
            	spec.add_dependency "protocol-http", "~> 0.2"
         | 
| 23 23 |  | 
| 24 24 | 
             
            	spec.add_development_dependency "covered"
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: protocol-http2
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.7.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Samuel Williams
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2019-06- | 
| 11 | 
            +
            date: 2019-06-17 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: protocol-hpack
         | 
| @@ -16,14 +16,14 @@ dependencies: | |
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - "~>"
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: '1. | 
| 19 | 
            +
                    version: '1.2'
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 24 | 
             
                - - "~>"
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: '1. | 
| 26 | 
            +
                    version: '1.2'
         | 
| 27 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 28 | 
             
              name: protocol-http
         | 
| 29 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -150,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 150 150 | 
             
                - !ruby/object:Gem::Version
         | 
| 151 151 | 
             
                  version: '0'
         | 
| 152 152 | 
             
            requirements: []
         | 
| 153 | 
            -
            rubygems_version: 3.0. | 
| 153 | 
            +
            rubygems_version: 3.0.2
         | 
| 154 154 | 
             
            signing_key: 
         | 
| 155 155 | 
             
            specification_version: 4
         | 
| 156 156 | 
             
            summary: A low level implementation of the HTTP/2 protocol.
         |