http-2-next 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63f956f2326b803ec836c578b8f98326934897307bda409f2c639e5c88893689
4
- data.tar.gz: fbb8a4486463e0e0e5c0d4ed04cf4120a73acdf169f99b99365a0805f89fe5c1
3
+ metadata.gz: 6a97513d9cfdae554f85ed0cbd3d0818ae0c3c6dc5bdf418a126d3a1993f1c52
4
+ data.tar.gz: a8712f76a43721ca5d67f281d2689853f7d6cf4ca224704e500c34ae03e10a53
5
5
  SHA512:
6
- metadata.gz: 628b410fe470df16e61d11732c65553381bcb060f2fe2639af4889aff5d9e3a046ee55e1d67599e1c268dd38c3f4ff22f8fb257ee100a96ba384a9ea1e976726
7
- data.tar.gz: 86e1148ca63e9d36745ab703ab29f174fdae5cb8473b167fa4f09138274899713cb57e7604c6ff51d0fae493ea98262b1d86adf94c42c0248e50721ae2f63add
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2Next
4
- VERSION = "1.0.2"
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.2
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-12-14 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