http-2-next 0.1.0

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