http-2-next 0.2.3 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57b5112f768b60bc8cba8e6f82925eafc0de71a947fc5282bcc0f4b10dd42b49
4
- data.tar.gz: 9e8470275022f7d9742232cb1cc2ffab1644a77268a21a656a702db219a3fb93
3
+ metadata.gz: 7db81040e42ff1170fbbcc93611c502b3b7fb1f3e4dcad7dd0ede42eaf08d0a4
4
+ data.tar.gz: 3f6027a4d12dc034726040554a5ba4aab96c145ff7acc7fc6c8287afdc4b49d3
5
5
  SHA512:
6
- metadata.gz: a466e82b5a77065453b531602daf30fc5c44bb4918e46f3bc0b48e80268b5496ae23b2bc3ee6f40c8fcf6920dc5ea1a0490c861ace6776f2f522aba090c0309f
7
- data.tar.gz: b469ea645f4a74fbf5e045a6205ecccf3104d93f190c93cdcfc2faf1f0046623596af728278d3839cddcba235fb1382a830899b5d1f0d7f1ba89734e1cdafc29
6
+ metadata.gz: edb1d8dcf5509b2d51c64a66bf5396f7603817988b57894ba9529e877714c4c3ea847fd403ce75bb285a545a17758a8405277c9c36323425a2cd83d52ce842d7
7
+ data.tar.gz: fff64e2206e276c3b174d309d8a32181058dfde60f507953a215d6d5850abb754e6db5c5a54247d946b61bf6a74b57a748d8f4138e9c8c533e18000bb69d40e3
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # HTTP-2-Next
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/http-2.svg)](http://rubygems.org/gems/http-2)
3
+ [![Gem Version](https://badge.fury.io/rb/http-2-next.svg)](http://rubygems.org/gems/http-2-next)
4
4
  [![Build status](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/http-2-next/commits/master)
5
- [![coverage report](https://gitlab.com/honeyryderchuck/httpx/badges/master/coverage.svg)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
5
+ [![coverage report](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
6
6
 
7
7
  **Attention!** This is a fork of the [http-2](https://github.com/igrigorik/http-2) gem.
8
8
 
data/lib/http/2/next.rb CHANGED
@@ -4,11 +4,8 @@ require "http/2/next/version"
4
4
  require "http/2/next/extensions"
5
5
  require "http/2/next/error"
6
6
  require "http/2/next/emitter"
7
- require "http/2/next/buffer"
8
7
  require "http/2/next/flow_buffer"
9
- require "http/2/next/huffman"
10
- require "http/2/next/huffman_statemachine"
11
- require "http/2/next/compressor"
8
+ require "http/2/next/header"
12
9
  require "http/2/next/framer"
13
10
  require "http/2/next/connection"
14
11
  require "http/2/next/client"
@@ -51,18 +51,19 @@ module HTTP2Next
51
51
  include Emitter
52
52
  include Error
53
53
 
54
+ using StringExtensions
55
+
54
56
  # Connection state (:new, :closed).
55
57
  attr_reader :state
56
58
 
57
59
  # Size of current connection flow control window (by default, set to
58
60
  # infinity, but is automatically updated on receipt of peer settings).
59
61
  attr_reader :local_window
60
- attr_reader :remote_window
62
+ attr_reader :remote_window, :remote_settings
61
63
  alias window local_window
62
64
 
63
65
  # Current settings value for local and peer
64
66
  attr_reader :local_settings
65
- attr_reader :remote_settings
66
67
 
67
68
  # Pending settings value
68
69
  # Sent but not ack'ed settings
@@ -72,6 +73,9 @@ module HTTP2Next
72
73
  # are not counted towards the stream limit).
73
74
  attr_reader :active_stream_count
74
75
 
76
+ # Max number of streams that can be in-transit in this connection.
77
+ attr_writer :max_streams
78
+
75
79
  # Initializes new connection object.
76
80
  #
77
81
  def initialize(settings = {})
@@ -82,6 +86,7 @@ module HTTP2Next
82
86
  @decompressor = Header::Decompressor.new(settings)
83
87
 
84
88
  @active_stream_count = 0
89
+ @max_streams = nil
85
90
  @last_activated_stream = 0
86
91
  @last_stream_id = 0
87
92
  @streams = {}
@@ -95,12 +100,13 @@ module HTTP2Next
95
100
  @remote_window_limit = @remote_settings[:settings_initial_window_size]
96
101
  @remote_window = @remote_window_limit
97
102
 
98
- @recv_buffer = Buffer.new
103
+ @recv_buffer = "".b
99
104
  @continuation = []
100
105
  @error = nil
101
106
 
102
107
  @h2c_upgrade = nil
103
108
  @closed_since = nil
109
+ @received_frame = false
104
110
  end
105
111
 
106
112
  def closed?
@@ -114,7 +120,7 @@ module HTTP2Next
114
120
  # @param parent [Stream]
115
121
  def new_stream(**args)
116
122
  raise ConnectionClosed if @state == :closed
117
- raise StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
123
+ raise StreamLimitExceeded if @active_stream_count >= (@max_streams || @remote_settings[:settings_max_concurrent_streams])
118
124
 
119
125
  connection_error(:protocol_error, msg: "id is smaller than previous") if @stream_id < @last_activated_stream
120
126
 
@@ -155,7 +161,7 @@ module HTTP2Next
155
161
  send(type: :goaway, last_stream: last_stream,
156
162
  error: error, payload: payload)
157
163
  @state = :closed
158
- @closed_since = Time.now
164
+ @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
159
165
  end
160
166
 
161
167
  # Sends a WINDOW_UPDATE frame to the peer.
@@ -172,7 +178,7 @@ module HTTP2Next
172
178
  # @param settings [Array or Hash]
173
179
  def settings(payload)
174
180
  payload = payload.to_a
175
- connection_error if validate_settings(@local_role, payload)
181
+ validate_settings(@local_role, payload)
176
182
  @pending_settings << payload
177
183
  send(type: :settings, stream: 0, payload: payload)
178
184
  @pending_settings << payload
@@ -209,6 +215,11 @@ module HTTP2Next
209
215
  end
210
216
 
211
217
  while (frame = @framer.parse(@recv_buffer))
218
+ if is_a?(Client) && !@received_frame
219
+ connection_error(:protocol_error, msg: "didn't receive settings") if frame[:type] != :settings
220
+ @received_frame = true
221
+ end
222
+
212
223
  # Implementations MUST discard frames
213
224
  # that have unknown or unsupported types.
214
225
  if frame[:type].nil?
@@ -236,7 +247,7 @@ module HTTP2Next
236
247
  @continuation.clear
237
248
 
238
249
  frame.delete(:length)
239
- frame[:payload] = Buffer.new(payload)
250
+ frame[:payload] = payload
240
251
  frame[:flags] << :end_headers
241
252
  end
242
253
 
@@ -469,7 +480,7 @@ module HTTP2Next
469
480
  # the connection, although a new connection can be established
470
481
  # for new streams.
471
482
  @state = :closed
472
- @closed_since = Time.now
483
+ @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
473
484
  emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
474
485
  when :altsvc
475
486
  # 4. The ALTSVC HTTP/2 Frame
@@ -494,7 +505,7 @@ module HTTP2Next
494
505
  when :ping
495
506
  ping_management(frame)
496
507
  else
497
- connection_error if (Time.now - @closed_since) > 15
508
+ connection_error if (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @closed_since) > 15
498
509
  end
499
510
  else
500
511
  connection_error
@@ -517,8 +528,6 @@ module HTTP2Next
517
528
  def validate_settings(role, settings)
518
529
  settings.each do |key, v|
519
530
  case key
520
- when :settings_header_table_size
521
- # Any value is valid
522
531
  when :settings_enable_push
523
532
  case role
524
533
  when :server
@@ -526,32 +535,41 @@ module HTTP2Next
526
535
  # Clients MUST reject any attempt to change the
527
536
  # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
528
537
  # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
529
- return ProtocolError.new("invalid #{key} value") unless v.zero?
538
+ next if v.zero?
539
+
540
+ connection_error(:protocol_error, msg: "invalid #{key} value")
530
541
  when :client
531
542
  # Any value other than 0 or 1 MUST be treated as a
532
543
  # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
533
- return ProtocolError.new("invalid #{key} value") unless v.zero? || v == 1
544
+ next if v.zero? || v == 1
545
+
546
+ connection_error(:protocol_error, msg: "invalid #{key} value")
534
547
  end
535
- when :settings_max_concurrent_streams
536
- # Any value is valid
537
548
  when :settings_initial_window_size
538
549
  # Values above the maximum flow control window size of 2^31-1 MUST
539
550
  # be treated as a connection error (Section 5.4.1) of type
540
551
  # FLOW_CONTROL_ERROR.
541
- return FlowControlError.new("invalid #{key} value") unless v <= 0x7fffffff
552
+ next if v <= 0x7fffffff
553
+
554
+ connection_error(:flow_control_error, msg: "invalid #{key} value")
542
555
  when :settings_max_frame_size
543
556
  # The initial value is 2^14 (16,384) octets. The value advertised
544
557
  # by an endpoint MUST be between this initial value and the maximum
545
558
  # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
546
559
  # Values outside this range MUST be treated as a connection error
547
560
  # (Section 5.4.1) of type PROTOCOL_ERROR.
548
- return ProtocolError.new("invalid #{key} value") unless v >= 16_384 && v <= 16_777_215
549
- when :settings_max_header_list_size
561
+ next if v >= 16_384 && v <= 16_777_215
562
+
563
+ connection_error(:protocol_error, msg: "invalid #{key} value")
564
+ # when :settings_max_concurrent_streams
565
+ # Any value is valid
566
+ # when :settings_header_table_size
567
+ # Any value is valid
568
+ # when :settings_max_header_list_size
550
569
  # Any value is valid
551
570
  # else # ignore unknown settings
552
571
  end
553
572
  end
554
- nil
555
573
  end
556
574
 
557
575
  # Update connection settings based on parameters set by the peer.
@@ -568,8 +586,7 @@ module HTTP2Next
568
586
  # Process pending settings we have sent.
569
587
  [@pending_settings.shift, :local]
570
588
  else
571
- check = validate_settings(@remote_role, frame[:payload])
572
- connection_error(check) if check
589
+ validate_settings(@remote_role, frame[:payload])
573
590
  [frame[:payload], :remote]
574
591
  end
575
592
 
@@ -656,7 +673,7 @@ module HTTP2Next
656
673
  #
657
674
  # @param frame [Hash]
658
675
  def decode_headers(frame)
659
- frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a? Buffer
676
+ frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a?(String)
660
677
  rescue CompressionError => e
661
678
  connection_error(:compression_error, e: e)
662
679
  rescue ProtocolError => e
@@ -671,15 +688,16 @@ module HTTP2Next
671
688
  # @return [Array of Frame]
672
689
  def encode_headers(frame)
673
690
  payload = frame[:payload]
674
- payload = @compressor.encode(payload) unless payload.is_a? Buffer
691
+ payload = @compressor.encode(payload) unless payload.is_a?(String)
675
692
 
676
693
  frames = []
677
694
 
678
- while payload.bytesize > 0
695
+ while payload && payload.bytesize > 0
679
696
  cont = frame.dup
680
697
  cont[:type] = :continuation
681
698
  cont[:flags] = []
682
- cont[:payload] = payload.slice!(0, @remote_settings[:settings_max_frame_size])
699
+ cont[:payload] = payload.byteslice(0, @remote_settings[:settings_max_frame_size])
700
+ payload = payload.byteslice(@remote_settings[:settings_max_frame_size]..-1)
683
701
  frames << cont
684
702
  end
685
703
  if frames.empty?
@@ -703,10 +721,10 @@ module HTTP2Next
703
721
  # @param priority [Integer]
704
722
  # @param window [Integer]
705
723
  # @param parent [Stream]
706
- def activate_stream(id: nil, **args)
724
+ def activate_stream(id:, **args)
707
725
  connection_error(msg: "Stream ID already exists") if @streams.key?(id)
708
726
 
709
- raise StreamLimitExceeded if @active_stream_count >= @local_settings[:settings_max_concurrent_streams]
727
+ raise StreamLimitExceeded if @active_stream_count >= (@max_streams || @local_settings[:settings_max_concurrent_streams])
710
728
 
711
729
  stream = Stream.new(connection: self, id: id, **args)
712
730
 
@@ -719,12 +737,12 @@ module HTTP2Next
719
737
  # to any in-flight frames while close is registered on both sides.
720
738
  # References to such streams will be purged whenever another stream
721
739
  # is closed, with a minimum of 15s RTT time window.
722
- @streams_recently_closed.reject do |_, v|
723
- to_delete = (Time.now - v) > 15
740
+ @streams_recently_closed.reject! do |stream_id, v|
741
+ to_delete = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - v) > 15
724
742
  @streams.delete stream_id if to_delete
725
743
  to_delete
726
744
  end
727
- @streams_recently_closed[id] = Time.now
745
+ @streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
728
746
  end
729
747
 
730
748
  stream.on(:promise, &method(:promise)) if is_a? Server
@@ -742,7 +760,7 @@ module HTTP2Next
742
760
 
743
761
  def verify_pseudo_headers(frame, mandatory_headers)
744
762
  headers = frame[:payload]
745
- return if headers.is_a?(Buffer)
763
+ return if headers.is_a?(String)
746
764
 
747
765
  pseudo_headers = headers.take_while do |field, value|
748
766
  # use this loop to validate pseudo-headers
@@ -9,19 +9,18 @@ module HTTP2Next
9
9
  #
10
10
  # @param event [Symbol]
11
11
  # @param block [Proc] callback function
12
- def add_listener(event, &block)
12
+ def on(event, &block)
13
13
  raise ArgumentError, "must provide callback" unless block_given?
14
14
 
15
15
  listeners(event.to_sym).push block
16
16
  end
17
- alias on add_listener
18
17
 
19
18
  # Subscribe to next event (at most once) for specified type.
20
19
  #
21
20
  # @param event [Symbol]
22
21
  # @param block [Proc] callback function
23
22
  def once(event, &block)
24
- add_listener(event) do |*args, &callback|
23
+ on(event) do |*args, &callback|
25
24
  block.call(*args, &callback)
26
25
  :delete
27
26
  end
@@ -10,6 +10,7 @@ module HTTP2Next
10
10
 
11
11
  class Error < StandardError
12
12
  def self.inherited(klass)
13
+ super
13
14
  type = klass.name.split("::").last
14
15
  type = type.gsub(/([^\^])([A-Z])/, '\1_\2').downcase.to_sym
15
16
  HTTP2Next::Error.types[type] = klass
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2Next
4
- module Extensions
4
+ module RegexpExtensions
5
5
  unless Regexp.method_defined?(:match?)
6
6
  refine Regexp do
7
7
  def match?(*args)
@@ -10,4 +10,29 @@ module HTTP2Next
10
10
  end
11
11
  end
12
12
  end
13
+
14
+ module StringExtensions
15
+ refine String do
16
+ def read(n)
17
+ chunk = byteslice(0..n - 1)
18
+ remaining = byteslice(n..-1)
19
+ remaining ? replace(remaining) : clear
20
+ chunk
21
+ end
22
+
23
+ def read_uint32
24
+ read(4).unpack1("N")
25
+ end
26
+
27
+ def shift_byte
28
+ read(1).ord
29
+ end
30
+
31
+ unless String.method_defined?(:unpack1)
32
+ def unpack1(format)
33
+ unpack(format).first
34
+ end
35
+ end
36
+ end
37
+ end
13
38
  end
@@ -73,6 +73,7 @@ module HTTP2Next
73
73
 
74
74
  while (frame = send_buffer.retrieve(@remote_window))
75
75
 
76
+ # puts "#{self.class} -> #{@remote_window}"
76
77
  sent = frame[:payload].bytesize
77
78
 
78
79
  manage_state(frame) do
@@ -127,7 +128,7 @@ module HTTP2Next
127
128
  # Frames with zero length with the END_STREAM flag set (that
128
129
  # is, an empty DATA frame) MAY be sent if there is no available space
129
130
  # in either flow control window.
130
- return if window_size == 0 && !(frame_size == 0 && end_stream)
131
+ return if window_size <= 0 && !(frame_size == 0 && end_stream)
131
132
 
132
133
  @buffer.shift
133
134
 
@@ -137,10 +138,13 @@ module HTTP2Next
137
138
 
138
139
  # Split frame so that it fits in the window
139
140
  # TODO: consider padding!
140
- frame[:payload] = payload.slice!(0, window_size)
141
- frame[:length] = frame[:payload].bytesize
142
- chunk[:length] = payload.bytesize
141
+ frame_bytes = payload.byteslice(0, window_size)
142
+ payload = payload.byteslice(window_size..-1)
143
+
144
+ frame[:payload] = frame_bytes
145
+ frame[:length] = frame_bytes.bytesize
143
146
  chunk[:payload] = payload
147
+ chunk[:length] = payload.bytesize
144
148
 
145
149
  # if no longer last frame in sequence...
146
150
  frame[:flags] -= [:end_stream] if end_stream
@@ -4,6 +4,8 @@ module HTTP2Next
4
4
  # Performs encoding, decoding, and validation of binary HTTP/2 frames.
5
5
  #
6
6
  class Framer
7
+ using StringExtensions
8
+
7
9
  include Error
8
10
 
9
11
  # Default value of max frame size (16384 bytes)
@@ -154,9 +156,10 @@ module HTTP2Next
154
156
  # Decodes common 9-byte header.
155
157
  #
156
158
  # @param buf [Buffer]
159
+ # @return [Hash] the corresponding frame
157
160
  def read_common_header(buf)
158
161
  frame = {}
159
- len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK)
162
+ len_hi, len_lo, type, flags, stream = buf.byteslice(0, 9).unpack(HEADERPACK)
160
163
 
161
164
  frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
162
165
  frame[:type], = FRAME_TYPES.find { |_t, pos| type == pos }
@@ -175,7 +178,7 @@ module HTTP2Next
175
178
  #
176
179
  # @param frame [Hash]
177
180
  def generate(frame)
178
- bytes = Buffer.new
181
+ bytes = "".b
179
182
  length = 0
180
183
 
181
184
  frame[:flags] ||= []
@@ -221,7 +224,7 @@ module HTTP2Next
221
224
  raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
222
225
 
223
226
  frame[:payload].each do |(k, v)|
224
- if k.is_a? Integer
227
+ if k.is_a? Integer # rubocop:disable Style/GuardClause
225
228
  DEFINED_SETTINGS.value?(k) || next
226
229
  else
227
230
  k = DEFINED_SETTINGS[k]
@@ -333,10 +336,10 @@ module HTTP2Next
333
336
  #
334
337
  # @param buf [Buffer]
335
338
  def parse(buf)
336
- return nil if buf.size < 9
339
+ return if buf.size < 9
337
340
 
338
341
  frame = read_common_header(buf)
339
- return nil if buf.size < 9 + frame[:length]
342
+ return if buf.size < 9 + frame[:length]
340
343
 
341
344
  raise ProtocolError, "payload too large" if frame[:length] > @local_max_frame_size
342
345
 
@@ -353,11 +356,11 @@ module HTTP2Next
353
356
  if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
354
357
  padded = frame[:flags].include?(:padded)
355
358
  if padded
356
- padlen = payload.read(1).unpack(UINT8).first
359
+ padlen = payload.read(1).unpack1(UINT8)
357
360
  frame[:padding] = padlen + 1
358
361
  raise ProtocolError, "padding too long" if padlen > payload.bytesize
359
362
 
360
- payload.slice!(-padlen, padlen) if padlen > 0
363
+ payload = payload.byteslice(0, payload.bytesize - padlen) if padlen > 0
361
364
  frame[:length] -= frame[:padding]
362
365
  frame[:flags].delete(:padded)
363
366
  end
@@ -371,7 +374,9 @@ module HTTP2Next
371
374
  e_sd = payload.read_uint32
372
375
  frame[:dependency] = e_sd & RBIT
373
376
  frame[:exclusive] = (e_sd & EBIT) != 0
374
- frame[:weight] = payload.getbyte + 1
377
+ weight = payload.byteslice(0, 1).ord + 1
378
+ frame[:weight] = weight
379
+ payload = payload.byteslice(1..-1)
375
380
  end
376
381
  frame[:payload] = payload.read(frame[:length])
377
382
  when :priority
@@ -380,7 +385,9 @@ module HTTP2Next
380
385
  e_sd = payload.read_uint32
381
386
  frame[:dependency] = e_sd & RBIT
382
387
  frame[:exclusive] = (e_sd & EBIT) != 0
383
- frame[:weight] = payload.getbyte + 1
388
+ weight = payload.byteslice(0, 1).ord + 1
389
+ frame[:weight] = weight
390
+ payload = payload.byteslice(1..-1)
384
391
  when :rst_stream
385
392
  raise FrameSizeError, "Invalid length for RST_STREAM (#{frame[:length]} != 4)" if frame[:length] != 4
386
393
 
@@ -395,7 +402,7 @@ module HTTP2Next
395
402
  raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
396
403
 
397
404
  (frame[:length] / 6).times do
398
- id = payload.read(2).unpack(UINT16).first
405
+ id = payload.read(2).unpack1(UINT16)
399
406
  val = payload.read_uint32
400
407
 
401
408
  # Unsupported or unrecognized settings MUST be ignored.
@@ -425,10 +432,12 @@ module HTTP2Next
425
432
  when :altsvc
426
433
  frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16)
427
434
 
428
- len = payload.getbyte
435
+ len = payload.byteslice(0, 1).ord
436
+ payload = payload.byteslice(1..-1)
429
437
  frame[:proto] = payload.read(len) if len > 0
430
438
 
431
- len = payload.getbyte
439
+ len = payload.byteslice(0, 1).ord
440
+ payload = payload.byteslice(1..-1)
432
441
  frame[:host] = payload.read(len) if len > 0
433
442
 
434
443
  frame[:origin] = payload.read(payload.size) unless payload.empty?
@@ -437,7 +446,7 @@ module HTTP2Next
437
446
  origins = []
438
447
 
439
448
  until payload.empty?
440
- len = payload.read(2).unpack(UINT16).first
449
+ len = payload.read(2).unpack1(UINT16)
441
450
  origins << payload.read(len)
442
451
  end
443
452
 
@@ -452,17 +461,16 @@ module HTTP2Next
452
461
 
453
462
  def pack_error(e)
454
463
  unless e.is_a? Integer
455
- raise CompressionError, "Unknown error ID for #{e}" if DEFINED_ERRORS[e].nil?
456
-
457
464
  e = DEFINED_ERRORS[e]
465
+
466
+ raise CompressionError, "Unknown error ID for #{e}" unless e
458
467
  end
459
468
 
460
469
  [e].pack(UINT32)
461
470
  end
462
471
 
463
472
  def unpack_error(error)
464
- name, = DEFINED_ERRORS.find { |_name, v| v == error }
465
- name || error
473
+ DEFINED_ERRORS.key(error) || error
466
474
  end
467
475
  end
468
476
  end