quicsilver 0.3.0 → 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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/.github/workflows/cibuildgem.yaml +93 -0
  4. data/.gitignore +3 -1
  5. data/CHANGELOG.md +32 -0
  6. data/Gemfile.lock +20 -2
  7. data/README.md +92 -29
  8. data/Rakefile +67 -2
  9. data/benchmarks/concurrent.rb +2 -2
  10. data/benchmarks/rails.rb +3 -3
  11. data/benchmarks/throughput.rb +2 -2
  12. data/examples/README.md +44 -91
  13. data/examples/benchmark.rb +111 -0
  14. data/examples/connection_pool_demo.rb +47 -0
  15. data/examples/example_helper.rb +18 -0
  16. data/examples/falcon_middleware.rb +44 -0
  17. data/examples/feature_demo.rb +125 -0
  18. data/examples/grpc_style.rb +97 -0
  19. data/examples/minimal_http3_server.rb +6 -18
  20. data/examples/priorities.rb +60 -0
  21. data/examples/protocol_http_server.rb +31 -0
  22. data/examples/rack_http3_server.rb +8 -20
  23. data/examples/rails_feature_test.rb +260 -0
  24. data/examples/simple_client_test.rb +2 -2
  25. data/examples/streaming_sse.rb +33 -0
  26. data/examples/trailers.rb +69 -0
  27. data/ext/quicsilver/extconf.rb +14 -0
  28. data/ext/quicsilver/quicsilver.c +39 -0
  29. data/lib/quicsilver/client/client.rb +138 -39
  30. data/lib/quicsilver/client/connection_pool.rb +106 -0
  31. data/lib/quicsilver/libmsquic.2.dylib +0 -0
  32. data/lib/quicsilver/protocol/adapter.rb +176 -0
  33. data/lib/quicsilver/protocol/control_stream_parser.rb +106 -0
  34. data/lib/quicsilver/protocol/frame_parser.rb +142 -0
  35. data/lib/quicsilver/protocol/frame_reader.rb +55 -0
  36. data/lib/quicsilver/protocol/frames.rb +18 -7
  37. data/lib/quicsilver/protocol/priority.rb +56 -0
  38. data/lib/quicsilver/protocol/qpack/encoder.rb +39 -1
  39. data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +16 -1
  40. data/lib/quicsilver/protocol/request_parser.rb +28 -140
  41. data/lib/quicsilver/protocol/response_encoder.rb +27 -2
  42. data/lib/quicsilver/protocol/response_parser.rb +22 -130
  43. data/lib/quicsilver/protocol/stream_input.rb +98 -0
  44. data/lib/quicsilver/protocol/stream_output.rb +59 -0
  45. data/lib/quicsilver/quicsilver.bundle +0 -0
  46. data/lib/quicsilver/server/request_handler.rb +96 -44
  47. data/lib/quicsilver/server/server.rb +316 -42
  48. data/lib/quicsilver/transport/configuration.rb +10 -1
  49. data/lib/quicsilver/transport/connection.rb +92 -63
  50. data/lib/quicsilver/version.rb +1 -1
  51. data/lib/quicsilver.rb +26 -3
  52. data/quicsilver.gemspec +10 -2
  53. metadata +69 -5
  54. data/examples/setup_certs.sh +0 -57
@@ -3,13 +3,23 @@
3
3
  module Quicsilver
4
4
  module Transport
5
5
  class Connection
6
+ include Protocol::ControlStreamParser
7
+
8
+ # MsQuic QUIC_STATUS_INVALID_STATE (POSIX errno ETOOMANYREFS = 0x59).
9
+ # Stream already shut down by peer — raised by StreamSend when the
10
+ # client has reset or closed the stream.
11
+ MSQUIC_INVALID_STATE = "0x59"
12
+
6
13
  attr_reader :handle, :data, :streams
7
14
  attr_reader :control_stream_id, :qpack_encoder_stream_id, :qpack_decoder_stream_id
8
15
  attr_reader :server_control_stream
16
+ attr_reader :peer_goaway_id, :local_goaway_id
17
+ attr_reader :stream_priorities
9
18
 
10
- def initialize(handle, data)
19
+ def initialize(handle, data, max_header_size: nil)
11
20
  @handle = handle
12
21
  @data = data
