http-2-next 1.0.1 → 1.0.3

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: 772dfd651b9130358e1de248ead9c68729e482dfdd518973a2d4b61d787d7807
4
- data.tar.gz: 59dca4e2a1319dc7363b00e3d52ee7889354f0b0bb114fe49bfc7cd47cbcb1e4
3
+ metadata.gz: 6a97513d9cfdae554f85ed0cbd3d0818ae0c3c6dc5bdf418a126d3a1993f1c52
4
+ data.tar.gz: a8712f76a43721ca5d67f281d2689853f7d6cf4ca224704e500c34ae03e10a53
5
5
  SHA512:
6
- metadata.gz: fb2dce4d9d3f663929771b3141b616030de65f1004e28acee1e01a7d4aa306daba51a839fb9c2e68337d056311369bfb6f1b3bb7dae3cf755a9510839185bef8
7
- data.tar.gz: 5c3e377005030768e6056e9cab0076a5e1165ef0aa4433c4920db2cbd29fed171867da9f4b6da5e2c7b64da340bc24f65e01397c90cc7f160ae520f563dbc132
6
+ metadata.gz: 303e98009c8a47229747659e4dfc4bd33636a52de907bc1164f1f33b4a4ba4a0e051459e30853cae3e88a5fdbb9b84d7e6fbbe387b51f8870e16d7923a28e757
7
+ data.tar.gz: c40a73c72fe006f2a371dda23adb7ba09dc11e791a4b7a7781a6e5e4593bbfd75e4a5a06e8e6b7755c1335857c84b3077430b6261337e039578f3978937f9e82
@@ -16,6 +16,14 @@ elsif !defined?(Base64)
16
16
  str.unpack1("m")
17
17
  end
18
18
 
19
+ def strict_encode64(bin)
20
+ [bin].pack("m0")
21
+ end
22
+
23
+ def strict_decode64(str)
24
+ str.unpack1("m0")
25
+ end
26
+
19
27
  def urlsafe_encode64(bin, padding: true)
20
28
  str = strict_encode64(bin)
21
29
  str.chomp!("==") or str.chomp!("=") unless padding
@@ -731,11 +731,13 @@ module HTTP2Next
731
731
  # to any in-flight frames while close is registered on both sides.
732
732
  # References to such streams will be purged whenever another stream
733
733
  # is closed, with a minimum of 15s RTT time window.
734
- @streams_recently_closed.reject! do |stream_id, v|
735
- to_delete = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - v) > 15
736
- @streams.delete stream_id if to_delete
737
- to_delete
738
- end
734
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
735
+
736
+ # TODO: use a drop_while! variant whenever there is one.
737
+ @streams_recently_closed = @streams_recently_closed.drop_while do |_, v|
738
+ (now - v) > 15
739
+ end.to_h
740
+
739
741
  @streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
740
742
  end
741
743
 
@@ -21,4 +21,33 @@ module HTTP2Next
21
21
  end
22
22
  end
23
23
  end
24
+
25
+ # this mixin handles backwards-compatibility for the new packing options
26
+ # shipping with ruby 3.3 (see https://docs.ruby-lang.org/en/3.3/packed_data_rdoc.html)
27
+ module PackingExtensions
28
+ if RUBY_VERSION < "3.3.0"
29
+ def pack(array_to_pack, template, buffer:, offset: -1)
30
+ packed_str = array_to_pack.pack(template)
31
+ case offset
32
+ when -1
33
+ buffer << packed_str
34
+ when 0
35
+ buffer.prepend(packed_str)
36
+ else
37
+ buffer.insert(offset, packed_str)
38
+ end
39
+ end
40
+ else
41
+ def pack(array_to_pack, template, buffer:, offset: -1)
42
+ case offset
43
+ when -1
44
+ array_to_pack.pack(template, buffer: buffer)
45
+ when 0
46
+ buffer.prepend(array_to_pack.pack(template))
47
+ else
48
+ buffer.insert(offset, array_to_pack.pack(template))
49
+ end
50
+ end
51
+ end
52
+ end
24
53
  end
@@ -7,6 +7,7 @@ module HTTP2Next
7
7
  using StringExtensions
