quicsilver 0.1.0 → 0.2.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.
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ class RequestRegistry
5
+ def initialize
6
+ @requests = {}
7
+ @mutex = Mutex.new
8
+ end
9
+
10
+ def track(stream_id, connection_handle, path:, method:, started_at: Time.now)
11
+ @mutex.synchronize do
12
+ @requests[stream_id] = {
13
+ connection_handle: connection_handle,
14
+ path: path,
15
+ method: method,
16
+ started_at: started_at
17
+ }
18
+ end
19
+ end
20
+
21
+ def complete(stream_id)
22
+ @mutex.synchronize { @requests.delete(stream_id) }
23
+ end
24
+
25
+ def active_count
26
+ @mutex.synchronize { @requests.size }
27
+ end
28
+
29
+ def active_requests
30
+ @mutex.synchronize { @requests.dup }
31
+ end
32
+
33
+ def requests_older_than(seconds)
34
+ cutoff = Time.now - seconds
35
+ @mutex.synchronize do
36
+ @requests.select { |_, r| r[:started_at] < cutoff }
37
+ end
38
+ end
39
+
40
+ def empty?
41
+ @mutex.synchronize { @requests.empty? }
42
+ end
43
+
44
+ def include?(stream_id)
45
+ @mutex.synchronize { @requests.key?(stream_id) }
46
+ end
47
+ end
48
+ end
@@ -2,109 +2,22 @@
2
2
 
3
3
  module Quicsilver
4
4
  class Server
5
- attr_reader :address, :port, :server_configuration, :running
5
+ attr_reader :address, :port, :server_configuration, :running, :connections, :request_registry, :shutting_down
6
6
 
7
7
  STREAM_EVENT_RECEIVE = "RECEIVE"
8
8
  STREAM_EVENT_RECEIVE_FIN = "RECEIVE_FIN"
9
9
  STREAM_EVENT_CONNECTION_ESTABLISHED = "CONNECTION_ESTABLISHED"
10
10
  STREAM_EVENT_SEND_COMPLETE = "SEND_COMPLETE"
11
+ STREAM_EVENT_CONNECTION_CLOSED = "CONNECTION_CLOSED"
11
12
 
12
- class << self
13
- def stream_buffers
14
- @stream_buffers ||= {}
15
- end
16
-
17
- def stream_handles
18
- @stream_handles ||= {}
19
- end
20
-
21
- def rack_app
22
- @rack_app
23
- end
24
-
25
- def rack_app=(app)
26
- @rack_app = app
27
- end
28
-
29
- def handle_stream(stream_id, event, data)
30
- case event
31
- when STREAM_EVENT_CONNECTION_ESTABLISHED
32
- puts "🔧 Ruby: Connection established with client"
33
- connection_handle = data.unpack1('Q') # Unpack 64-bit pointer
34
- stream = Quicsilver.open_stream(connection_handle, true) # unidirectional
35
- control_data = Quicsilver::HTTP3.build_control_stream
36
- Quicsilver.send_stream(stream, control_data, false) # no FIN
37
- when STREAM_EVENT_SEND_COMPLETE
38
- puts "🔧 Ruby: Control stream sent to client"
39
- when STREAM_EVENT_RECEIVE
40
- # Accumulate data
41
- stream_buffers[stream_id] ||= ""
42
- stream_buffers[stream_id] += data
43
- puts "🔧 Ruby: Stream #{stream_id}: Buffering #{data.bytesize} bytes (total: #{stream_buffers[stream_id].bytesize})"
44
- when STREAM_EVENT_RECEIVE_FIN
45
- # Extract stream handle from data (first 8 bytes)
46
- stream_handle = data[0, 8].unpack1('Q')
47
- actual_data = data[8..-1] || ""
48
-
49
- # Store stream handle for later use
50
- stream_handles[stream_id] = stream_handle
51
-
52
- # Final chunk - process complete message
53
- stream_buffers[stream_id] ||= ""
54
- stream_buffers[stream_id] += actual_data
55
- complete_data = stream_buffers[stream_id]
56
-
57
- # Handle bidirectional streams (client requests)
58
- if bidirectional?(stream_id)
59
- handle_http3_request(stream_id, complete_data)
60
- else
61
- # Unidirectional stream (control/QPACK)
62
- puts "✅ Ruby: Stream #{stream_id}: Control/QPACK stream (#{complete_data.bytesize} bytes)"
63
- end
64
-
65
- # Clean up buffers
66
- stream_buffers.delete(stream_id)
67
- stream_handles.delete(stream_id)
68
- end
69
- end
13
+ ServerStopError = Class.new(StandardError)
70
14
 
