http-2 1.0.2 → 1.1.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 +4 -4
- data/lib/http/2/client.rb +1 -0
- data/lib/http/2/connection.rb +116 -96
- data/lib/http/2/emitter.rb +2 -9
- data/lib/http/2/extensions.rb +21 -1
- data/lib/http/2/flow_buffer.rb +45 -35
- data/lib/http/2/framer.rb +113 -85
- data/lib/http/2/header/compressor.rb +47 -31
- data/lib/http/2/header/decompressor.rb +27 -20
- data/lib/http/2/header/encoding_context.rb +77 -80
- data/lib/http/2/header/huffman.rb +14 -10
- data/lib/http/2/header/huffman_statemachine.rb +1 -1
- data/lib/http/2/server.rb +7 -1
- data/lib/http/2/stream.rb +9 -4
- data/lib/http/2/version.rb +1 -1
- data/lib/http/2.rb +5 -0
- data/sig/{next.rbs → 2.rbs} +29 -18
- data/sig/client.rbs +2 -0
- data/sig/connection.rbs +20 -8
- data/sig/emitter.rbs +2 -4
- data/sig/extensions.rbs +3 -1
- data/sig/flow_buffer.rbs +7 -5
- data/sig/frame_buffer.rbs +1 -1
- data/sig/framer.rbs +5 -0
- data/sig/header/compressor.rbs +6 -4
- data/sig/header/decompressor.rbs +3 -2
- data/sig/header/encoding_context.rbs +24 -6
- data/sig/header/huffman.rbs +19 -3
- data/sig/header.rbs +11 -8
- data/sig/stream.rbs +8 -5
- metadata +4 -7
data/lib/http/2/flow_buffer.rb
CHANGED
@@ -7,6 +7,8 @@ module HTTP2
|
|
7
7
|
module FlowBuffer
|
8
8
|
include Error
|
9
9
|
|
10
|
+
attr_reader :send_buffer
|
11
|
+
|
10
12
|
MAX_WINDOW_SIZE = (2 << 30) - 1
|
11
13
|
|
12
14
|
# Amount of buffered data. Only DATA payloads are subject to flow stream
|
@@ -14,7 +16,7 @@ module HTTP2
|
|
14
16
|
#
|
15
17
|
# @return [Integer]
|
16
18
|
def buffered_amount
|
17
|
-
send_buffer.bytesize
|
19
|
+
@send_buffer.bytesize
|
18
20
|
end
|
19
21
|
|
20
22
|
def flush
|
@@ -23,13 +25,9 @@ module HTTP2
|
|
23
25
|
|
24
26
|
private
|
25
27
|
|
26
|
-
def send_buffer
|
27
|
-
@send_buffer ||= FrameBuffer.new
|
28
|
-
end
|
29
|
-
|
30
28
|
def update_local_window(frame)
|
31
29
|
frame_size = frame[:payload].bytesize
|
32
|
-
frame_size += frame
|
30
|
+
frame_size += frame.fetch(:padding, 0)
|
33
31
|
@local_window -= frame_size
|
34
32
|
end
|
35
33
|
|
@@ -69,31 +67,47 @@ module HTTP2
|
|
69
67
|
# @param frame [Hash]
|
70
68
|
# @param encode [Boolean] set to true by connection
|
71
69
|
def send_data(frame = nil, encode = false)
|
72
|
-
|
70
|
+
if frame
|
71
|
+
if @send_buffer.empty?
|
72
|
+
frame_size = frame[:payload].bytesize
|
73
|
+
end_stream = frame[:flags].include?(:end_stream)
|
74
|
+
# if buffer is empty, and frame is either end 0 length OR
|
75
|
+
# is within available window size, skip buffering and send immediately.
|
76
|
+
if @remote_window.positive?
|
77
|
+
return send_frame(frame, encode) if frame_size <= @remote_window
|
78
|
+
elsif frame_size.zero? && end_stream
|
79
|
+
return send_frame(frame, encode)
|
80
|
+
end
|
81
|
+
end
|
73
82
|
|
74
|
-
|
83
|
+
@send_buffer << frame
|
84
|
+
end
|
75
85
|
|
76
|
-
|
77
|
-
|
86
|
+
while (frame = @send_buffer.retrieve(@remote_window))
|
87
|
+
send_frame(frame, encode)
|
88
|
+
end
|
89
|
+
end
|
78
90
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
91
|
+
def send_frame(frame, encode)
|
92
|
+
sent = frame[:payload].bytesize
|
93
|
+
|
94
|
+
manage_state(frame) do
|
95
|
+
if encode
|
96
|
+
encode(frame)
|
97
|
+
else
|
98
|
+
emit(:frame, frame)
|
86
99
|
end
|
100
|
+
@remote_window -= sent
|
87
101
|
end
|
88
102
|
end
|
89
103
|
|
90
104
|
def process_window_update(frame:, encode: false)
|
91
105
|
return if frame[:ignore]
|
92
106
|
|
93
|
-
if frame[:increment]
|
94
|
-
raise ProtocolError, "increment MUST be higher than zero" if
|
107
|
+
if (increment = frame[:increment])
|
108
|
+
raise ProtocolError, "increment MUST be higher than zero" if increment.zero?
|
95
109
|
|
96
|
-
@remote_window +=
|
110
|
+
@remote_window += increment
|
97
111
|
error(:flow_control_error, msg: "window size too large") if @remote_window > MAX_WINDOW_SIZE
|
98
112
|
end
|
99
113
|
send_data(nil, encode)
|
@@ -114,45 +128,41 @@ module HTTP2
|
|
114
128
|
end
|
115
129
|
|
116
130
|
def empty?
|
117
|
-
@
|
131
|
+
@buffer.empty?
|
118
132
|
end
|
119
133
|
|
120
134
|
def retrieve(window_size)
|
121
135
|
frame = @buffer.first or return
|
122
136
|
|
123
137
|
frame_size = frame[:payload].bytesize
|
124
|
-
end_stream = frame[:flags].include?
|
138
|
+
end_stream = frame[:flags].include?(:end_stream)
|
125
139
|
|
126
140
|
# Frames with zero length with the END_STREAM flag set (that
|
127
141
|
# is, an empty DATA frame) MAY be sent if there is no available space
|
128
142
|
# in either flow control window.
|
129
|
-
return if window_size <= 0 && !(frame_size
|
130
|
-
|
131
|
-
@buffer.shift
|
143
|
+
return if window_size <= 0 && !(frame_size.zero? && end_stream)
|
132
144
|
|
133
145
|
if frame_size > window_size
|
134
|
-
payload = frame[:payload]
|
135
146
|
chunk = frame.dup
|
147
|
+
payload = frame[:payload]
|
136
148
|
|
137
149
|
# Split frame so that it fits in the window
|
138
150
|
# TODO: consider padding!
|
139
|
-
frame_bytes = payload.byteslice(0, window_size)
|
140
|
-
payload = payload.byteslice(window_size..-1)
|
141
151
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
152
|
+
chunk[:payload] = payload.byteslice(0, window_size)
|
153
|
+
chunk[:length] = window_size
|
154
|
+
frame[:payload] = payload.byteslice(window_size..-1)
|
155
|
+
frame[:length] = frame_size - window_size
|
146
156
|
|
147
157
|
# if no longer last frame in sequence...
|
148
|
-
|
158
|
+
chunk[:flags] -= [:end_stream] if end_stream
|
149
159
|
|
150
|
-
@buffer.unshift(chunk)
|
151
160
|
@bytesize -= window_size
|
161
|
+
chunk
|
152
162
|
else
|
153
163
|
@bytesize -= frame_size
|
164
|
+
@buffer.shift
|
154
165
|
end
|
155
|
-
frame
|
156
166
|
end
|
157
167
|
end
|
158
168
|
end
|
data/lib/http/2/framer.rb
CHANGED
@@ -36,6 +36,8 @@ module HTTP2
|
|
36
36
|
origin: 0xc
|
37
37
|
}.freeze
|
38
38
|
|
39
|
+
FRAME_TYPES_BY_NAME = FRAME_TYPES.invert.freeze
|
40
|
+
|
39
41
|
FRAME_TYPES_WITH_PADDING = %i[data headers push_promise].freeze
|
40
42
|
|
41
43
|
# Per frame flags as defined by the spec
|
@@ -126,30 +128,43 @@ module HTTP2
|
|
126
128
|
# @param buffer [String] buffer to pack bytes into
|
127
129
|
# @return [String]
|
128
130
|
def common_header(frame, buffer:)
|
129
|
-
|
131
|
+
type = frame[:type]
|
132
|
+
|
133
|
+
raise CompressionError, "Invalid frame type (#{type})" unless FRAME_TYPES[type]
|
134
|
+
|
135
|
+
length = frame[:length]
|
136
|
+
|
137
|
+
raise CompressionError, "Frame size is too large: #{length}" if length > @remote_max_frame_size
|
130
138
|
|
131
|
-
raise CompressionError, "Frame size is
|
139
|
+
raise CompressionError, "Frame size is invalid: #{length}" if length < 0
|
132
140
|
|
133
|
-
|
141
|
+
stream_id = frame.fetch(:stream, 0)
|
134
142
|
|
135
|
-
raise CompressionError, "Stream ID (#{
|
143
|
+
raise CompressionError, "Stream ID (#{stream_id}) is too large" if stream_id > MAX_STREAM_ID
|
136
144
|
|
137
|
-
if
|
145
|
+
if type == :window_update && frame[:increment] > MAX_WINDOWINC
|
138
146
|
raise CompressionError, "Window increment (#{frame[:increment]}) is too large"
|
139
147
|
end
|
140
148
|
|
149
|
+
header = buffer
|
150
|
+
|
151
|
+
if buffer.frozen?
|
152
|
+
header = String.new("", encoding: Encoding::BINARY, capacity: buffer.bytesize + 9) # header length
|
153
|
+
header << buffer
|
154
|
+
end
|
155
|
+
|
141
156
|
pack([
|
142
|
-
(
|
143
|
-
(
|
144
|
-
FRAME_TYPES[
|
157
|
+
(length >> FRAME_LENGTH_HISHIFT),
|
158
|
+
(length & FRAME_LENGTH_LOMASK),
|
159
|
+
FRAME_TYPES[type],
|
145
160
|
frame[:flags].reduce(0) do |acc, f|
|
146
|
-
position = FRAME_FLAGS[
|
147
|
-
raise CompressionError, "Invalid frame flag (#{f}) for #{
|
161
|
+
position = FRAME_FLAGS[type][f]
|
162
|
+
raise CompressionError, "Invalid frame flag (#{f}) for #{type}" unless position
|
148
163
|
|
149
164
|
acc | (1 << position)
|
150
165
|
end,
|
151
|
-
|
152
|
-
], HEADERPACK, buffer:
|
166
|
+
stream_id
|
167
|
+
], HEADERPACK, buffer: header, offset: 0) # 8+16,8,8,32
|
153
168
|
end
|
154
169
|
|
155
170
|
# Decodes common 9-byte header.
|
@@ -157,19 +172,21 @@ module HTTP2
|
|
157
172
|
# @param buf [Buffer]
|
158
173
|
# @return [Hash] the corresponding frame
|
159
174
|
def read_common_header(buf)
|
160
|
-
frame = {}
|
161
175
|
len_hi, len_lo, type, flags, stream = buf.byteslice(0, 9).unpack(HEADERPACK)
|
162
176
|
|
163
|
-
|
164
|
-
|
165
|
-
if frame[:type]
|
166
|
-
frame[:flags] = FRAME_FLAGS[frame[:type]].each_with_object([]) do |(name, pos), acc|
|
167
|
-
acc << name if flags.anybits?((1 << pos))
|
168
|
-
end
|
169
|
-
end
|
177
|
+
type = FRAME_TYPES_BY_NAME[type]
|
178
|
+
length = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
|
170
179
|
|
171
|
-
|
172
|
-
|
180
|
+
return { length: length } unless type
|
181
|
+
|
182
|
+
{
|
183
|
+
type: type,
|
184
|
+
flags: FRAME_FLAGS[type].filter_map do |name, pos|
|
185
|
+
name if flags.anybits?((1 << pos))
|
186
|
+
end,
|
187
|
+
length: length,
|
188
|
+
stream: stream & RBIT
|
189
|
+
}
|
173
190
|
end
|
174
191
|
|
175
192
|
# Generates encoded HTTP/2 frame.
|
@@ -177,53 +194,60 @@ module HTTP2
|
|
177
194
|
#
|
178
195
|
# @param frame [Hash]
|
179
196
|
def generate(frame)
|
180
|
-
bytes = "".b
|
181
197
|
length = 0
|
182
|
-
|
183
|
-
frame[:flags] ||= []
|
184
|
-
frame[:stream] ||= 0
|
198
|
+
frame[:flags] ||= EMPTY
|
185
199
|
|
186
200
|
case frame[:type]
|
187
|
-
when :data
|
188
|
-
bytes
|
189
|
-
bytes.
|
190
|
-
length += frame[:payload].bytesize
|
201
|
+
when :data, :continuation
|
202
|
+
bytes = frame[:payload]
|
203
|
+
length = bytes.bytesize
|
191
204
|
|
192
205
|
when :headers
|
206
|
+
headers = frame[:payload]
|
207
|
+
|
193
208
|
if frame[:weight] || frame[:dependency] || !frame[:exclusive].nil?
|
194
209
|
unless frame[:weight] && frame[:dependency] && !frame[:exclusive].nil?
|
195
210
|
raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
|
196
211
|
end
|
197
212
|
|
198
|
-
frame[:flags] += [:priority] unless frame[:flags].include?
|
213
|
+
frame[:flags] += [:priority] unless frame[:flags].include?(:priority)
|
199
214
|
end
|
200
215
|
|
201
|
-
if frame[:flags].include?
|
216
|
+
if frame[:flags].include?(:priority)
|
217
|
+
length = 5 + headers.bytesize
|
218
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
202
219
|
pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
|
203
220
|
pack([frame[:weight] - 1], UINT8, buffer: bytes)
|
204
|
-
|
221
|
+
append_str(bytes, headers)
|
222
|
+
else
|
223
|
+
length = headers.bytesize
|
224
|
+
bytes = headers
|
205
225
|
end
|
206
226
|
|
207
|
-
bytes << frame[:payload]
|
208
|
-
length += frame[:payload].bytesize
|
209
|
-
|
210
227
|
when :priority
|
211
228
|
unless frame[:weight] && frame[:dependency] && !frame[:exclusive].nil?
|
212
229
|
raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
|
213
230
|
end
|
214
231
|
|
232
|
+
length = 5
|
233
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
215
234
|
pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
|
216
235
|
pack([frame[:weight] - 1], UINT8, buffer: bytes)
|
217
|
-
length += 5
|
218
236
|
|
219
237
|
when :rst_stream
|
238
|
+
length = 4
|
239
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
220
240
|
pack_error(frame[:error], buffer: bytes)
|
221
|
-
length += 4
|
222
241
|
|
223
242
|
when :settings
|
224
|
-
|
243
|
+
if (stream_id = frame[:stream]) && stream_id.nonzero?
|
244
|
+
raise CompressionError, "Invalid stream ID (#{stream_id})"
|
245
|
+
end
|
225
246
|
|
226
|
-
frame[:payload]
|
247
|
+
settings = frame[:payload]
|
248
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
249
|
+
|
250
|
+
settings.each do |(k, v)|
|
227
251
|
if k.is_a? Integer # rubocop:disable Style/GuardClause
|
228
252
|
DEFINED_SETTINGS.value?(k) || next
|
229
253
|
else
|
@@ -238,42 +262,42 @@ module HTTP2
|
|
238
262
|
end
|
239
263
|
|
240
264
|
when :push_promise
|
265
|
+
length = 4 + frame[:payload].bytesize
|
266
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
241
267
|
pack([frame[:promise_stream] & RBIT], UINT32, buffer: bytes)
|
242
|
-
bytes
|
243
|
-
length += 4 + frame[:payload].bytesize
|
268
|
+
append_str(bytes, frame[:payload])
|
244
269
|
|
245
270
|
when :ping
|
246
|
-
|
271
|
+
bytes = frame[:payload]
|
272
|
+
raise CompressionError, "Invalid payload size (#{bytes.size} != 8 bytes)" if bytes.bytesize != 8
|
247
273
|
|
248
|
-
|
249
|
-
length += 8
|
274
|
+
length = 8
|
250
275
|
|
251
276
|
when :goaway
|
277
|
+
data = frame[:payload]
|
278
|
+
length = 8
|
279
|
+
length += data.bytesize if data
|
280
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
281
|
+
|
252
282
|
pack([frame[:last_stream] & RBIT], UINT32, buffer: bytes)
|
253
283
|
pack_error(frame[:error], buffer: bytes)
|
254
|
-
length += 8
|
255
284
|
|
256
|
-
if
|
257
|
-
bytes << frame[:payload]
|
258
|
-
length += frame[:payload].bytesize
|
259
|
-
end
|
285
|
+
append_str(bytes, data) if data
|
260
286
|
|
261
287
|
when :window_update
|
288
|
+
length = 4
|
289
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
262
290
|
pack([frame[:increment] & RBIT], UINT32, buffer: bytes)
|
263
|
-
length += 4
|
264
|
-
|
265
|
-
when :continuation
|
266
|
-
bytes << frame[:payload]
|
267
|
-
length += frame[:payload].bytesize
|
268
291
|
|
269
292
|
when :altsvc
|
293
|
+
length = 6
|
294
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
270
295
|
pack([frame[:max_age], frame[:port]], UINT32 + UINT16, buffer: bytes)
|
271
|
-
length += 6
|
272
296
|
if frame[:proto]
|
273
297
|
raise CompressionError, "Proto too long" if frame[:proto].bytesize > 255
|
274
298
|
|
275
299
|
pack([frame[:proto].bytesize], UINT8, buffer: bytes)
|
276
|
-
bytes
|
300
|
+
append_str(bytes, frame[:proto])
|
277
301
|
length += 1 + frame[:proto].bytesize
|
278
302
|
else
|
279
303
|
pack([0], UINT8, buffer: bytes)
|
@@ -283,24 +307,25 @@ module HTTP2
|
|
283
307
|
raise CompressionError, "Host too long" if frame[:host].bytesize > 255
|
284
308
|
|
285
309
|
pack([frame[:host].bytesize], UINT8, buffer: bytes)
|
286
|
-
bytes
|
310
|
+
append_str(bytes, frame[:host])
|
287
311
|
length += 1 + frame[:host].bytesize
|
288
312
|
else
|
289
313
|
pack([0], UINT8, buffer: bytes)
|
290
314
|
length += 1
|
291
315
|
end
|
292
316
|
if frame[:origin]
|
293
|
-
bytes
|
317
|
+
append_str(bytes, frame[:origin])
|
294
318
|
length += frame[:origin].bytesize
|
295
319
|
end
|
296
320
|
|
297
321
|
when :origin
|
298
|
-
frame[:payload]
|
322
|
+
origins = frame[:payload]
|
323
|
+
length = origins.sum(&:bytesize) + (2 * origins.size)
|
324
|
+
bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
|
325
|
+
origins.each do |origin|
|
299
326
|
pack([origin.bytesize], UINT16, buffer: bytes)
|
300
|
-
bytes
|
301
|
-
length += 2 + origin.bytesize
|
327
|
+
append_str(bytes, origin)
|
302
328
|
end
|
303
|
-
|
304
329
|
end
|
305
330
|
|
306
331
|
# Process padding.
|
@@ -319,12 +344,12 @@ module HTTP2
|
|
319
344
|
|
320
345
|
length += padlen
|
321
346
|
pack([padlen -= 1], UINT8, buffer: bytes, offset: 0)
|
322
|
-
frame[:flags]
|
347
|
+
frame[:flags] += [:padded]
|
323
348
|
|
324
349
|
# Padding: Padding octets that contain no application semantic value.
|
325
350
|
# Padding octets MUST be set to zero when sending and ignored when
|
326
351
|
# receiving.
|
327
|
-
bytes
|
352
|
+
append_str(bytes, ("\0" * padlen))
|
328
353
|
end
|
329
354
|
|
330
355
|
frame[:length] = length
|
@@ -339,22 +364,27 @@ module HTTP2
|
|
339
364
|
return if buf.size < 9
|
340
365
|
|
341
366
|
frame = read_common_header(buf)
|
342
|
-
return if buf.size < 9 + frame[:length]
|
343
367
|
|
344
|
-
|
368
|
+
type = frame[:type]
|
369
|
+
length = frame[:length]
|
370
|
+
flags = frame[:flags]
|
371
|
+
|
372
|
+
return if buf.size < 9 + length
|
373
|
+
|
374
|
+
raise ProtocolError, "payload too large" if length > @local_max_frame_size
|
345
375
|
|
346
376
|
read_str(buf, 9)
|
347
|
-
payload = read_str(buf,
|
377
|
+
payload = read_str(buf, length)
|
348
378
|
|
349
379
|
# Implementations MUST discard frames
|
350
380
|
# that have unknown or unsupported types.
|
351
381
|
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.5
|
352
|
-
return frame
|
382
|
+
return frame unless type
|
353
383
|
|
354
384
|
# Process padding
|
355
385
|
padlen = 0
|
356
|
-
if FRAME_TYPES_WITH_PADDING.include?(
|
357
|
-
padded =
|
386
|
+
if FRAME_TYPES_WITH_PADDING.include?(type)
|
387
|
+
padded = flags.include?(:padded)
|
358
388
|
if padded
|
359
389
|
padlen = read_str(payload, 1).unpack1(UINT8)
|
360
390
|
frame[:padding] = padlen + 1
|
@@ -362,15 +392,15 @@ module HTTP2
|
|
362
392
|
|
363
393
|
payload = payload.byteslice(0, payload.bytesize - padlen) if padlen > 0
|
364
394
|
frame[:length] -= frame[:padding]
|
365
|
-
|
395
|
+
flags.delete(:padded)
|
366
396
|
end
|
367
397
|
end
|
368
398
|
|
369
|
-
case
|
399
|
+
case type
|
370
400
|
when :data, :ping, :continuation
|
371
|
-
frame[:payload] = read_str(payload,
|
401
|
+
frame[:payload] = read_str(payload, length)
|
372
402
|
when :headers
|
373
|
-
if
|
403
|
+
if flags.include?(:priority)
|
374
404
|
e_sd = read_uint32(payload)
|
375
405
|
frame[:dependency] = e_sd & RBIT
|
376
406
|
frame[:exclusive] = e_sd.anybits?(EBIT)
|
@@ -378,9 +408,9 @@ module HTTP2
|
|
378
408
|
frame[:weight] = weight
|
379
409
|
payload = payload.byteslice(1..-1)
|
380
410
|
end
|
381
|
-
frame[:payload] = read_str(payload,
|
411
|
+
frame[:payload] = read_str(payload, length)
|
382
412
|
when :priority
|
383
|
-
raise FrameSizeError, "Invalid length for PRIORITY_STREAM (#{
|
413
|
+
raise FrameSizeError, "Invalid length for PRIORITY_STREAM (#{length} != 5)" if length != 5
|
384
414
|
|
385
415
|
e_sd = read_uint32(payload)
|
386
416
|
frame[:dependency] = e_sd & RBIT
|
@@ -389,7 +419,7 @@ module HTTP2
|
|
389
419
|
frame[:weight] = weight
|
390
420
|
payload = payload.byteslice(1..-1)
|
391
421
|
when :rst_stream
|
392
|
-
raise FrameSizeError, "Invalid length for RST_STREAM (#{
|
422
|
+
raise FrameSizeError, "Invalid length for RST_STREAM (#{length} != 4)" if length != 4
|
393
423
|
|
394
424
|
frame[:error] = unpack_error read_uint32(payload)
|
395
425
|
|
@@ -397,9 +427,9 @@ module HTTP2
|
|
397
427
|
# NOTE: frame[:length] might not match the number of frame[:payload]
|
398
428
|
# because unknown extensions are ignored.
|
399
429
|
frame[:payload] = []
|
400
|
-
raise ProtocolError, "Invalid settings payload length" unless (
|
430
|
+
raise ProtocolError, "Invalid settings payload length" unless (length % 6).zero?
|
401
431
|
|
402
|
-
raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if
|
432
|
+
raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if frame[:stream].nonzero?
|
403
433
|
|
404
434
|
(frame[:length] / 6).times do
|
405
435
|
id = read_str(payload, 2).unpack1(UINT16)
|
@@ -412,17 +442,15 @@ module HTTP2
|
|
412
442
|
end
|
413
443
|
when :push_promise
|
414
444
|
frame[:promise_stream] = read_uint32(payload) & RBIT
|
415
|
-
frame[:payload] = read_str(payload,
|
445
|
+
frame[:payload] = read_str(payload, length)
|
416
446
|
when :goaway
|
417
447
|
frame[:last_stream] = read_uint32(payload) & RBIT
|
418
448
|
frame[:error] = unpack_error read_uint32(payload)
|
419
449
|
|
420
|
-
size =
|
450
|
+
size = length - 8 # for last_stream and error
|
421
451
|
frame[:payload] = read_str(payload, size) if size > 0
|
422
452
|
when :window_update
|
423
|
-
if
|
424
|
-
raise FrameSizeError, "Invalid length for WINDOW_UPDATE (#{frame[:length]} not multiple of 4)"
|
425
|
-
end
|
453
|
+
raise FrameSizeError, "Invalid length for WINDOW_UPDATE (#{length} not multiple of 4)" if length % 4 != 0
|
426
454
|
|
427
455
|
frame[:increment] = read_uint32(payload) & RBIT
|
428
456
|
when :altsvc
|
@@ -5,6 +5,7 @@ module HTTP2
|
|
5
5
|
# Responsible for encoding header key-value pairs using HPACK algorithm.
|
6
6
|
class Compressor
|
7
7
|
include PackingExtensions
|
8
|
+
include BufferUtils
|
8
9
|
|
9
10
|
# @param options [Hash] encoding options
|
10
11
|
def initialize(options = {})
|
@@ -34,8 +35,8 @@ module HTTP2
|
|
34
35
|
# @param buffer [String] buffer to pack bytes into
|
35
36
|
# @param offset [Integer] offset to insert packed bytes in buffer
|
36
37
|
# @return [String] binary string
|
37
|
-
def integer(i, n, buffer:, offset:
|
38
|
-
limit = (
|
38
|
+
def integer(i, n, buffer:, offset: buffer.size)
|
39
|
+
limit = (1 << n) - 1
|
39
40
|
return pack([i], "C", buffer: buffer, offset: offset) if i < limit
|
40
41
|
|
41
42
|
bytes = []
|
@@ -70,19 +71,23 @@ module HTTP2
|
|
70
71
|
# :shorter Use Huffman when the result is strictly shorter
|
71
72
|
#
|
72
73
|
# @param str [String]
|
74
|
+
# @param buffer [String]
|
73
75
|
# @return [String] binary string
|
74
|
-
def string(str)
|
76
|
+
def string(str, buffer = "".b)
|
75
77
|
case @cc.options[:huffman]
|
76
78
|
when :always
|
77
|
-
huffman_string(str)
|
79
|
+
huffman_string(str, buffer)
|
78
80
|
when :never
|
79
|
-
plain_string(str)
|
81
|
+
plain_string(str, buffer)
|
80
82
|
else
|
81
|
-
huffman =
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
huffman = Huffman.encode(str)
|
84
|
+
if huffman.bytesize < str.bytesize
|
85
|
+
huffman_offset = buffer.bytesize
|
86
|
+
append_str(buffer, huffman)
|
87
|
+
set_huffman_size(buffer, huffman_offset)
|
88
|
+
else
|
89
|
+
plain_string(str, buffer)
|
90
|
+
end
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
@@ -92,27 +97,30 @@ module HTTP2
|
|
92
97
|
# @param buffer [String]
|
93
98
|
# @return [Buffer]
|
94
99
|
def header(h, buffer = "".b)
|
95
|
-
|
100
|
+
type = h[:type]
|
101
|
+
rep = HEADREP[type]
|
102
|
+
offset = buffer.size
|
96
103
|
|
97
|
-
case
|
104
|
+
case type
|
98
105
|
when :indexed
|
99
106
|
integer(h[:name] + 1, rep[:prefix], buffer: buffer)
|
100
107
|
when :changetablesize
|
101
108
|
integer(h[:value], rep[:prefix], buffer: buffer)
|
102
109
|
else
|
103
|
-
|
104
|
-
|
110
|
+
name = h[:name]
|
111
|
+
if name.is_a? Integer
|
112
|
+
integer(name + 1, rep[:prefix], buffer: buffer)
|
105
113
|
else
|
106
114
|
integer(0, rep[:prefix], buffer: buffer)
|
107
|
-
|
115
|
+
string(name, buffer)
|
108
116
|
end
|
109
117
|
|
110
|
-
|
118
|
+
string(h[:value], buffer)
|
111
119
|
end
|
112
120
|
|
113
121
|
# set header representation pattern on first byte
|
114
|
-
fb = buffer.ord | rep[:pattern]
|
115
|
-
buffer.setbyte(
|
122
|
+
fb = buffer[offset].ord | rep[:pattern]
|
123
|
+
buffer.setbyte(offset, fb)
|
116
124
|
|
117
125
|
buffer
|
118
126
|
end
|
@@ -123,11 +131,10 @@ module HTTP2
|
|
123
131
|
# @return [Buffer]
|
124
132
|
def encode(headers)
|
125
133
|
buffer = "".b
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
buffer << header(cmd)
|
134
|
+
headers.partition { |f, _| f.start_with? ":" }.each do |hs|
|
135
|
+
@cc.encode(hs) do |cmd|
|
136
|
+
header(cmd, buffer)
|
137
|
+
end
|
131
138
|
end
|
132
139
|
|
133
140
|
buffer
|
@@ -136,22 +143,31 @@ module HTTP2
|
|
136
143
|
private
|
137
144
|
|
138
145
|
# @param str [String]
|
146
|
+
# @param buffer [String]
|
139
147
|
# @return [String] binary string
|
140
|
-
def huffman_string(str)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
huffman
|
148
|
+
def huffman_string(str, buffer = "".b)
|
149
|
+
huffman_offset = buffer.bytesize
|
150
|
+
Huffman.encode(str, buffer)
|
151
|
+
set_huffman_size(buffer, huffman_offset)
|
145
152
|
end
|
146
153
|
|
147
154
|
# @param str [String]
|
155
|
+
# @param buffer [String]
|
148
156
|
# @return [String] binary string
|
149
|
-
def plain_string(str)
|
150
|
-
plain = "".b
|
157
|
+
def plain_string(str, plain = "".b)
|
151
158
|
integer(str.bytesize, 7, buffer: plain)
|
152
|
-
plain
|
159
|
+
append_str(plain, str)
|
153
160
|
plain
|
154
161
|
end
|
162
|
+
|
163
|
+
# @param buffer [String]
|
164
|
+
# @param huffman_offset [Integer] buffer offset where huffman string was introduced
|
165
|
+
# @return [String] binary string
|
166
|
+
def set_huffman_size(buffer, huffman_offset)
|
167
|
+
integer(buffer.bytesize - huffman_offset, 7, buffer: buffer, offset: huffman_offset)
|
168
|
+
buffer.setbyte(huffman_offset, buffer[huffman_offset].ord | 0x80)
|
169
|
+
buffer
|
170
|
+
end
|
155
171
|
end
|
156
172
|
end
|
157
173
|
end
|