8
8
 
9
9
  include Error
10
+ include PackingExtensions
10
11
 
11
12
  # Default value of max frame size (16384 bytes)
12
13
  DEFAULT_MAX_FRAME_SIZE = 2 << 13
@@ -123,10 +124,9 @@ module HTTP2Next
123
124
  # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1
124
125
  #
125
126
  # @param frame [Hash]
127
+ # @param buffer [String] buffer to pack bytes into
126
128
  # @return [String]
127
- def common_header(frame)
128
- header = []
129
-
129
+ def common_header(frame, buffer:)
130
130
  raise CompressionError, "Invalid frame type (#{frame[:type]})" unless FRAME_TYPES[frame[:type]]
131
131
 
132
132
  raise CompressionError, "Frame size is too large: #{frame[:length]}" if frame[:length] > @remote_max_frame_size
@@ -139,18 +139,18 @@ module HTTP2Next
139
139
  raise CompressionError, "Window increment (#{frame[:increment]}) is too large"
140
140
  end
141
141
 
142
- header << (frame[:length] >> FRAME_LENGTH_HISHIFT)
143
- header << (frame[:length] & FRAME_LENGTH_LOMASK)
144
- header << FRAME_TYPES[frame[:type]]
145
- header << frame[:flags].reduce(0) do |acc, f|
146
- position = FRAME_FLAGS[frame[:type]][f]
147
- raise CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" unless position
148
-
149
- acc | (1 << position)
150
- end
151
-
152
- header << frame[:stream]
153
- header.pack(HEADERPACK) # 8+16,8,8,32
142
+ pack([
143
+ (frame[:length] >> FRAME_LENGTH_HISHIFT),
144
+ (frame[:length] & FRAME_LENGTH_LOMASK),
145
+ FRAME_TYPES[frame[:type]],
146
+ frame[:flags].reduce(0) do |acc, f|
147
+ position = FRAME_FLAGS[frame[:type]][f]
148
+ raise CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" unless position
149
+
150
+ acc | (1 << position)
151
+ end,
152
+ frame[:stream]
153
+ ], HEADERPACK, buffer: buffer, offset: 0) # 8+16,8,8,32
154
154
  end
155
155
 
156
156
  # Decodes common 9-byte header.
@@ -200,8 +200,8 @@ module HTTP2Next
200
200
  end
201
201
 
202
202
  if frame[:flags].include? :priority
203
- bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)].pack(UINT32)
204
- bytes << [frame[:weight] - 1].pack(UINT8)
203
+ pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
204
+ pack([frame[:weight] - 1], UINT8, buffer: bytes)
205
205
  length += 5
206
206
  end
207
207
 
@@ -213,12 +213,12 @@ module HTTP2Next
213
213
  raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
214
214
  end
215
215
 
216
- bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)].pack(UINT32)
217
- bytes << [frame[:weight] - 1].pack(UINT8)
216
+ pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
217
+ pack([frame[:weight] - 1], UINT8, buffer: bytes)
218
218
  length += 5
219
219
 
220
220
  when :rst_stream
221
- bytes << pack_error(frame[:error])
221
+ pack_error(frame[:error], buffer: bytes)
222
222
  length += 4
223
223
 
224
224
  when :settings
@@ -233,13 +233,13 @@ module HTTP2Next
233
233
  raise CompressionError, "Unknown settings ID for #{k}" if k.nil?
234
234
  end
235
235
 
236
- bytes << [k].pack(UINT16)
237
- bytes << [v].pack(UINT32)
236
+ pack([k], UINT16, buffer: bytes)
237
+ pack([v], UINT32, buffer: bytes)
238
238
  length += 6
239
239
  end
240
240
 
241
241
  when :push_promise
242
- bytes << [frame[:promise_stream] & RBIT].pack(UINT32)
242
+ pack([frame[:promise_stream] & RBIT], UINT32, buffer: bytes)
243
243
  bytes << frame[:payload]
244
244
  length += 4 + frame[:payload].bytesize
245
245
 
@@ -250,8 +250,8 @@ module HTTP2Next
250
250
  length += 8
251
251
 
252
252
  when :goaway