71
- private
15
+ class << self
16
+ attr_accessor :instance
72
17
 
73
- def bidirectional?(stream_id)
74
- # Client-initiated bidirectional streams have bit 0x02 clear
75
- (stream_id & 0x02) == 0
76
- end
77
-
78
- def handle_http3_request(stream_id, data)
79
- parser = HTTP3::RequestParser.new(data)
80
- parser.parse
81
- env = parser.to_rack_env
82
-
83
- if env && rack_app
84
- puts "✅ Ruby: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
85
-
86
- # Call Rack app
87
- status, headers, body = rack_app.call(env)
88
-
89
- # Encode response
90
- encoder = HTTP3::ResponseEncoder.new(status, headers, body)
91
- response_data = encoder.encode
92
-
93
- # Get stream handle from stored handles
94
- stream_handle = stream_handles[stream_id]
95
- if stream_handle
96
- # Send response
97
- Quicsilver.send_stream(stream_handle, response_data, true)
98
- puts "✅ Ruby: Response sent: #{status}"
99
- else
100
- puts "❌ Ruby: Stream handle not found for stream #{stream_id}"
101
- end
102
- else
103
- puts "❌ Ruby: Failed to parse request"
104
- end
105
- rescue => e
106
- puts "❌ Ruby: Error handling request: #{e.class} - #{e.message}"
107
- puts e.backtrace.first(5)
18
+ # Callback from C extension - delegates to server instance
19
+ def handle_stream(connection_data, stream_id, event, data)
20
+ instance&.handle_stream_event(connection_data, stream_id, event, data)
108
21
  end
109
22
  end
110
23
 
@@ -114,30 +27,36 @@ module Quicsilver
114
27
  @app = app || default_rack_app
115
28
  @server_configuration = server_configuration || ServerConfiguration.new
116
29
  @running = false
30
+ @shutting_down = false
117
31
  @listener_data = nil
32
+ @connections = {}
33
+ @request_registry = RequestRegistry.new
118
34
 
119
- # Set class-level rack app so handle_stream can access it
120
- self.class.rack_app = @app
35
+ self.class.instance = self
121
36
  end
122
37
 
123
38
  def start
124
39
  raise ServerIsRunningError, "Server is already running" if @running
125
40
 
126
- # Initialize MSQUIC if not already done
127
41
  Quicsilver.open_connection
128
-
129
42
  config = Quicsilver.create_server_configuration(@server_configuration.to_h)
130
- unless config
131
- raise ServerConfigurationError, "Failed to create server configuration"
132
- end
43
+ raise ServerConfigurationError, "Failed to create server configuration" unless config
133
44
 
134
45
  # Create and start the listener
135
- @listener_data = start_listener(config)
136
- start_server(config)
46
+ result = Quicsilver.create_listener(config)
47
+ @listener_data = ListenerData.new(result[0], result[1])
48
+ raise ServerListenerError, "Failed to create listener #{@address}:#{@port}" unless @listener_data
49
+
50
+ unless Quicsilver.start_listener(@listener_data.listener_handle, @address, @port)
51
+ Quicsilver.close_configuration(config)
52
+ cleanup_failed_server
53
+ raise ServerListenerError, "Failed to start listener on #{@address}:#{@port}"
54
+ end
137
55
 
138
56
  @running = true
139
57
 