22
+ @max_header_size = max_header_size
13
23
  @streams = {}
14
24
  @response_buffers = {}
15
25
  @mutex = Mutex.new
@@ -24,6 +34,9 @@ module Quicsilver
24
34
 
25
35
  @settings = {}
26
36
  @settings_received = false
37
+ @peer_goaway_id = nil
38
+ @local_goaway_id = nil
39
+ @stream_priorities = {}
27
40
  end
28
41
 
29
42
  # === Setup (called after connection established) ===
@@ -31,13 +44,17 @@ module Quicsilver
31
44
  def setup_http3_streams
32
45
  # Control stream (required)
33
46
  @server_control_stream = open_stream(unidirectional: true)
34
- @server_control_stream.send(Protocol.build_control_stream)
47
+ @server_control_stream.send(Protocol.build_control_stream(max_field_section_size: @max_header_size))
35
48
 
36
49
  # QPACK encoder/decoder streams
37
50
  [0x02, 0x03].each do |type|
38
51
  stream = open_stream(unidirectional: true)
39
52
  stream.send([type].pack("C"))
40
53
  end
54
+
55
+ # GREASE unidirectional stream (RFC 9297)
56
+ stream = open_stream(unidirectional: true)
57
+ stream.send(Protocol.encode_varint(Protocol.grease_id) + "GREASE".b)
41
58
  end
42
59
 
43
60
  # === Stream Management ===
@@ -62,14 +79,14 @@ module Quicsilver
62
79
 
63
80
  def buffer_data(stream_id, data)
64
81
  @mutex.synchronize do
65
- (@response_buffers[stream_id] ||= StringIO.new("".b)).write(data)
82
+ (@response_buffers[stream_id] ||= "".b) << data
66
83
  end
67
84
  end
68
85
 
69
86
  def complete_stream(stream_id, final_data)
70
87
  @mutex.synchronize do
71
88
  buffer = @response_buffers.delete(stream_id)
72
- (buffer&.string || "".b) + (final_data || "".b)
89
+ (buffer || "".b) + (final_data || "".b)
73
90
  end
74
91
  end
75
92
 
@@ -79,13 +96,36 @@ module Quicsilver
79
96
  return unless @server_control_stream
80
97
 
81
98
  stream_id ||= last_client_stream_id
99
+ validate_goaway_id!(stream_id)
100
+
82
101
  @server_control_stream.send(Protocol.build_goaway_frame(stream_id))
102
+ @local_goaway_id = stream_id
103
+ rescue ArgumentError
104
+ raise # Re-raise validation errors
83
105
  rescue => e
84
106
  Quicsilver.logger.error("Failed to send GOAWAY: #{e.message}")
85
107
  end
86
108
 
87
- def send_response(stream, status, headers, body, head_request: false)
88
- encoder = Protocol::ResponseEncoder.new(status, headers, body, head_request: head_request)
109
+ # RFC 9114 §5.2: GOAWAY IDs MUST NOT increase from a previous value.
110
+ def validate_goaway_id!(stream_id)
111
+ if @local_goaway_id && stream_id > @local_goaway_id
112
+ raise ArgumentError, "GOAWAY stream ID #{stream_id} exceeds previous #{@local_goaway_id}"
113
+ end
114
+ end
115
+
116
+ # Send an informational (1xx) response before the final response.
117
+ # RFC 9114 §4.1: encoded as a HEADERS frame, no FIN.
118
+ def send_informational(stream, status, headers)
119
+ data = Protocol::ResponseEncoder.encode_informational(status, headers)
120
+ Quicsilver.send_stream(stream.stream_handle, data, false)
121
+ rescue RuntimeError => e
122
+ raise unless e.message.include?(MSQUIC_INVALID_STATE) || e.message.include?("StreamSend failed")
123
+ Quicsilver.logger.debug("Stream send failed (client likely reset): #{e.message}")
124
+ end
125
+
126
+ def send_response(stream, status, headers, body, head_request: false, trailers: nil)
127
+ body = [] if body.nil?
128
+ encoder = Protocol::ResponseEncoder.new(status, headers, body, head_request: head_request, trailers: trailers)
89
129
 
90
130
  if body.respond_to?(:to_ary)
91
131
  Quicsilver.send_stream(stream.stream_handle, encoder.encode, true)