253
- bytes << [frame[:last_stream] & RBIT].pack(UINT32)
254
- bytes << pack_error(frame[:error])
253
+ pack([frame[:last_stream] & RBIT], UINT32, buffer: bytes)
254
+ pack_error(frame[:error], buffer: bytes)
255
255
  length += 8
256
256
 
257
257
  if frame[:payload]
@@ -260,7 +260,7 @@ module HTTP2Next
260
260
  end
261
261
 
262
262
  when :window_update
263
- bytes << [frame[:increment] & RBIT].pack(UINT32)
263
+ pack([frame[:increment] & RBIT], UINT32, buffer: bytes)
264
264
  length += 4
265
265
 
266
266
  when :continuation
@@ -268,26 +268,26 @@ module HTTP2Next
268
268
  length += frame[:payload].bytesize
269
269
 
270
270
  when :altsvc
271
- bytes << [frame[:max_age], frame[:port]].pack(UINT32 + UINT16)
271
+ pack([frame[:max_age], frame[:port]], UINT32 + UINT16, buffer: bytes)
272
272
  length += 6
273
273
  if frame[:proto]
274
274
  raise CompressionError, "Proto too long" if frame[:proto].bytesize > 255
275
275
 
276
- bytes << [frame[:proto].bytesize].pack(UINT8)
276
+ pack([frame[:proto].bytesize], UINT8, buffer: bytes)
277
277
  bytes << frame[:proto]
278
278
  length += 1 + frame[:proto].bytesize
279
279
  else
280
- bytes << [0].pack(UINT8)
280
+ pack([0], UINT8, buffer: bytes)
281
281
  length += 1
282
282
  end
283
283
  if frame[:host]
284
284
  raise CompressionError, "Host too long" if frame[:host].bytesize > 255
285
285
 
286
- bytes << [frame[:host].bytesize].pack(UINT8)
286
+ pack([frame[:host].bytesize], UINT8, buffer: bytes)
287
287
  bytes << frame[:host]
288
288
  length += 1 + frame[:host].bytesize
289
289
  else
290
- bytes << [0].pack(UINT8)
290
+ pack([0], UINT8, buffer: bytes)
291
291
  length += 1
292
292
  end
293
293
  if frame[:origin]
@@ -297,7 +297,7 @@ module HTTP2Next
297
297
 
298
298
  when :origin
299
299
  frame[:payload].each do |origin|
300
- bytes << [origin.bytesize].pack(UINT16)
300
+ pack([origin.bytesize], UINT16, buffer: bytes)
301
301
  bytes << origin
302
302
  length += 2 + origin.bytesize
303
303
  end
@@ -319,7 +319,7 @@ module HTTP2Next
319
319
  end
320
320
 
321
321
  length += padlen
322
- bytes.prepend([padlen -= 1].pack(UINT8))
322
+ pack([padlen -= 1], UINT8, buffer: bytes, offset: 0)
323
323
  frame[:flags] << :padded
324
324
 
325
325
  # Padding: Padding octets that contain no application semantic value.
@@ -329,7 +329,7 @@ module HTTP2Next
329
329
  end
330
330
 
331
331
  frame[:length] = length
332
- bytes.prepend(common_header(frame))
332
+ common_header(frame, buffer: bytes)
333
333
  end
334
334
 
335
335
  # Decodes complete HTTP/2 frame from provided buffer. If the buffer
@@ -456,14 +456,14 @@ module HTTP2Next
456
456
 
457
457
  private
458
458
 
459
- def pack_error(error)
459
+ def pack_error(error, buffer:)
460
460
  unless error.is_a? Integer
461
461
  error = DEFINED_ERRORS[error]
462
462
 
463
463
  raise CompressionError, "Unknown error ID for #{error}" unless error
464
464
  end
465
465
 
466
- [error].pack(UINT32)
466
+ pack([error], UINT32, buffer: buffer)
467
467
  end
468
468
 
469
469
  def unpack_error(error)
@@ -4,6 +4,8 @@ module HTTP2Next
4
4
  module Header
5
5
  # Responsible for encoding header key-value pairs using HPACK algorithm.
6
6
  class Compressor
