protocol-http2 0.3.0 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aea99dc493ccb40d8116646387ff26724010de491bd248e8d925d0dead5d60d3
4
- data.tar.gz: 1ee5cb421bfd1674181ddf079534bf793c69ae328bbd1e80fe7ab7a154cca9f3
3
+ metadata.gz: 59f70b2d8905a24deb2b5f6400347631c226dea2814f23a0701d64ca343c8e7e
4
+ data.tar.gz: b6370e82f47701f4eb7ad21049524cf559d7c1303a839a802a88dfc8f2808c16
5
5
  SHA512:
6
- metadata.gz: a6bae8dd0f45b18b0430158c844ef4c6eddc0a5b6233215663f61c9628021a4213f1d1e2cfa6dede159845f3216d27b25dba47e78b51cfa7fcacf56fc8c08cfb
7
- data.tar.gz: 43d2b449bad3864708bcb68219999ecc058f633c2866d73331c09d1bb7c2f2ab9c4922313b6124f239bf19a8dc38d4c26638aa7c3563fd9fada4dfe29374bc2b
6
+ metadata.gz: f2c32cb5174ed643a415b836a2faa53a325a5e3252e1db55be9fafbc117fd78beb3768b467879f27b146da001caf3a92c74137924e5cf4c9eb86006b7d5a4a42
7
+ data.tar.gz: 05a417ab4821acb8e0db930440f8a7583ae265f1dd14363eea42963cc4fb313e413aa91d3c81ca4453a413f5db42ef6934e6c4d76cef5f9ac7b757f5f7c6a67e
@@ -27,6 +27,10 @@ module Protocol
27
27
  super(framer, 1)
28
28
  end
29
29
 
30
+ def valid_remote_stream_id?(stream_id)
31
+ stream_id.even?
32
+ end
33
+
30
34
  def send_connection_preface(settings = [])
31
35
  if @state == :new
32
36
  @framer.write_connection_preface
@@ -50,6 +50,7 @@ module Protocol
50
50
  0
51
51
  end
52
52
 
53
+ # The size of a frame payload is limited by the maximum size that a receiver advertises in the SETTINGS_MAX_FRAME_SIZE setting.
53
54
  def maximum_frame_size
54
55
  @remote_settings.maximum_frame_size
55
56
  end
@@ -78,11 +79,15 @@ module Protocol
78
79
  @state == :closed
79
80
  end
80
81
 
81
- def close
82
+ def connection_error!(error, message)
82
83
  return if @framer.closed?
83
84
 
84
- send_goaway unless closed?
85
+ send_goaway(error, message)
85
86
 
87
+ self.close
88
+ end
89
+
90
+ def close
86
91
  @framer.close
87
92
  end
88
93
 
@@ -117,16 +122,21 @@ module Protocol
117
122
  frame.apply(self)
118
123
 
119
124
  return frame
125
+ rescue GoawayError
126
+ # This is not a connection error. We are done.
127
+ self.close
128
+
129
+ raise
120
130
  rescue ProtocolError => error
121
- send_goaway(error.code || PROTOCOL_ERROR, error.message)
131
+ connection_error!(error.code || PROTOCOL_ERROR, error.message)
122
132
 
123
133
  raise
124
134
  rescue HPACK::CompressionError => error
125
- send_goaway(COMPRESSION_ERROR, error.message)
135
+ connection_error!(COMPRESSION_ERROR, error.message)
126
136
 
127
137
  raise
128
138
  rescue
129
- send_goaway(PROTOCOL_ERROR, $!.message)
139
+ connection_error!(PROTOCOL_ERROR, $!.message)
130
140
 
131
141
  raise
132
142
  end
@@ -236,6 +246,10 @@ module Protocol
236
246
 
237
247
  def receive_ping(frame)
238
248
  if @state != :closed
249
+ if frame.stream_id != 0
250
+ raise ProtocolError, "Ping received for non-zero stream!"
251
+ end
252
+
239
253
  unless frame.acknowledgement?
240
254
  reply = frame.acknowledge
241
255
 
@@ -251,17 +265,32 @@ module Protocol
251
265
 
252
266
  if stream = @streams[frame.stream_id]
