nng 0.1.0 → 1.0.1

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.
data/ext/rbnng/socket.c CHANGED
@@ -1,126 +1,696 @@
1
- /*
2
- * Copyright (c) 2021 Adib Saad
3
- *
4
- */
5
- #include "socket.h"
6
- #include "msg.h"
7
1
  #include "rbnng.h"
8
- #include <ruby.h>
9
- #include <ruby/thread.h>
2
+
3
+ #include <unistd.h>
4
+ #include <fcntl.h>
5
+
6
+ #include <nng/protocol/bus0/bus.h>
7
+ #include <nng/protocol/pair0/pair.h>
8
+ #include <nng/protocol/pair1/pair.h>
9
+ #include <nng/protocol/pubsub0/pub.h>
10
+ #include <nng/protocol/pubsub0/sub.h>
11
+ #include <nng/protocol/pipeline0/pull.h>
12
+ #include <nng/protocol/pipeline0/push.h>
13
+ #include <nng/protocol/reqrep0/rep.h>
14
+ #include <nng/protocol/reqrep0/req.h>
15
+ #include <nng/protocol/survey0/respond.h>
16
+ #include <nng/protocol/survey0/survey.h>
17
+
18
+ /* ── TypedData ──────────────────────────────────────────────────── */
10
19
 
11
20
  static void
12
- socket_free(void* ptr)
21
+ socket_free(void *ptr)
13
22
  {
14
- RbnngSocket* p_rbnngSocket = (RbnngSocket*)ptr;
15
- nng_close(p_rbnngSocket->socket);
16
- xfree(p_rbnngSocket);
23
+ rbnng_socket_t *s = ptr;
24
+ if (s->initialized)
25
+ nng_close(s->socket);
26
+ if (s->notify_fds[0] >= 0) close(s->notify_fds[0]);
27
+ if (s->notify_fds[1] >= 0) close(s->notify_fds[1]);
28
+ xfree(s);
17
29
  }
18
30
 
19
- VALUE
31
+ static size_t
32
+ socket_memsize(const void *ptr)
33
+ {
34
+ return sizeof(rbnng_socket_t);
35
+ }
36
+
37
+ const rb_data_type_t rbnng_socket_type = {
38
+ .wrap_struct_name = "NNG::Socket::Base",
39
+ .function = {
40
+ .dmark = NULL,
41
+ .dfree = socket_free,
42
+ .dsize = socket_memsize,
43
+ },
44
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
45
+ };
46
+
47
+ static VALUE
20
48
  socket_alloc(VALUE klass)
21
49
  {
22
- RbnngSocket* p_rbnngSocket = ZALLOC(RbnngSocket);
23
- return rb_data_object_wrap(klass, p_rbnngSocket, 0, socket_free);
50
+ rbnng_socket_t *s;
51
+ VALUE obj = TypedData_Make_Struct(klass, rbnng_socket_t, &rbnng_socket_type, s);
52
+ s->notify_fds[0] = -1;
53
+ s->notify_fds[1] = -1;
54
+ return obj;
24
55
  }
25
56
 
26
- void*
27
- socket_get_msg_blocking(RbnngSocket* p_rbnngSocket)
57
+ static inline rbnng_socket_t *
58
+ socket_get(VALUE self)
28
59
  {
29
- nng_msg* p_msg = NULL;
30
- int rv;
31
- if ((rv = nng_recvmsg(p_rbnngSocket->socket, &p_msg, 0)) != 0) {
32
- return rv;
33
- }
60
+ rbnng_socket_t *s;
61
+ TypedData_Get_Struct(self, rbnng_socket_t, &rbnng_socket_type, s);
62
+ if (!s->initialized)
63
+ rb_raise(rb_eRuntimeError, "socket not initialized");
64
+ return s;
65
+ }
66
+
67
+ /* ── GVL release helpers ────────────────────────────────────────── */
68
+
69
+ typedef struct {
70
+ nng_socket socket;
71
+ nng_msg *msg;
72
+ int rv;
73
+ } recv_args_t;
34
74
 
35
- p_rbnngSocket->p_getMsgResult = p_msg;
36
- return 0;
75
+ static void *
76
+ recv_without_gvl(void *arg)
77
+ {
78
+ recv_args_t *a = arg;
79
+ a->msg = NULL;
80
+ a->rv = nng_recvmsg(a->socket, &a->msg, 0);
81
+ return NULL;
37
82
  }
38
83
 
