protocol-http2 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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.