253
267
  stream.receive_data(frame)
254
-
255
- if stream.closed?
256
- @streams.delete(stream.id)
257
- end
258
268
  else
259
- raise ProtocolError, "Bad stream"
269
+ raise ProtocolError, "Cannot receive data for idle stream #{frame.stream_id}"
260
270
  end
261
271
  end
262
272
 
273
+ def valid_remote_stream_id?
274
+ false
275
+ end
276
+
277
+ # Accept a stream from the other side of the connnection.
278
+ def accept_stream(stream_id)
279
+ unless valid_remote_stream_id?(stream_id)
280
+ raise ProtocolError, "Invalid remote stream id: #{stream_id}"
281
+ end
282
+
283
+ if stream_id <= @remote_stream_id
284
+ raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!"
285
+ end
286
+
287
+ @remote_stream_id = stream_id
288
+ create_stream(stream_id)
289
+ end
290
+
291
+ # Create a stream on this side of the connection.
263
292
  def create_stream(stream_id = next_stream_id)
264
- Stream.new(self, stream_id)
293
+ @streams[stream_id] = Stream.new(self, stream_id)
265
294
  end
266
295
 
267
296
  def receive_headers(frame)
@@ -271,49 +300,30 @@ module Protocol
271
300
 
272
301
  if stream = @streams[frame.stream_id]
273
302
  stream.receive_headers(frame)
274
-
275
- if stream.closed?
276
- @streams.delete(stream.id)
277
- end
278
- elsif frame.stream_id > @remote_stream_id
303
+ else
279
304
  if @streams.count < self.maximum_concurrent_streams
280
- stream = create_stream(frame.stream_id)
305
+ stream = accept_stream(frame.stream_id)
281
306
  stream.receive_headers(frame)
282
-
283
- @remote_stream_id = stream.id
284
- @streams[stream.id] = stream
285
307
  else
286
308
  raise ProtocolError, "Exceeded maximum concurrent streams"
287
309
  end
288
310
  end
289
311
  end
290
312
 
291
- def deleted_stream? frame
292
- frame.stream_id <= @local_stream_id or frame.stream_id <= @remote_stream_id
293
- end
294
-
295
313
  def receive_priority(frame)
296
314
  if stream = @streams[frame.stream_id]
297
315
  stream.receive_priority(frame)
298
- elsif deleted_stream? frame
299
- # ignore
300
316
  else
301
- stream = create_stream(frame.stream_id)
317
+ stream = accept_stream(frame.stream_id)
302
318
  stream.receive_priority(frame)
303
-
304
- @streams[frame.stream_id] = stream
305
319
  end
306
320
  end
307
321
 
308
322
  def receive_reset_stream(frame)
309
323
  if stream = @streams[frame.stream_id]
310
324
  stream.receive_reset_stream(frame)
311
-
312
- @streams.delete(stream.id)
313
- elsif deleted_stream? frame
314
- # ignore
315
325
  else
316
- raise ProtocolError, "Bad stream"
326
+ raise StreamClosed, "Cannot reset stream #{frame.stream_id}"
317
327
  end
318
328
  end
319
329
 
@@ -321,11 +331,14 @@ module Protocol
321
331
  if frame.connection?
322
332
  super
323
333
  elsif stream = @streams[frame.stream_id]
324
- stream.receive_window_update(frame)
325
- elsif deleted_stream? frame
326
- # ignore
334
+ begin
335
+ stream.receive_window_update(frame)
336
+ rescue ProtocolError => error
337
+ stream.send_reset_stream(error.code)
338
+ end
327
339
  else
328
- raise ProtocolError, "Cannot update window of non-existant stream: #{frame.stream_id}"
340
+ # Receiving any frame other than HEADERS or PRIORITY on a stream in this state MUST be treated as a connection error of type PROTOCOL_ERROR.
341
+ raise ProtocolError, "Cannot update window of idle stream #{frame.stream_id}"
329
342
  end
330
343
  end
331
344
 
@@ -336,8 +349,12 @@ module Protocol
336
349
  end
337
350
  end
338
351
 