7
+ include PackingExtensions
8
+
7
9
  # @param options [Hash] encoding options
8
10
  def initialize(options = {})
9
11
  @cc = EncodingContext.new(options)
@@ -29,10 +31,12 @@ module HTTP2Next
29
31
  #
30
32
  # @param i [Integer] value to encode
31
33
  # @param n [Integer] number of available bits
34
+ # @param buffer [String] buffer to pack bytes into
35
+ # @param offset [Integer] offset to insert packed bytes in buffer
32
36
  # @return [String] binary string
33
- def integer(i, n)
37
+ def integer(i, n, buffer:, offset: 0)
34
38
  limit = (2**n) - 1
35
- return [i].pack("C") if i < limit
39
+ return pack([i], "C", buffer: buffer, offset: offset) if i < limit
36
40
 
37
41
  bytes = []
38
42
  bytes.push limit unless n.zero?
@@ -44,7 +48,7 @@ module HTTP2Next
44
48
  end
45
49
 
46
50
  bytes.push i
47
- bytes.pack("C*")
51
+ pack(bytes, "C*", buffer: buffer, offset: offset)
48
52
  end
49
53
 
50
54
  # Encodes provided value via string literal representation.
@@ -68,20 +72,16 @@ module HTTP2Next
68
72
  # @param str [String]
69
73
  # @return [String] binary string
70
74
  def string(str)
71
- plain = nil
72
- huffman = nil
73
- plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY) unless @cc.options[:huffman] == :always
74
- unless @cc.options[:huffman] == :never
75
- huffman = Huffman.new.encode(str)
76
- huffman = integer(huffman.bytesize, 7) << huffman
77
- huffman.setbyte(0, huffman.ord | 0x80)
78
- end
79
75
  case @cc.options[:huffman]
80
76
  when :always
81
- huffman
77
+ huffman_string(str)
82
78
  when :never
83
- plain
79
+ plain_string(str)
84
80
  else
81
+ huffman = huffman_string(str)
82
+
83
+ plain = plain_string(str)
84
+
85
85
  huffman.bytesize < plain.bytesize ? huffman : plain
86
86
  end
87
87
  end
@@ -96,14 +96,14 @@ module HTTP2Next
96
96
 
97
97
  case h[:type]
98
98
  when :indexed
99
- buffer << integer(h[:name] + 1, rep[:prefix])
99
+ integer(h[:name] + 1, rep[:prefix], buffer: buffer)
100
100
  when :changetablesize
101
- buffer << integer(h[:value], rep[:prefix])
101
+ integer(h[:value], rep[:prefix], buffer: buffer)
102
102
  else
103
103
  if h[:name].is_a? Integer
104
- buffer << integer(h[:name] + 1, rep[:prefix])
104
+ integer(h[:name] + 1, rep[:prefix], buffer: buffer)
105
105
  else
106
- buffer << integer(0, rep[:prefix])
106
+ integer(0, rep[:prefix], buffer: buffer)
107
107
  buffer << string(h[:name])
108
108
  end
109
109
 
@@ -132,6 +132,26 @@ module HTTP2Next
132
132
 
133
133
  buffer
134
134
  end
135
+
136
+ private
137
+
138
+ # @param str [String]
139
+ # @return [String] binary string
140
+ def huffman_string(str)
141
+ huffman = Huffman.new.encode(str)
142
+ integer(huffman.bytesize, 7, buffer: huffman, offset: 0)
143
+ huffman.setbyte(0, huffman.ord | 0x80)
144
+ huffman
145
+ end
146
+
147
+ # @param str [String]
148
+ # @return [String] binary string
149
+ def plain_string(str)
150
+ plain = "".b
151
+ integer(str.bytesize, 7, buffer: plain)
152
+ plain << str.dup.force_encoding(Encoding::BINARY)
153
+ plain
154
+ end
135
155
  end
136
156
  end
137
157
  end
@@ -13,6 +13,7 @@ module HTTP2Next
13
13
  using StringExtensions
14
14
 
15
15
  include Error
16
+ include PackingExtensions
16
17
 
17
18
  BITS_AT_ONCE = 4
