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.
@@ -0,0 +1,807 @@
1
+ #include <ruby.h>
2
+ #include "msquic.h"
3
+ #include <pthread.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+
7
+ static VALUE mQuicsilver;
8
+
9
+ // Event queue for callbacks
10
+ typedef struct CallbackEvent {
11
+ char* event_type;
12
+ uint64_t stream_id;
13
+ char* data;
14
+ size_t data_len;
15
+ struct CallbackEvent* next;
16
+ } CallbackEvent;
17
+
18
+ static CallbackEvent* event_queue_head = NULL;
19
+ static CallbackEvent* event_queue_tail = NULL;
20
+ static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
21
+
22
+ // Global MSQUIC API table
23
+ static const QUIC_API_TABLE* MsQuic = NULL;
24
+
25
+ // Global registration handle
26
+ static HQUIC Registration = NULL;
27
+
28
+ // Registration configuration
29
+ static const QUIC_REGISTRATION_CONFIG RegConfig = { "quicsilver", QUIC_EXECUTION_PROFILE_LOW_LATENCY };
30
+
31
+ // Connection state tracking
32
+ typedef struct {
33
+ int connected;
34
+ int failed;
35
+ QUIC_STATUS error_status;
36
+ uint64_t error_code;
37
+ } ConnectionContext;
38
+
39
+ // Listener state tracking
40
+ typedef struct {
41
+ int started;
42
+ int stopped;
43
+ int failed;
44
+ QUIC_STATUS error_status;
45
+ HQUIC Configuration;
46
+ } ListenerContext;
47
+
48
+ // Stream state tracking
49
+ typedef struct {
50
+ int started;
51
+ int shutdown;
52
+ QUIC_STATUS error_status;
53
+ } StreamContext;
54
+
55
+ // Enqueue a callback event (thread-safe)
56
+ static void
57
+ enqueue_callback_event(const char* event_type, uint64_t stream_id, const char* data, size_t data_len)
58
+ {
59
+ CallbackEvent* event = (CallbackEvent*)malloc(sizeof(CallbackEvent));
60
+ if (event == NULL) return;
61
+
62
+ event->event_type = strdup(event_type);
63
+ event->stream_id = stream_id;
64
+ event->data = (char*)malloc(data_len);
65
+ if (event->data != NULL) {
66
+ memcpy(event->data, data, data_len);
67
+ }
68
+ event->data_len = data_len;
69
+ event->next = NULL;
70
+
71
+ pthread_mutex_lock(&queue_mutex);
72
+ if (event_queue_tail == NULL) {
73
+ event_queue_head = event_queue_tail = event;
74
+ } else {
75
+ event_queue_tail->next = event;
76
+ event_queue_tail = event;
77
+ }
78
+ pthread_mutex_unlock(&queue_mutex);
79
+ }
80
+
81
+ // Process all pending callback events (called from Ruby)
82
+ static VALUE
83
+ quicsilver_process_events(VALUE self)
84
+ {
85
+ CallbackEvent* event;
86
+ VALUE server_class = rb_const_get(mQuicsilver, rb_intern("Server"));
87
+ int processed = 0;
88
+
89
+ while (1) {
90
+ pthread_mutex_lock(&queue_mutex);
91
+ event = event_queue_head;
92
+ if (event != NULL) {
93
+ event_queue_head = event->next;
94
+ if (event_queue_head == NULL) {
95
+ event_queue_tail = NULL;
96
+ }
97
+ }
98
+ pthread_mutex_unlock(&queue_mutex);
99
+
100
+ if (event == NULL) break;
101
+
102
+ if (!NIL_P(server_class)) {
103
+ rb_funcall(server_class, rb_intern("handle_stream"), 3,
104
+ ULL2NUM(event->stream_id),
105
+ rb_str_new_cstr(event->event_type),
106
+ rb_str_new(event->data, event->data_len));
107
+ processed++;
108
+ }
109
+
110
+ free(event->event_type);
111
+ free(event->data);
112
+ free(event);
113
+ }
114
+
115
+ return INT2NUM(processed);
116
+ }
117
+
118
+ QUIC_STATUS
119
+ StreamCallback(HQUIC Stream, void* Context, QUIC_STREAM_EVENT* Event)
120
+ {
121
+ StreamContext* ctx = (StreamContext*)Context;
122
+
123
+ if (ctx == NULL) {
124
+ return QUIC_STATUS_SUCCESS;
125
+ }
126
+
127
+ switch (Event->Type) {
128
+ case QUIC_STREAM_EVENT_RECEIVE:
129
+ // Client sent data - enqueue for Ruby processing
130
+ if (Event->RECEIVE.BufferCount > 0) {
131
+ const QUIC_BUFFER* buffer = &Event->RECEIVE.Buffers[0];
132
+ const char* event_type = (Event->RECEIVE.Flags & QUIC_RECEIVE_FLAG_FIN) ? "RECEIVE_FIN" : "RECEIVE";
133
+
134
+ // Get the QUIC protocol stream ID (0, 4, 8, 12...)
135
+ uint64_t stream_id = 0;
136
+ uint32_t stream_id_len = sizeof(stream_id);
137
+ MsQuic->GetParam(Stream, QUIC_PARAM_STREAM_ID, &stream_id_len, &stream_id);
138
+
139
+ // Pack stream handle pointer along with data for RECEIVE_FIN
140
+ if (Event->RECEIVE.Flags & QUIC_RECEIVE_FLAG_FIN) {
141
+ // Create combined buffer: [stream_handle(8 bytes)][data]
142
+ size_t total_len = sizeof(HQUIC) + buffer->Length;
143
+ char* combined = (char*)malloc(total_len);
144
+ if (combined != NULL) {
145
+ memcpy(combined, &Stream, sizeof(HQUIC));
146
+ memcpy(combined + sizeof(HQUIC), buffer->Buffer, buffer->Length);
147
+ enqueue_callback_event(event_type, stream_id, combined, total_len);
148
+ free(combined);
149
+ }
150
+ } else {
151
+ enqueue_callback_event(event_type, stream_id, (const char*)buffer->Buffer, buffer->Length);
152
+ }
153
+ }
154
+ break;
155
+ case QUIC_STREAM_EVENT_SEND_COMPLETE:
156
+ break;
157
+ case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
158
+ ctx->shutdown = 1;
159
+ break;
160
+ case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
161
+ break;
162
+ case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
163
+ break;
164
+ }
165
+
166
+ return QUIC_STATUS_SUCCESS;
167
+ }
168
+
169
+ // Connection callback
170
+ static QUIC_STATUS QUIC_API
171
+ ConnectionCallback(HQUIC Connection, void* Context, QUIC_CONNECTION_EVENT* Event)
172
+ {
173
+ ConnectionContext* ctx = (ConnectionContext*)Context;
174
+ HQUIC Stream;
175
+ StreamContext* stream_ctx;
176
+
177
+ if (ctx == NULL) {
178
+ return QUIC_STATUS_SUCCESS;
179
+ }
180
+
181
+ switch (Event->Type) {
182
+ case QUIC_CONNECTION_EVENT_CONNECTED:
183
+ ctx->connected = 1;
184
+ ctx->failed = 0;
185
+ // Notify Ruby about new connection
186
+ enqueue_callback_event("CONNECTION_ESTABLISHED", 0, (const char*)&Connection, sizeof(HQUIC));
187
+ break;
188
+ case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
189
+ ctx->connected = 0;
190
+ ctx->failed = 1;
191
+ ctx->error_status = Event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status;
192
+ ctx->error_code = Event->SHUTDOWN_INITIATED_BY_TRANSPORT.ErrorCode;
193
+ break;
194
+ case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER:
195
+ ctx->connected = 0;
196
+ ctx->failed = 1;
197
+ ctx->error_status = QUIC_STATUS_SUCCESS; // Peer initiated, not an error
198
+ ctx->error_code = Event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode;
199
+ break;
200
+ case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
201
+ ctx->connected = 0;
202
+ break;
203
+ case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED:
204
+ // Client opened a stream
205
+ Stream = Event->PEER_STREAM_STARTED.Stream;
206
+ stream_ctx = (StreamContext*)malloc(sizeof(StreamContext));
207
+ if (stream_ctx != NULL) {
208
+ stream_ctx->started = 1;
209
+ stream_ctx->shutdown = 0;
210
+ stream_ctx->error_status = QUIC_STATUS_SUCCESS;
211
+
212
+ // Set the stream callback handler to handle data events
213
+ MsQuic->SetCallbackHandler(Stream, (void*)StreamCallback, stream_ctx);
214
+ }
215
+ break;
216
+ default:
217
+ break;
218
+ }
219
+
220
+ return QUIC_STATUS_SUCCESS;
221
+ }
222
+
223
+ // Listener callback to handle incoming connections
224
+ static QUIC_STATUS QUIC_API
225
+ ListenerCallback(HQUIC Listener, void* Context, QUIC_LISTENER_EVENT* Event)
226
+ {
227
+ ListenerContext* ctx = (ListenerContext*)Context;
228
+ ConnectionContext* conn_ctx;
229
+
230
+ if (ctx == NULL) {
231
+ return QUIC_STATUS_SUCCESS;
232
+ }
233
+
234
+ switch (Event->Type) {
235
+ case QUIC_LISTENER_EVENT_NEW_CONNECTION:
236
+ // Create a connection context for the new connection
237
+ conn_ctx = (ConnectionContext*)malloc(sizeof(ConnectionContext));
238
+ if (conn_ctx != NULL) {
239
+ conn_ctx->connected = 0;
240
+ conn_ctx->failed = 0;
241
+ conn_ctx->error_status = QUIC_STATUS_SUCCESS;
242
+ conn_ctx->error_code = 0;
243
+
244
+ // Set the connection callback
245
+ MsQuic->SetCallbackHandler(Event->NEW_CONNECTION.Connection, (void*)ConnectionCallback, conn_ctx);
246
+
247
+ // Accept the new connection with the server configuration
248
+ QUIC_STATUS Status = MsQuic->ConnectionSetConfiguration(Event->NEW_CONNECTION.Connection, ctx->Configuration);
249
+ if (QUIC_FAILED(Status)) {
250
+ free(conn_ctx);
251
+ return Status;
252
+ }
253
+ } else {
254
+ // Reject the connection if we can't allocate context
255
+ return QUIC_STATUS_OUT_OF_MEMORY;
256
+ }
257
+ break;
258
+
259
+ case QUIC_LISTENER_EVENT_STOP_COMPLETE:
260
+ ctx->stopped = 1;
261
+ break;
262
+
263
+ default:
264
+ break;
265
+ }
266
+
267
+ return QUIC_STATUS_SUCCESS;
268
+ }
269
+
270
+ // Initialize MSQUIC
271
+ static VALUE
272
+ quicsilver_open(VALUE self)
273
+ {
274
+ QUIC_STATUS Status;
275
+
276
+ // Check if already initialized
277
+ if (MsQuic != NULL) {
278
+ return Qtrue;
279
+ }
280
+
281
+ // Open a handle to the library and get the API function table
282
+ if (QUIC_FAILED(Status = MsQuicOpenVersion(2, (const void**)&MsQuic))) {
283
+ rb_raise(rb_eRuntimeError, "MsQuicOpenVersion failed, 0x%x!", Status);
284
+ return Qfalse;
285
+ }
286
+
287
+ // Create a registration for the app's connections
288
+ if (QUIC_FAILED(Status = MsQuic->RegistrationOpen(&RegConfig, &Registration))) {
289
+ rb_raise(rb_eRuntimeError, "RegistrationOpen failed, 0x%x!", Status);
290
+ MsQuicClose(MsQuic);
291
+ MsQuic = NULL;
292
+ return Qfalse;
293
+ }
294
+
295
+ return Qtrue;
296
+ }
297
+
298
+ // Create a QUIC configuration (for client connections)
299
+ static VALUE
300
+ quicsilver_create_configuration(VALUE self, VALUE unsecure)
301
+ {
302
+ if (MsQuic == NULL) {
303
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized. Call Quicsilver.open_connection first.");
304
+ return Qnil;
305
+ }
306
+
307
+ QUIC_STATUS Status;
308
+ HQUIC Configuration = NULL;
309
+
310
+ // Basic settings
311
+ QUIC_SETTINGS Settings = {0};
312
+ Settings.IdleTimeoutMs = 10000; // 10 second idle timeout to match server
313
+ Settings.IsSet.IdleTimeoutMs = TRUE;
314
+
315
+ // Simple ALPN for now - Ruby can customize this later
316
+ QUIC_BUFFER Alpn = { sizeof("h3") - 1, (uint8_t*)"h3" };
317
+
318
+ // Create configuration
319
+ if (QUIC_FAILED(Status = MsQuic->ConfigurationOpen(Registration, &Alpn, 1, &Settings, sizeof(Settings), NULL, &Configuration))) {
320
+ rb_raise(rb_eRuntimeError, "ConfigurationOpen failed, 0x%x!", Status);
321
+ return Qnil;
322
+ }
323
+
324
+ // Set up credentials
325
+ QUIC_CREDENTIAL_CONFIG CredConfig = {0};
326
+ CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE;
327
+ CredConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT;
328
+
329
+ if (RTEST(unsecure)) {
330
+ CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION;
331
+ }
332
+
333
+ if (QUIC_FAILED(Status = MsQuic->ConfigurationLoadCredential(Configuration, &CredConfig))) {
334
+ rb_raise(rb_eRuntimeError, "ConfigurationLoadCredential failed, 0x%x!", Status);
335
+ MsQuic->ConfigurationClose(Configuration);
336
+ return Qnil;
337
+ }
338
+
339
+ // Return the configuration handle as a Ruby integer (pointer)
340
+ return ULL2NUM((uintptr_t)Configuration);
341
+ }
342
+
343
+ // Create a QUIC server configuration
344
+ static VALUE
345
+ quicsilver_create_server_configuration(VALUE self, VALUE config_hash)
346
+ {
347
+ if (MsQuic == NULL) {
348
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized. Call Quicsilver.open_connection first.");
349
+ return Qnil;
350
+ }
351
+ VALUE cert_file_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("cert_file")));
352
+ VALUE key_file_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("key_file")));
353
+ VALUE idle_timeout_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("idle_timeout")));
354
+ VALUE server_resumption_level_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("server_resumption_level")));
355
+ VALUE peer_bidi_stream_count_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("peer_bidi_stream_count")));
356
+ VALUE peer_unidi_stream_count_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("peer_unidi_stream_count")));
357
+ VALUE alpn_val = rb_hash_aref(config_hash, ID2SYM(rb_intern("alpn")));
358
+
359
+ QUIC_STATUS Status;
360
+ HQUIC Configuration = NULL;
361
+
362
+ const char* cert_path = StringValueCStr(cert_file_val);
363
+ const char* key_path = StringValueCStr(key_file_val);
364
+ uint32_t idle_timeout_ms = NUM2INT(idle_timeout_val);
365
+ uint32_t server_resumption_level = NUM2INT(server_resumption_level_val);
366
+ uint32_t peer_bidi_stream_count = NUM2INT(peer_bidi_stream_count_val);
367
+ uint32_t peer_unidi_stream_count = NUM2INT(peer_unidi_stream_count_val);
368
+ const char* alpn_str = StringValueCStr(alpn_val);
369
+
370
+ QUIC_SETTINGS Settings = {0};
371
+ Settings.IdleTimeoutMs = idle_timeout_ms;
372
+ Settings.IsSet.IdleTimeoutMs = TRUE;
373
+ Settings.ServerResumptionLevel = server_resumption_level;
374
+ Settings.IsSet.ServerResumptionLevel = TRUE;
375
+ Settings.PeerBidiStreamCount = peer_bidi_stream_count;
376
+ Settings.IsSet.PeerBidiStreamCount = TRUE;
377
+ Settings.PeerUnidiStreamCount = peer_unidi_stream_count;
378
+ Settings.IsSet.PeerUnidiStreamCount = TRUE;
379
+
380
+ QUIC_BUFFER Alpn = { (uint32_t)strlen(alpn_str), (uint8_t*)alpn_str };
381
+
382
+ // Create configuration
383
+ if (QUIC_FAILED(Status = MsQuic->ConfigurationOpen(Registration, &Alpn, 1, &Settings, sizeof(Settings), NULL, &Configuration))) {
384
+ rb_raise(rb_eRuntimeError, "Server ConfigurationOpen failed, 0x%x!", Status);
385
+ return Qnil;
386
+ }
387
+
388
+ // Set up server credentials with certificate files
389
+ QUIC_CREDENTIAL_CONFIG CredConfig = {0};
390
+ QUIC_CERTIFICATE_FILE CertFile = {0};
391
+
392
+ CertFile.CertificateFile = cert_path;
393
+ CertFile.PrivateKeyFile = key_path;
394
+
395
+ CredConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE;
396
+ CredConfig.CertificateFile = &CertFile;
397
+ CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION;
398
+
399
+ if (QUIC_FAILED(Status = MsQuic->ConfigurationLoadCredential(Configuration, &CredConfig))) {
400
+ rb_raise(rb_eRuntimeError, "Server ConfigurationLoadCredential failed, 0x%x!", Status);
401
+ MsQuic->ConfigurationClose(Configuration);
402
+ return Qnil;
403
+ }
404
+
405
+ // Return the configuration handle as a Ruby integer (pointer)
406
+ return ULL2NUM((uintptr_t)Configuration);
407
+ }
408
+
409
+ // Create a QUIC connection with context
410
+ static VALUE
411
+ quicsilver_create_connection(VALUE self)
412
+ {
413
+ if (MsQuic == NULL) {
414
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized. Call Quicsilver.open_connection first.");
415
+ return Qnil;
416
+ }
417
+
418
+ QUIC_STATUS Status;
419
+ HQUIC Connection = NULL;
420
+
421
+ // Allocate and initialize connection context
422
+ ConnectionContext* ctx = (ConnectionContext*)malloc(sizeof(ConnectionContext));
423
+ if (ctx == NULL) {
424
+ rb_raise(rb_eRuntimeError, "Failed to allocate connection context");
425
+ return Qnil;
426
+ }
427
+
428
+ ctx->connected = 0;
429
+ ctx->failed = 0;
430
+ ctx->error_status = QUIC_STATUS_SUCCESS;
431
+ ctx->error_code = 0;
432
+
433
+ // Create connection with enhanced callback and context
434
+ if (QUIC_FAILED(Status = MsQuic->ConnectionOpen(Registration, ConnectionCallback, ctx, &Connection))) {
435
+ free(ctx);
436
+ rb_raise(rb_eRuntimeError, "ConnectionOpen failed, 0x%x!", Status);
437
+ return Qnil;
438
+ }
439
+
440
+ // Return both the connection handle and context as an array
441
+ VALUE result = rb_ary_new2(2);
442
+ rb_ary_push(result, ULL2NUM((uintptr_t)Connection));
443
+ rb_ary_push(result, ULL2NUM((uintptr_t)ctx));
444
+ return result;
445
+ }
446
+
447
+ // Start a QUIC connection
448
+ static VALUE
449
+ quicsilver_start_connection(VALUE self, VALUE connection_handle, VALUE config_handle, VALUE hostname, VALUE port)
450
+ {
451
+ if (MsQuic == NULL) {
452
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized.");
453
+ return Qfalse;
454
+ }
455
+
456
+ HQUIC Connection = (HQUIC)(uintptr_t)NUM2ULL(connection_handle);
457
+ HQUIC Configuration = (HQUIC)(uintptr_t)NUM2ULL(config_handle);
458
+ const char* Target = StringValueCStr(hostname);
459
+ uint16_t Port = (uint16_t)NUM2INT(port);
460
+
461
+ QUIC_STATUS Status;
462
+ if (QUIC_FAILED(Status = MsQuic->ConnectionStart(Connection, Configuration, QUIC_ADDRESS_FAMILY_UNSPEC, Target, Port))) {
463
+ rb_raise(rb_eRuntimeError, "ConnectionStart failed, 0x%x!", Status);
464
+ return Qfalse;
465
+ }
466
+
467
+ return Qtrue;
468
+ }
469
+
470
+ // Wait for connection to complete (connected or failed)
471
+ static VALUE
472
+ quicsilver_wait_for_connection(VALUE self, VALUE context_handle, VALUE timeout_ms)
473
+ {
474
+ ConnectionContext* ctx = (ConnectionContext*)(uintptr_t)NUM2ULL(context_handle);
475
+ int timeout = NUM2INT(timeout_ms);
476
+ int elapsed = 0;
477
+ const int sleep_interval = 10; // 10ms
478
+
479
+ while (elapsed < timeout && !ctx->connected && !ctx->failed) {
480
+ usleep(sleep_interval * 1000); // Convert to microseconds
481
+ elapsed += sleep_interval;
482
+ }
483
+
484
+ if (ctx->connected) {
485
+ return rb_hash_new();
486
+ } else if (ctx->failed) {
487
+ VALUE error_info = rb_hash_new();
488
+ rb_hash_aset(error_info, rb_str_new_cstr("error"), Qtrue);
489
+ rb_hash_aset(error_info, rb_str_new_cstr("status"), ULL2NUM(ctx->error_status));
490
+ rb_hash_aset(error_info, rb_str_new_cstr("code"), ULL2NUM(ctx->error_code));
491
+
492
+ return error_info;
493
+ } else {
494
+ VALUE timeout_info = rb_hash_new();
495
+ rb_hash_aset(timeout_info, rb_str_new_cstr("timeout"), Qtrue);
496
+ return timeout_info;
497
+ }
498
+ }
499
+
500
+ // Check connection status
501
+ static VALUE
502
+ quicsilver_connection_status(VALUE self, VALUE context_handle)
503
+ {
504
+ ConnectionContext* ctx = (ConnectionContext*)(uintptr_t)NUM2ULL(context_handle);
505
+
506
+ VALUE status = rb_hash_new();
507
+ rb_hash_aset(status, rb_str_new_cstr("connected"), ctx->connected ? Qtrue : Qfalse);
508
+ rb_hash_aset(status, rb_str_new_cstr("failed"), ctx->failed ? Qtrue : Qfalse);
509
+
510
+ if (ctx->failed) {
511
+ rb_hash_aset(status, rb_str_new_cstr("error_status"), ULL2NUM(ctx->error_status));
512
+ rb_hash_aset(status, rb_str_new_cstr("error_code"), ULL2NUM(ctx->error_code));
513
+ }
514
+
515
+ return status;
516
+ }
517
+
518
+ // Close a QUIC connection and free context
519
+ static VALUE
520
+ quicsilver_close_connection_handle(VALUE self, VALUE connection_data)
521
+ {
522
+ if (MsQuic == NULL) {
523
+ return Qnil;
524
+ }
525
+
526
+ // Extract connection handle and context from array
527
+ VALUE connection_handle = rb_ary_entry(connection_data, 0);
528
+ VALUE context_handle = rb_ary_entry(connection_data, 1);
529
+
530
+ HQUIC Connection = (HQUIC)(uintptr_t)NUM2ULL(connection_handle);
531
+ ConnectionContext* ctx = (ConnectionContext*)(uintptr_t)NUM2ULL(context_handle);
532
+
533
+ // Only close if connection is valid
534
+ if (Connection != NULL) {
535
+ MsQuic->ConnectionClose(Connection);
536
+ }
537
+
538
+ // Free context if valid
539
+ if (ctx != NULL) {
540
+ free(ctx);
541
+ }
542
+
543
+ return Qnil;
544
+ }
545
+
546
+ // Close a QUIC configuration
547
+ static VALUE
548
+ quicsilver_close_configuration(VALUE self, VALUE config_handle)
549
+ {
550
+ if (MsQuic == NULL) {
551
+ return Qnil;
552
+ }
553
+
554
+ HQUIC Configuration = (HQUIC)(uintptr_t)NUM2ULL(config_handle);
555
+ MsQuic->ConfigurationClose(Configuration);
556
+ return Qnil;
557
+ }
558
+
559
+ // Close MSQUIC
560
+ static VALUE
561
+ quicsilver_close(VALUE self)
562
+ {
563
+ if (MsQuic != NULL) {
564
+ if (Registration != NULL) {
565
+ // This will block until all outstanding child objects have been closed
566
+ MsQuic->RegistrationClose(Registration);
567
+ Registration = NULL;
568
+ }
569
+ MsQuicClose(MsQuic);
570
+ MsQuic = NULL;
571
+ }
572
+
573
+ return Qnil;
574
+ }
575
+
576
+ // Create a QUIC listener
577
+ static VALUE
578
+ quicsilver_create_listener(VALUE self, VALUE config_handle)
579
+ {
580
+ if (MsQuic == NULL) {
581
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized.");
582
+ return Qnil;
583
+ }
584
+
585
+ HQUIC Configuration = (HQUIC)(uintptr_t)NUM2ULL(config_handle);
586
+ HQUIC Listener = NULL;
587
+ QUIC_STATUS Status;
588
+
589
+ // Create listener context
590
+ ListenerContext* ctx = (ListenerContext*)malloc(sizeof(ListenerContext));
591
+ if (ctx == NULL) {
592
+ rb_raise(rb_eRuntimeError, "Failed to allocate listener context");
593
+ return Qnil;
594
+ }
595
+
596
+ ctx->started = 0;
597
+ ctx->stopped = 0;
598
+ ctx->failed = 0;
599
+ ctx->error_status = QUIC_STATUS_SUCCESS;
600
+ ctx->Configuration = Configuration;
601
+
602
+ // Create listener
603
+ if (QUIC_FAILED(Status = MsQuic->ListenerOpen(Registration, ListenerCallback, ctx, &Listener))) {
604
+ free(ctx);
605
+ rb_raise(rb_eRuntimeError, "ListenerOpen failed, 0x%x!", Status);
606
+ return Qnil;
607
+ }
608
+
609
+ // Return listener handle and context
610
+ VALUE result = rb_ary_new2(2);
611
+ rb_ary_push(result, ULL2NUM((uintptr_t)Listener));
612
+ rb_ary_push(result, ULL2NUM((uintptr_t)ctx));
613
+ return result;
614
+ }
615
+
616
+ // Start listener on specific address and port
617
+ static VALUE
618
+ quicsilver_start_listener(VALUE self, VALUE listener_handle, VALUE address, VALUE port)
619
+ {
620
+ if (MsQuic == NULL) {
621
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized.");
622
+ return Qfalse;
623
+ }
624
+
625
+ HQUIC Listener = (HQUIC)(uintptr_t)NUM2ULL(listener_handle);
626
+ uint16_t Port = (uint16_t)NUM2INT(port);
627
+
628
+ // Setup address - properly initialize the entire structure
629
+ QUIC_ADDR Address;
630
+ memset(&Address, 0, sizeof(Address));
631
+
632
+ // Set up for localhost/any address
633
+ QuicAddrSetFamily(&Address, QUIC_ADDRESS_FAMILY_INET);
634
+ QuicAddrSetPort(&Address, Port);
635
+
636
+ QUIC_STATUS Status;
637
+
638
+ // Create QUIC_BUFFER for the address
639
+ QUIC_BUFFER AlpnBuffer = { sizeof("h3") - 1, (uint8_t*)"h3" };
640
+
641
+ if (QUIC_FAILED(Status = MsQuic->ListenerStart(Listener, &AlpnBuffer, 1, &Address))) {
642
+ rb_raise(rb_eRuntimeError, "ListenerStart failed, 0x%x!", Status);
643
+ return Qfalse;
644
+ }
645
+
646
+ return Qtrue;
647
+ }
648
+
649
+ // Stop listener
650
+ static VALUE
651
+ quicsilver_stop_listener(VALUE self, VALUE listener_handle)
652
+ {
653
+ if (MsQuic == NULL) {
654
+ return Qfalse;
655
+ }
656
+
657
+ HQUIC Listener = (HQUIC)(uintptr_t)NUM2ULL(listener_handle);
658
+ MsQuic->ListenerStop(Listener);
659
+ return Qtrue;
660
+ }
661
+
662
+ // Close listener
663
+ static VALUE
664
+ quicsilver_close_listener(VALUE self, VALUE listener_data)
665
+ {
666
+ if (MsQuic == NULL) {
667
+ return Qnil;
668
+ }
669
+
670
+ VALUE listener_handle = rb_ary_entry(listener_data, 0);
671
+ VALUE context_handle = rb_ary_entry(listener_data, 1);
672
+
673
+ HQUIC Listener = (HQUIC)(uintptr_t)NUM2ULL(listener_handle);
674
+ ListenerContext* ctx = (ListenerContext*)(uintptr_t)NUM2ULL(context_handle);
675
+
676
+ MsQuic->ListenerClose(Listener);
677
+
678
+ if (ctx != NULL) {
679
+ free(ctx);
680
+ }
681
+
682
+ return Qnil;
683
+ }
684
+
685
+ // Open a QUIC stream
686
+ static VALUE
687
+ quicsilver_open_stream(VALUE self, VALUE connection_handle, VALUE unidirectional)
688
+ {
689
+ if (MsQuic == NULL) {
690
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized.");
691
+ return Qnil;
692
+ }
693
+
694
+ HQUIC Connection = (HQUIC)(uintptr_t)NUM2ULL(connection_handle);
695
+ HQUIC Stream = NULL;
696
+
697
+ StreamContext* ctx = (StreamContext*)malloc(sizeof(StreamContext));
698
+ if (ctx == NULL) {
699
+ rb_raise(rb_eRuntimeError, "Failed to allocate stream context");
700
+ return Qnil;
701
+ }
702
+
703
+ ctx->started = 1;
704
+ ctx->shutdown = 0;
705
+ ctx->error_status = QUIC_STATUS_SUCCESS;
706
+
707
+ // Use flag based on parameter
708
+ QUIC_STREAM_OPEN_FLAGS flags = RTEST(unidirectional)
709
+ ? QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL
710
+ : QUIC_STREAM_OPEN_FLAG_NONE;
711
+
712
+ // Create stream
713
+ QUIC_STATUS Status = MsQuic->StreamOpen(Connection, flags, StreamCallback, ctx, &Stream);
714
+ if (QUIC_FAILED(Status)) {
715
+ free(ctx);
716
+ rb_raise(rb_eRuntimeError, "StreamOpen failed, 0x%x!", Status);
717
+ return Qnil;
718
+ }
719
+
720
+ // Start the stream
721
+ Status = MsQuic->StreamStart(Stream, QUIC_STREAM_START_FLAG_NONE);
722
+ if (QUIC_FAILED(Status)) {
723
+ free(ctx);
724
+ MsQuic->StreamClose(Stream);
725
+ rb_raise(rb_eRuntimeError, "StreamStart failed, 0x%x!", Status);
726
+ return Qnil;
727
+ }
728
+
729
+ return ULL2NUM((uintptr_t)Stream);
730
+ }
731
+
732
+ // Send data on a QUIC stream
733
+ static VALUE
734
+ quicsilver_send_stream(VALUE self, VALUE stream_handle, VALUE data, VALUE send_fin)
735
+ {
736
+ if (MsQuic == NULL) {
737
+ rb_raise(rb_eRuntimeError, "MSQUIC not initialized.");
738
+ return Qnil;
739
+ }
740
+
741
+ HQUIC Stream = (HQUIC)(uintptr_t)NUM2ULL(stream_handle);
742
+ // Use StringValuePtr and RSTRING_LEN for binary data with null bytes
743
+ const char* data_str = RSTRING_PTR(data);
744
+ uint32_t data_len = (uint32_t)RSTRING_LEN(data);
745
+
746
+ void* SendBufferRaw = malloc(sizeof(QUIC_BUFFER) + data_len);
747
+ if (SendBufferRaw == NULL) {
748
+ rb_raise(rb_eRuntimeError, "SendBuffer allocation failed!");
749
+ return Qnil;
750
+ }
751
+
752
+ QUIC_BUFFER* SendBuffer = (QUIC_BUFFER*)SendBufferRaw;
753
+ SendBuffer->Buffer = (uint8_t*)SendBufferRaw + sizeof(QUIC_BUFFER);
754
+ SendBuffer->Length = data_len;
755
+
756
+ memcpy(SendBuffer->Buffer, data_str, data_len);
757
+
758
+ // Use flag based on parameter (default to FIN for backwards compat)
759
+ QUIC_SEND_FLAGS flags = (NIL_P(send_fin) || RTEST(send_fin))
760
+ ? QUIC_SEND_FLAG_FIN
761
+ : QUIC_SEND_FLAG_NONE;
762
+
763
+ QUIC_STATUS Status = MsQuic->StreamSend(Stream, SendBuffer, 1, flags, SendBufferRaw);
764
+ if (QUIC_FAILED(Status)) {
765
+ free(SendBufferRaw);
766
+ rb_raise(rb_eRuntimeError, "StreamSend failed, 0x%x!", Status);
767
+ return Qfalse;
768
+ }
769
+
770
+ return Qtrue;
771
+ }
772
+
773
+ // Initialize the extension
774
+ void
775
+ Init_quicsilver(void)
776
+ {
777
+ mQuicsilver = rb_define_module("Quicsilver");
778
+
779
+ // Core initialization
780
+ rb_define_singleton_method(mQuicsilver, "open_connection", quicsilver_open, 0);
781
+ rb_define_singleton_method(mQuicsilver, "close_connection", quicsilver_close, 0);
782
+
783
+ // Configuration management
784
+ rb_define_singleton_method(mQuicsilver, "create_configuration", quicsilver_create_configuration, 1);
785
+ rb_define_singleton_method(mQuicsilver, "create_server_configuration", quicsilver_create_server_configuration, 1);
786
+ rb_define_singleton_method(mQuicsilver, "close_configuration", quicsilver_close_configuration, 1);
787
+
788
+ // Connection management
789
+ rb_define_singleton_method(mQuicsilver, "create_connection", quicsilver_create_connection, 0);
790
+ rb_define_singleton_method(mQuicsilver, "start_connection", quicsilver_start_connection, 4);
791
+ rb_define_singleton_method(mQuicsilver, "wait_for_connection", quicsilver_wait_for_connection, 2);
792
+ rb_define_singleton_method(mQuicsilver, "connection_status", quicsilver_connection_status, 1);
793
+ rb_define_singleton_method(mQuicsilver, "close_connection_handle", quicsilver_close_connection_handle, 1);
794
+
795
+ // Listener management
796
+ rb_define_singleton_method(mQuicsilver, "create_listener", quicsilver_create_listener, 1);
797
+ rb_define_singleton_method(mQuicsilver, "start_listener", quicsilver_start_listener, 3);
798
+ rb_define_singleton_method(mQuicsilver, "stop_listener", quicsilver_stop_listener, 1);
799
+ rb_define_singleton_method(mQuicsilver, "close_listener", quicsilver_close_listener, 1);
800
+
801
+ // Stream management
802
+ rb_define_singleton_method(mQuicsilver, "open_stream", quicsilver_open_stream, 2);
803
+ rb_define_singleton_method(mQuicsilver, "send_stream", quicsilver_send_stream, 3);
804
+
805
+ // Event processing
806
+ rb_define_singleton_method(mQuicsilver, "process_events", quicsilver_process_events, 0);
807
+ }