http-2 0.6.3 → 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.
@@ -18,12 +18,6 @@ module HTTP2
18
18
  # @see ProtocolError
19
19
  class CompressionError < ProtocolError; end
20
20
 
21
- # Raised on invalid reference for current compression context: the
22
- # client and server contexts are out of sync.
23
- #
24
- # @see ProtocolError
25
- class HeaderException < ProtocolError; end
26
-
27
21
  # Raised on invalid flow control frame or command.
28
22
  #
29
23
  # @see ProtocolError
@@ -1,8 +1,5 @@
1
1
  module HTTP2
2
2
 
3
- # Maximum size of a DATA payload (16383 bytes, ~16K).
4
- MAX_FRAME_SIZE = 2**14-1
5
-
6
3
  # Implementation of stream and connection DATA flow control: frames may
7
4
  # be split and / or may be buffered based on current flow control window.
8
5
  #
@@ -30,16 +27,21 @@ module HTTP2
30
27
  def send_data(frame = nil, encode = false)
31
28
  @send_buffer.push frame if !frame.nil?
32
29
 
33
- while @window > 0 && !@send_buffer.empty? do
30
+ # FIXME: Frames with zero length with the END_STREAM flag set (that
31
+ # is, an empty DATA frame) MAY be sent if there is no available space
32
+ # in either flow control window.
33
+ while @remote_window > 0 && !@send_buffer.empty? do
34
34
  frame = @send_buffer.shift
35
35
 
36
36
  sent, frame_size = 0, frame[:payload].bytesize
37
37
 
38
- if frame_size > @window
38
+ if frame_size > @remote_window
39
39
  payload = frame.delete(:payload)
40
40
  chunk = frame.dup
41
41
 
42
- frame[:payload] = payload.slice!(0, @window)
42
+ # Split frame so that it fits in the window
43
+ # TODO: consider padding!
44
+ frame[:payload] = payload.slice!(0, @remote_window)
43
45
  chunk[:length] = payload.bytesize
44
46
  chunk[:payload] = payload
45
47
 
@@ -49,14 +51,14 @@ module HTTP2
49
51
  end
50
52
 
51
53
  @send_buffer.unshift chunk
52
- sent = @window
54
+ sent = @remote_window
53
55
  else
54
56
  sent = frame_size
55
57
  end
56
58
 
57
- frame = encode(frame) if encode
58
- emit(:frame, frame)
59
- @window -= sent
59
+ frames = encode ? encode(frame) : [frame]
60
+ frames.each {|f| emit(:frame, f) }
61
+ @remote_window -= sent
60
62
  end
61
63
  end
62
64
  end
@@ -1,12 +1,15 @@
1
1
  module HTTP2
2
2
 
3
- # Performs encoding, decoding, and validation of binary HTTP 2.0 frames.
3
+ # Performs encoding, decoding, and validation of binary HTTP/2 frames.
4
4
  #
5
5
  class Framer
6
6
  include Error
7
7
 
8
- # Maximum frame size (65535 bytes)
9
- MAX_PAYLOAD_SIZE = 2**16-1
8
+ # Default value of max frame size (16384 bytes)
9
+ DEFAULT_MAX_FRAME_SIZE = 2**14
10
+
11
+ # Current maximum frame size
12
+ attr_accessor :max_frame_size
10
13
 
11
14
  # Maximum stream ID (2^31)
12
15
  MAX_STREAM_ID = 0x7fffffff
@@ -14,7 +17,7 @@ module HTTP2
14
17
  # Maximum window increment value (2^31)
15
18
  MAX_WINDOWINC = 0x7fffffff
16
19
 
17
- # HTTP 2.0 frame type mapping as defined by the spec
20
+ # HTTP/2 frame type mapping as defined by the spec
18
21
  FRAME_TYPES = {
19
22
  data: 0x0,
20
23
  headers: 0x1,
@@ -24,36 +27,45 @@ module HTTP2
24
27
  push_promise: 0x5,
25
28
  ping: 0x6,
26
29
  goaway: 0x7,
27
- window_update: 0x9,
28
- continuation: 0xa
30
+ window_update: 0x8,
31
+ continuation: 0x9,
32
+ altsvc: 0xa,
29
33
  }
