http-2 0.6.3 → 0.7.0

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