quicsilver 0.5.0-x86_64-linux

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +49 -0
  3. data/.github/workflows/cibuildgem.yaml +117 -0
  4. data/.gitignore +21 -0
  5. data/.gitmodules +3 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +175 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +67 -0
  10. data/LICENSE +21 -0
  11. data/README.md +261 -0
  12. data/Rakefile +130 -0
  13. data/benchmarks/baseline.rb +175 -0
  14. data/benchmarks/compare.rb +398 -0
  15. data/benchmarks/components.rb +191 -0
  16. data/benchmarks/helpers.rb +162 -0
  17. data/benchmarks/latency.rb +128 -0
  18. data/benchmarks/loss.rb +193 -0
  19. data/benchmarks/mobile.rb +125 -0
  20. data/bin/console +14 -0
  21. data/bin/setup +8 -0
  22. data/docs/connection_identity.md +141 -0
  23. data/examples/README.md +58 -0
  24. data/examples/benchmark.rb +191 -0
  25. data/examples/connection_pool_demo.rb +47 -0
  26. data/examples/example_helper.rb +69 -0
  27. data/examples/falcon_middleware.rb +44 -0
  28. data/examples/feature_demo.rb +125 -0
  29. data/examples/grpc_style.rb +97 -0
  30. data/examples/latency_aware_routing.rb +382 -0
  31. data/examples/lifecycle_drain.rb +264 -0
  32. data/examples/minimal_http3_server.rb +14 -0
  33. data/examples/priorities.rb +60 -0
  34. data/examples/protocol_http_server.rb +31 -0
  35. data/examples/rack_http3_server.rb +42 -0
  36. data/examples/rails_feature_test.rb +280 -0
  37. data/examples/server_stats.rb +117 -0
  38. data/examples/simple_client_test.rb +26 -0
  39. data/examples/streaming_sse.rb +33 -0
  40. data/examples/trailers.rb +69 -0
  41. data/examples/webtransport_smoke.rb +546 -0
  42. data/examples/worker_load_balancing.rb +425 -0
  43. data/ext/quicsilver/extconf.rb +124 -0
  44. data/ext/quicsilver/include/msquic.h +2226 -0
  45. data/ext/quicsilver/include/msquic_posix.h +696 -0
  46. data/ext/quicsilver/include/quic_sal_stub.h +314 -0
  47. data/ext/quicsilver/msquic_patch.rb +68 -0
  48. data/ext/quicsilver/patches/reliable-reset.patch +723 -0
  49. data/ext/quicsilver/patches/stream-final-size.patch +47 -0
  50. data/ext/quicsilver/quicsilver.c +2741 -0
  51. data/lib/quicsilver/3.4/quicsilver.so +0 -0
  52. data/lib/quicsilver/4.0/quicsilver.so +0 -0
  53. data/lib/quicsilver/client/client.rb +708 -0
  54. data/lib/quicsilver/client/connection_pool.rb +202 -0
  55. data/lib/quicsilver/client/request.rb +190 -0
  56. data/lib/quicsilver/errors.rb +39 -0
  57. data/lib/quicsilver/libmsquic.so.2 +0 -0
  58. data/lib/quicsilver/protocol/adapter.rb +196 -0
  59. data/lib/quicsilver/protocol/capsule.rb +38 -0
  60. data/lib/quicsilver/protocol/connect_stream_decoder.rb +75 -0
  61. data/lib/quicsilver/protocol/control_stream_parser.rb +117 -0
  62. data/lib/quicsilver/protocol/datagram.rb +40 -0
  63. data/lib/quicsilver/protocol/frame_parser.rb +141 -0
  64. data/lib/quicsilver/protocol/frame_reader.rb +84 -0
  65. data/lib/quicsilver/protocol/frames.rb +454 -0
  66. data/lib/quicsilver/protocol/priority.rb +65 -0
  67. data/lib/quicsilver/protocol/qpack/decoder.rb +127 -0
  68. data/lib/quicsilver/protocol/qpack/encoder.rb +217 -0
  69. data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +140 -0
  70. data/lib/quicsilver/protocol/qpack/huffman.rb +459 -0
  71. data/lib/quicsilver/protocol/request_encoder.rb +50 -0
  72. data/lib/quicsilver/protocol/request_parser.rb +289 -0
  73. data/lib/quicsilver/protocol/response_encoder.rb +107 -0
  74. data/lib/quicsilver/protocol/response_parser.rb +147 -0
  75. data/lib/quicsilver/protocol/stream_input.rb +98 -0
  76. data/lib/quicsilver/protocol/stream_output.rb +59 -0
  77. data/lib/quicsilver/protocol/unidirectional_stream.rb +12 -0
  78. data/lib/quicsilver/protocol/web_transport.rb +214 -0
  79. data/lib/quicsilver/rack/context.rb +33 -0
  80. data/lib/quicsilver/response.rb +40 -0
  81. data/lib/quicsilver/server/listener_data.rb +14 -0
  82. data/lib/quicsilver/server/rack_adapter.rb +77 -0
  83. data/lib/quicsilver/server/receive_queue.rb +106 -0
  84. data/lib/quicsilver/server/request_handler.rb +149 -0
  85. data/lib/quicsilver/server/request_registry.rb +63 -0
  86. data/lib/quicsilver/server/scheduler.rb +50 -0
  87. data/lib/quicsilver/server/schedulers/fiber_scheduler.rb +79 -0
  88. data/lib/quicsilver/server/schedulers/thread_scheduler.rb +76 -0
  89. data/lib/quicsilver/server/server.rb +1080 -0
  90. data/lib/quicsilver/server/web_transport_flow_control.rb +270 -0
  91. data/lib/quicsilver/server/web_transport_manager.rb +281 -0
  92. data/lib/quicsilver/server/web_transport_registry.rb +40 -0
  93. data/lib/quicsilver/server/web_transport_session.rb +812 -0
  94. data/lib/quicsilver/server/web_transport_stream.rb +326 -0
  95. data/lib/quicsilver/transport/configuration.rb +234 -0
  96. data/lib/quicsilver/transport/connection.rb +594 -0
  97. data/lib/quicsilver/transport/connection_stats.rb +53 -0
  98. data/lib/quicsilver/transport/event_loop.rb +74 -0
  99. data/lib/quicsilver/transport/inbound_stream.rb +53 -0
  100. data/lib/quicsilver/transport/stream.rb +43 -0
  101. data/lib/quicsilver/transport/stream_event.rb +49 -0
  102. data/lib/quicsilver/transport/stream_id.rb +26 -0
  103. data/lib/quicsilver/version.rb +3 -0
  104. data/lib/quicsilver.rb +96 -0
  105. data/lib/rackup/handler/quicsilver.rb +96 -0
  106. data/quicsilver.gemspec +66 -0
  107. metadata +362 -0
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Transport
5
+ # Two threads: one drives MsQuic, one delivers its events to Ruby.
6
+ #
7
+ # The poll thread runs Quicsilver.poll, which releases the GVL for the
8
+ # whole MsQuic iteration: decryption, loss recovery, stream reassembly and
9
+ # the callbacks that queue events for Ruby all happen without the lock.
10
+ # The dispatcher thread runs Quicsilver.dispatch_events, which waits on
11
+ # that queue without the GVL, then takes it to call Server.handle_stream
12
+ # (or Client#handle_stream_event) for each event in order.
13
+ #
14
+ # Before, a single thread did both, and every byte MsQuic processed was
15
+ # processed while holding the GVL, because the callback at the end of it
16
+ # called into Ruby. Splitting them lets the datapath run in parallel with
17
+ # the dispatcher and the worker pool.
18
+ #
19
+ # Event order is preserved: the queue is FIFO and there is one consumer.
20
+ class EventLoop
21
+ # Events delivered per GVL hold. Bounds how long a batch runs; the
22
+ # dispatcher yields early only when the server's admission queue is
23
+ # under pressure (Server#enqueue_work). QUICSILVER_DISPATCH_BATCH
24
+ # overrides it for measurement.
25
+ DISPATCH_BATCH = Integer(ENV.fetch("QUICSILVER_DISPATCH_BATCH", "64"))
26
+ DISPATCH_WAIT_MS = 1000
27
+
28
+ def initialize
29
+ @running = false
30
+ @thread = nil
31
+ @dispatcher = nil
32
+ @mutex = Mutex.new
33
+ end
34
+
35
+ def start
36
+ @mutex.synchronize do
37
+ return if @running
38
+
39
+ @running = true
40
+ Quicsilver.open_event_queue
41
+ @dispatcher = Thread.new do
42
+ Thread.current.name = "quicsilver-dispatch"
43
+ loop do
44
+ delivered = Quicsilver.dispatch_events(DISPATCH_BATCH, DISPATCH_WAIT_MS)
45
+ break if delivered.nil? # queue closed and drained
46
+ end
47
+ end
48
+ @thread = Thread.new do
49
+ Thread.current.name = "quicsilver-poll"
50
+ Quicsilver.poll while @running
51
+ end
52
+ end
53
+ end
54
+
55
+ # Stop the poll thread first so nothing else is queued, then close the
56
+ # queue so the dispatcher exits once it has delivered what is there.
57
+ def stop
58
+ @running = false
59
+ Quicsilver.wake
60
+ @thread&.join(2)
61
+ Quicsilver.close_event_queue
62
+ @dispatcher&.join(2)
63
+ end
64
+
65
+ def join
66
+ @thread&.join
67
+ end
68
+ end
69
+ end
70
+
71
+ def self.event_loop
72
+ @event_loop ||= Transport::EventLoop.new.tap(&:start)
73
+ end
74
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Transport
5
+ class InboundStream
6
+ attr_reader :stream_id, :is_unidirectional, :buffer
7
+ attr_accessor :stream_handle
8
+
9
+ def initialize(stream_id, is_unidirectional: nil)
10
+ @stream_id = stream_id
11
+ @is_unidirectional = is_unidirectional.nil? ? !bidirectional? : is_unidirectional
12
+ @buffer = StringIO.new.tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
13
+ @stream_handle = nil
14
+ end
15
+
16
+ def bidirectional?
17
+ StreamId.bidirectional?(stream_id)
18
+ end
19
+
20
+ def writable?
21
+ !stream_handle.nil?
22
+ end
23
+
24
+ def append_data(data)
25
+ @buffer.write(data)
26
+ end
27
+
28
+ def data
29
+ @buffer.string
30
+ end
31
+
32
+ def send(data, fin: false)
33
+ return unless writable?
34
+ Quicsilver.send_stream(@stream_handle, data, fin)
35
+ end
36
+
37
+ def reset(error_code = Protocol::H3_REQUEST_CANCELLED)
38
+ return unless writable?
39
+ Quicsilver.stream_reset(@stream_handle, error_code)
40
+ end
41
+
42
+ def stop_sending(error_code = Protocol::H3_REQUEST_CANCELLED)
43
+ return unless writable?
44
+ Quicsilver.stream_stop_sending(@stream_handle, error_code)
45
+ end
46
+
47
+ def abort(error_code = Protocol::H3_REQUEST_CANCELLED)
48
+ return unless writable?
49
+ Quicsilver.stream_abort(@stream_handle, error_code)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Transport
5
+ # The handle is an opaque native registry token, not a pointer.
6
+ class Stream
7
+ attr_reader :handle
8
+
9
+ def initialize(handle)
10
+ @handle = handle
11
+ end
12
+
13
+ # Returns nil until asynchronous stream startup completes, or after shutdown.
14
+ def stream_id
15
+ Quicsilver.get_stream_id(@handle)
16
+ end
17
+
18
+ def send(data, fin: false)
19
+ Quicsilver.send_stream(@handle, data, fin)
20
+ end
21
+
22
+ def reliable_offset=(offset)
23
+ Quicsilver.set_stream_reliable_offset(@handle, offset)
24
+ end
25
+
26
+ def grant_receive_credit(bytes, chunks)
27
+ Quicsilver.grant_stream_receive_credit(@handle, bytes, chunks)
28
+ end
29
+
30
+ def reset(error_code = Protocol::H3_REQUEST_CANCELLED)
31
+ Quicsilver.stream_reset(@handle, error_code)
32
+ end
33
+
34
+ def stop_sending(error_code = Protocol::H3_REQUEST_CANCELLED)
35
+ Quicsilver.stream_stop_sending(@handle, error_code)
36
+ end
37
+
38
+ def abort(error_code = Protocol::H3_REQUEST_CANCELLED)
39
+ Quicsilver.stream_abort(@handle, error_code)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Transport
5
+ # Parses the binary data packed by the C extension for stream completion events.
6
+ # C packs events as:
7
+ # RECEIVE_FIN: [stream_handle(8)][payload...]
8
+ # STREAM_RESET: [stream_handle(8)][error_code(8)][final_size(8)]
9
+ # STOP_SENDING: [stream_handle(8)][error_code(8)]
10
+ class StreamEvent
11
+ # A reset stream's final size is unknown to MsQuic until RESET_STREAM or
12
+ # FIN settles it; the C extension reports that as this sentinel so it
13
+ # never has to be confused with a real length.
14
+ FINAL_SIZE_UNKNOWN = (2**64) - 1
15
+
16
+ attr_reader :handle, :data, :error_code
17
+
18
+ # The total number of bytes the peer sent on this stream, from the
19
+ # RESET_STREAM frame (RFC 9000 4.5). nil when it is not settled or the
20
+ # event does not carry one. WebTransport session flow control needs it to
21
+ # charge the bytes a reset discarded (draft-ietf-webtrans-http3-16 5.4).
22
+ attr_reader :final_size
23
+
24
+ def initialize(raw_data, event_type)
25
+ @handle = raw_data[0, 8].unpack1("Q")
26
+ remaining = raw_data[8..] || "".b
27
+
28
+ case event_type
29
+ when "RECEIVE", "RECEIVE_FIN"
30
+ @data = remaining
31
+ when "STREAM_RESET", "STOP_SENDING"
32
+ @error_code = remaining.unpack1("Q")
33
+ @final_size = parse_final_size(remaining)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ # Older packings carry only the error code, so a missing final size is
40
+ # not an error.
41
+ def parse_final_size(remaining)
42
+ return unless remaining.bytesize >= 16
43
+
44
+ value = remaining.unpack1("@8Q")
45
+ value unless value == FINAL_SIZE_UNKNOWN
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Transport
5
+ module StreamId
6
+ INITIATOR_MASK = 0x01
7
+ DIRECTION_MASK = 0x02
8
+ REQUEST_STREAM_MASK = INITIATOR_MASK | DIRECTION_MASK
9
+
10
+ module_function
11
+
12
+ def bidirectional?(stream_id)
13
+ (stream_id & DIRECTION_MASK).zero?
14
+ end
15
+
16
+ def unidirectional?(stream_id)
17
+ !bidirectional?(stream_id)
18
+ end
19
+
20
+ # HTTP/3 requests run on client-initiated bidirectional QUIC streams.
21
+ def request?(stream_id)
22
+ (stream_id & REQUEST_STREAM_MASK).zero?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Quicsilver
2
+ VERSION = "0.5.0"
3
+ end
data/lib/quicsilver.rb ADDED
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require_relative "quicsilver/version"
5
+ require_relative "quicsilver/errors"
6
+ require_relative "quicsilver/response"
7
+
8
+ # Protocol layer
9
+ require_relative "quicsilver/protocol/frames"
10
+ require_relative "quicsilver/protocol/datagram"
11
+ require_relative "quicsilver/protocol/capsule"
12
+ require_relative "quicsilver/protocol/web_transport"
13
+ require_relative "quicsilver/protocol/unidirectional_stream"
14
+ require_relative "quicsilver/protocol/priority"
15
+ require_relative "quicsilver/protocol/qpack/encoder"
16
+ require_relative "quicsilver/protocol/request_parser"
17
+ require_relative "quicsilver/protocol/request_encoder"
18
+ require_relative "quicsilver/protocol/response_parser"
19
+ require_relative "quicsilver/protocol/response_encoder"
20
+ require_relative "quicsilver/protocol/stream_input"
21
+ require_relative "quicsilver/protocol/stream_output"
22
+ require_relative "quicsilver/protocol/adapter"
23
+ require_relative "quicsilver/protocol/control_stream_parser"
24
+ require "protocol/rack"
25
+
26
+ # Rack integration
27
+ require_relative "quicsilver/rack/context"
28
+
29
+ # Transport layer
30
+ require_relative "quicsilver/transport/stream_id"
31
+ require_relative "quicsilver/transport/stream"
32
+ require_relative "quicsilver/transport/stream_event"
33
+ require_relative "quicsilver/transport/inbound_stream"
34
+ require_relative "quicsilver/transport/event_loop"
35
+ require_relative "quicsilver/transport/configuration"
36
+ require_relative "quicsilver/transport/connection"
37
+ require_relative "quicsilver/transport/connection_stats"
38
+
39
+ # Server
40
+ require_relative "quicsilver/server/listener_data"
41
+ require_relative "quicsilver/server/request_registry"
42
+ require_relative "quicsilver/server/request_handler"
43
+ require_relative "quicsilver/server/rack_adapter"
44
+ require_relative "quicsilver/server/web_transport_flow_control"
45
+ require_relative "quicsilver/server/web_transport_manager"
46
+ require_relative "quicsilver/server/server"
47
+
48
+ # Client
49
+ require_relative "quicsilver/client/request"
50
+ require_relative "quicsilver/client/connection_pool"
51
+ require_relative "quicsilver/client/client"
52
+
53
+ # C extension
54
+ begin
55
+ ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
56
+ require "quicsilver/#{ruby_version}/quicsilver"
57
+ rescue LoadError
58
+ require "quicsilver/quicsilver"
59
+ end
60
+
61
+ # Rackup handler
62
+ require_relative "rackup/handler/quicsilver"
63
+
64
+ module Quicsilver
65
+ class << self
66
+ attr_writer :logger
67
+
68
+ def stream_abort(handle, error_code)
69
+ receiving_stopped = stream_stop_sending(handle, error_code)
70
+ sending_reset = stream_reset(handle, error_code)
71
+ receiving_stopped || sending_reset
72
+ end
73
+
74
+ def logger
75
+ @logger ||= default_logger
76
+ end
77
+
78
+ private
79
+
80
+ def default_logger
81
+ Logger.new($stdout, level: Logger::INFO).tap do |log|
82
+ log.progname = "Quicsilver"
83
+ end
84
+ end
85
+ end
86
+
87
+ # Release pooled client connections on process exit.
88
+ # Closes connection handles so the OS doesn't leak UDP sockets.
89
+ # MsQuic itself is cleaned up by the OS when the process exits.
90
+ at_exit do
91
+ begin
92
+ Client.close_pool
93
+ rescue StandardError # rubocop:disable Lint/SuppressedException
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rackup/handler"
4
+
5
+ module Quicsilver
6
+ module RackHandler
7
+ DEFAULT_OPTIONS = {
8
+ Host: "0.0.0.0",
9
+ Port: 4433,
10
+ }
11
+
12
+ class << self
13
+ def run(app, **options)
14
+ normalized_options = {
15
+ host: options[:Host] || options[:host] || DEFAULT_OPTIONS[:Host],
16
+ port: (options[:Port] || options[:port] || DEFAULT_OPTIONS[:Port]).to_i,
17
+ }
18
+
19
+ cert_file, key_file = certificate_paths(options)
20
+ config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
21
+
22
+ server = ::Quicsilver::Server.new(
23
+ normalized_options[:port],
24
+ address: normalized_options[:host],
25
+ app: app,
26
+ server_configuration: config
27
+ )
28
+
29
+ yield server if block_given?
30
+
31
+ server.start
32
+ end
33
+
34
+ def valid_options
35
+ {
36
+ "Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
37
+ "Port=PORT" => "Port to listen on (default: 4433)",
38
+ "cert_file=PATH" => "Path to TLS certificate file (required in production)",
39
+ "key_file=PATH" => "Path to TLS key file (required in production)"
40
+ }
41
+ end
42
+
43
+ private
44
+
45
+ def certificate_paths(options)
46
+ cert_file = options[:cert_file]
47
+ key_file = options[:key_file]
48
+
49
+ if cert_file.nil? && key_file.nil?
50
+ if production?(options)
51
+ raise ArgumentError, "cert_file and key_file are required in production"
52
+ else
53
+ localhost_certificate_paths
54
+ end
55
+ elsif cert_file.nil? || key_file.nil?
56
+ raise ArgumentError, "cert_file and key_file must be provided together"
57
+ else
58
+ [cert_file, key_file]
59
+ end
60
+ end
61
+
62
+ def production?(options)
63
+ env = options[:environment] || ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
64
+ env == "production"
65
+ end
66
+
67
+ def localhost_certificate_paths
68
+ require "localhost/authority"
69
+ authority = Localhost::Authority.fetch
70
+ cert_file = authority.certificate_path
71
+ key_file = authority.key_path
72
+
73
+ Quicsilver.logger.info("Using auto-generated certificates for localhost")
74
+ Quicsilver.logger.info(" Cert: #{cert_file}")
75
+ Quicsilver.logger.info(" Key: #{key_file}")
76
+
77
+ [cert_file, key_file]
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ module Rackup
84
+ module Handler
85
+ module Quicsilver
86
+ def self.run(app, **options, &block)
87
+ ::Quicsilver::RackHandler.run(app, **options, &block)
88
+ end
89
+
90
+ def self.valid_options
91
+ ::Quicsilver::RackHandler.valid_options
92
+ end
93
+ end
94
+ register :quicsilver, Quicsilver
95
+ end
96
+ end
@@ -0,0 +1,66 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "quicsilver/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "quicsilver"
7
+ spec.version = Quicsilver::VERSION
8
+ spec.authors = ["Haroon Ahmed"]
9
+ spec.email = ["haroon.ahmed25@gmail.com"]
10
+
11
+ spec.summary = %q{HTTP/3 server implementation for Ruby}
12
+ spec.description = %q{HTTP/3 server implementation for Ruby}
13
+ spec.homepage = "https://github.com/hahmed/quicsilver"
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
20
+
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = "https://github.com/hahmed/quicsilver"
23
+ spec.metadata["changelog_uri"] = "https://github.com/hahmed/quicsilver/blob/main/CHANGELOG.md"
24
+ else
25
+ raise "RubyGems 2.0 or newer is required to protect against " \
26
+ "public gem pushes."
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
+ files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|vendor)/}) }
33
+
34
+ if ENV["RELEASE"]
35
+ if RUBY_PLATFORM.include?("darwin")
36
+ files += Dir["lib/quicsilver/libmsquic*.dylib"]
37
+ elsif RUBY_PLATFORM.include?("linux")
38
+ files += Dir["lib/quicsilver/libmsquic.so*"]
39
+ end
40
+ end
41
+
42
+ files
43
+ end
44
+ spec.bindir = "exe"
45
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
46
+ spec.require_paths = ["lib"]
47
+ spec.required_ruby_version = ">= 3.4.0"
48
+
49
+ spec.extensions = ['ext/quicsilver/extconf.rb']
50
+
51
+ spec.add_development_dependency "bundler", "~> 2.0"
52
+ spec.add_development_dependency "rake", "~> 13.0"
53
+ spec.add_development_dependency 'rake-compiler', '~> 1.2'
54
+ spec.add_development_dependency 'rake-compiler-dock', '~> 1.3'
55
+ spec.add_development_dependency "cibuildgem"
56
+ spec.add_development_dependency "minitest", "~> 5.0"
57
+ spec.add_development_dependency "minitest-focus", "~> 1.3"
58
+ spec.add_development_dependency "benchmark-ips", "~> 2.12"
59
+ spec.add_dependency "protocol-http", "~> 0.49"
60
+ spec.add_dependency "protocol-rack", "~> 0.22"
61
+ spec.add_dependency "console" # TODO: drop when protocol-rack declares this in its gemspec
62
+ spec.add_dependency "logger"
63
+ spec.add_dependency "localhost", "~> 1.6"
64
+ spec.add_dependency "rack", "~> 3.0"
65
+ spec.add_dependency "rackup", "~> 2.0"
66
+ end