@@ -96,7 +136,7 @@ module Quicsilver
96
136
  end
97
137
  rescue RuntimeError => e
98
138
  # Stream may have been reset by client - this is expected
99
- raise unless e.message.include?("0x59") || e.message.include?("StreamSend failed")
139
+ raise unless e.message.include?(MSQUIC_INVALID_STATE) || e.message.include?("StreamSend failed")
100
140
  Quicsilver.logger.debug("Stream send failed (client likely reset): #{e.message}")
101
141
  end
102
142
 
@@ -106,7 +146,7 @@ module Quicsilver
106
146
  Quicsilver.send_stream(stream.stream_handle, encoder.encode, true)
107
147
  rescue RuntimeError => e
108
148
  # Stream may have been reset by client - this is expected
109
- raise unless e.message.include?("0x59") || e.message.include?("StreamSend failed")
149
+ raise unless e.message.include?(MSQUIC_INVALID_STATE) || e.message.include?("StreamSend failed")
110
150
  Quicsilver.logger.debug("Stream send failed (client likely reset): #{e.message}")
111
151
  end
112
152
 
@@ -116,10 +156,10 @@ module Quicsilver
116
156
  # Called on each RECEIVE event — control streams never send FIN.
117
157
  def receive_unidirectional_data(stream_id, data)
118
158
  @mutex.synchronize do
119
- (@response_buffers[stream_id] ||= StringIO.new("".b)).write(data)
159
+ (@response_buffers[stream_id] ||= "".b) << data
120
160
  end
121
161
 
122
- buf = @mutex.synchronize { @response_buffers[stream_id]&.string || "".b }
162
+ buf = @mutex.synchronize { @response_buffers[stream_id] || "".b }
123
163
  return if buf.empty?
124
164
 
125
165
  # First time seeing this stream: identify stream type
@@ -134,26 +174,26 @@ module Quicsilver
134
174
  @control_stream_id = stream_id
135
175
  @uni_stream_types[stream_id] = :control
136
176
  # Remove the stream type byte from the buffer
137
- @mutex.synchronize { @response_buffers[stream_id] = StringIO.new(buf[type_len..] || "".b) }
177
+ @mutex.synchronize { @response_buffers[stream_id] = (buf[type_len..] || "".b) }
138
178
  when 0x01
139
179
  raise Protocol::FrameError, "Client must not send push streams"
140
180
  when 0x02 # QPACK encoder stream
141
181
  raise Protocol::FrameError, "Duplicate QPACK encoder stream" if @qpack_encoder_stream_id
142
182
  @qpack_encoder_stream_id = stream_id
143
183
  @uni_stream_types[stream_id] = :qpack_encoder
144
- @mutex.synchronize { @response_buffers[stream_id] = StringIO.new(buf[type_len..] || "".b) }
184
+ @mutex.synchronize { @response_buffers[stream_id] = (buf[type_len..] || "".b) }
145
185
  when 0x03 # QPACK decoder stream
146
186
  raise Protocol::FrameError, "Duplicate QPACK decoder stream" if @qpack_decoder_stream_id
147
187
  @qpack_decoder_stream_id = stream_id
148
188
  @uni_stream_types[stream_id] = :qpack_decoder
149
- @mutex.synchronize { @response_buffers[stream_id] = StringIO.new(buf[type_len..] || "".b) }
189
+ @mutex.synchronize { @response_buffers[stream_id] = (buf[type_len..] || "".b) }
150
190
  else
151
191
  # Unknown unidirectional stream types MUST be ignored (RFC 9114 §6.2)
152
192
  @uni_stream_types[stream_id] = :unknown
153
193
  return
154
194
  end
155
195
 
156
- buf = @mutex.synchronize { @response_buffers[stream_id]&.string || "".b }
196
+ buf = @mutex.synchronize { @response_buffers[stream_id] || "".b }
157
197
  end
158
198
 
159
199
  stream_type = @uni_stream_types[stream_id]
@@ -163,13 +203,13 @@ module Quicsilver
163
203
  when :control
164
204
  parse_control_frames(buf)
165
205
  # Clear parsed data from buffer
166
- @mutex.synchronize { @response_buffers[stream_id] = StringIO.new("".b) }
206
+ @mutex.synchronize { @response_buffers[stream_id] = "".b }
167
207
  when :qpack_encoder