30
34
 
35
+ FRAME_TYPES_WITH_PADDING = [ :data, :headers, :push_promise ]
36
+
31
37
  # Per frame flags as defined by the spec
32
38
  FRAME_FLAGS = {
33
39
  data: {
34
- end_stream: 0, reserved: 1
40
+ end_stream: 0,
41
+ padded: 3, compressed: 5
35
42
  },
36
43
  headers: {
37
- end_stream: 0, reserved: 1,
38
- end_headers: 2, priority: 3
44
+ end_stream: 0, end_headers: 2,
45
+ padded: 3, priority: 5,
39
46
  },
40
47
  priority: {},
41
48
  rst_stream: {},
42
- settings: {},
43
- push_promise: { end_push_promise: 0 },
44
- ping: { pong: 0 },
49
+ settings: { ack: 0 },
50
+ push_promise: {
51
+ end_headers: 2,
52
+ padded: 3,
53
+ },
54
+ ping: { ack: 0 },
45
55
  goaway: {},
46
56
  window_update:{},
47
- continuation: {
48
- end_stream: 0, end_headers: 1
49
- }
57
+ continuation: { end_headers: 2 },
58
+ altsvc: {},
50
59
  }
51
60
 
52
61
  # Default settings as defined by the spec
53
62
  DEFINED_SETTINGS = {
54
- settings_max_concurrent_streams: 4,
55
- settings_initial_window_size: 7,
56
- settings_flow_control_options: 10
63
+ settings_header_table_size: 1,
64
+ settings_enable_push: 2,
65
+ settings_max_concurrent_streams: 3,
66
+ settings_initial_window_size: 4,
67
+ settings_max_frame_size: 5,
68
+ settings_max_header_list_size: 6,
57
69
  }
58
70
 
59
71
  # Default error types as defined by the spec
@@ -62,22 +74,38 @@ module HTTP2
62
74
  protocol_error: 1,
63
75
  internal_error: 2,
64
76
  flow_control_error: 3,
77
+ settings_timeout: 4,
65
78
  stream_closed: 5,
66
- frame_too_large: 6,
79
+ frame_size_error: 6,
67
80
  refused_stream: 7,
68
81
  cancel: 8,
69
- compression_error: 9
82
+ compression_error: 9,
83
+ connect_error: 10,
84
+ enhance_your_calm: 11,
85
+ inadequate_security: 12,
70
86
  }
71
87
 
72
88
  RBIT = 0x7fffffff
73
89
  RBYTE = 0x0fffffff
74
- HEADERPACK = "nCCN"
75
- UINT32 = "N"
76
-
77
- private_constant :RBIT, :RBYTE, :HEADERPACK, :UINT32
90
+ EBIT = 0x80000000
91
+ UINT32 = "N".freeze
92
+ UINT16 = "n".freeze
93
+ UINT8 = "C".freeze
94
+ HEADERPACK = (UINT8 + UINT16 + UINT8 + UINT8 + UINT32).freeze
95
+ FRAME_LENGTH_HISHIFT = 16
96
+ FRAME_LENGTH_LOMASK = 0xFFFF
97
+ BINARY = 'binary'.freeze
98
+
99
+ private_constant :RBIT, :RBYTE, :EBIT, :HEADERPACK, :UINT32, :UINT16, :UINT8, :BINARY
100
+
101
+ # Initializes new framer object.
102
+ #
103
+ def initialize
104
+ @max_frame_size = DEFAULT_MAX_FRAME_SIZE
105
+ end
78
106
 
79
- # Generates common 8-byte frame header.
80
- # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-4.1
107
+ # Generates common 9-byte frame header.
108
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-4.1
81
109
  #
82
110
  # @param frame [Hash]
83
111
  # @return [String]
@@ -88,10 +116,14 @@ module HTTP2
88
116
  raise CompressionError.new("Invalid frame type (#{frame[:type]})")
89
117
  end
90
118
 
91
- if frame[:length] > MAX_PAYLOAD_SIZE
119
+ if frame[:length] > @max_frame_size
92
120
  raise CompressionError.new("Frame size is too large: #{frame[:length]}")
93
121
  end
94
122
 