352
+ def receive_continuation(frame)
353
+ raise ProtocolError, "Received unexpected continuation: #{frame.class}"
354
+ end
355
+
339
356
  def receive_frame(frame)
340
- warn "Unhandled frame #{frame.inspect}"
357
+ # ignore.
341
358
  end
342
359
  end
343
360
  end
@@ -37,9 +37,21 @@ module Protocol
37
37
  super
38
38
 
39
39
  unless end_headers?
40
- @continuation = ContinuationFrame.new
40
+ continuation = ContinuationFrame.new
41
+ continuation.read_header(stream)
42
+
43
+ # We validate the frame type here:
44
+ unless continuation.valid_type?
45
+ raise ProtocolError, "Invalid frame type: #{@type}!"
46
+ end
47
+
48
+ if continuation.stream_id != @stream_id
49
+ raise ProtocolError, "Invalid stream id: #{continuation.stream_id} for continuation of stream id: #{@stream_id}!"
50
+ end
41
51
 
42
- @continuation.read(stream, maximum_frame_size)
52
+ continuation.read(stream, maximum_frame_size)
53
+
54
+ @continuation = continuation
43
55
  end
44
56
  end
45
57
 
@@ -93,6 +105,11 @@ module Protocol
93
105
  include Continued
94
106
 
95
107
  TYPE = 0x9
108
+
109
+ # This is only invoked if the continuation is received out of the normal flow.
110
+ def apply(connection)
111
+ connection.receive_continuation(self)
112
+ end
96
113
  end
97
114
  end
98
115
  end
@@ -86,6 +86,12 @@ module Protocol
86
86
  attr :code
87
87
  end
88
88
 
89
+ class StreamClosed < ProtocolError
90
+ def initialize(message)
91
+ super message, STREAM_CLOSED
92
+ end
93
+ end
94
+
89
95
  class GoawayError < ProtocolError
90
96
  end
91
97
 
@@ -27,6 +27,8 @@ module Protocol
27
27
  maximum_frame_size = self.maximum_frame_size
28
28
  available_size = @remote_window.available
29
29
 
30
+ # puts "available_size=#{available_size} maximum_frame_size=#{maximum_frame_size}"
31
+
30
32
  if available_size < maximum_frame_size
31
33
  return available_size
32
34
  else
@@ -71,8 +73,14 @@ module Protocol
71
73
  def receive_window_update(frame)
72
74
  was_full = @remote_window.full?
73
75
 
74
- # puts "expand remote_window=#{@remote_window} by #{frame.unpack}"
75
- @remote_window.expand(frame.unpack)
76
+ amount = frame.unpack
77
+ # puts "expand remote_window=#{@remote_window} by #{amount}"
78
+
79
+ if amount != 0
80
+ @remote_window.expand(amount)
81
+ else
82
+ raise ProtocolError, "Invalid window size increment: #{amount}!"
83
+ end
76
84
 
77
85
  self.window_updated if was_full
78
86
  end
@@ -29,6 +29,7 @@ module Protocol
29
29
  PRIORITY = 0x20
30
30
 
31
31
  MAXIMUM_ALLOWED_WINDOW_SIZE = 0x7FFFFFFF
32
+ MINIMUM_ALLOWED_FRAME_SIZE = 0x4000
32
33
  MAXIMUM_ALLOWED_FRAME_SIZE = 0xFFFFFF
33
34
 
34
35
  class Frame
@@ -46,7 +47,7 @@ module Protocol
46
47
  LENGTH_LOMASK = 0xFFFF
47
48
 
48
49
  # @param length [Integer] the length of the payload, or nil if the header has not been read yet.
49
- def initialize(stream_id = 0, flags = 0, type = self.class.const_get(:TYPE), length = nil, payload = nil)
50
+ def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil)
50
51
  @length = length
51
52
  @type = type
52
53
  @flags = flags
@@ -54,6 +55,10 @@ module Protocol
54
55
  @payload = payload
55
56
  end
56
57
 
58
+ def valid_type?
59
+ @type == self.class::TYPE
60
+ end
61
+
57
62
  def <=> other
58
63
  to_ary <=> other.to_ary
59
64
  end