168
208
  validate_qpack_encoder_data(buf)
169
- @mutex.synchronize { @response_buffers[stream_id] = StringIO.new("".b) }
209
+ @mutex.synchronize { @response_buffers[stream_id] = "".b }
170
210
  when :qpack_decoder
171
211
  validate_qpack_decoder_data(buf)
172
- @mutex.synchronize { @response_buffers[stream_id] = StringIO.new("".b) }
212
+ @mutex.synchronize { @response_buffers[stream_id] = "".b }
173
213
  end
174
214
  end
175
215
 
@@ -221,6 +261,25 @@ module Quicsilver
221
261
  @settings
222
262
  end
223
263
 
264
+ # Get the priority for a stream. Returns default Priority if not set.
265
+ def stream_priority(stream_id)
266
+ @stream_priorities[stream_id] || Protocol::Priority.new
267
+ end
268
+
269
+ # Apply priority to a QUIC stream via MsQuic.
270
+ # MsQuic: 0 = lowest, 0xFFFF = highest.
271
+ # HTTP urgency: 0 = highest, 7 = lowest.
272
+ # Maps urgency into evenly spaced bands across the uint16 range.
273
+ # The priority is queued and applied on the MsQuic event thread.
274
+ def apply_stream_priority(stream, priority)
275
+ handle = stream.respond_to?(:stream_handle) ? stream.stream_handle : nil
276
+ return unless handle
277
+ quic_priority = (7 - priority.urgency) * 0x2000
278
+ Quicsilver.set_stream_priority(handle, quic_priority)
279
+ rescue => e
280
+ Quicsilver.logger.debug("Failed to set stream priority: #{e.message}")
281
+ end
282
+
224
283
  def critical_stream?(stream_id)
225
284
  stream_id == @control_stream_id ||
226
285
  stream_id == @qpack_encoder_stream_id ||
@@ -245,11 +304,6 @@ module Quicsilver
245
304
  @streams.keys.select { |id| (id & 0x02) == 0 }.max || 0
246
305
  end
247
306
 
248
- # RFC 9114 §7.2.4.1 / §11.2.2: HTTP/2 setting identifiers forbidden in HTTP/3
249
- # 0x00 = SETTINGS_HEADER_TABLE_SIZE (reserved), 0x02-0x05 = various HTTP/2 settings
250
- # Note: 0x08 (SETTINGS_ENABLE_CONNECT_PROTOCOL) is valid in HTTP/3 per RFC 9220
251
- HTTP2_SETTINGS = [0x00, 0x02, 0x03, 0x04, 0x05].freeze
252
-
253
307
  # Frame types forbidden on the control stream
254
308
  FORBIDDEN_ON_CONTROL = [
255
309
  0x00, # DATA — request streams only
@@ -261,53 +315,28 @@ module Quicsilver
261
315
  0x09, # HTTP/2 CONTINUATION (reserved)
262
316
  ].freeze
263
317
 
264
- def parse_control_frames(data)
265
- offset = 0
266
- first_frame = !@settings_received
267
-
268
- while offset < data.bytesize
269
- frame_type, type_len = Protocol.decode_varint(data.bytes, offset)
270
- frame_length, length_len = Protocol.decode_varint(data.bytes, offset + type_len)
271
- break if type_len == 0 || length_len == 0
272
-
273
- if first_frame && frame_type != Protocol::FRAME_SETTINGS
274
- raise Protocol::FrameError.new("First frame on control stream must be SETTINGS", error_code: Protocol::H3_MISSING_SETTINGS)
275
- end
276
- first_frame = false
277
-
278
- if FORBIDDEN_ON_CONTROL.include?(frame_type)
279
- raise Protocol::FrameError, "Frame type 0x#{frame_type.to_s(16)} not allowed on control stream"
280
- end
318
+ def on_settings_received(settings)
319
+ @settings.merge!(settings)
320
+ end
281
321
 
282
- if frame_type == Protocol::FRAME_SETTINGS
283
- raise Protocol::FrameError, "Duplicate SETTINGS frame on control stream" if @settings_received
284
- parse_settings(data[offset + type_len + length_len, frame_length])
285
- @settings_received = true
286
- end
322
+ def handle_control_frame(type, payload)
323
+ if FORBIDDEN_ON_CONTROL.include?(type)
324
+ raise Protocol::FrameError, "Frame type 0x#{type.to_s(16)} not allowed on control stream"
325
+ end
287
326
 