123
+ if frame[:length] < 0
124
+ raise CompressionError.new("Frame size is invalid: #{frame[:length]}")
125
+ end
126
+
95
127
  if frame[:stream] > MAX_STREAM_ID
96
128
  raise CompressionError.new("Stream ID (#{frame[:stream]}) is too large")
97
129
  end
@@ -100,7 +132,8 @@ module HTTP2
100
132
  raise CompressionError.new("Window increment (#{frame[:increment]}) is too large")
101
133
  end
102
134
 
103
- header << frame[:length]
135
+ header << (frame[:length] >> FRAME_LENGTH_HISHIFT)
136
+ header << (frame[:length] & FRAME_LENGTH_LOMASK)
104
137
  header << FRAME_TYPES[frame[:type]]
105
138
  header << frame[:flags].reduce(0) do |acc, f|
106
139
  position = FRAME_FLAGS[frame[:type]][f]
@@ -113,27 +146,30 @@ module HTTP2
113
146
  end
114
147
 
115
148
  header << frame[:stream]
116
- header.pack(HEADERPACK) # 16,8,8,32
149
+ header.pack(HEADERPACK) # 8+16,8,8,32
117
150
  end
118
151
 
119
- # Decodes common 8-byte header.
152
+ # Decodes common 9-byte header.
120
153
  #
121
154
  # @param buf [Buffer]
122
155
  def readCommonHeader(buf)
123
156
  frame = {}
124
- frame[:length], type, flags, stream = buf.slice(0,8).unpack(HEADERPACK)
157
+ len_hi, len_lo, type, flags, stream = buf.slice(0,9).unpack(HEADERPACK)
125
158
 
159
+ frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
126
160
  frame[:type], _ = FRAME_TYPES.select { |t,pos| type == pos }.first
127
- frame[:flags] = FRAME_FLAGS[frame[:type]].reduce([]) do |acc, (name, pos)|
128
- acc << name if (flags & (1 << pos)) > 0
129
- acc
161
+ if frame[:type]
162
+ frame[:flags] = FRAME_FLAGS[frame[:type]].reduce([]) do |acc, (name, pos)|
163
+ acc << name if (flags & (1 << pos)) > 0
164
+ acc
165
+ end
130
166
  end
131
167
 
132
168
  frame[:stream] = stream & RBIT
133
169
  frame
134
170
  end
135
171
 
136
- # Generates encoded HTTP 2.0 frame.
172
+ # Generates encoded HTTP/2 frame.
137
173
  # - http://tools.ietf.org/html/draft-ietf-httpbis-http2
138
174
  #
139
175
  # @param frame [Hash]
@@ -150,21 +186,31 @@ module HTTP2
150
186
  length += frame[:payload].bytesize
151
187
 
152
188
  when :headers
153
- if frame[:priority]
189
+ if frame[:weight] || frame[:stream_dependency] || !frame[:exclusive].nil?
190
+ unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil?
191
+ raise CompressionError.new("Must specify all of priority parameters for #{frame[:type]}")
192
+ end
154
193
  frame[:flags] += [:priority] if !frame[:flags].include? :priority
155
194
  end
156
195
 
157
196
  if frame[:flags].include? :priority
158
- bytes << [frame[:priority] & RBIT].pack(UINT32)
159
- length += 4
197
+ bytes << [(frame[:exclusive] ? EBIT : 0) |
198
+ (frame[:stream_dependency] & RBIT)].pack(UINT32)
199
+ bytes << [frame[:weight] - 1].pack(UINT8)
200
+ length += 5
160
201
  end
161
202
 
162
203
  bytes << frame[:payload]
163
204
  length += frame[:payload].bytesize
164
205
 
165
206
  when :priority
166
- bytes << [frame[:priority] & RBIT].pack(UINT32)
167
- length += 4
207
+ unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil?
208
+ raise CompressionError.new("Must specify all of priority parameters for #{frame[:type]}")
209
+ end
210
+ bytes << [(frame[:exclusive] ? EBIT : 0) |
211
+ (frame[:stream_dependency] & RBIT)].pack(UINT32)
212
+ bytes << [frame[:weight] - 1].pack(UINT8)
213
+ length += 5
168
214
 
169
215
  when :rst_stream
170
216
  bytes << pack_error(frame[:error])