@@ -147,11 +152,18 @@ module Protocol
147
152
  length = (length_hi << LENGTH_HISHIFT) | length_lo
148
153
  stream_id = stream_id & STREAM_ID_MASK
149
154
 
155
+ # puts "parse_header: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id}"
156
+
150
157
  return length, type, flags, stream_id
151
158
  end
152
159
 
153
160
  def read_header(stream)
154
- @length, @type, @flags, @stream_id = Frame.parse_header(stream.read(9))
161
+ if buffer = stream.read(9)
162
+ @length, @type, @flags, @stream_id = Frame.parse_header(buffer)
163
+ # puts "read_header: #{@length} #{@type} #{@flags} #{@stream_id}"
164
+ else
165
+ raise EOFError, "Could not read frame header!"
166
+ end
155
167
  end
156
168
 
157
169
  def read_payload(stream)
@@ -82,11 +82,12 @@ module Protocol
82
82
  def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
83
83
  # Read the header:
84
84
  length, type, flags, stream_id = read_header
85
-
86
- # puts "framer: read_frame #{type} #{length}"
85
+ # puts "read_frame: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id} -> klass=#{@frames[type].inspect}"
87
86
 
88
87
  # Allocate the frame:
89
88
  klass = @frames[type] || Frame
89
+ # puts "read_frame #{klass} id=#{stream_id} length=#{length} flags=#{flags}"
90
+
90
91
  frame = klass.new(stream_id, flags, type, length)
91
92
 
92
93
  # Read the payload:
@@ -71,8 +71,8 @@ module Protocol
71
71
  def read_payload(stream)
72
72
  super
73
73
 
74
- if @length > 8
75
- raise FrameSizeError, "Invalid frame length"
74
+ if @length != 8
75
+ raise FrameSizeError, "Invalid frame length: #{@length} != 8!"
76
76
  end
77
77
  end
78
78
  end
@@ -27,6 +27,10 @@ module Protocol
27
27
  super(framer, 2)
28
28
  end
29
29
 
30
+ def valid_remote_stream_id?(stream_id)
31
+ stream_id.odd?
32
+ end
33
+
30
34
  def read_connection_preface(settings = [])
31
35
  if @state == :new
32
36
  @framer.read_connection_preface
@@ -68,10 +68,12 @@ module Protocol
68
68
  attr :maximum_frame_size
69
69
 
70
70
  def maximum_frame_size= value
71
- if value <= MAXIMUM_ALLOWED_FRAME_SIZE
72
- @maximum_frame_size = value
73
- else
71
+ if value > MAXIMUM_ALLOWED_FRAME_SIZE
74
72
  raise ProtocolError, "Invalid value for maximum_frame_size: #{value} > #{MAXIMUM_ALLOWED_FRAME_SIZE}"
73
+ elsif value < MINIMUM_ALLOWED_FRAME_SIZE
74
+ raise ProtocolError, "Invalid value for maximum_frame_size: #{value} < #{MINIMUM_ALLOWED_FRAME_SIZE}"
75
+ else
76
+ @maximum_frame_size = value
75
77
  end
76
78
  end
77
79
 
@@ -77,8 +77,6 @@ module Protocol
77
77
  @connection = connection
78
78
  @id = id
79
79
 
80
- @connection.streams[@id] = self
81
-
82
80
  @state = :idle
83
81
 
84
82
  @priority = nil
@@ -101,6 +99,14 @@ module Protocol
101
99
  attr :local_window
102
100
  attr :remote_window
103
101
 
102
+ def priority= priority
103
+ if priority.stream_id == @id
104
+ raise ProtocolError, "Stream #{@id} cannot depend on itself!"
105
+ end
106
+
107
+ @priority = priority
108
+ end
109
+
104
110
  def maximum_frame_size
105
111
  @connection.available_frame_size
106
112
  end
@@ -141,7 +147,7 @@ module Protocol
141
147
  return frame
142
148
  end
143
149
 
144
- #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.
150
+ # 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.
145
151
  def send_headers(*args)
146
152
  if @state == :idle
147
153
  frame = write_headers(*args)
@@ -165,7 +171,7 @@ module Protocol
165
171
  frame = write_headers(*args)