140
- puts "✅ QUIC server started successfully on #{@address}:#{@port}"
58
+ Quicsilver.event_loop.start
59
+ Quicsilver.event_loop.join # Block until shutdown
141
60
  rescue ServerConfigurationError, ServerListenerError => e
142
61
  cleanup_failed_server
143
62
  @running = false
@@ -161,53 +80,107 @@ module Quicsilver
161
80
  def stop
162
81
  return unless @running
163
82
 
164
- puts "🛑 Stopping QUIC server..."
165
-
166
- if @listener_data
167
- listener_handle = @listener_data[0]
168
- Quicsilver.stop_listener(listener_handle)
169
- Quicsilver.close_listener(@listener_data)
170
- @listener_data = nil
83
+ if @listener_data && @listener_data.listener_handle
84
+ Quicsilver.stop_listener(@listener_data.listener_handle)
85
+ Quicsilver.close_listener([@listener_data.listener_handle, @listener_data.context_handle])
171
86
  end
172
87
 
88
+ Quicsilver.event_loop.stop # Stop event loop so start unblocks
173
89
  @running = false
174
- puts "👋 Server stopped"
175
- rescue
176
- puts "⚠️ Error during server shutdown"
177
- # Continue with cleanup even if there are errors
90
+ @listener_data = nil
91
+ rescue => e
178
92
  @listener_data = nil
179
93
  @running = false
94
+ raise ServerStopError, "Failed to stop server: #{e.message}"
180
95
  end
181
96
 
182
97
  def running?
183
98
  @running
184
99
  end
185
100
 
186
- def server_info
187
- {
188
- address: @address,
189
- port: @port,
190
- running: @running,
191
- cert_file: @cert_file,
192
- key_file: @key_file
193
- }
194
- end
101
+ # Graceful shutdown: send GOAWAY, wait for in-flight requests, then stop
102
+ def shutdown(timeout: 30)
103
+ return unless @running
104
+ return if @shutting_down
105
+
106
+ @shutting_down = true
107
+ Quicsilver.logger.info("Initiating graceful shutdown (timeout: #{timeout}s)")
108
+
109
+ # Phase 1: Send GOAWAY with max stream ID to all connections
110
+ # This tells clients to stop sending new requests
111
+ @connections.each_value do |connection|
112
+ send_goaway(connection, HTTP3::MAX_STREAM_ID)
113
+ end
114
+
115
+ # Phase 2: Wait for in-flight requests to drain
116
+ deadline = Time.now + timeout
117
+ until @request_registry.empty? || Time.now > deadline
118
+ sleep 0.1
119
+ end
195
120
 
196
- def wait_for_connections(timeout: nil)
197
- if timeout
198
- end_time = Time.now + timeout
199
- while Time.now < end_time && @running
200
- Quicsilver.process_events
201
- sleep(0.01) # Poll every 10ms
121
+ # Log any requests that didn't complete
122
+ unless @request_registry.empty?
123
+ @request_registry.active_requests.each do |stream_id, req|
124
+ elapsed = Time.now - req[:started_at]
125
+ Quicsilver.logger.warn("Force-closing request: #{req[:method]} #{req[:path]} (stream: #{stream_id}, elapsed: #{elapsed.round(2)}s)")
202
126
  end
