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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/LICENSE +22 -0
- data/README.md +308 -0
- data/ext/rbnng/device.c +43 -0
- data/ext/rbnng/exceptions.c +116 -207
- data/ext/rbnng/extconf.rb +6 -19
- data/ext/rbnng/message.c +183 -0
- data/ext/rbnng/pipe.c +81 -0
- data/ext/rbnng/rbnng.c +15 -29
- data/ext/rbnng/rbnng.h +59 -26
- data/ext/rbnng/socket.c +661 -91
- data/ext/rbnng/stats.c +106 -0
- data/ext/rbnng/tls.c +127 -0
- data/lib/nng/socket/base.rb +131 -0
- data/lib/nng/socket/bus0.rb +10 -0
- data/lib/nng/socket/pair0.rb +10 -0
- data/lib/nng/socket/pair1.rb +10 -0
- data/lib/nng/socket/pub0.rb +9 -0
- data/lib/nng/socket/pull0.rb +9 -0
- data/lib/nng/socket/push0.rb +9 -0
- data/lib/nng/socket/readable.rb +17 -0
- data/lib/nng/socket/rep0.rb +10 -0
- data/lib/nng/socket/req0.rb +10 -0
- data/lib/nng/socket/respondent0.rb +10 -0
- data/lib/nng/socket/sub0.rb +9 -0
- data/lib/nng/socket/surveyor0.rb +10 -0
- data/lib/nng/socket/writable.rb +17 -0
- data/lib/nng/tls.rb +25 -0
- data/lib/nng/version.rb +3 -1
- data/lib/nng.rb +24 -1
- metadata +44 -31
- data/ext/rbnng/bus0.c +0 -43
- data/ext/rbnng/exceptions.h +0 -12
- data/ext/rbnng/msg.c +0 -61
- data/ext/rbnng/msg.h +0 -22
- data/ext/rbnng/pair.c +0 -65
- data/ext/rbnng/pub0.c +0 -36
- data/ext/rbnng/pull0.c +0 -36
- data/ext/rbnng/push0.c +0 -36
- data/ext/rbnng/rep0.c +0 -150
- data/ext/rbnng/req0.c +0 -38
- data/ext/rbnng/respondent0.c +0 -40
- data/ext/rbnng/socket.h +0 -36
- data/ext/rbnng/sub0.c +0 -41
- data/ext/rbnng/surveyor0.c +0 -40
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
|
-
|
|
9
|
-
#include <
|
|
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*
|
|
21
|
+
socket_free(void *ptr)
|
|
13
22
|
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
57
|
+
static inline rbnng_socket_t *
|
|
58
|
+
socket_get(VALUE self)
|
|
28
59
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
93
|
+
send_args_t *a = arg;
|
|
94
|
+
a->rv = nng_sendmsg(a->socket, a->msg, 0);
|
|
95
|
+
return NULL;
|
|
96
|
+
}
|
|
44
97
|
|
|
45
|
-
|
|
46
|
-
rb_thread_call_without_gvl(socket_get_msg_blocking, p_rbnngSocket, 0, 0);
|
|
98
|
+
/* ── Shared methods (defined on Base) ───────────────────────────── */
|
|
47
99
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
61
|
-
|
|
110
|
+
static VALUE
|
|
111
|
+
socket_listen(VALUE self, VALUE url)
|
|
62
112
|
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
return Qnil;
|
|
85
144
|
}
|
|
86
145
|
|
|
87
|
-
VALUE
|
|
88
|
-
|
|
146
|
+
static VALUE
|
|
147
|
+
socket_dial(VALUE self, VALUE url)
|
|
89
148
|
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
}
|