288
- offset += type_len + length_len + frame_length
327
+ if type == Protocol::FRAME_PRIORITY_UPDATE
328
+ parse_priority_update(payload)
289
329
  end
290
330
  end
291
331
 
292
- def parse_settings(payload)
293
- offset = 0
294
- seen = Set.new
295
- while offset < payload.bytesize
296
- id, id_len = Protocol.decode_varint(payload.bytes, offset)
297
- value, value_len = Protocol.decode_varint(payload.bytes, offset + id_len)
298
- break if id_len == 0 || value_len == 0
299
-
300
- if HTTP2_SETTINGS.include?(id)
301
- raise Protocol::FrameError.new("HTTP/2 setting identifier 0x#{id.to_s(16)} not allowed in HTTP/3", error_code: Protocol::H3_SETTINGS_ERROR)
302
- end
303
-
304
- raise Protocol::FrameError, "Duplicate setting identifier 0x#{id.to_s(16)}" if seen.include?(id)
305
- seen.add(id)
306
-
307
- @settings[id] = value
308
- offset += id_len + value_len
309
- end
332
+ # RFC 9218 §7: Parse PRIORITY_UPDATE frame.
333
+ # Payload is a stream ID varint followed by a Priority Field Value string.
334
+ def parse_priority_update(payload)
335
+ stream_id, consumed = Protocol.decode_varint(payload.bytes, 0)
336
+ priority_value = payload[consumed..]
337
+ @stream_priorities[stream_id] = Protocol::Priority.parse(priority_value)
310
338
  end
339
+
311
340
  # RFC 9204 §4.1.3: Validate QPACK encoder stream instructions.
312
341
  # We advertise QPACK_MAX_TABLE_CAPACITY = 0, so any Set Dynamic Table Capacity
313
342
  # instruction with value > 0 is an error.
@@ -1,3 +1,3 @@
1
1
  module Quicsilver
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/quicsilver.rb CHANGED
@@ -3,15 +3,21 @@
3
3
  require "logger"
4
4
  require_relative "quicsilver/version"
5
5
 
6
- # Protocol layer (pure HTTP/3 codec)
6
+ # Protocol layer
7
7
  require_relative "quicsilver/protocol/frames"
8
+ require_relative "quicsilver/protocol/priority"
8
9
  require_relative "quicsilver/protocol/qpack/encoder"
9
10
  require_relative "quicsilver/protocol/request_parser"
10
11
  require_relative "quicsilver/protocol/request_encoder"
11
12
  require_relative "quicsilver/protocol/response_parser"
12
13
  require_relative "quicsilver/protocol/response_encoder"
14
+ require_relative "quicsilver/protocol/stream_input"
15
+ require_relative "quicsilver/protocol/stream_output"
16
+ require_relative "quicsilver/protocol/adapter"
17
+ require_relative "quicsilver/protocol/control_stream_parser"
18
+ require "protocol/rack"
13
19
 
14
- # Transport layer (QUIC primitives)
20
+ # Transport layer
15
21
  require_relative "quicsilver/transport/stream"
16
22
  require_relative "quicsilver/transport/stream_event"
17
23
  require_relative "quicsilver/transport/inbound_stream"
@@ -27,10 +33,17 @@ require_relative "quicsilver/server/server"
27
33
 
28
34
  # Client
29
35
  require_relative "quicsilver/client/request"
36
+ require_relative "quicsilver/client/connection_pool"
30
37
  require_relative "quicsilver/client/client"
31
38
 
32
39
  # C extension
33
- require_relative "quicsilver/quicsilver"
40
+ # Load precompiled binary if available, fall back to native extension
41
+ begin
42
+ ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
43
+ require_relative "quicsilver/#{ruby_version}/quicsilver"
44
+ rescue LoadError
45
+ require_relative "quicsilver/quicsilver"
46
+ end
34
47
 
35
48
  # Rackup handler
36
49
  require_relative "rackup/handler/quicsilver"
@@ -59,4 +72,14 @@ module Quicsilver
59
72
  end
60
73
  end
61
74
  end