203
- else
204
- # Keep the server running indefinitely
205
- # Process events from MSQUIC callbacks
206
- loop do
207
- Quicsilver.process_events
208
- sleep(0.01) # Poll every 10ms
209
- break unless @running
127
+ end
128
+
129
+ # Phase 3: Send final GOAWAY with actual last stream ID and shutdown connections
130
+ @connections.each_value do |connection|
131
+ last_stream_id = connection.streams.keys.select { |id| (id & 0x02) == 0 }.max || 0
132
+ send_goaway(connection, last_stream_id)
133
+
134
+ # Graceful QUIC shutdown (sends CONNECTION_CLOSE to peer)
135
+ Quicsilver.connection_shutdown(connection.handle, 0, false)
136
+ end
137
+
138
+ # Give connections a moment to close gracefully
139
+ sleep 0.1
140
+
141
+ # Phase 4: Hard stop
142
+ stop
143
+ @shutting_down = false
144
+
145
+ Quicsilver.logger.info("Graceful shutdown complete")
146
+ end
147
+
148
+ def handle_stream_event(connection_data, stream_id, event, data)
149
+ connection_handle = connection_data[0]
150
+
151
+ case event
152
+ when STREAM_EVENT_CONNECTION_ESTABLISHED
153
+ connection = Connection.new(connection_handle, connection_data)
154
+ @connections[connection_handle] = connection
155
+ setup_http3_streams(connection)
156
+ when STREAM_EVENT_CONNECTION_CLOSED
157
+ @connections.delete(connection_handle)&.streams&.clear
158
+ when STREAM_EVENT_SEND_COMPLETE
159
+ # Buffer cleanup handled in C extension
160
+ when STREAM_EVENT_RECEIVE
161
+ return unless connection = @connections[connection_handle]
162
+
163
+ stream = connection.get_stream(stream_id) || QuicStream.new(stream_id)
164
+ connection.add_stream(stream) unless connection.get_stream(stream_id)
165
+ stream.append_data(data)
166
+ when STREAM_EVENT_RECEIVE_FIN
167
+ return unless connection = @connections[connection_handle]
168
+
169
+ # Extract stream handle from data (first 8 bytes)
170
+ stream_handle = data[0, 8].unpack1('Q')
171
+ actual_data = data[8..-1] || ""
172
+
173
+ stream = connection.get_stream(stream_id) || QuicStream.new(stream_id)
174
+ stream.stream_handle = stream_handle
175
+ stream.append_data(actual_data)
176
+
177
+ if stream.bidirectional?
178
+ handle_request(connection, stream)
179
+ else
180
+ handle_unidirectional_stream(connection, stream) # Unidirectional stream (control/QPACK)
210
181
  end
182
+
183
+ connection.remove_stream(stream_id)
211
184
  end
212
185
  end
213
186
 
@@ -221,38 +194,162 @@ module Quicsilver
221
194
  }
222
195
  end
223
196
 
224
- def start_server(config)
225
- result = Quicsilver.start_listener(@listener_data.listener_handle, @address, @port)
226
- unless result
227
- Quicsilver.close_configuration(config)
228
- cleanup_failed_server
229
- raise ServerListenerError, "Failed to start listener on #{@address}:#{@port}"
197
+ def cleanup_failed_server
198
+ if @listener_data
199
+ begin
200
+ Quicsilver.stop_listener(@listener_data.listener_handle) if @listener_data.listener_handle
201
+ Quicsilver.close_listener([@listener_data.listener_handle, @listener_data.context_handle]) if @listener_data.listener_handle
202
+ rescue
203
+ # Ignore cleanup errors
204
+ ensure
205
+ @listener_data = nil
206
+ end
230
207
  end
231
208
  end
232
209
 
233
- def start_listener(config)
234
- result = Quicsilver.create_listener(config)
235
- listener_data = ListenerData.new(result[0], result[1])
210
+ def setup_http3_streams(connection)
211
+ connection_data = connection.data
236
212
 
237
- unless listener_data
238
- Quicsilver.close_configuration(config)
239
- raise ServerListenerError, "Failed to create listener on #{@address}:#{@port}"
213
+ # Send control stream (required) - store handle for GOAWAY
214
+ control_stream = Quicsilver.open_stream(connection_data, true)
215
+ control_data = HTTP3.build_control_stream
216
+ Quicsilver.send_stream(control_stream, control_data, false)
217
+ connection.server_control_stream = control_stream
218
+
219
+ # Open QPACK encoder/decoder streams (required)
220
+ [0x02, 0x03].each do |type|
221
+ stream = Quicsilver.open_stream(connection_data, true)
222
+ Quicsilver.send_stream(stream, [type].pack('C'), false)
240
223
  end
224
+ end
225
+
241
226
 
