quicsilver 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5e879e4531621883da97721256a2b0db478ba995d8bf54c93ddf959fca9cd36d
4
+ data.tar.gz: 579023d40239d8c7a77671be44fb37265d735f864a13072887598406908c0aae
5
+ SHA512:
6
+ metadata.gz: 25c070017404e15d673cfcb267ef90722dd255af0c68dd3488f1065ebe8fcc84614054b7ae3b5b0e2cd1bc74ca725a7ddd4e0500b80f849580ebef3306705443
7
+ data.tar.gz: 61f273afb32f2a45d07bc2f634a8f6d52973654b4e874ed01dd87a674126046f2e332741ca464b6f41a025e206064172a9614c602bb7285dab0b68b6efa7d09a
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /vendor/*
10
+ /certs/*
11
+ *.bundle
12
+ lib/quicsilver/quicsilver.bundle
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "vendor/msquic"]
2
+ path = vendor/msquic
3
+ url = https://github.com/microsoft/msquic.git
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-10-28
9
+
10
+ ### Added
11
+ - Initial HTTP/3 server implementation using Microsoft MSQUIC
12
+ - HTTP/3 client for testing and development
13
+ - Rack support
14
+ - HTTP/3 frame encoding/decoding (DATA, HEADERS, SETTINGS)
15
+ - QPACK header compression (static table support)
16
+ - Bidirectional request/response streams
17
+ - Request body buffering for large payloads
18
+
19
+ ### Limitations
20
+ This is still in prototype, it has the following known limitations:
21
+ - No server push, GOAWAY, or trailer support
22
+ - Limited error handling
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in quicsilver.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ quicsilver (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.25.5)
10
+ rake (10.5.0)
11
+ rake-compiler (1.3.0)
12
+ rake
13
+ rake-compiler-dock (1.9.1)
14
+
15
+ PLATFORMS
16
+ arm64-darwin-23
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ bundler (~> 2.0)
21
+ minitest (~> 5.0)
22
+ quicsilver!
23
+ rake (~> 10.0)
24
+ rake-compiler (~> 1.2)
25
+ rake-compiler-dock (~> 1.3)
26
+
27
+ BUNDLED WITH
28
+ 2.6.5
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Quicsilver
2
+
3
+ HTTP/3 server for Ruby with Rack support.
4
+
5
+ Disclaimer: currenly in early prototype.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ git clone <repository>
11
+ cd quicsilver
12
+ bundle install
13
+ rake compile
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ### 1. Set up certificates
19
+
20
+ ```bash
21
+ bash examples/setup_certs.sh
22
+ ```
23
+
24
+ ### 2. Run a Rack app over HTTP/3
25
+
26
+ ```ruby
27
+ require "quicsilver"
28
+
29
+ # Define your Rack app
30
+ app = ->(env) {
31
+ path = env['PATH_INFO']
32
+
33
+ case path
34
+ when '/'
35
+ [200, {'Content-Type' => 'text/plain'}, ["Hello HTTP/3!"]]
36
+ when '/api/users'
37
+ [200, {'Content-Type' => 'application/json'}, ['{"users": ["alice", "bob"]}']]
38
+ else
39
+ [404, {'Content-Type' => 'text/plain'}, ["Not Found"]]
40
+ end
41
+ }
42
+
43
+ # Start HTTP/3 server with Rack app
44
+ server = Quicsilver::Server.new(4433, app: app)
45
+ server.start
46
+ server.wait_for_connections
47
+ ```
48
+
49
+ ### 3. Test with the client
50
+
51
+ ```bash
52
+ ruby examples/minimal_http3_client.rb
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ### Rack HTTP/3 Server
58
+
59
+ ```ruby
60
+ require "quicsilver"
61
+
62
+ app = ->(env) {
63
+ [200, {'Content-Type' => 'text/html'}, ["<h1>Hello from HTTP/3!</h1>"]]
64
+ }
65
+
66
+ server = Quicsilver::Server.new(4433, app: app)
67
+ server.start
68
+ server.wait_for_connections
69
+ ```
70
+
71
+ ### HTTP/3 Client
72
+
73
+ ```ruby
74
+ require "quicsilver"
75
+
76
+ client = Quicsilver::Client.new("127.0.0.1", 4433, unsecure: true)
77
+ client.connect
78
+
79
+ # Send HTTP/3 request
80
+ request = Quicsilver::HTTP3::RequestEncoder.new(
81
+ method: 'GET',
82
+ path: '/api/users',
83
+ authority: 'example.com'
84
+ )
85
+ client.send_data(request.encode)
86
+
87
+ client.disconnect
88
+ ```
89
+
90
+ ## Development
91
+
92
+ ```bash
93
+ # Run tests
94
+ rake test
95
+
96
+ # Build extension
97
+ rake compile
98
+
99
+ # Clean build artifacts
100
+ rake clean
101
+ ```
102
+
103
+ ## License
104
+
105
+ MIT License
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ require "rake/extensiontask"
4
+
5
+ Rake::ExtensionTask.new('quicsilver') do |ext|
6
+ ext.lib_dir = 'lib/quicsilver'
7
+ end
8
+
9
+ task :setup do
10
+ # Initialize git submodule if it doesn't exist
11
+ unless File.exist?('vendor/msquic')
12
+ sh 'git submodule add https://github.com/microsoft/msquic.git vendor/msquic'
13
+ sh 'cd vendor/msquic && git submodule update --init --recursive'
14
+ end
15
+ end
16
+
17
+ task :build_msquic => :setup do
18
+ # Build MSQUIC using CMake with proper macOS framework linking
19
+ sh 'cd vendor/msquic && cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXE_LINKER_FLAGS="-framework CoreServices" -DCMAKE_SHARED_LINKER_FLAGS="-framework CoreServices"'
20
+ sh 'cd vendor/msquic && cmake --build build --config Release'
21
+ end
22
+
23
+ task :build => [:build_msquic, :compile]
24
+
25
+ Rake::TestTask.new(:test) do |t|
26
+ t.libs << "test"
27
+ t.libs << "lib"
28
+ t.test_files = FileList["test/**/*_test.rb"]
29
+ end
30
+
31
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "quicsilver"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,105 @@
1
+ # Quicsilver Examples
2
+
3
+ This directory contains examples for testing your Ruby QUIC implementation.
4
+
5
+ ## šŸš€ Quick Start
6
+
7
+ ### 1. Generate Certificates
8
+
9
+ ```bash
10
+ cd examples
11
+ ./setup_certs.sh
12
+ ```
13
+
14
+ This creates the necessary certificate files in the `certs/` directory.
15
+
16
+ ### 2. Run the Server
17
+
18
+ ```bash
19
+ # Terminal 1
20
+ ruby examples/test_server.rb
21
+ ```
22
+
23
+ ### 3. Test the Connection
24
+
25
+ ```bash
26
+ # Terminal 2
27
+ ruby examples/test_connection.rb
28
+ ```
29
+
30
+ ## šŸ“ Files
31
+
32
+ | File | Purpose |
33
+ |------|---------|
34
+ | `setup_certs.sh` | Generate certificates for testing |
35
+ | `test_server.rb` | Ruby QUIC server |
36
+ | `test_connection.rb` | QUIC client test |
37
+
38
+ ## āœ… Expected Output
39
+
40
+ **Server:**
41
+ ```
42
+ šŸ”„ Quicsilver QUIC Server Test
43
+ ════════════════════════════════════════
44
+ šŸ“‹ Server Info:
45
+ server_id: a47271c7ae4276d5
46
+ address: 127.0.0.1
47
+ port: 4433
48
+ running: false
49
+ cert_file: /path/to/certs/server.crt
50
+ key_file: /path/to/certs/server.key
51
+ max_connections: 100
52
+
53
+ šŸš€ Starting QUIC server on 127.0.0.1:4433
54
+ āœ… QUIC server started successfully!
55
+ šŸ”— Listening for connections...
56
+ šŸŽÆ Server is running! Press Ctrl+C to stop
57
+ ```
58
+
59
+ **Client:**
60
+ ```
61
+ šŸ”— Testing QUIC Connection to 127.0.0.1:4433...
62
+
63
+ Connected to 127.0.0.1:4433
64
+ āœ… SUCCESS! Connected to QUIC server
65
+ šŸ• Connection time: 13.5ms
66
+ šŸ“Š Connected status: true
67
+ šŸ“‹ Connection info: {"connected" => true, "failed" => false, ...}
68
+
69
+ 🧪 Testing connection stability...
70
+ ā±ļø Tick 1: Connected = true
71
+ ā±ļø Tick 2: Connected = true
72
+ ā±ļø Tick 3: Connected = true
73
+ āœ… Connection test completed!
74
+ šŸ”’ Connection closed cleanly
75
+ ```
76
+
77
+ ## šŸ”§ Manual Certificate Setup
78
+
79
+ If you prefer to create certificates manually:
80
+
81
+ ```bash
82
+ mkdir -p certs
83
+ cd certs
84
+
85
+ # Generate private key
86
+ openssl genrsa -out server.key 2048
87
+
88
+ # Generate certificate with proper QUIC extensions
89
+ openssl req -new -x509 -key server.key -out server.crt -days 365 \
90
+ -subj "/CN=localhost/O=QuicsilverTest/C=US" \
91
+ -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \
92
+ -addext "extendedKeyUsage=serverAuth"
93
+ ```
94
+
95
+ ## šŸ› Troubleshooting
96
+
97
+ **Port in use error:** The server automatically cleans up port 4433 before starting.
98
+
99
+ **Connection refused:** Make sure the server is running before testing the client.
100
+
101
+ **Certificate errors:** Run `./setup_certs.sh` to regenerate certificates with proper QUIC extensions.
102
+
103
+ ---
104
+
105
+ That's it! No Docker, no external servers, just pure Ruby QUIC. šŸŽÆ
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "quicsilver"
5
+
6
+ puts "šŸ”Œ Minimal HTTP/3 Client Example"
7
+ puts "=" * 40
8
+
9
+ # Create client
10
+ client = Quicsilver::Client.new("127.0.0.1", 4433, unsecure: true)
11
+
12
+ puts "šŸ”§ Connecting to server..."
13
+ begin
14
+ client.connect
15
+ puts "āœ… Connected successfully!"
16
+ puts "šŸ“‹ Connection info: #{client.connection_info}"
17
+
18
+ # HTTP/3 requests using RequestEncoder
19
+ require_relative '../lib/quicsilver/http3/request_encoder'
20
+
21
+ request1 = Quicsilver::HTTP3::RequestEncoder.new(
22
+ method: 'GET',
23
+ path: '/api/users',
24
+ authority: 'example.com'
25
+ )
26
+ client.send_data(request1.encode)
27
+
28
+ request2 = Quicsilver::HTTP3::RequestEncoder.new(
29
+ method: 'GET',
30
+ path: '/api/posts/123',
31
+ authority: 'example.com'
32
+ )
33
+ client.send_data(request2.encode)
34
+
35
+ request3 = Quicsilver::HTTP3::RequestEncoder.new(
36
+ method: 'POST',
37
+ path: '/api/messages',
38
+ authority: 'example.com',
39
+ body: '{"text":"Hello world"}'
40
+ )
41
+ client.send_data(request3.encode)
42
+
43
+ # JSON payloads (API requests) - now as proper HTTP/3 POST requests
44
+ request4 = Quicsilver::HTTP3::RequestEncoder.new(
45
+ method: 'POST',
46
+ path: '/api/subscribe',
47
+ authority: 'example.com',
48
+ headers: { 'content-type' => 'application/json' },
49
+ body: '{"action":"subscribe","channel":"orders"}'
50
+ )
51
+ client.send_data(request4.encode)
52
+
53
+ request5 = Quicsilver::HTTP3::RequestEncoder.new(
54
+ method: 'POST',
55
+ path: '/api/update',
56
+ authority: 'example.com',
57
+ headers: { 'content-type' => 'application/json' },
58
+ body: '{"action":"update","user_id":42,"status":"online"}'
59
+ )
60
+ client.send_data(request5.encode)
61
+
62
+ # These old manually-crafted requests use incorrect QPACK indices - removed
63
+
64
+ # Metrics/telemetry
65
+ # client.send_data("METRIC:cpu=45.2,mem=1024,ts=#{Time.now.to_i}")
66
+ # client.send_data("EVENT:login,user=alice,ip=192.168.1.100")
67
+
68
+ # Large message test - now as HTTP/3 request
69
+ request8 = Quicsilver::HTTP3::RequestEncoder.new(
70
+ method: 'POST',
71
+ path: '/upload',
72
+ authority: 'example.com',
73
+ body: "X" * 50000 # 50KB
74
+ )
75
+ client.send_data(request8.encode)
76
+
77
+ # Keep connection alive for a bit
78
+ puts "ā³ Connection established. Press Enter to disconnect..."
79
+ gets
80
+
81
+ rescue Quicsilver::ConnectionError => e
82
+ puts "āŒ Connection failed: #{e.message}"
83
+ rescue Quicsilver::TimeoutError => e
84
+ puts "ā° Connection timed out: #{e.message}"
85
+ ensure
86
+ puts "šŸ”Œ Disconnecting..."
87
+ client.disconnect
88
+ puts "šŸ‘‹ Disconnected"
89
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "quicsilver"
5
+
6
+ puts "šŸš€ Minimal HTTP/3 Server Example"
7
+ puts "=" * 40
8
+
9
+ # First, set up certificates if they don't exist
10
+ unless File.exist?("certs/server.crt") && File.exist?("certs/server.key")
11
+ puts "šŸ“ Setting up certificates..."
12
+ system("bash examples/setup_certs.sh")
13
+ end
14
+
15
+ # Create and start the server
16
+ server = Quicsilver::Server.new(4433)
17
+
18
+ puts "šŸ”§ Starting server..."
19
+ server.start
20
+
21
+ puts "āœ… Server is running on port 4433"
22
+ puts "šŸ“‹ Server info: #{server.server_info}"
23
+
24
+ # Keep the server running
25
+ puts "ā³ Server is running. Press Ctrl+C to stop..."
26
+ begin
27
+ server.wait_for_connections
28
+ rescue Interrupt
29
+ puts "\nšŸ›‘ Stopping server..."
30
+ server.stop
31
+ puts "šŸ‘‹ Server stopped"
32
+ end
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "quicsilver"
5
+
6
+ puts "šŸš€ Rack HTTP/3 Server Example"
7
+ puts "=" * 40
8
+
9
+ # First, set up certificates if they don't exist
10
+ unless File.exist?("certs/server.crt") && File.exist?("certs/server.key")
11
+ puts "šŸ“ Setting up certificates..."
12
+ system("bash examples/setup_certs.sh")
13
+ end
14
+
15
+ # Define a simple Rack app
16
+ app = ->(env) {
17
+ path = env['PATH_INFO']
18
+ method = env['REQUEST_METHOD']
19
+
20
+ case path
21
+ when '/'
22
+ [200,
23
+ {'Content-Type' => 'text/plain'},
24
+ ["Welcome to Quicsilver HTTP/3!\n"]]
25
+ when '/api/users'
26
+ [200,
27
+ {'Content-Type' => 'application/json'},
28
+ ['{"users": ["alice", "bob", "charlie"]}']]
29
+ when '/api/status'
30
+ [200,
31
+ {'Content-Type' => 'application/json'},
32
+ ["{\"status\": \"ok\", \"method\": \"#{method}\", \"path\": \"#{path}\"}"]]
33
+ else
34
+ [404,
35
+ {'Content-Type' => 'text/plain'},
36
+ ["Not Found: #{path}\n"]]
37
+ end
38
+ }
39
+
40
+ # Create and start the server with the Rack app
41
+ server = Quicsilver::Server.new(4433, app: app)
42
+
43
+ puts "šŸ”§ Starting server..."
44
+ server.start
45
+
46
+ puts "āœ… Server is running on port 4433"
47
+ puts "šŸ“‹ Try these requests:"
48
+ puts " curl --http3 -k https://127.0.0.1:4433/"
49
+ puts " curl --http3 -k https://127.0.0.1:4433/api/users"
50
+ puts " curl --http3 -k https://127.0.0.1:4433/api/status"
51
+
52
+ # Keep the server running
53
+ puts "ā³ Server is running. Press Ctrl+C to stop..."
54
+ begin
55
+ server.wait_for_connections
56
+ rescue Interrupt
57
+ puts "\nšŸ›‘ Stopping server..."
58
+ server.stop
59
+ puts "šŸ‘‹ Server stopped"
60
+ end
@@ -0,0 +1,57 @@
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"
@@ -0,0 +1,25 @@
1
+ require 'mkmf'
2
+
3
+ # Find MSQUIC in the submodule
4
+ msquic_dir = File.expand_path('../../../vendor/msquic', __FILE__)
5
+
6
+ # Add MSQUIC include directory
7
+ $CFLAGS << " -I#{msquic_dir}/src/inc"
8
+ $CFLAGS << " -I#{msquic_dir}/src/inc/public"
9
+
10
+ # Add our fixes header
11
+ $CFLAGS << " -I#{File.expand_path('.', __FILE__)}"
12
+
13
+ # Add MSQUIC library directory
14
+ lib_dir = "#{msquic_dir}/build/bin/Release"
15
+ $LDFLAGS << " -L#{lib_dir}"
16
+
17
+ # Set rpath so the extension can find the library at runtime
18
+ $LDFLAGS << " -Wl,-rpath,#{lib_dir}"
19
+
20
+ # Find the MSQUIC library
21
+ unless find_library('msquic', nil, lib_dir)
22
+ raise "MSQUIC library not found. Please run 'rake build_msquic' first."
23
+ end
24
+
25
+ create_makefile('quicsilver/quicsilver')