39
- VALUE
40
- socket_get_msg(VALUE self)
84
+ typedef struct {
85
+ nng_socket socket;
86
+ nng_msg *msg;
87
+ int rv;
88
+ } send_args_t;
89
+
90
+ static void *
91
+ send_without_gvl(void *arg)
41
92
  {
42
- RbnngSocket* p_rbnngSocket;
43
- Data_Get_Struct(self, RbnngSocket, p_rbnngSocket);
93
+ send_args_t *a = arg;
94
+ a->rv = nng_sendmsg(a->socket, a->msg, 0);
95
+ return NULL;
96
+ }
44
97
 
45
- int rv =
46
- rb_thread_call_without_gvl(socket_get_msg_blocking, p_rbnngSocket, 0, 0);
98
+ /* ── Shared methods (defined on Base) ───────────────────────────── */
47
99
 
48
- if (rv == 0) {
49
- RbnngMsg* p_newMsg;
50
- VALUE newMsg = rb_class_new_instance(0, 0, rbnng_MsgClass);
51
- Data_Get_Struct(newMsg, RbnngMsg, p_newMsg);
52
- p_newMsg->p_msg = p_rbnngSocket->p_getMsgResult;
53
- return newMsg;
54
- } else {
55
- raise_error(rv);
100
+ static VALUE
101
+ socket_close(VALUE self)
102
+ {
103
+ rbnng_socket_t *s = socket_get(self);
104
+ int rv = nng_close(s->socket);
105
+ if (rv != 0)
106
+ raise_nng_error(rv);
56
107
  return Qnil;
57
- }
58
108
  }
59
109
 
60
- void*
61
- socket_send_msg_blocking(void* data)
110
+ static VALUE
111
+ socket_listen(VALUE self, VALUE url)
62
112
  {
63
- RbnngSendMsgReq* p_sendMsgReq = (RbnngSendMsgReq*)data;
64
- RbnngSocket* p_rbnngSocket;
65
- Data_Get_Struct(p_sendMsgReq->socketObj, RbnngSocket, p_rbnngSocket);
66
- int rv;
67
- nng_msg* p_msg;
68
- if ((rv = nng_msg_alloc(&p_msg, 0)) != 0) {
69
- return rv;
70
- }
113
+ rbnng_socket_t *s = socket_get(self);
114
+ nng_listener lp;
115
+ int rv = nng_listen(s->socket, StringValueCStr(url), &lp, 0);
116
+ if (rv != 0)
117
+ raise_nng_error(rv);
71
118
 
72
- rv = nng_msg_append(p_msg,
73
- StringValuePtr(p_sendMsgReq->nextMsg),
74
- RSTRING_LEN(p_sendMsgReq->nextMsg));
75
- if (rv != 0) {
76
- return rv;
77
- }
119
+ /* Try to get the resolved URL from the listener (e.g. tcp://host:0 → actual port) */
120
+ char *resolved;
121
+ rv = nng_listener_get_string(lp, NNG_OPT_URL, &resolved);
122
+ if (rv == 0) {
123
+ int is_abstract_auto = (strcmp(resolved, "abstract://") == 0);
124
+ VALUE result = rb_str_new_cstr(resolved);
125
+ nng_strfree(resolved);
78
126
 
79
- // nng_sendmsg takes ownership of p_msg, so no need to free it afterwards.
80
- if ((rv = nng_sendmsg(p_rbnngSocket->socket, p_msg, 0)) != 0) {
81
- return rv;
82
- }
127
+ /* For abstract:// with auto-generated name, the URL stays "abstract://"
128
+ * but the actual name is in LOCADDR */
129
+ if (is_abstract_auto) {
130
+ nng_sockaddr sa;
131
+ rv = nng_listener_get_addr(lp, NNG_OPT_LOCADDR, &sa);
132
+ if (rv == 0 && sa.s_family == NNG_AF_ABSTRACT && sa.s_abstract.sa_len > 0) {
133
+ char buf[128];
134
+ int n = snprintf(buf, sizeof(buf), "abstract://%.*s",
135
+ (int)sa.s_abstract.sa_len,
136
+ (const char *)sa.s_abstract.sa_name);
137
+ return rb_str_new(buf, n);
138
+ }
139
+ }
83
140
 
84
- return 0;
141
+ return result;
142
+ }
143
+ return Qnil;
85
144
  }
86
145
 