75
+
76
+ # Release pooled client connections on process exit.
77
+ # Closes connection handles so the OS doesn't leak UDP sockets.
78
+ # MsQuic itself is cleaned up by the OS when the process exits.
79
+ at_exit do
80
+ begin
81
+ Client.close_pool
82
+ rescue StandardError # rubocop:disable Lint/SuppressedException
83
+ end
84
+ end
62
85
  end
data/quicsilver.gemspec CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = %q{HTTP/3 server implementation for Ruby}
12
12
  spec.description = %q{HTTP/3 server implementation for Ruby}
13
13
  spec.homepage = "https://github.com/hahmed/quicsilver"
14
+ spec.license = "MIT"
14
15
 
15
16
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
17
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -28,12 +29,16 @@ Gem::Specification.new do |spec|
28
29
  # Specify which files should be added to the gem when it is released.
29
30
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
30
31
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
31
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
32
+ files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|vendor)/}) }
33
+ # Include precompiled binaries if present
34
+ files += Dir["lib/quicsilver/libmsquic*"]
35
+ files += Dir["lib/quicsilver/quicsilver.{bundle,so}"]
36
+ files
32
37
  end
33
38
  spec.bindir = "exe"
34
39
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
40
  spec.require_paths = ["lib"]
36
- spec.required_ruby_version = ">= 3.2.0"
41
+ spec.required_ruby_version = ">= 3.4.0"
37
42
 
38
43
  spec.extensions = ['ext/quicsilver/extconf.rb']
39
44
 
@@ -44,6 +49,9 @@ Gem::Specification.new do |spec|
44
49
  spec.add_development_dependency "minitest", "~> 5.0"
45
50
  spec.add_development_dependency "minitest-focus", "~> 1.3"
46
51
  spec.add_development_dependency "benchmark-ips", "~> 2.12"
52
+ spec.add_dependency "protocol-http", "~> 0.49"
53
+ spec.add_dependency "protocol-rack", "~> 0.22"
54
+ spec.add_dependency "console" # TODO: drop when protocol-rack declares this in its gemspec
47
55
  spec.add_dependency "logger"
48
56
  spec.add_dependency "localhost", "~> 1.6"
49
57
  spec.add_dependency "rack", "~> 3.0"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quicsilver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Haroon Ahmed
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-03-23 00:00:00.000000000 Z
10
+ date: 2026-04-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bundler
@@ -107,6 +107,48 @@ dependencies:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
109
  version: '2.12'
110
+ - !ruby/object:Gem::Dependency
111
+ name: protocol-http
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.49'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.49'
124
+ - !ruby/object:Gem::Dependency
125
+ name: protocol-rack
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '0.22'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.22'
138
+ - !ruby/object:Gem::Dependency
139
+ name: console
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
110
152
  - !ruby/object:Gem::Dependency
111
153
  name: logger
112
154
  requirement: !ruby/object:Gem::Requirement
@@ -172,6 +214,7 @@ extensions:
172
214
  extra_rdoc_files: []
173
215
  files:
174
216
  - ".github/workflows/ci.yml"
217
+ - ".github/workflows/cibuildgem.yaml"
175
218
  - ".gitignore"
176
219
  - ".gitmodules"
177
220
  - ".ruby-version"
@@ -190,16 +233,33 @@ files:
190
233
  - bin/console
191
234
  - bin/setup
192
235
  - examples/README.md
236
+ - examples/benchmark.rb
237
+ - examples/connection_pool_demo.rb
238
+ - examples/example_helper.rb
239
+ - examples/falcon_middleware.rb
240
+ - examples/feature_demo.rb
241
+ - examples/grpc_style.rb
193
242
  - examples/minimal_http3_server.rb
243
+ - examples/priorities.rb
244
+ - examples/protocol_http_server.rb
194
245
  - examples/rack_http3_server.rb
195
- - examples/setup_certs.sh
246
+ - examples/rails_feature_test.rb
196
247
  - examples/simple_client_test.rb
248
+ - examples/streaming_sse.rb
249
+ - examples/trailers.rb
197
250
  - ext/quicsilver/extconf.rb
198
251
  - ext/quicsilver/quicsilver.c
199
252
  - lib/quicsilver.rb
200
253
  - lib/quicsilver/client/client.rb