242
- listener_data
227
+ def handle_control_stream(connection, stream)
228
+ return if stream.data.empty?
229
+
230
+ case stream.data[0].ord
231
+ when 0x00 then connection.set_control_stream(stream.stream_id)
232
+ when 0x02 then connection.set_qpack_encoder_stream(stream.stream_id)
233
+ when 0x03 then connection.set_qpack_decoder_stream(stream.stream_id)
234
+ end
243
235
  end
244
236
 
245
- def cleanup_failed_server
246
- if @listener_data
247
- begin
248
- Quicsilver.stop_listener(@listener_data)
249
- Quicsilver.close_listener(@listener_data)
250
- rescue
251
- # Ignore cleanup errors
252
- ensure
253
- @listener_data = nil
237
+ def handle_unidirectional_stream(connection, stream)
238
+ data = stream.data
239
+ return if data.empty?
240
+
241
+ stream_type = data[0].ord
242
+ payload = data[1..-1]
243
+
244
+ case stream_type
245
+ when 0x00 # Control stream
246
+ connection.set_control_stream(stream.stream_id)
247
+ parse_client_control_stream(payload)
248
+ when 0x02 # QPACK encoder stream
249
+ # Store encoder stream for sending dynamic table updates
250
+ connection.set_qpack_encoder_stream(stream.stream_id)
251
+ when 0x03 # QPACK decoder stream
252
+ # Store decoder stream for receiving acknowledgments
253
+ connection.set_qpack_decoder_stream(stream.stream_id)
254
+ else
255
+ raise "⚠️ Ruby: Stream #{stream.stream_id}: Unknown stream type: 0x#{stream_type.to_s(16)}"
256
+ end
257
+ end
258
+
259
+ def parse_client_control_stream(data)
260
+ offset = 0
261
+ while offset < data.bytesize
262
+ frame_type, type_len = HTTP3.decode_varint(data.bytes, offset)
263
+ frame_length, length_len = HTTP3.decode_varint(data.bytes, offset + type_len)
264
+
265
+ if frame_type == HTTP3::FRAME_SETTINGS
266
+ # Parse client settings
267
+ settings_payload = data[offset + type_len + length_len, frame_length]
268
+ parse_settings_frame(settings_payload)
254
269
  end
270
+
271
+ offset += type_len + length_len + frame_length
255
272
  end
256
273
  end