18
19
  EOS = 256
@@ -80,13 +80,15 @@ module HTTP2Next
80
80
  # Process received HTTP2-Settings payload
81
81
  buf = "".b
82
82
  buf << Base64.urlsafe_decode64(settings.to_s)
83
- header = @framer.common_header(
84
- length: buf.bytesize,
85
- type: :settings,
86
- stream: 0,
87
- flags: []
83
+ @framer.common_header(
84
+ {
85
+ length: buf.bytesize,
86
+ type: :settings,
87
+ stream: 0,
88
+ flags: []
89
+ },
90
+ buffer: buf
88
91
  )
89
- buf.prepend(header)
90
92
  receive(buf)
91
93
 
92
94
  # Activate stream (id: 1) with on HTTP/1.1 request parameters
@@ -170,7 +170,7 @@ module HTTP2Next
170
170
  trailers = frame[:payload]
171
171
  return unless trailers.respond_to?(:each)
172
172
 
173
- trailers.each do |field, _|
173
+ trailers.each do |field, _| # rubocop:disable Style/HashEachMethods
174
174
  @_trailers.delete(field)
175
175
  break if @_trailers.empty?
176
176
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2Next
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.3"
5
5
  end
@@ -0,0 +1,5 @@
1
+ module HTTP2Next
2
+ module PackingExtensions
3
+ def pack: (Array[Integer] array_to_pack, String template, buffer: String, ?offset: Integer) -> String
4
+ end
5
+ end
data/sig/framer.rbs CHANGED
@@ -1,5 +1,7 @@
1
1
  module HTTP2Next
2
2
  class Framer
3
+ include PackingExtensions
4
+
3
5
  DEFAULT_MAX_FRAME_SIZE: Integer
4
6
 
5
7
  MAX_STREAM_ID: Integer
@@ -33,7 +35,7 @@ module HTTP2Next
33
35
 
34
36
  attr_accessor remote_max_frame_size: Integer
35
37
 
36
- def common_header: (frame) -> String
38
+ def common_header: (frame, buffer: String) -> String
37
39
 
38
40
  def read_common_frame: (String) -> frame
39
41
 
@@ -45,7 +47,7 @@ module HTTP2Next
45
47
 
46
48
  def initialize: (?Integer local_max_frame_size, ?Integer remote_max_frame_size) -> untyped
47
49
 
48
- def pack_error: (Integer | Symbol error) -> String
50
+ def pack_error: (Integer | Symbol error, buffer: String) -> String
49
51
 
50
52
  def unpack_error: (Integer) -> (Symbol | Integer)
51
53
  end
@@ -1,23 +1,27 @@
1
1
  module HTTP2Next
2
2
  module Header
3
3
  class Compressor
4
+ include PackingExtensions
5
+
4
6
  @cc: EncodingContext
5
7
 
6
8
  def table_size=: (Integer) -> void
7
9
 
8
- def integer: (Integer, Integer) -> String
10
+ def integer: (Integer, Integer, buffer: String, ?offset: Integer) -> String
9
11
 
10
12
  def string: (String) -> String
11
13
 
12
- def header: (header_command, String) -> String
13
- | (header_command) -> String
14
+ def header: (header_command, ?String) -> String
14
15
 
15
16
  def encode: (Enumerable[header_pair]) -> String
16
17
 
17
18
  private
18
19
 
19
- def initialize: (context_hash options) -> untyped
20
- | () -> untyped
20
+ def initialize: (?context_hash options) -> void
21
+
22
+ def huffman_string: (String str) -> String
23
+
24
+ def plain_string: (String str) -> String
21
25
  end
22
26
  end
23
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http-2-next
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-10-13 00:00:00.000000000 Z
13
+ date: 2024-01-11 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: Pure-ruby HTTP 2.0 protocol implementation
16
16
  email:
@@ -43,6 +43,7 @@ files:
43
43
  - sig/connection.rbs
44
44
  - sig/emitter.rbs
45
45
  - sig/error.rbs
46
+ - sig/extensions.rbs
46
47
  - sig/flow_buffer.rbs
47
48
  - sig/frame_buffer.rbs
48
49
  - sig/framer.rbs