87
- VALUE
88
- socket_send_msg(VALUE self, VALUE rb_strMsg)
146
+ static VALUE
147
+ socket_dial(VALUE self, VALUE url)
89
148
  {
90
- Check_Type(rb_strMsg, T_STRING);
91
- RbnngSendMsgReq sendMsgReq = {
92
- .socketObj = self,
93
- .nextMsg = rb_strMsg,
94
- };
95
- int rv =
96
- rb_thread_call_without_gvl(socket_send_msg_blocking, &sendMsgReq, 0, 0);
97
- if (rv != 0) {
98
- raise_error(rv);
99
- }
149
+ rbnng_socket_t *s = socket_get(self);
150
+ int rv = nng_dial(s->socket, StringValueCStr(url), NULL, 0);
151
+ if (rv != 0)
152
+ raise_nng_error(rv);
153
+ return Qnil;
100
154
  }
101
155
 
102
- VALUE
103
- socket_dial(VALUE self, VALUE url)
156
+ /* _listen_tls(url, cert_pem, key_pem, ca_pem, verify, server_name) */
157
+ static VALUE
158
+ socket_listen_tls(VALUE self, VALUE url, VALUE cert_pem, VALUE key_pem,
159
+ VALUE ca_pem, VALUE verify, VALUE server_name)
104
160
  {
105
- Check_Type(url, T_STRING);
106
- RbnngSocket* p_rbnngSocket;
107
- Data_Get_Struct(self, RbnngSocket, p_rbnngSocket);
108
- int rv;
109
- if ((rv = nng_dial(p_rbnngSocket->socket, StringValueCStr(url), 0, 0)) != 0) {
110
- raise_error(rv);
111
- }
161
+ rbnng_socket_t *s = socket_get(self);
162
+ rbnng_tls_listen(s->socket, StringValueCStr(url),
163
+ cert_pem, key_pem, ca_pem,
164
+ RTEST(verify), server_name);
165
+ return Qnil;
112
166
  }
113
167
 