254
+ - lib/quicsilver/client/connection_pool.rb
201
255
  - lib/quicsilver/client/request.rb
256
+ - lib/quicsilver/libmsquic.2.dylib
257
+ - lib/quicsilver/protocol/adapter.rb
258
+ - lib/quicsilver/protocol/control_stream_parser.rb
259
+ - lib/quicsilver/protocol/frame_parser.rb
260
+ - lib/quicsilver/protocol/frame_reader.rb
202
261
  - lib/quicsilver/protocol/frames.rb
262
+ - lib/quicsilver/protocol/priority.rb
203
263
  - lib/quicsilver/protocol/qpack/decoder.rb
204
264
  - lib/quicsilver/protocol/qpack/encoder.rb
205
265
  - lib/quicsilver/protocol/qpack/header_block_decoder.rb
@@ -208,6 +268,9 @@ files:
208
268
  - lib/quicsilver/protocol/request_parser.rb
209
269
  - lib/quicsilver/protocol/response_encoder.rb
210
270
  - lib/quicsilver/protocol/response_parser.rb
271
+ - lib/quicsilver/protocol/stream_input.rb
272
+ - lib/quicsilver/protocol/stream_output.rb
273
+ - lib/quicsilver/quicsilver.bundle
211
274
  - lib/quicsilver/server/listener_data.rb
212
275
  - lib/quicsilver/server/request_handler.rb
213
276
  - lib/quicsilver/server/request_registry.rb
@@ -222,7 +285,8 @@ files:
222
285
  - lib/rackup/handler/quicsilver.rb
223
286
  - quicsilver.gemspec
224
287
  homepage: https://github.com/hahmed/quicsilver
225
- licenses: []
288
+ licenses:
289
+ - MIT
226
290
  metadata:
227
291
  allowed_push_host: https://rubygems.org
228
292
  homepage_uri: https://github.com/hahmed/quicsilver
@@ -235,7 +299,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
235
299
  requirements:
236
300
  - - ">="
237
301
  - !ruby/object:Gem::Version
238
- version: 3.2.0
302
+ version: 3.4.0
239
303
  required_rubygems_version: !ruby/object:Gem::Requirement
240
304
  requirements:
241
305
  - - ">="
@@ -1,57 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Quicsilver Certificate Setup
4
- # This script generates self-signed certificates for QUIC testing
5
-
6
- echo "🔐 Generating certificates for Quicsilver QUIC testing..."
7
-
8
- # Create certs directory if it doesn't exist
9
- mkdir -p ../certs
10
- cd ../certs
11
-
12
- # Create OpenSSL config with proper TLS server extensions
13
- cat > openssl.conf << 'EOF'
14
- [req]
15
- distinguished_name = req_distinguished_name
16
- req_extensions = v3_req
17
- prompt = no
18
-
19
- [req_distinguished_name]
20
- CN = localhost
21
- O = QuicsilverTest
22
- C = US
23
-
24
- [v3_req]
25
- keyUsage = keyEncipherment, dataEncipherment
26
- extendedKeyUsage = serverAuth
27
- subjectAltName = @alt_names
28
-
29
- [alt_names]
30
- DNS.1 = localhost
31
- IP.1 = 127.0.0.1
32
- EOF
33
-
34
- # Generate private key and certificate
35
- echo "📝 Generating private key..."
36
- openssl genrsa -out server.key 2048
37
-
38
- echo "📜 Generating certificate..."
39
- openssl req -new -x509 -key server.key -out server.crt -days 365 \
40
- -config openssl.conf -extensions v3_req
41
-
42
- # Create PKCS#12 format (optional, for other tools)
43
- echo "📦 Creating PKCS#12 bundle..."
44
- openssl pkcs12 -export -out server.p12 -inkey server.key -in server.crt \
45
- -passout pass:password -name "localhost"
46
-
47
- # Clean up
48
- rm openssl.conf
49
-
50
- echo "✅ Certificate files created:"
51
- echo " 📄 server.crt - Certificate file"
52
- echo " 🔑 server.key - Private key file"
53
- echo " 📦 server.p12 - PKCS#12 bundle (password: 'password')"
54
- echo ""
55
- echo "🚀 You can now run:"
56
- echo " Terminal 1: ruby examples/test_server.rb"
57
- echo " Terminal 2: ruby examples/test_connection.rb"