274
+
275
+ def parse_settings_frame(payload)
276
+ offset = 0
277
+ settings = {}
278
+
279
+ while offset < payload.bytesize
280
+ setting_id, id_len = HTTP3.decode_varint(payload.bytes, offset)
281
+ setting_value, value_len = HTTP3.decode_varint(payload.bytes, offset + id_len)
282
+ settings[setting_id] = setting_value
283
+ offset += id_len + value_len
284
+ end
285
+
286
+ settings
287
+ end
288
+
289
+ def handle_request(connection, stream)
290
+ parser = HTTP3::RequestParser.new(stream.data)
291
+ parser.parse
292
+ env = parser.to_rack_env
293
+
294
+ if env && @app
295
+ # Track request
296
+ @request_registry.track(
297
+ stream.stream_id,
298
+ connection.handle,
299
+ path: env["PATH_INFO"] || "/",
300
+ method: env["REQUEST_METHOD"] || "GET"
301
+ )
302
+
303
+ # Call Rack app
304
+ status, headers, body = @app.call(env)
305
+ encoder = HTTP3::ResponseEncoder.new(status, headers, body)
306
+
307
+ raise "Stream handle not found for stream #{stream.stream_id}" unless stream.ready_to_send?
308
+
309
+ # Rack convention: body.to_ary means bufferable, otherwise stream
310
+ if body.respond_to?(:to_ary)
311
+ # Buffer mode - small responses (Arrays), send all at once
312
+ Quicsilver.send_stream(stream.stream_handle, encoder.encode, true)
313
+ else
314
+ # Stream mode - lazy bodies (ActionController::Live, SSE), send incrementally
315
+ encoder.stream_encode do |frame_data, fin|
316
+ Quicsilver.send_stream(stream.stream_handle, frame_data, fin) unless frame_data.empty? && !fin
317
+ end
318
+ end
319
+
320
+ # Mark request complete
321
+ @request_registry.complete(stream.stream_id)
322
+ else
323
+ # failed to parse request
324
+ if stream.ready_to_send?
325
+ error_response = encode_error_response(400, "Bad Request")
326
+ Quicsilver.send_stream(stream.stream_handle, error_response, true)
327
+ end
328
+ end
329
+ rescue => e
330
+ Quicsilver.logger.error("Error handling request: #{e.class} - #{e.message}")
331
+ Quicsilver.logger.debug(e.backtrace.first(5).join("\n"))
332
+ error_response = encode_error_response(500, "Internal Server Error")
333
+
334
+ Quicsilver.send_stream(stream.stream_handle, error_response, true) if stream.ready_to_send?
335
+ ensure
336
+ # Always complete the request, even on error
337
+ @request_registry.complete(stream.stream_id) if @request_registry.include?(stream.stream_id)
338
+ end
339
+
340
+ def encode_error_response(status, message)
341
+ body = ["#{status} #{message}"]
342
+ encoder = HTTP3::ResponseEncoder.new(status, {"content-type" => "text/plain"}, body)
343
+ encoder.encode
344
+ end
345
+
346
+ def send_goaway(connection, stream_id)
347
+ return unless connection.server_control_stream
348
+
349
+ goaway_frame = HTTP3.build_goaway_frame(stream_id)
350
+ Quicsilver.send_stream(connection.server_control_stream, goaway_frame, false)
351
+ rescue => e
352
+ Quicsilver.logger.error("Failed to send GOAWAY to connection #{connection.handle}: #{e.message}")
353
+ end
257
354
  end
258
355
  end
@@ -1,23 +1,49 @@
1
1
  # frozen_string_literal: true
2
-
2
+
3
+ require "localhost"
4
+
3
5
  module Quicsilver
4
6
  class ServerConfiguration
5
- attr_reader :cert_file, :key_file, :idle_timeout, :server_resumption_level, :peer_bidi_stream_count,
6
- :peer_unidi_stream_count
7
+ attr_reader :cert_file, :key_file, :idle_timeout, :server_resumption_level, :peer_bidi_stream_count,
8
+ :peer_unidi_stream_count, :stream_recv_window, :stream_recv_buffer, :conn_flow_control_window
7
9
 
8
10
  QUIC_SERVER_RESUME_AND_ZERORTT = 1
9
11
  QUIC_SERVER_RESUME_ONLY = 2
10
12
  QUIC_SERVER_RESUME_AND_REUSE = 3
11
13
  QUIC_SERVER_RESUME_AND_REUSE_ZERORTT = 4
12
14
 
15
+ DEFAULT_CERT_FILE = "certificates/server.crt"
16
+ DEFAULT_KEY_FILE = "certificates/server.key"
17
+ DEFAULT_ALPN = "h3"
18
+
19
+ # Flow control defaults (msquic defaults)
20
+ # See: https://github.com/microsoft/msquic/blob/main/docs/Settings.md
21
+ DEFAULT_STREAM_RECV_WINDOW = 65_536 # 64KB - initial stream receive window
22
+ DEFAULT_STREAM_RECV_BUFFER = 4_096 # 4KB - stream buffer size
23
+ DEFAULT_CONN_FLOW_CONTROL_WINDOW = 16_777_216 # 16MB - connection-wide flow control
24
+
13
25
  def initialize(cert_file = nil, key_file = nil, options = {})
14
- @cert_file = cert_file.nil? ? "certs/server.crt" : cert_file
15
- @key_file = key_file.nil? ? "certs/server.key" : key_file
16
26
  @idle_timeout = options[:idle_timeout].nil? ? 10000 : options[:idle_timeout]