@@ -176,7 +222,9 @@ module HTTP2
176
222
  end
177
223
 
178
224
  frame[:payload].each do |(k,v)|
179
- if !k.is_a? Integer
225
+ if k.is_a? Integer
226
+ DEFINED_SETTINGS.has_value?(k) or next
227
+ else
180
228
  k = DEFINED_SETTINGS[k]
181
229
 
182
230
  if k.nil?
@@ -184,9 +232,9 @@ module HTTP2
184
232
  end
185
233
  end
186
234
 
187
- bytes << [k & RBYTE].pack(UINT32)
235
+ bytes << [k].pack(UINT16)
188
236
  bytes << [v].pack(UINT32)
189
- length += 8
237
+ length += 6
190
238
  end
191
239
 
192
240
  when :push_promise
@@ -219,46 +267,130 @@ module HTTP2
219
267
  when :continuation
220
268
  bytes << frame[:payload]
221
269
  length += frame[:payload].bytesize
270
+
271
+ when :altsvc
272
+ bytes << [frame[:max_age], frame[:port]].pack(UINT32 + UINT16)
273
+ length += 6
274
+ if frame[:proto]
275
+ frame[:proto].bytesize > 255 and raise CompressionError.new("Proto too long")
276
+ bytes << [frame[:proto].bytesize].pack(UINT8) << frame[:proto].force_encoding(BINARY)
277
+ length += 1 + frame[:proto].bytesize
278
+ else
279
+ bytes << [0].pack(UINT8)
280
+ length += 1
281
+ end
282
+ if frame[:host]
283
+ frame[:host].bytesize > 255 and raise CompressionError.new("Host too long")
284
+ bytes << [frame[:host].bytesize].pack(UINT8) << frame[:host].force_encoding(BINARY)
285
+ length += 1 + frame[:host].bytesize
286
+ else
287
+ bytes << [0].pack(UINT8)
288
+ length += 1
289
+ end
290
+ if frame[:origin]
291
+ bytes << frame[:origin]
292
+ length += frame[:origin].bytesize
293
+ end
294
+ end
295
+
296
+ # Process padding.
297
+ # frame[:padding] gives number of extra octets to be added.
298
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-12#section-6.1
299
+ if frame[:padding]
300
+ unless FRAME_TYPES_WITH_PADDING.include?(frame[:type])
301
+ raise CompressionError.new("Invalid padding flag for #{frame[:type]}")
302
+ end
303
+
304
+ padlen = frame[:padding]
305
+
306
+ if padlen <= 0 || padlen > 256 || padlen + length > @max_frame_size
307
+ raise CompressionError.new("Invalid padding #{padlen}")
308
+ end
309
+
310
+ length += padlen
311
+ bytes.prepend([padlen -= 1].pack(UINT8))
312
+ frame[:flags] << :padded
313
+
314
+ # Padding: Padding octets that contain no application semantic value.
315
+ # Padding octets MUST be set to zero when sending and ignored when
316
+ # receiving.
317
+ bytes << "\0" * padlen
222
318
  end
223
319
 
224
320
  frame[:length] = length
225
321
  bytes.prepend(commonHeader(frame))
226
322
  end
227
323
 
228
- # Decodes complete HTTP 2.0 frame from provided buffer. If the buffer
324
+ # Decodes complete HTTP/2 frame from provided buffer. If the buffer
229
325
  # does not contain enough data, no further work is performed.
230
326
  #
231
327
  # @param buf [Buffer]
232
328
  def parse(buf)
233
- return nil if buf.size < 8
329
+ return nil if buf.size < 9
234
330
  frame = readCommonHeader(buf)
235
- return nil if buf.size < 8 + frame[:length]
331
+ return nil if buf.size < 9 + frame[:length]
236
332
 
237
- buf.read(8)
333
+ buf.read(9)
238
334
  payload = buf.read(frame[:length])
239
335
 
