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