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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +42 -0
- data/.gitignore +3 -1
- data/CHANGELOG.md +27 -5
- data/Gemfile.lock +10 -0
- data/LICENSE +21 -0
- data/README.md +33 -54
- data/benchmarks/benchmark.rb +68 -0
- data/benchmarks/quicsilver_server.rb +46 -0
- data/examples/minimal_http3_server.rb +0 -6
- data/examples/rack_http3_server.rb +0 -6
- data/examples/simple_client_test.rb +26 -0
- data/ext/quicsilver/quicsilver.c +165 -36
- data/lib/quicsilver/client.rb +171 -101
- data/lib/quicsilver/connection.rb +42 -0
- data/lib/quicsilver/event_loop.rb +38 -0
- data/lib/quicsilver/http3/request_encoder.rb +41 -20
- data/lib/quicsilver/http3/request_parser.rb +42 -24
- data/lib/quicsilver/http3/response_encoder.rb +138 -25
- data/lib/quicsilver/http3/response_parser.rb +160 -0
- data/lib/quicsilver/http3.rb +205 -51
- data/lib/quicsilver/quic_stream.rb +36 -0
- data/lib/quicsilver/request_registry.rb +48 -0
- data/lib/quicsilver/server.rb +257 -160
- data/lib/quicsilver/server_configuration.rb +36 -7
- data/lib/quicsilver/version.rb +1 -1
- data/lib/quicsilver.rb +22 -0
- data/lib/rackup/handler/quicsilver.rb +78 -0
- data/quicsilver.gemspec +7 -2
- metadata +72 -7
- data/examples/minimal_http3_client.rb +0 -89
data/ext/quicsilver/quicsilver.c
CHANGED
|
@@ -8,6 +8,9 @@ static VALUE mQuicsilver;
|
|
|
8
8
|
|
|
9
9
|
// Event queue for callbacks
|
|
10
10
|
typedef struct CallbackEvent {
|
|
11
|
+
HQUIC connection;
|
|
12
|
+
void* connection_ctx; // ConnectionContext pointer for building connection_data
|
|
13
|
+
VALUE client_obj; // Ruby client object (for routing callbacks)
|
|
11
14
|
char* event_type;
|
|
12
15
|
uint64_t stream_id;
|
|
13
16
|
char* data;
|
|
@@ -34,6 +37,7 @@ typedef struct {
|
|
|
34
37
|
int failed;
|
|
35
38
|
QUIC_STATUS error_status;
|
|
36
39
|
uint64_t error_code;
|
|
40
|
+
VALUE client_obj; // Ruby client object (Qnil for server connections)
|
|
37
41
|
} ConnectionContext;
|
|
38
42
|
|
|
39
43
|
// Listener state tracking
|
|
@@ -47,18 +51,25 @@ typedef struct {
|
|
|
47
51
|
|
|
48
52
|
// Stream state tracking
|
|
49
53
|
typedef struct {
|
|
54
|
+
HQUIC connection;
|
|
55
|
+
void* connection_ctx; // ConnectionContext pointer (for building connection_data)
|
|
56
|
+
VALUE client_obj; // Ruby client object (copied from connection context)
|
|
50
57
|
int started;
|
|
51
58
|
int shutdown;
|
|
52
59
|
QUIC_STATUS error_status;
|
|
53
60
|
} StreamContext;
|
|
54
61
|
|
|
55
62
|
// Enqueue a callback event (thread-safe)
|
|
63
|
+
// NOTE: Called from QUIC callback threads without GVL - cannot use Ruby API here
|
|
56
64
|
static void
|
|
57
|
-
enqueue_callback_event(const char* event_type, uint64_t stream_id, const char* data, size_t data_len)
|
|
65
|
+
enqueue_callback_event(HQUIC connection, void* connection_ctx, VALUE client_obj, const char* event_type, uint64_t stream_id, const char* data, size_t data_len)
|
|
58
66
|
{
|
|
59
67
|
CallbackEvent* event = (CallbackEvent*)malloc(sizeof(CallbackEvent));
|
|
60
68
|
if (event == NULL) return;
|
|
61
69
|
|
|
70
|
+
event->connection = connection;
|
|
71
|
+
event->connection_ctx = connection_ctx;
|
|
72
|
+
event->client_obj = client_obj; // Store raw VALUE - will validate before use
|
|
62
73
|
event->event_type = strdup(event_type);
|
|
63
74
|
event->stream_id = stream_id;
|
|
64
75
|
event->data = (char*)malloc(data_len);
|
|
@@ -78,15 +89,35 @@ enqueue_callback_event(const char* event_type, uint64_t stream_id, const char* d
|
|
|
78
89
|
pthread_mutex_unlock(&queue_mutex);
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
// Free a single event
|
|
93
|
+
static void
|
|
94
|
+
free_event(CallbackEvent* event)
|
|
95
|
+
{
|
|
96
|
+
if (event == NULL) return;
|
|
97
|
+
free(event->event_type);
|
|
98
|
+
free(event->data);
|
|
99
|
+
free(event);
|
|
100
|
+
}
|
|
101
|
+
|
|
81
102
|
// Process all pending callback events (called from Ruby)
|
|
82
103
|
static VALUE
|
|
83
104
|
quicsilver_process_events(VALUE self)
|
|
84
105
|
{
|
|
85
106
|
CallbackEvent* event;
|
|
86
|
-
VALUE server_class =
|
|
107
|
+
VALUE server_class = Qnil;
|
|
87
108
|
int processed = 0;
|
|
88
109
|
|
|
89
|
-
|
|
110
|
+
// Get Server class for server events (client_obj == Qnil)
|
|
111
|
+
server_class = rb_const_get_at(mQuicsilver, rb_intern("Server"));
|
|
112
|
+
if (rb_class_real(CLASS_OF(server_class)) != rb_cClass) {
|
|
113
|
+
server_class = Qnil;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Process events in a loop, but limit iterations to avoid GVL starvation
|
|
117
|
+
int max_iterations = 100;
|
|
118
|
+
int iteration = 0;
|
|
119
|
+
|
|
120
|
+
while (iteration++ < max_iterations) {
|
|
90
121
|
pthread_mutex_lock(&queue_mutex);
|
|
91
122
|
event = event_queue_head;
|
|
92
123
|
if (event != NULL) {
|
|
@@ -99,17 +130,42 @@ quicsilver_process_events(VALUE self)
|
|
|
99
130
|
|
|
100
131
|
if (event == NULL) break;
|
|
101
132
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
// Route based on client_obj:
|
|
134
|
+
// - If Qnil: server event, route to Server.handle_stream with connection_data
|
|
135
|
+
// - If not Qnil: client event, route to client_obj.handle_stream_event
|
|
136
|
+
int handled = 0;
|
|
137
|
+
|
|
138
|
+
if (NIL_P(event->client_obj)) {
|
|
139
|
+
// Server event - build connection_data array [connection_handle, context_ptr]
|
|
140
|
+
if (!NIL_P(server_class)) {
|
|
141
|
+
VALUE connection_data = rb_ary_new2(2);
|
|
142
|
+
rb_ary_push(connection_data, ULL2NUM((uintptr_t)event->connection));
|
|
143
|
+
rb_ary_push(connection_data, ULL2NUM((uintptr_t)event->connection_ctx));
|
|
144
|
+
|
|
145
|
+
rb_funcall(server_class, rb_intern("handle_stream"), 4,
|
|
146
|
+
connection_data,
|
|
147
|
+
ULL2NUM(event->stream_id),
|
|
148
|
+
rb_str_new_cstr(event->event_type),
|
|
149
|
+
rb_str_new(event->data, event->data_len));
|
|
150
|
+
handled = 1;
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
// Client event - validate object is a real Ruby object before calling
|
|
154
|
+
// This catches use-after-free when connection was closed but events still queued
|
|
155
|
+
if (RB_TYPE_P(event->client_obj, T_OBJECT)) {
|
|
156
|
+
rb_funcall(event->client_obj, rb_intern("handle_stream_event"), 3,
|
|
157
|
+
ULL2NUM(event->stream_id),
|
|
158
|
+
rb_str_new_cstr(event->event_type),
|
|
159
|
+
rb_str_new(event->data, event->data_len));
|
|
160
|
+
handled = 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (handled) {
|
|
107
165
|
processed++;
|
|
108
166
|
}
|
|
109
167
|
|
|
110
|
-
|
|
111
|
-
free(event->data);
|
|
112
|
-
free(event);
|
|
168
|
+
free_event(event);
|
|
113
169
|
}
|
|
114
170
|
|
|
115
171
|
return INT2NUM(processed);
|
|
@@ -144,15 +200,19 @@ StreamCallback(HQUIC Stream, void* Context, QUIC_STREAM_EVENT* Event)
|
|
|
144
200
|
if (combined != NULL) {
|
|
145
201
|
memcpy(combined, &Stream, sizeof(HQUIC));
|
|
146
202
|
memcpy(combined + sizeof(HQUIC), buffer->Buffer, buffer->Length);
|
|
147
|
-
enqueue_callback_event(event_type, stream_id, combined, total_len);
|
|
203
|
+
enqueue_callback_event(ctx->connection, ctx->connection_ctx, ctx->client_obj, event_type, stream_id, combined, total_len);
|
|
148
204
|
free(combined);
|
|
149
205
|
}
|
|
150
206
|
} else {
|
|
151
|
-
enqueue_callback_event(event_type, stream_id, (const char*)buffer->Buffer, buffer->Length);
|
|
207
|
+
enqueue_callback_event(ctx->connection, ctx->connection_ctx, ctx->client_obj, event_type, stream_id, (const char*)buffer->Buffer, buffer->Length);
|
|
152
208
|
}
|
|
153
209
|
}
|
|
154
210
|
break;
|
|
155
211
|
case QUIC_STREAM_EVENT_SEND_COMPLETE:
|
|
212
|
+
// Free the send buffer that was allocated in quicsilver_send_stream
|
|
213
|
+
if (Event->SEND_COMPLETE.ClientContext != NULL) {
|
|
214
|
+
free(Event->SEND_COMPLETE.ClientContext);
|
|
215
|
+
}
|
|
156
216
|
break;
|
|
157
217
|
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
|
|
158
218
|
ctx->shutdown = 1;
|
|
@@ -162,7 +222,7 @@ StreamCallback(HQUIC Stream, void* Context, QUIC_STREAM_EVENT* Event)
|
|
|
162
222
|
case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
|
|
163
223
|
break;
|
|
164
224
|
}
|
|
165
|
-
|
|
225
|
+
|
|
166
226
|
return QUIC_STATUS_SUCCESS;
|
|
167
227
|
}
|
|
168
228
|
|
|
@@ -182,8 +242,8 @@ ConnectionCallback(HQUIC Connection, void* Context, QUIC_CONNECTION_EVENT* Event
|
|
|
182
242
|
case QUIC_CONNECTION_EVENT_CONNECTED:
|
|
183
243
|
ctx->connected = 1;
|
|
184
244
|
ctx->failed = 0;
|
|
185
|
-
// Notify Ruby about new connection
|
|
186
|
-
enqueue_callback_event("CONNECTION_ESTABLISHED", 0, (const char*)&Connection, sizeof(HQUIC));
|
|
245
|
+
// Notify Ruby about new connection - pass ctx pointer for building connection_data
|
|
246
|
+
enqueue_callback_event(Connection, ctx, ctx->client_obj, "CONNECTION_ESTABLISHED", 0, (const char*)&Connection, sizeof(HQUIC));
|
|
187
247
|
break;
|
|
188
248
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
|
|
189
249
|
ctx->connected = 0;
|
|
@@ -199,12 +259,17 @@ ConnectionCallback(HQUIC Connection, void* Context, QUIC_CONNECTION_EVENT* Event
|
|
|
199
259
|
break;
|
|
200
260
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
|
|
201
261
|
ctx->connected = 0;
|
|
262
|
+
// Notify Ruby to clean up connection resources
|
|
263
|
+
enqueue_callback_event(Connection, ctx, ctx->client_obj, "CONNECTION_CLOSED", 0, (const char*)&Connection, sizeof(HQUIC));
|
|
202
264
|
break;
|
|
203
265
|
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED:
|
|
204
266
|
// Client opened a stream
|
|
205
267
|
Stream = Event->PEER_STREAM_STARTED.Stream;
|
|
206
268
|
stream_ctx = (StreamContext*)malloc(sizeof(StreamContext));
|
|
207
269
|
if (stream_ctx != NULL) {
|
|
270
|
+
stream_ctx->connection = Connection;
|
|
271
|
+
stream_ctx->connection_ctx = ctx; // Store connection context pointer
|
|
272
|
+
stream_ctx->client_obj = ctx->client_obj; // Copy from connection context
|
|
208
273
|
stream_ctx->started = 1;
|
|
209
274
|
stream_ctx->shutdown = 0;
|
|
210
275
|
stream_ctx->error_status = QUIC_STATUS_SUCCESS;
|
|
@@ -240,16 +305,18 @@ ListenerCallback(HQUIC Listener, void* Context, QUIC_LISTENER_EVENT* Event)
|
|
|
240
305
|
conn_ctx->failed = 0;
|
|
241
306
|
conn_ctx->error_status = QUIC_STATUS_SUCCESS;
|
|
242
307
|
conn_ctx->error_code = 0;
|
|
243
|
-
|
|
308
|
+
conn_ctx->client_obj = Qnil; // Server connections have no client object
|
|
309
|
+
|
|
244
310
|
// Set the connection callback
|
|
245
311
|
MsQuic->SetCallbackHandler(Event->NEW_CONNECTION.Connection, (void*)ConnectionCallback, conn_ctx);
|
|
246
|
-
|
|
312
|
+
|
|
247
313
|
// Accept the new connection with the server configuration
|
|
248
314
|
QUIC_STATUS Status = MsQuic->ConnectionSetConfiguration(Event->NEW_CONNECTION.Connection, ctx->Configuration);
|
|
249
315
|
if (QUIC_FAILED(Status)) {
|
|
250
316
|
free(conn_ctx);
|
|
251
317
|
return Status;
|
|
252
318
|
}
|
|
319
|
+
|
|
253
320
|
} else {
|
|
254
321
|
// Reject the connection if we can't allocate context
|
|
255
322
|
return QUIC_STATUS_OUT_OF_MEMORY;
|
|
@@ -355,7 +422,10 @@ quicsilver_create_server_configuration(VALUE self, VALUE config_hash)
|
|
|
355
422
|
VALUE peer_bidi_stream_count_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("peer_bidi_stream_count")));
|
|
356
423
|
VALUE peer_unidi_stream_count_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("peer_unidi_stream_count")));
|
|
357
424
|
VALUE alpn_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("alpn")));
|
|
358
|
-
|
|
425
|
+
VALUE stream_recv_window_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("stream_recv_window")));
|
|
426
|
+
VALUE stream_recv_buffer_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("stream_recv_buffer")));
|
|
427
|
+
VALUE conn_flow_control_window_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("conn_flow_control_window")));
|
|
428
|
+
|
|
359
429
|
QUIC_STATUS Status;
|
|
360
430
|
HQUIC Configuration = NULL;
|
|
361
431
|
|
|
@@ -366,9 +436,12 @@ quicsilver_create_server_configuration(VALUE self, VALUE config_hash)
|
|
|
366
436
|
uint32_t peer_bidi_stream_count = NUM2INT(peer_bidi_stream_count_val);
|
|
367
437
|
uint32_t peer_unidi_stream_count = NUM2INT(peer_unidi_stream_count_val);
|
|
368
438
|
const char* alpn_str = StringValueCStr(alpn_val);
|
|
439
|
+
uint32_t stream_recv_window = NUM2UINT(stream_recv_window_val);
|
|
440
|
+
uint32_t stream_recv_buffer = NUM2UINT(stream_recv_buffer_val);
|
|
441
|
+
uint32_t conn_flow_control_window = NUM2UINT(conn_flow_control_window_val);
|
|
369
442
|
|
|
370
443
|
QUIC_SETTINGS Settings = {0};
|
|
371
|
-
Settings.IdleTimeoutMs = idle_timeout_ms;
|
|
444
|
+
Settings.IdleTimeoutMs = idle_timeout_ms;
|
|
372
445
|
Settings.IsSet.IdleTimeoutMs = TRUE;
|
|
373
446
|
Settings.ServerResumptionLevel = server_resumption_level;
|
|
374
447
|
Settings.IsSet.ServerResumptionLevel = TRUE;
|
|
@@ -377,6 +450,14 @@ quicsilver_create_server_configuration(VALUE self, VALUE config_hash)
|
|
|
377
450
|
Settings.PeerUnidiStreamCount = peer_unidi_stream_count;
|
|
378
451
|
Settings.IsSet.PeerUnidiStreamCount = TRUE;
|
|
379
452
|
|
|
453
|
+
// Flow control / backpressure settings
|
|
454
|
+
Settings.StreamRecvWindowDefault = stream_recv_window;
|
|
455
|
+
Settings.IsSet.StreamRecvWindowDefault = TRUE;
|
|
456
|
+
Settings.StreamRecvBufferDefault = stream_recv_buffer;
|
|
457
|
+
Settings.IsSet.StreamRecvBufferDefault = TRUE;
|
|
458
|
+
Settings.ConnFlowControlWindow = conn_flow_control_window;
|
|
459
|
+
Settings.IsSet.ConnFlowControlWindow = TRUE;
|
|
460
|
+
|
|
380
461
|
QUIC_BUFFER Alpn = { (uint32_t)strlen(alpn_str), (uint8_t*)alpn_str };
|
|
381
462
|
|
|
382
463
|
// Create configuration
|
|
@@ -408,35 +489,44 @@ quicsilver_create_server_configuration(VALUE self, VALUE config_hash)
|
|
|
408
489
|
|
|
409
490
|
// Create a QUIC connection with context
|
|
410
491
|
static VALUE
|
|
411
|
-
quicsilver_create_connection(VALUE self)
|
|
492
|
+
quicsilver_create_connection(VALUE self, VALUE client_obj)
|
|
412
493
|
{
|
|
413
494
|
if (MsQuic == NULL) {
|
|
414
495
|
rb_raise(rb_eRuntimeError, "MSQUIC not initialized. Call Quicsilver.open_connection first.");
|
|
415
496
|
return Qnil;
|
|
416
497
|
}
|
|
417
|
-
|
|
498
|
+
|
|
418
499
|
QUIC_STATUS Status;
|
|
419
500
|
HQUIC Connection = NULL;
|
|
420
|
-
|
|
501
|
+
|
|
421
502
|
// Allocate and initialize connection context
|
|
422
503
|
ConnectionContext* ctx = (ConnectionContext*)malloc(sizeof(ConnectionContext));
|
|
423
504
|
if (ctx == NULL) {
|
|
424
505
|
rb_raise(rb_eRuntimeError, "Failed to allocate connection context");
|
|
425
506
|
return Qnil;
|
|
426
507
|
}
|
|
427
|
-
|
|
508
|
+
|
|
428
509
|
ctx->connected = 0;
|
|
429
510
|
ctx->failed = 0;
|
|
430
511
|
ctx->error_status = QUIC_STATUS_SUCCESS;
|
|
431
512
|
ctx->error_code = 0;
|
|
432
|
-
|
|
513
|
+
ctx->client_obj = client_obj; // Store Ruby client object (Qnil for server)
|
|
514
|
+
|
|
515
|
+
// Protect from GC if it's a Ruby object
|
|
516
|
+
if (!NIL_P(client_obj)) {
|
|
517
|
+
rb_gc_register_address(&ctx->client_obj);
|
|
518
|
+
}
|
|
519
|
+
|
|
433
520
|
// Create connection with enhanced callback and context
|
|
434
521
|
if (QUIC_FAILED(Status = MsQuic->ConnectionOpen(Registration, ConnectionCallback, ctx, &Connection))) {
|
|
522
|
+
if (!NIL_P(client_obj)) {
|
|
523
|
+
rb_gc_unregister_address(&ctx->client_obj);
|
|
524
|
+
}
|
|
435
525
|
free(ctx);
|
|
436
526
|
rb_raise(rb_eRuntimeError, "ConnectionOpen failed, 0x%x!", Status);
|
|
437
527
|
return Qnil;
|
|
438
528
|
}
|
|
439
|
-
|
|
529
|
+
|
|
440
530
|
// Return both the connection handle and context as an array
|
|
441
531
|
VALUE result = rb_ary_new2(2);
|
|
442
532
|
rb_ary_push(result, ULL2NUM((uintptr_t)Connection));
|
|
@@ -522,27 +612,55 @@ quicsilver_close_connection_handle(VALUE self, VALUE connection_data)
|
|
|
522
612
|
if (MsQuic == NULL) {
|
|
523
613
|
return Qnil;
|
|
524
614
|
}
|
|
525
|
-
|
|
615
|
+
|
|
526
616
|
// Extract connection handle and context from array
|
|
527
617
|
VALUE connection_handle = rb_ary_entry(connection_data, 0);
|
|
528
618
|
VALUE context_handle = rb_ary_entry(connection_data, 1);
|
|
529
|
-
|
|
619
|
+
|
|
530
620
|
HQUIC Connection = (HQUIC)(uintptr_t)NUM2ULL(connection_handle);
|
|
531
621
|
ConnectionContext* ctx = (ConnectionContext*)(uintptr_t)NUM2ULL(context_handle);
|
|
532
|
-
|
|
533
|
-
// Only close if connection is valid
|
|
622
|
+
|
|
534
623
|
if (Connection != NULL) {
|
|
535
624
|
MsQuic->ConnectionClose(Connection);
|
|
536
625
|
}
|
|
537
|
-
|
|
626
|
+
|
|
538
627
|
// Free context if valid
|
|
539
628
|
if (ctx != NULL) {
|
|
629
|
+
// Unregister from GC if client object was set
|
|
630
|
+
if (!NIL_P(ctx->client_obj)) {
|
|
631
|
+
rb_gc_unregister_address(&ctx->client_obj);
|
|
632
|
+
}
|
|
540
633
|
free(ctx);
|
|
541
634
|
}
|
|
542
|
-
|
|
635
|
+
|
|
543
636
|
return Qnil;
|
|
544
637
|
}
|
|
545
638
|
|
|
639
|
+
// Gracefully shutdown a QUIC connection (sends CONNECTION_CLOSE frame to peer)
|
|
640
|
+
// silent = true: immediate shutdown without notifying peer
|
|
641
|
+
// silent = false: graceful shutdown with CONNECTION_CLOSE frame
|
|
642
|
+
static VALUE
|
|
643
|
+
quicsilver_connection_shutdown(VALUE self, VALUE connection_handle, VALUE error_code, VALUE silent)
|
|
644
|
+
{
|
|
645
|
+
if (MsQuic == NULL) {
|
|
646
|
+
rb_raise(rb_eRuntimeError, "MSQUIC not initialized.");
|
|
647
|
+
return Qnil;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
HQUIC Connection = (HQUIC)(uintptr_t)NUM2ULL(connection_handle);
|
|
651
|
+
uint64_t ErrorCode = NUM2ULL(error_code);
|
|
652
|
+
|
|
653
|
+
if (Connection != NULL) {
|
|
654
|
+
QUIC_CONNECTION_SHUTDOWN_FLAGS flags = RTEST(silent)
|
|
655
|
+
? QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT
|
|
656
|
+
: QUIC_CONNECTION_SHUTDOWN_FLAG_NONE;
|
|
657
|
+
|
|
658
|
+
MsQuic->ConnectionShutdown(Connection, flags, ErrorCode);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return Qtrue;
|
|
662
|
+
}
|
|
663
|
+
|
|
546
664
|
// Close a QUIC configuration
|
|
547
665
|
static VALUE
|
|
548
666
|
quicsilver_close_configuration(VALUE self, VALUE config_handle)
|
|
@@ -683,15 +801,22 @@ quicsilver_close_listener(VALUE self, VALUE listener_data)
|
|
|
683
801
|
}
|
|
684
802
|
|
|
685
803
|
// Open a QUIC stream
|
|
804
|
+
// Accepts connection_data array [connection_handle, context_handle]
|
|
805
|
+
// Works uniformly for both client and server
|
|
686
806
|
static VALUE
|
|
687
|
-
quicsilver_open_stream(VALUE self, VALUE
|
|
807
|
+
quicsilver_open_stream(VALUE self, VALUE connection_data, VALUE unidirectional)
|
|
688
808
|
{
|
|
689
809
|
if (MsQuic == NULL) {
|
|
690
810
|
rb_raise(rb_eRuntimeError, "MSQUIC not initialized.");
|
|
691
811
|
return Qnil;
|
|
692
812
|
}
|
|
693
813
|
|
|
814
|
+
// Extract connection handle and context from array
|
|
815
|
+
VALUE connection_handle = rb_ary_entry(connection_data, 0);
|
|
816
|
+
VALUE context_handle = rb_ary_entry(connection_data, 1);
|
|
817
|
+
|
|
694
818
|
HQUIC Connection = (HQUIC)(uintptr_t)NUM2ULL(connection_handle);
|
|
819
|
+
ConnectionContext* conn_ctx = (ConnectionContext*)(uintptr_t)NUM2ULL(context_handle);
|
|
695
820
|
HQUIC Stream = NULL;
|
|
696
821
|
|
|
697
822
|
StreamContext* ctx = (StreamContext*)malloc(sizeof(StreamContext));
|
|
@@ -699,7 +824,10 @@ quicsilver_open_stream(VALUE self, VALUE connection_handle, VALUE unidirectional
|
|
|
699
824
|
rb_raise(rb_eRuntimeError, "Failed to allocate stream context");
|
|
700
825
|
return Qnil;
|
|
701
826
|
}
|
|
702
|
-
|
|
827
|
+
|
|
828
|
+
ctx->connection = Connection;
|
|
829
|
+
ctx->connection_ctx = conn_ctx; // Store connection context pointer
|
|
830
|
+
ctx->client_obj = conn_ctx ? conn_ctx->client_obj : Qnil;
|
|
703
831
|
ctx->started = 1;
|
|
704
832
|
ctx->shutdown = 0;
|
|
705
833
|
ctx->error_status = QUIC_STATUS_SUCCESS;
|
|
@@ -785,11 +913,12 @@ Init_quicsilver(void)
|
|
|
785
913
|
rb_define_singleton_method(mQuicsilver, "create_server_configuration", quicsilver_create_server_configuration, 1);
|
|
786
914
|
rb_define_singleton_method(mQuicsilver, "close_configuration", quicsilver_close_configuration, 1);
|
|
787
915
|
|
|
788
|
-
// Connection management
|
|
789
|
-
rb_define_singleton_method(mQuicsilver, "create_connection", quicsilver_create_connection,
|
|
916
|
+
// Connection management
|
|
917
|
+
rb_define_singleton_method(mQuicsilver, "create_connection", quicsilver_create_connection, 1);
|
|
790
918
|
rb_define_singleton_method(mQuicsilver, "start_connection", quicsilver_start_connection, 4);
|
|
791
919
|
rb_define_singleton_method(mQuicsilver, "wait_for_connection", quicsilver_wait_for_connection, 2);
|
|
792
920
|
rb_define_singleton_method(mQuicsilver, "connection_status", quicsilver_connection_status, 1);
|
|
921
|
+
rb_define_singleton_method(mQuicsilver, "connection_shutdown", quicsilver_connection_shutdown, 3);
|
|
793
922
|
rb_define_singleton_method(mQuicsilver, "close_connection_handle", quicsilver_close_connection_handle, 1);
|
|
794
923
|
|
|
795
924
|
// Listener management
|