114
- VALUE
115
- socket_listen(VALUE self, VALUE url)
168
+ /* _dial_tls(url, cert_pem, key_pem, ca_pem, verify, server_name) */
169
+ static VALUE
170
+ socket_dial_tls(VALUE self, VALUE url, VALUE cert_pem, VALUE key_pem,
171
+ VALUE ca_pem, VALUE verify, VALUE server_name)
172
+ {
173
+ rbnng_socket_t *s = socket_get(self);
174
+ rbnng_tls_dial(s->socket, StringValueCStr(url),
175
+ cert_pem, key_pem, ca_pem,
176
+ RTEST(verify), server_name);
177
+ return Qnil;
178
+ }
179
+
180
+ static VALUE
181
+ socket_receive(VALUE self)
182
+ {
183
+ rbnng_socket_t *s = socket_get(self);
184
+ nng_msg *msg = NULL;
185
+
186
+ /* Fast path: try non-blocking (no GVL release needed) */
187
+ int rv = nng_recvmsg(s->socket, &msg, NNG_FLAG_NONBLOCK);
188
+ if (rv == 0)
189
+ return rbnng_msg_wrap(msg);
190
+
191
+ if (rv != NNG_EAGAIN)
192
+ raise_nng_error(rv);
193
+
194
+ /* Slow path: nothing ready, release GVL and block */
195
+ recv_args_t args;
196
+ args.socket = s->socket;
197
+
198
+ rb_thread_call_without_gvl(recv_without_gvl, &args, RUBY_UBF_IO, NULL);
199
+
200
+ if (args.rv != 0)
201
+ raise_nng_error(args.rv);
202
+ return rbnng_msg_wrap(args.msg);
203
+ }
204
+
205
+ static VALUE
206
+ socket_send(VALUE self, VALUE data)
207
+ {
208
+ rbnng_socket_t *s = socket_get(self);
209
+ StringValue(data);
210
+
211
+ nng_msg *msg;
212
+ int rv = nng_msg_alloc(&msg, 0);
213
+ if (rv != 0)
214
+ raise_nng_error(rv);
215
+
216
+ rv = nng_msg_append(msg, RSTRING_PTR(data), RSTRING_LEN(data));
217
+ if (rv != 0) {
218
+ nng_msg_free(msg);
219
+ raise_nng_error(rv);
220
+ }
221
+
222
+ /* Fast path: try non-blocking (no GVL release needed) */
223
+ rv = nng_sendmsg(s->socket, msg, NNG_FLAG_NONBLOCK);
224
+ if (rv == 0)
225
+ return Qnil;
226
+
227
+ if (rv != NNG_EAGAIN) {
228
+ nng_msg_free(msg);
229
+ raise_nng_error(rv);
230
+ }
231
+
232
+ /* Slow path: socket not ready, release GVL and block */
233
+ send_args_t args;
234
+ args.socket = s->socket;
235
+ args.msg = msg;
236
+
237
+ rb_thread_call_without_gvl(send_without_gvl, &args, RUBY_UBF_IO, NULL);
238
+
239
+ if (args.rv != 0) {
240
+ nng_msg_free(msg);
241
+ raise_nng_error(args.rv);
242
+ }
243
+ return Qnil;
244
+ }
245
+
246
+ static VALUE
247
+ socket_forward(VALUE self, VALUE msg_obj)
248
+ {
249
+ rbnng_socket_t *s = socket_get(self);
250
+ rbnng_msg_t *m;
251
+ TypedData_Get_Struct(msg_obj, rbnng_msg_t, &rbnng_msg_type, m);
252
+
253
+ if (!m->msg)
254
+ rb_raise(rb_eRuntimeError, "message already consumed");
255
+
256
+ nng_msg *raw_msg = m->msg;
257
+ m->msg = NULL; /* consume */
258
+
259
+ send_args_t args;
260
+ args.socket = s->socket;
261
+ args.msg = raw_msg;
262
+
263
+ rb_thread_call_without_gvl(send_without_gvl, &args, RUBY_UBF_IO, NULL);
264
+
265
+ if (args.rv != 0) {
266
+ nng_msg_free(raw_msg);
267
+ raise_nng_error(args.rv);
268
+ }
269
+ return Qnil;
270
+ }
271
+
272
+ static VALUE
273
+ socket_recv_fd(VALUE self)
274
+ {
275
+ rbnng_socket_t *s = socket_get(self);
276
+ int fd;
277
+ int rv = nng_socket_get_int(s->socket, NNG_OPT_RECVFD, &fd);
278
+ if (rv != 0)
279
+ raise_nng_error(rv);
280
+ return INT2FIX(fd);
281
+ }
282
+
283
+ static VALUE
284
+ socket_send_fd(VALUE self)
116
285
  {
117
- Check_Type(url, T_STRING);
118
- RbnngSocket* p_rbnngSocket;
119
- Data_Get_Struct(self, RbnngSocket, p_rbnngSocket);
286
+ rbnng_socket_t *s = socket_get(self);
287
+ int fd;
288
+ int rv = nng_socket_get_int(s->socket, NNG_OPT_SENDFD, &fd);
289
+ if (rv != 0)
290
+ raise_nng_error(rv);
291
+ return INT2FIX(fd);
292
+ }
293
+
294
+ /* ── Option getters/setters ─────────────────────────────────────── */
295
+
296
+ static VALUE
297
+ socket_get_opt_int(VALUE self, VALUE name)
298
+ {
299
+ rbnng_socket_t *s = socket_get(self);
300
+ int val;
301
+ int rv = nng_socket_get_int(s->socket, StringValueCStr(name), &val);
302
+ if (rv != 0)
303
+ raise_nng_error(rv);
304
+ return INT2NUM(val);
305
+ }
306
+
307
+ static VALUE
308
+ socket_set_opt_int(VALUE self, VALUE name, VALUE val)
309
+ {
310
+ rbnng_socket_t *s = socket_get(self);
311
+ int rv = nng_socket_set_int(s->socket, StringValueCStr(name), NUM2INT(val));
312
+ if (rv != 0)
313
+ raise_nng_error(rv);
314
+ return Qnil;
315
+ }
316
+
317
+ static VALUE
318
+ socket_get_opt_ms(VALUE self, VALUE name)
319
+ {
320
+ rbnng_socket_t *s = socket_get(self);
321
+ nng_duration val;
322
+ int rv = nng_socket_get_ms(s->socket, StringValueCStr(name), &val);
323
+ if (rv != 0)
324
+ raise_nng_error(rv);
325
+ return INT2NUM(val);
326
+ }
327
+
328
+ static VALUE
329
+ socket_set_opt_ms(VALUE self, VALUE name, VALUE val)
330
+ {
331
+ rbnng_socket_t *s = socket_get(self);
332
+ int rv = nng_socket_set_ms(s->socket, StringValueCStr(name), NUM2INT(val));
333
+ if (rv != 0)
334
+ raise_nng_error(rv);
335
+ return Qnil;
336
+ }
337
+
338
+ static VALUE
339
+ socket_get_opt_size(VALUE self, VALUE name)
340
+ {
341
+ rbnng_socket_t *s = socket_get(self);
342
+ size_t val;
343
+ int rv = nng_socket_get_size(s->socket, StringValueCStr(name), &val);
344
+ if (rv != 0)
345
+ raise_nng_error(rv);
346
+ return SIZET2NUM(val);
347
+ }
348
+
349
+ static VALUE
350
+ socket_set_opt_size(VALUE self, VALUE name, VALUE val)
351
+ {
352
+ rbnng_socket_t *s = socket_get(self);
353
+ int rv = nng_socket_set_size(s->socket, StringValueCStr(name), NUM2SIZET(val));
354
+ if (rv != 0)
355
+ raise_nng_error(rv);
356
+ return Qnil;
357
+ }
358
+
359
+ static VALUE
360
+ socket_get_opt_string(VALUE self, VALUE name)
361
+ {
362
+ rbnng_socket_t *s = socket_get(self);
363
+ char *val;
364
+ int rv = nng_socket_get_string(s->socket, StringValueCStr(name), &val);
365
+ if (rv != 0)
366
+ raise_nng_error(rv);
367
+ VALUE str = rb_str_new_cstr(val);
368
+ nng_strfree(val);
369
+ return str;
370
+ }
371
+
372
+ static VALUE
373
+ socket_set_opt_string(VALUE self, VALUE name, VALUE val)
374
+ {
375
+ rbnng_socket_t *s = socket_get(self);
376
+ int rv = nng_socket_set_string(s->socket, StringValueCStr(name),
377
+ StringValueCStr(val));
378
+ if (rv != 0)
379
+ raise_nng_error(rv);
380
+ return Qnil;
381
+ }
382
+
383
+ /* ── Protocol initializers ──────────────────────────────────────── */
384
+
385
+ typedef struct {
386
+ int raw;
387
+ VALUE timeout;
388
+ } init_kwargs_t;
389
+
390
+ static init_kwargs_t
391
+ parse_init_kwargs(int argc, VALUE *argv)
392
+ {
393
+ init_kwargs_t kw = { .raw = 0, .timeout = Qnil };
394
+ VALUE opts = Qnil;
395
+ rb_scan_args(argc, argv, ":", &opts);
396
+ if (!NIL_P(opts)) {
397
+ kw.raw = RTEST(rb_hash_lookup2(opts, ID2SYM(rb_intern("raw")), Qfalse));
398
+ kw.timeout = rb_hash_lookup2(opts, ID2SYM(rb_intern("timeout")), Qnil);
399
+ }
400
+ return kw;
401
+ }
402
+
403
+ static void
404
+ apply_timeout(nng_socket socket, VALUE timeout)
405
+ {
406
+ if (NIL_P(timeout)) return;
407
+
408
+ int ms = (int)(NUM2DBL(timeout) * 1000);
409
+ int rv;
410
+ rv = nng_socket_set_ms(socket, NNG_OPT_RECVTIMEO, ms);
411
+ if (rv != 0) raise_nng_error(rv);
412
+ rv = nng_socket_set_ms(socket, NNG_OPT_SENDTIMEO, ms);
413
+ if (rv != 0) raise_nng_error(rv);
414
+ }
415
+
416
+ #define DEF_INIT(name, open_fn, open_raw_fn) \
417
+ static VALUE \
418
+ name(int argc, VALUE *argv, VALUE self) \
419
+ { \
420
+ rbnng_socket_t *s; \
421
+ TypedData_Get_Struct(self, rbnng_socket_t, \
422
+ &rbnng_socket_type, s); \
423
+ if (s->initialized) \
424
+ rb_raise(rb_eRuntimeError, "socket already initialized"); \
425
+ init_kwargs_t kw = parse_init_kwargs(argc, argv); \
426
+ int rv = kw.raw ? open_raw_fn(&s->socket) \
427
+ : open_fn(&s->socket); \
428
+ if (rv != 0) \
429
+ raise_nng_error(rv); \
430
+ s->initialized = 1; \
431
+ rb_ivar_set(self, rb_intern("@raw"), kw.raw ? Qtrue : Qfalse);\
432
+ apply_timeout(s->socket, kw.timeout); \
433
+ return self; \
434
+ }
435
+
436
+ DEF_INIT(pair0_init, nng_pair0_open, nng_pair0_open_raw)
437
+ DEF_INIT(pair1_init, nng_pair1_open, nng_pair1_open_raw)
438
+ DEF_INIT(bus0_init_inner, nng_bus0_open, nng_bus0_open_raw)
439
+ DEF_INIT(pub0_init, nng_pub0_open, nng_pub0_open_raw)
440
+ DEF_INIT(sub0_init_inner, nng_sub0_open, nng_sub0_open_raw)
441
+ DEF_INIT(push0_init, nng_push0_open, nng_push0_open_raw)
442
+ DEF_INIT(pull0_init, nng_pull0_open, nng_pull0_open_raw)
443
+ DEF_INIT(req0_init, nng_req0_open, nng_req0_open_raw)
444
+ DEF_INIT(rep0_init, nng_rep0_open, nng_rep0_open_raw)
445
+ DEF_INIT(surveyor0_init, nng_surveyor0_open, nng_surveyor0_open_raw)
446
+ DEF_INIT(respondent0_init,nng_respondent0_open, nng_respondent0_open_raw)
447
+
448
+ /* Sub0: subscribe with optional prefix: kwarg (default: subscribe to all) */
449
+ static VALUE
450
+ sub0_init(int argc, VALUE *argv, VALUE self)
451
+ {
452
+ rbnng_socket_t *s;
453
+ TypedData_Get_Struct(self, rbnng_socket_t, &rbnng_socket_type, s);
454
+ if (s->initialized)
455
+ rb_raise(rb_eRuntimeError, "socket already initialized");
456
+
457
+ VALUE opts = Qnil;
458
+ rb_scan_args(argc, argv, ":", &opts);
459
+
460
+ int raw = 0;
461
+ VALUE prefix = Qnil;
462
+ VALUE timeout = Qnil;
463
+ if (!NIL_P(opts)) {
464
+ raw = RTEST(rb_hash_lookup2(opts, ID2SYM(rb_intern("raw")), Qfalse));
465
+ prefix = rb_hash_lookup2(opts, ID2SYM(rb_intern("prefix")), Qnil);
466
+ timeout = rb_hash_lookup2(opts, ID2SYM(rb_intern("timeout")), Qnil);
467
+ }
468
+
469
+ int rv = raw ? nng_sub0_open_raw(&s->socket)
470
+ : nng_sub0_open(&s->socket);
471
+ if (rv != 0)
472
+ raise_nng_error(rv);
473
+ s->initialized = 1;
474
+ rb_ivar_set(self, rb_intern("@raw"), raw ? Qtrue : Qfalse);
475
+
476
+ if (NIL_P(prefix)) {
477
+ rv = nng_socket_set(s->socket, NNG_OPT_SUB_SUBSCRIBE, NULL, 0);
478
+ } else {
479
+ StringValue(prefix);
480
+ rv = nng_socket_set(s->socket, NNG_OPT_SUB_SUBSCRIBE,
481
+ RSTRING_PTR(prefix), RSTRING_LEN(prefix));
482
+ }
483
+ if (rv != 0)
484
+ raise_nng_error(rv);
485
+
486
+ apply_timeout(s->socket, timeout);
487
+
488
+ return self;
489
+ }
490
+
491
+ /* Sub0: subscribe to a topic prefix at runtime */
492
+ static VALUE
493
+ sub0_subscribe(VALUE self, VALUE prefix)
494
+ {
495
+ rbnng_socket_t *s = socket_get(self);
496
+ StringValue(prefix);
497
+ int rv = nng_socket_set(s->socket, NNG_OPT_SUB_SUBSCRIBE,
498
+ RSTRING_PTR(prefix), RSTRING_LEN(prefix));
499
+ if (rv != 0)
500
+ raise_nng_error(rv);
501
+ return self;
502
+ }
503
+
504
+ /* Sub0: unsubscribe from a topic prefix */
505
+ static VALUE
506
+ sub0_unsubscribe(VALUE self, VALUE prefix)
507
+ {
508
+ rbnng_socket_t *s = socket_get(self);
509
+ StringValue(prefix);
510
+ int rv = nng_socket_set(s->socket, NNG_OPT_SUB_UNSUBSCRIBE,
511
+ RSTRING_PTR(prefix), RSTRING_LEN(prefix));
512
+ if (rv != 0)
513
+ raise_nng_error(rv);
514
+ return self;
515
+ }
516
+
517
+ /* ── Pipe notification ─────────────────────────────────────────── */
518
+
519
+ /*
520
+ * Event packet written by the C callback (5 bytes):
521
+ * byte 0: event type (1 = connect / ADD_POST, 2 = disconnect / REM_POST)
522
+ * bytes 1-4: pipe id (uint32 little-endian)
523
+ *
524
+ * The callback runs on an NNG internal thread with the socket lock held,
525
+ * so it cannot touch Ruby. Writing to a pipe(2) is async-signal-safe.
526
+ */
527
+
528
+ /* event packet: 1 byte event type + nng_pipe (uint32_t) */
529
+ #define PIPE_EVENT_SIZE (1 + sizeof(nng_pipe))
530
+
531
+ static void
532
+ pipe_notify_cb(nng_pipe p, nng_pipe_ev ev, void *arg)
533
+ {
534
+ rbnng_socket_t *s = arg;
535
+ if (s->notify_fds[1] < 0) return;
536
+
537
+ uint8_t buf[PIPE_EVENT_SIZE];
538
+ buf[0] = (ev == NNG_PIPE_EV_ADD_POST) ? 1 : 2;
539
+ memcpy(buf + 1, &p, sizeof(nng_pipe));
540
+
541
+ /* best-effort write; if the fd is full we drop the event */
542
+ (void)write(s->notify_fds[1], buf, PIPE_EVENT_SIZE);
543
+ }
544
+
545
+ static VALUE
546
+ socket_pipe_notify_start(VALUE self)
547
+ {
548
+ rbnng_socket_t *s = socket_get(self);
549
+ if (s->notify_fds[0] >= 0)
550
+ return INT2NUM(s->notify_fds[0]); /* already started */
551
+
552
+ int fds[2];
553
+ if (pipe(fds) < 0)
554
+ rb_sys_fail("pipe");
555
+
556
+ /* make both ends non-blocking:
557
+ * - write end: so the NNG callback never stalls
558
+ * - read end: so recv_pipe_event returns nil immediately when empty */
559
+ int flags;
560
+ flags = fcntl(fds[0], F_GETFL);
561
+ if (flags < 0 || fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) < 0) {
562
+ close(fds[0]);
563
+ close(fds[1]);
564
+ rb_sys_fail("fcntl");
565
+ }
566
+ flags = fcntl(fds[1], F_GETFL);
567
+ if (flags < 0 || fcntl(fds[1], F_SETFL, flags | O_NONBLOCK) < 0) {
568
+ close(fds[0]);
569
+ close(fds[1]);
570
+ rb_sys_fail("fcntl");
571
+ }
572
+
573
+ s->notify_fds[0] = fds[0];
574
+ s->notify_fds[1] = fds[1];
575
+
576
+ int rv;
577
+ rv = nng_pipe_notify(s->socket, NNG_PIPE_EV_ADD_POST, pipe_notify_cb, s);
578
+ if (rv != 0) {
579
+ close(s->notify_fds[0]);
580
+ close(s->notify_fds[1]);
581
+ s->notify_fds[0] = -1;
582
+ s->notify_fds[1] = -1;
583
+ raise_nng_error(rv);
584
+ }
585
+ rv = nng_pipe_notify(s->socket, NNG_PIPE_EV_REM_POST, pipe_notify_cb, s);
586
+ if (rv != 0) {
587
+ close(s->notify_fds[0]);
588
+ close(s->notify_fds[1]);
589
+ s->notify_fds[0] = -1;
590
+ s->notify_fds[1] = -1;
591
+ raise_nng_error(rv);
592
+ }
593
+
594
+ return INT2NUM(s->notify_fds[0]);
595
+ }
596
+
597
+ static VALUE
598
+ socket_pipe_notify_fd(VALUE self)
599
+ {
600
+ rbnng_socket_t *s = socket_get(self);
601
+ if (s->notify_fds[0] < 0)
602
+ rb_raise(rb_eRuntimeError, "pipe_notify_start has not been called");
603
+ return INT2NUM(s->notify_fds[0]);
604
+ }
605
+
606
+ static VALUE
607
+ socket_recv_pipe_event(VALUE self)
608
+ {
609
+ rbnng_socket_t *s = socket_get(self);
610
+ if (s->notify_fds[0] < 0)
611
+ rb_raise(rb_eRuntimeError, "pipe_notify_start has not been called");
612
+
613
+ uint8_t buf[PIPE_EVENT_SIZE];
614
+ ssize_t n = read(s->notify_fds[0], buf, PIPE_EVENT_SIZE);
615
+ if (n < (ssize_t)PIPE_EVENT_SIZE)
616
+ return Qnil;
617
+
618
+ VALUE sym = (buf[0] == 1) ? ID2SYM(rb_intern("connect"))
619
+ : ID2SYM(rb_intern("disconnect"));
620
+
621
+ nng_pipe p;
622
+ memcpy(&p, buf + 1, sizeof(nng_pipe));
623
+
624
+ return rb_ary_new3(2, sym, rbnng_pipe_wrap(p));
625
+ }
626
+
627
+ /* Bus0: set default recv timeout so broadcast doesn't block forever */
628
+ static VALUE
629
+ bus0_init(int argc, VALUE *argv, VALUE self)
630
+ {
631
+ bus0_init_inner(argc, argv, self);
632
+ rbnng_socket_t *s = socket_get(self);
633
+ nng_duration cur;
634
+ int rv = nng_socket_get_ms(s->socket, NNG_OPT_RECVTIMEO, &cur);
635
+ if (rv == 0 && cur < 0) {
636
+ /* no timeout was set via kwarg, apply default */
637
+ rv = nng_socket_set_ms(s->socket, NNG_OPT_RECVTIMEO, 100);
638
+ if (rv != 0)
639
+ raise_nng_error(rv);
640
+ }
641
+ return self;
642
+ }
643
+
644
+ /* ── Ruby class registration ───────────────────────────────────── */
645
+
646
+ #define REG_PROTO(mod, name, init_fn) \
647
+ do { \
648
+ VALUE cls = rb_define_class_under(mod, name, base); \
649
+ rb_define_method(cls, "initialize", init_fn, -1); \
650
+ } while (0)
651
+
652
+ void
653
+ rbnng_socket_init(VALUE nng_module)
654
+ {
655
+ VALUE mod = rb_define_module_under(nng_module, "Socket");
656
+
657
+ VALUE base = rb_define_class_under(mod, "Base", rb_cObject);
658
+ rb_define_alloc_func(base, socket_alloc);
659
+
660
+ rb_define_method(base, "close", socket_close, 0);
661
+ rb_define_method(base, "listen", socket_listen, 1);
662
+ rb_define_method(base, "dial", socket_dial, 1);
663
+ rb_define_method(base, "receive", socket_receive, 0);
664
+ rb_define_method(base, "send", socket_send, 1);
665
+ rb_define_method(base, "forward", socket_forward, 1);
666
+ rb_define_method(base, "recv_fd", socket_recv_fd, 0);
667
+ rb_define_method(base, "send_fd", socket_send_fd, 0);
668
+ rb_define_method(base, "_listen_tls", socket_listen_tls, 6);
669
+ rb_define_method(base, "_dial_tls", socket_dial_tls, 6);
670
+ rb_define_method(base, "get_opt_int", socket_get_opt_int, 1);
671
+ rb_define_method(base, "set_opt_int", socket_set_opt_int, 2);
672
+ rb_define_method(base, "get_opt_ms", socket_get_opt_ms, 1);
673
+ rb_define_method(base, "set_opt_ms", socket_set_opt_ms, 2);
674
+ rb_define_method(base, "get_opt_size", socket_get_opt_size, 1);
675
+ rb_define_method(base, "set_opt_size", socket_set_opt_size, 2);
676
+ rb_define_method(base, "get_opt_string", socket_get_opt_string, 1);
677
+ rb_define_method(base, "set_opt_string", socket_set_opt_string, 2);
678
+ rb_define_method(base, "pipe_notify_start", socket_pipe_notify_start, 0);
679
+ rb_define_method(base, "pipe_notify_fd", socket_pipe_notify_fd, 0);
680
+ rb_define_method(base, "recv_pipe_event", socket_recv_pipe_event, 0);
120
681
 
121
- int rv;
122
- if ((rv = nng_listen(p_rbnngSocket->socket, StringValueCStr(url), NULL, 0)) !=
123
- 0) {
124
- raise_error(rv);
125
- }
682
+ REG_PROTO(mod, "Pair0", pair0_init);
683
+ REG_PROTO(mod, "Pair1", pair1_init);
684
+ REG_PROTO(mod, "Bus0", bus0_init);
685
+ REG_PROTO(mod, "Pub0", pub0_init);
686
+ VALUE cSub0 = rb_define_class_under(mod, "Sub0", base);
687
+ rb_define_method(cSub0, "initialize", sub0_init, -1);
688
+ rb_define_method(cSub0, "subscribe", sub0_subscribe, 1);
689
+ rb_define_method(cSub0, "unsubscribe", sub0_unsubscribe, 1);
690
+ REG_PROTO(mod, "Push0", push0_init);
691
+ REG_PROTO(mod, "Pull0", pull0_init);
692
+ REG_PROTO(mod, "Req0", req0_init);
693
+ REG_PROTO(mod, "Rep0", rep0_init);
694
+ REG_PROTO(mod, "Surveyor0", surveyor0_init);
695
+ REG_PROTO(mod, "Respondent0", respondent0_init);
126
696
  }