17
27
  @server_resumption_level = options[:server_resumption_level].nil? ? QUIC_SERVER_RESUME_AND_ZERORTT : options[:server_resumption_level]
18
28
  @peer_bidi_stream_count = options[:peer_bidi_stream_count].nil? ? 10 : options[:peer_bidi_stream_count]
19
29
  @peer_unidi_stream_count = options[:peer_unidi_stream_count].nil? ? 10 : options[:peer_unidi_stream_count]
20
- @alpn = options[:alpn].nil? ? "h3" : options[:alpn]
30
+ @alpn = options[:alpn].nil? ? DEFAULT_ALPN : options[:alpn]
31
+
32
+ # Flow control / backpressure settings
33
+ @stream_recv_window = options[:stream_recv_window].nil? ? DEFAULT_STREAM_RECV_WINDOW : options[:stream_recv_window]
34
+ @stream_recv_buffer = options[:stream_recv_buffer].nil? ? DEFAULT_STREAM_RECV_BUFFER : options[:stream_recv_buffer]
35
+ @conn_flow_control_window = options[:conn_flow_control_window].nil? ? DEFAULT_CONN_FLOW_CONTROL_WINDOW : options[:conn_flow_control_window]
36
+
37
+ @cert_file = cert_file.nil? ? DEFAULT_CERT_FILE : cert_file
38
+ @key_file = key_file.nil? ? DEFAULT_KEY_FILE : key_file
39
+
40
+ unless File.exist?(@cert_file)
41
+ raise ServerConfigurationError, "Certificate file not found: #{@cert_file}"
42
+ end
43
+
44
+ unless File.exist?(@key_file)
45
+ raise ServerConfigurationError, "Key file not found: #{@key_file}"
46
+ end
21
47
  end
22
48
 
23
49
  # Common HTTP/3 ALPN Values:
@@ -42,7 +68,10 @@ module Quicsilver
42
68
  server_resumption_level: @server_resumption_level,
43
69
  peer_bidi_stream_count: @peer_bidi_stream_count,
44
70
  peer_unidi_stream_count: @peer_unidi_stream_count,
45
- alpn: alpn
71
+ alpn: alpn,
72
+ stream_recv_window: @stream_recv_window,
73
+ stream_recv_buffer: @stream_recv_buffer,
74
+ conn_flow_control_window: @conn_flow_control_window
46
75
  }
47
76
  end
48
77
  end
@@ -1,3 +1,3 @@
1
1
  module Quicsilver
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/quicsilver.rb CHANGED
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "logger"
3
4
  require_relative "quicsilver/version"
4
5
  require_relative "quicsilver/client"
6
+ require_relative "quicsilver/connection"
7
+ require_relative "quicsilver/event_loop"
8
+ require_relative "quicsilver/quic_stream"
5
9
  require_relative "quicsilver/listener_data"
10
+ require_relative "quicsilver/request_registry"
6
11
  require_relative "quicsilver/server"
7
12
  require_relative "quicsilver/server_configuration"
8
13
  require_relative "quicsilver/http3"
@@ -10,6 +15,7 @@ require_relative "quicsilver/http3/request_parser"
10
15
  require_relative "quicsilver/http3/request_encoder"
11
16
  require_relative "quicsilver/http3/response_encoder"
12
17
  require_relative "quicsilver/quicsilver"
18
+ require_relative "rackup/handler/quicsilver"
13
19
 
14
20
  module Quicsilver
15
21
  class Error < StandardError; end
@@ -19,4 +25,20 @@ module Quicsilver
19
25
  class ServerError < Error; end
20
26
  class ConnectionError < Error; end
21
27
  class TimeoutError < Error; end
28
+
29
+ class << self
30
+ attr_writer :logger
31
+
32
+ def logger
33
+ @logger ||= default_logger
34
+ end
35
+
36
+ private
37
+
38
+ def default_logger
39
+ Logger.new($stdout, level: Logger::INFO).tap do |log|
40
+ log.progname = "Quicsilver"
41
+ end
42
+ end
43
+ end
22
44
  end