336
+ # Implementations MUST discard frames
337
+ # that have unknown or unsupported types.
338
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.5
339
+ return nil if frame[:type].nil?
340
+
341
+ # Process padding
342
+ padlen = 0
343
+ if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
344
+ padded = frame[:flags].include?(:padded)
345
+ if padded
346
+ padlen = payload.read(1).unpack(UINT8).first
347
+ frame[:padding] = padlen + 1
348
+ padlen > payload.bytesize and raise ProtocolError.new("padding too long")
349
+ padlen > 0 and payload.slice!(-padlen,padlen)
350
+ frame[:length] -= frame[:padding]
351
+ frame[:flags].delete(:padded)
352
+ end
353
+ end
354
+
240
355
  case frame[:type]
241
356
  when :data
242
357
  frame[:payload] = payload.read(frame[:length])
243
358
  when :headers
244
359
  if frame[:flags].include? :priority
245
- frame[:priority] = payload.read_uint32 & RBIT
360
+ e_sd = payload.read_uint32
361
+ frame[:stream_dependency] = e_sd & RBIT
362
+ frame[:exclusive] = (e_sd & EBIT) != 0
363
+ frame[:weight] = payload.getbyte + 1
246
364
  end
247
365
  frame[:payload] = payload.read(frame[:length])
248
366
  when :priority
249
- frame[:priority] = payload.read_uint32 & RBIT
367
+ e_sd = payload.read_uint32
368
+ frame[:stream_dependency] = e_sd & RBIT
369
+ frame[:exclusive] = (e_sd & EBIT) != 0
370
+ frame[:weight] = payload.getbyte + 1
250
371
  when :rst_stream
251
372
  frame[:error] = unpack_error payload.read_uint32
252
373
 
253
374
  when :settings
254
- frame[:payload] = {}
255
- (frame[:length] / 8).times do
256
- id = payload.read_uint32 & RBYTE
375
+ # NOTE: frame[:length] might not match the number of frame[:payload]
376
+ # because unknown extensions are ignored.
377
+ frame[:payload] = []
378
+ unless frame[:length] % 6 == 0
379
+ raise ProtocolError.new("Invalid settings payload length")
380
+ end
381
+
382
+ if frame[:stream] != 0
383
+ raise ProtocolError.new("Invalid stream ID (#{frame[:stream]})")
384
+ end
385
+
386
+ (frame[:length] / 6).times do
387
+ id = payload.read(2).unpack(UINT16).first
257
388
  val = payload.read_uint32
258
389
 
259
390
  # Unsupported or unrecognized settings MUST be ignored.
391
+ # Here we send it along.
260
392
  name, _ = DEFINED_SETTINGS.select { |name, v| v == id }.first
261
- frame[:payload][name] = val if name
393
+ frame[:payload] << [name, val] if name
262
394
  end
263
395
  when :push_promise
264
396
  frame[:promise_stream] = payload.read_uint32 & RBIT
@@ -269,12 +401,26 @@ module HTTP2
269
401
  frame[:last_stream] = payload.read_uint32 & RBIT
270
402
  frame[:error] = unpack_error payload.read_uint32
271
403
 
272
- size = frame[:length] - 8
404
+ size = frame[:length] - 8 # for last_stream and error
273
405
  frame[:payload] = payload.read(size) if size > 0
274
406
  when :window_update
275
407
  frame[:increment] = payload.read_uint32 & RBIT
276
408
  when :continuation
277
409
  frame[:payload] = payload.read(frame[:length])
410
+ when :altsvc
411
+ frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16)
412
+
413
+ len = payload.getbyte
414
+ len > 0 and frame[:proto] = payload.read(len)
415
+
416
+ len = payload.getbyte
417
+ len > 0 and frame[:host] = payload.read(len)
418
+
419
+ if payload.size > 0
420
+ frame[:origin] = payload.read(payload.size)
421
+ end
422
+ else
423
+ # Unknown frame type is explicitly allowed
278
424
  end
279
425
 
280
426
  frame
@@ -284,11 +430,11 @@ module HTTP2
284
430
 
285
431
  def pack_error(e)
286
432
  if !e.is_a? Integer
287
- e = DEFINED_ERRORS[e]
288
-
289
- if e.nil?
433
+ if DEFINED_ERRORS[e].nil?
290
434
  raise CompressionError.new("Unknown error ID for #{e}")
291
435
  end
436
+
437
+ e = DEFINED_ERRORS[e]
292
438
  end
293
439
 
294
440
  [e].pack(UINT32)