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.
- checksums.yaml +7 -0
- data/.autotest +20 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +46 -0
- data/.travis.yml +13 -0
- data/Gemfile +18 -0
- data/README.md +285 -0
- data/Rakefile +11 -0
- data/example/README.md +40 -0
- data/example/client.rb +117 -0
- data/example/helper.rb +19 -0
- data/example/keys/mycert.pem +23 -0
- data/example/keys/mykey.pem +27 -0
- data/example/server.rb +97 -0
- data/example/upgrade_server.rb +193 -0
- data/http-2.gemspec +23 -0
- data/lib/http/2/buffer.rb +34 -0
- data/lib/http/2/client.rb +51 -0
- data/lib/http/2/compressor.rb +557 -0
- data/lib/http/2/connection.rb +654 -0
- data/lib/http/2/emitter.rb +45 -0
- data/lib/http/2/error.rb +44 -0
- data/lib/http/2/flow_buffer.rb +67 -0
- data/lib/http/2/framer.rb +440 -0
- data/lib/http/2/huffman.rb +323 -0
- data/lib/http/2/huffman_statemachine.rb +272 -0
- data/lib/http/2/server.rb +132 -0
- data/lib/http/2/stream.rb +576 -0
- data/lib/http/2/version.rb +3 -0
- data/lib/http/2.rb +13 -0
- data/lib/tasks/generate_huffman_table.rb +166 -0
- data/spec/buffer_spec.rb +21 -0
- data/spec/client_spec.rb +92 -0
- data/spec/compressor_spec.rb +535 -0
- data/spec/connection_spec.rb +581 -0
- data/spec/emitter_spec.rb +54 -0
- data/spec/framer_spec.rb +487 -0
- data/spec/helper.rb +128 -0
- data/spec/hpack_test_spec.rb +79 -0
- data/spec/huffman_spec.rb +68 -0
- data/spec/server_spec.rb +51 -0
- data/spec/stream_spec.rb +794 -0
- metadata +116 -0
data/lib/http/2/error.rb
ADDED
@@ -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
|