166
172
 
167
173
  if frame.end_stream?
168
- close!
174
+ close
169
175
  end
170
176
  else
171
177
  raise ProtocolError, "Cannot send headers in state: #{@state}"
@@ -201,17 +207,15 @@ module Protocol
201
207
  frame = write_data(*args)
202
208
 
203
209
  if frame.end_stream?
204
- close!
210
+ close
205
211
  end
206
212
  else
207
213
  raise ProtocolError, "Cannot send data in state: #{@state}"
208
214
  end
209
215
  end
210
216
 
211
- def close!(state = :closed)
217
+ def close(state = :closed)
212
218
  @state = state
213
-
214
- @connection.streams.delete(@id)
215
219
  end
216
220
 
217
221
  def send_reset_stream(error_code = 0)
@@ -221,7 +225,7 @@ module Protocol
221
225
 
222
226
  write_frame(frame)
223
227
 
224
- close!(:reset)
228
+ close(:reset)
225
229
  else
226
230
  raise ProtocolError, "Cannot reset stream in state: #{@state}"
227
231
  end
@@ -232,7 +236,7 @@ module Protocol
232
236
  priority, data = frame.unpack
233
237
 
234
238
  if priority
235
- @priority = priority
239
+ self.priority = priority
236
240
  end
237
241
 
238
242
  @connection.decode_headers(data)
@@ -259,7 +263,7 @@ module Protocol
259
263
  @headers = process_headers(frame)
260
264
  elsif @state == :half_closed_local
261
265
  if frame.end_stream?
262
- close!
266
+ close
263
267
  end
264
268
 
265
269
  @headers = process_headers(frame)
@@ -284,7 +288,7 @@ module Protocol
284
288
  consume_local_window(frame)
285
289
 
286
290
  if frame.end_stream?
287
- close!
291
+ close
288
292
  end
289
293
 
290
294
  @data = frame.unpack
@@ -296,12 +300,12 @@ module Protocol
296
300
  end
297
301
 
298
302
  def receive_priority(frame)
299
- @priority = frame.unpack
303
+ self.priority = frame.unpack
300
304
  end
301
305
 
302
306
  def receive_reset_stream(frame)
303
307
  if @state != :idle and @state != :closed
304
- close!
308
+ close
305
309
 
306
310
  return frame.unpack
307
311
  else
@@ -367,6 +371,10 @@ module Protocol
367
371
 
368
372
  return stream, headers
369
373
  end
374
+
375
+ def inspect
376
+ "\#<#{self.class} id=#{@id} state=#{@state}>"
377
+ end
370
378
  end
371
379
  end
372
380
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Protocol
22
22
  module HTTP2
23
- VERSION = "0.3.0"
23
+ VERSION = "0.4.1"
24
24
  end
25
25
  end
@@ -31,8 +31,6 @@ module Protocol
31
31
  # These two fields are primarily used for efficiently sending window updates:
32
32
  @used = 0
33
33
  @capacity = capacity
34
-
35
- fail unless capacity
36
34
  end
37
35
 
38
36
  def dup
@@ -41,7 +39,7 @@ module Protocol
41
39
 
42
40
  # The window is completely full?
43
41
  def full?
44
- @available.zero?
42
+ @available <= 0
45
43
  end
46
44
 
47
45
  attr :used
@@ -67,6 +65,10 @@ module Protocol
67
65
  def expand(amount)
68
66
  @available += amount
69
67
  @used -= amount
68
+
69
+ if @available > MAXIMUM_ALLOWED_WINDOW_SIZE
70
+ raise FlowControlError, "Expanding window by #{amount} caused overflow: #{@available} > #{MAXIMUM_ALLOWED_WINDOW_SIZE}!"
71
+ end
70
72
  end
71
73
 
72
74
  def limited?
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.3.0
4
+ version: 0.4.1
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-05-23 00:00:00.000000000 Z
11
+ date: 2019-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: protocol-hpack
@@ -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.2
153
+ rubygems_version: 3.0.3
154
154
  signing_key:
155
155
  specification_version: 4
156
156
  summary: A low level implementation of the HTTP/2 protocol.