http-2-next 0.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.
@@ -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