asterism-zenoh 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,1553 @@
1
+ /*
2
+ * asterism-zenoh: Asterism::Zenoh for CRuby, over zenoh-c.
3
+ *
4
+ * The Ruby API is the one of the mruby / PicoRuby gem
5
+ * (ruby-asterism/picoruby-asterism-zenoh, over zenoh-pico): sessions
6
+ * (client, or peer with an optional listener), put / subscribe, get /
7
+ * queryable (query and reply), liveliness tokens and watches, attachments,
8
+ * Session#zid. Code written for one runs on the other.
9
+ *
10
+ * Threading model: zenoh-c runs the protocol on its own threads and calls
11
+ * the callbacks below from them. No callback touches Ruby (no GVL, no Ruby
12
+ * object, no Ruby allocator). Each subscriber, liveliness watch, queryable
13
+ * and get owns a channel (struct zch): zenoh-c's FIFO channel holds what was
14
+ * received, and the gem's callback in front of it keeps the counters and
15
+ * drops the oldest entry when the channel is full (as the mruby gem's ring
16
+ * does; zenoh-c's own FIFO would block zenoh's thread instead). Ruby takes
17
+ * the entries out with each_pending / each_reply from the application's own
18
+ * thread. Session#poll only checks the connection: receiving needs no poll.
19
+ *
20
+ * The GVL is released while zenoh-c may wait: opening a session (connecting
21
+ * to the router), put, get, liveliness_get and close.
22
+ *
23
+ * Losing the connection (same rules as the mruby gem): a client session is
24
+ * closed when it has no router left, a peer session that only connects when
25
+ * it has no peer left; a listening peer session stays open. The check runs
26
+ * in poll, closed?, put and get. From then on poll returns false, closed?
27
+ * is true and put raises Asterism::Zenoh::Error. There is no reconnection.
28
+ *
29
+ * Lifetime: closing is optional. A session keeps lists of its live
30
+ * subscribers, queryables and tokens and undeclares them before it closes,
31
+ * so they can be freed in any order. A channel is owned jointly by its Ruby
32
+ * object and zenoh-c (which drops the gem's closure when the entity is
33
+ * undeclared or the get is finished); whichever lets go last frees it.
34
+ */
35
+ #include <pthread.h>
36
+ #include <stdatomic.h>
37
+ #include <stdbool.h>
38
+ #include <stdint.h>
39
+ #include <stdlib.h>
40
+ #include <string.h>
41
+
42
+ #include <ruby.h>
43
+ #include <ruby/encoding.h>
44
+ #include <ruby/thread.h>
45
+
46
+ #include <zenoh.h>
47
+
48
+ #define ZRB_DEFAULT_DEPTH 16
49
+ #define ZRB_MAX_DEPTH 1024
50
+ #define ZRB_DEFAULT_POLL_STEPS 8
51
+ #define ZRB_DEFAULT_GET_TIMEOUT_MS 2000
52
+ #define ZRB_MAX_GET_TIMEOUT_MS 600000
53
+ /* Same values as the mruby gem's PICORUBY_ZENOH_*_MS. */
54
+ #define ZRB_CONNECT_TIMEOUT_MS 3000
55
+ #define ZRB_SEND_TIMEOUT_MS 3000
56
+
57
+ static VALUE mAsterism, mZenoh, eZenohError;
58
+ static VALUE cSession, cSubscriber, cWatch, cQueryable, cQuery, cGet, cToken;
59
+ static ID id_attachment, id_target, id_consolidation, id_complete, id_mode, id_listen;
60
+ static ID id_iv_session, id_iv_key;
61
+ static ID id_client, id_peer, id_all, id_all_complete, id_best_matching;
62
+ static ID id_none, id_latest, id_monotonic, id_auto;
63
+
64
+ /* ------------------------------------------------------------- channels */
65
+
66
+ typedef enum { ZCH_SAMPLE, ZCH_REPLY, ZCH_QUERY } zch_kind;
67
+
68
+ /* What zenoh-c received for one Ruby object, waiting to be taken. */
69
+ typedef struct {
70
+ zch_kind kind;
71
+ pthread_mutex_t lock; /* the gem's callback, and Ruby taking an entry */
72
+ uint32_t depth;
73
+ atomic_uint queued; /* entries in the FIFO */
74
+ atomic_uint received;
75
+ atomic_uint dropped; /* oldest entries dropped because the FIFO was full */
76
+ atomic_uint errors; /* error replies (gets) */
77
+ atomic_bool done; /* zenoh-c dropped the gem's closure */
78
+ atomic_int refs; /* the Ruby object and zenoh-c's closure */
79
+ union {
80
+ z_owned_closure_sample_t sample;
81
+ z_owned_closure_reply_t reply;
82
+ z_owned_closure_query_t query;
83
+ } tx; /* the FIFO's sending end */
84
+ union {
85
+ z_owned_fifo_handler_sample_t sample;
86
+ z_owned_fifo_handler_reply_t reply;
87
+ z_owned_fifo_handler_query_t query;
88
+ } rx; /* the FIFO's receiving end */
89
+ } zch;
90
+
91
+ static zch *zch_new(zch_kind kind, uint32_t depth) {
92
+ zch *c = (zch *)calloc(1, sizeof(zch));
93
+ if (c == NULL) {
94
+ rb_raise(rb_eNoMemError, "cannot allocate a zenoh channel");
95
+ }
96
+ c->kind = kind;
97
+ c->depth = depth;
98
+ pthread_mutex_init(&c->lock, NULL);
99
+ atomic_init(&c->queued, 0);
100
+ atomic_init(&c->received, 0);
101
+ atomic_init(&c->dropped, 0);
102
+ atomic_init(&c->errors, 0);
103
+ atomic_init(&c->done, false);
104
+ atomic_init(&c->refs, 1); /* the Ruby side; zch_closure_* adds zenoh's */
105
+ switch (kind) {
106
+ case ZCH_SAMPLE:
107
+ z_fifo_channel_sample_new(&c->tx.sample, &c->rx.sample, depth);
108
+ break;
109
+ case ZCH_REPLY:
110
+ z_fifo_channel_reply_new(&c->tx.reply, &c->rx.reply, depth);
111
+ break;
112
+ case ZCH_QUERY:
113
+ z_fifo_channel_query_new(&c->tx.query, &c->rx.query, depth);
114
+ break;
115
+ }
116
+ return c;
117
+ }
118
+
119
+ static void zch_unref(zch *c) {
120
+ if (c == NULL || atomic_fetch_sub(&c->refs, 1) != 1) {
121
+ return;
122
+ }
123
+ /* Dropping the receiving end drops what is left (a query left there
124
+ * sends its final reply). */
125
+ switch (c->kind) {
126
+ case ZCH_SAMPLE:
127
+ z_drop(z_move(c->tx.sample));
128
+ z_drop(z_move(c->rx.sample));
129
+ break;
130
+ case ZCH_REPLY:
131
+ z_drop(z_move(c->tx.reply));
132
+ z_drop(z_move(c->rx.reply));
133
+ break;
134
+ case ZCH_QUERY:
135
+ z_drop(z_move(c->tx.query));
136
+ z_drop(z_move(c->rx.query));
137
+ break;
138
+ }
139
+ pthread_mutex_destroy(&c->lock);
140
+ free(c);
141
+ }
142
+
143
+ /* zenoh-c is done with the gem's closure: the entity was undeclared, or
144
+ * the get finished (every reply in, the time ran out, the session closed). */
145
+ static void zch_on_drop(void *ctx) {
146
+ zch *c = (zch *)ctx;
147
+ atomic_store(&c->done, true);
148
+ zch_unref(c);
149
+ }
150
+
151
+ /* Called with c->lock held, from zenoh's thread. Makes room for one entry
152
+ * by dropping the oldest when the FIFO is full, so sending never blocks. */
153
+ static void zch_make_room(zch *c) {
154
+ if (atomic_load(&c->queued) < c->depth) {
155
+ return;
156
+ }
157
+ z_result_t r = Z_CHANNEL_NODATA;
158
+ switch (c->kind) {
159
+ case ZCH_SAMPLE: {
160
+ z_owned_sample_t old;
161
+ r = z_fifo_handler_sample_try_recv(z_loan(c->rx.sample), &old);
162
+ if (r == Z_OK) {
163
+ z_drop(z_move(old));
164
+ }
165
+ break;
166
+ }
167
+ case ZCH_REPLY: {
168
+ z_owned_reply_t old;
169
+ r = z_fifo_handler_reply_try_recv(z_loan(c->rx.reply), &old);
170
+ if (r == Z_OK) {
171
+ z_drop(z_move(old));
172
+ }
173
+ break;
174
+ }
175
+ case ZCH_QUERY: {
176
+ /* Finishing the oldest unanswered query (its final reply goes out). */
177
+ z_owned_query_t old;
178
+ r = z_fifo_handler_query_try_recv(z_loan(c->rx.query), &old);
179
+ if (r == Z_OK) {
180
+ z_drop(z_move(old));
181
+ }
182
+ break;
183
+ }
184
+ }
185
+ if (r == Z_OK) {
186
+ atomic_fetch_sub(&c->queued, 1);
187
+ atomic_fetch_add(&c->dropped, 1);
188
+ }
189
+ }
190
+
191
+ /* The gem's callbacks, on zenoh's threads. They never touch Ruby. */
192
+ static void zch_on_sample(z_loaned_sample_t *sample, void *ctx) {
193
+ zch *c = (zch *)ctx;
194
+ pthread_mutex_lock(&c->lock);
195
+ zch_make_room(c);
196
+ z_closure_sample_call(z_loan(c->tx.sample), sample);
197
+ atomic_fetch_add(&c->queued, 1);
198
+ atomic_fetch_add(&c->received, 1);
199
+ pthread_mutex_unlock(&c->lock);
200
+ }
201
+
202
+ static void zch_on_reply(z_loaned_reply_t *reply, void *ctx) {
203
+ zch *c = (zch *)ctx;
204
+ if (!z_reply_is_ok(reply)) {
205
+ atomic_fetch_add(&c->errors, 1);
206
+ return;
207
+ }
208
+ pthread_mutex_lock(&c->lock);
209
+ zch_make_room(c);
210
+ z_closure_reply_call(z_loan(c->tx.reply), reply);
211
+ atomic_fetch_add(&c->queued, 1);
212
+ atomic_fetch_add(&c->received, 1);
213
+ pthread_mutex_unlock(&c->lock);
214
+ }
215
+
216
+ static void zch_on_query(z_loaned_query_t *query, void *ctx) {
217
+ zch *c = (zch *)ctx;
218
+ pthread_mutex_lock(&c->lock);
219
+ zch_make_room(c);
220
+ z_closure_query_call(z_loan(c->tx.query), query);
221
+ atomic_fetch_add(&c->queued, 1);
222
+ atomic_fetch_add(&c->received, 1);
223
+ pthread_mutex_unlock(&c->lock);
224
+ }
225
+
226
+ /* The closure handed to zenoh-c; it holds a reference to c. */
227
+ static void zch_closure_sample(zch *c, z_owned_closure_sample_t *cb) {
228
+ atomic_fetch_add(&c->refs, 1);
229
+ z_closure_sample(cb, zch_on_sample, zch_on_drop, c);
230
+ }
231
+
232
+ static void zch_closure_reply(zch *c, z_owned_closure_reply_t *cb) {
233
+ atomic_fetch_add(&c->refs, 1);
234
+ z_closure_reply(cb, zch_on_reply, zch_on_drop, c);
235
+ }
236
+
237
+ static void zch_closure_query(zch *c, z_owned_closure_query_t *cb) {
238
+ atomic_fetch_add(&c->refs, 1);
239
+ z_closure_query(cb, zch_on_query, zch_on_drop, c);
240
+ }
241
+
242
+ /* Ruby side: take one entry (Z_OK) or nothing (Z_CHANNEL_NODATA). */
243
+ static z_result_t zch_take_sample(zch *c, z_owned_sample_t *out) {
244
+ pthread_mutex_lock(&c->lock);
245
+ z_result_t r = z_fifo_handler_sample_try_recv(z_loan(c->rx.sample), out);
246
+ if (r == Z_OK) {
247
+ atomic_fetch_sub(&c->queued, 1);
248
+ }
249
+ pthread_mutex_unlock(&c->lock);
250
+ return r;
251
+ }
252
+
253
+ static z_result_t zch_take_reply(zch *c, z_owned_reply_t *out) {
254
+ pthread_mutex_lock(&c->lock);
255
+ z_result_t r = z_fifo_handler_reply_try_recv(z_loan(c->rx.reply), out);
256
+ if (r == Z_OK) {
257
+ atomic_fetch_sub(&c->queued, 1);
258
+ }
259
+ pthread_mutex_unlock(&c->lock);
260
+ return r;
261
+ }
262
+
263
+ static z_result_t zch_take_query(zch *c, z_owned_query_t *out) {
264
+ pthread_mutex_lock(&c->lock);
265
+ z_result_t r = z_fifo_handler_query_try_recv(z_loan(c->rx.query), out);
266
+ if (r == Z_OK) {
267
+ atomic_fetch_sub(&c->queued, 1);
268
+ }
269
+ pthread_mutex_unlock(&c->lock);
270
+ return r;
271
+ }
272
+
273
+ /* Finish every query still waiting (each sends its final reply). */
274
+ static void zch_finish_queries(zch *c) {
275
+ z_owned_query_t q;
276
+ while (zch_take_query(c, &q) == Z_OK) {
277
+ z_drop(z_move(q));
278
+ }
279
+ }
280
+
281
+ /* -------------------------------------------------------------- helpers */
282
+
283
+ static VALUE zrb_str_from_view(const z_loaned_string_t *s) {
284
+ return rb_utf8_str_new(z_string_data(s), (long)z_string_len(s));
285
+ }
286
+
287
+ static VALUE zrb_key_str(const z_loaned_keyexpr_t *ke) {
288
+ z_view_string_t ks;
289
+ z_keyexpr_as_view_string(ke, &ks);
290
+ return zrb_str_from_view(z_loan(ks));
291
+ }
292
+
293
+ /* Bytes as a binary String (they are not necessarily text). */
294
+ static VALUE zrb_str_from_bytes(const z_loaned_bytes_t *b) {
295
+ size_t len = z_bytes_len(b);
296
+ VALUE str = rb_str_new(NULL, (long)len);
297
+ z_bytes_reader_t reader = z_bytes_get_reader(b);
298
+ size_t got = z_bytes_reader_read(&reader, (uint8_t *)RSTRING_PTR(str), len);
299
+ if (got != len) {
300
+ rb_str_set_len(str, (long)got);
301
+ }
302
+ return str;
303
+ }
304
+
305
+ /* nil when there is no attachment, or an empty one (as the mruby gem). */
306
+ static VALUE zrb_attachment_value(const z_loaned_bytes_t *b) {
307
+ if (b == NULL || z_bytes_len(b) == 0) {
308
+ return Qnil;
309
+ }
310
+ return zrb_str_from_bytes(b);
311
+ }
312
+
313
+ static void zrb_view_key(z_view_keyexpr_t *ke, const char *key) {
314
+ if (z_view_keyexpr_from_str(ke, key) != Z_OK) {
315
+ rb_raise(rb_eArgError, "invalid key expression: %s", key);
316
+ }
317
+ }
318
+
319
+ /* An owned copy of the key, for calls made without the GVL. */
320
+ static void zrb_owned_key(z_owned_keyexpr_t *ke, const char *key) {
321
+ if (z_keyexpr_from_str(ke, key) != Z_OK) {
322
+ rb_raise(rb_eArgError, "invalid key expression: %s", key);
323
+ }
324
+ }
325
+
326
+ static long zrb_check_depth(VALUE v) {
327
+ long depth = NIL_P(v) ? ZRB_DEFAULT_DEPTH : NUM2LONG(v);
328
+ if (depth < 1 || depth > ZRB_MAX_DEPTH) {
329
+ rb_raise(rb_eArgError, "depth must be 1..%d", ZRB_MAX_DEPTH);
330
+ }
331
+ return depth;
332
+ }
333
+
334
+ static long zrb_check_timeout(VALUE v) {
335
+ long t = NIL_P(v) ? ZRB_DEFAULT_GET_TIMEOUT_MS : NUM2LONG(v);
336
+ if (t < 1 || t > ZRB_MAX_GET_TIMEOUT_MS) {
337
+ rb_raise(rb_eArgError, "timeout must be 1..%d ms", ZRB_MAX_GET_TIMEOUT_MS);
338
+ }
339
+ return t;
340
+ }
341
+
342
+ /* The attachment: keyword: nil or a String. */
343
+ static VALUE zrb_kw_attachment(VALUE v) {
344
+ if (v == Qundef || NIL_P(v)) {
345
+ return Qnil;
346
+ }
347
+ if (!RB_TYPE_P(v, T_STRING)) {
348
+ rb_raise(rb_eTypeError, "attachment must be a String or nil");
349
+ }
350
+ return v;
351
+ }
352
+
353
+ static void zrb_bytes_from_str(z_owned_bytes_t *out, VALUE str, const char *what) {
354
+ if (z_bytes_copy_from_buf(out, (const uint8_t *)RSTRING_PTR(str), (size_t)RSTRING_LEN(str)) != Z_OK) {
355
+ rb_raise(eZenohError, "cannot allocate the %s", what);
356
+ }
357
+ }
358
+
359
+ /* A Symbol (or String) keyword value as an ID. */
360
+ static ID zrb_sym_id(VALUE v) {
361
+ if (RB_TYPE_P(v, T_STRING)) {
362
+ return rb_intern_str(v);
363
+ }
364
+ if (SYMBOL_P(v)) {
365
+ return SYM2ID(v);
366
+ }
367
+ rb_raise(rb_eTypeError, "expected a Symbol, got %" PRIsVALUE, rb_obj_class(v));
368
+ return 0; /* not reached */
369
+ }
370
+
371
+ /* No .new: the objects come from Session.open and the Session methods (as in
372
+ * the mruby gem, where new is undefined too). */
373
+ static void zrb_no_new(VALUE klass) {
374
+ rb_undef_alloc_func(klass);
375
+ rb_undef_method(rb_singleton_class(klass), "new");
376
+ }
377
+
378
+ /* Keep the session object alive while obj is reachable. */
379
+ static void zrb_hold_session(VALUE obj, VALUE session, VALUE key) {
380
+ rb_ivar_set(obj, id_iv_session, session);
381
+ if (!NIL_P(key)) {
382
+ rb_ivar_set(obj, id_iv_key, rb_str_dup(key));
383
+ }
384
+ }
385
+
386
+ /* ------------------------------------------------------- object structs */
387
+
388
+ typedef struct zrb_session zrb_session;
389
+ typedef struct zrb_sub zrb_sub;
390
+ typedef struct zrb_qable zrb_qable;
391
+ typedef struct zrb_token zrb_token;
392
+
393
+ struct zrb_session {
394
+ z_owned_session_t session;
395
+ pthread_mutex_t op_lock; /* calls made without the GVL, and close */
396
+ bool open;
397
+ bool peer; /* peer mode (otherwise client) */
398
+ bool listening; /* peer mode with a listener: stays open without peers */
399
+ zrb_sub *subs; /* live subscribers and liveliness watches */
400
+ zrb_qable *qables;
401
+ zrb_token *tokens;
402
+ };
403
+
404
+ struct zrb_sub {
405
+ z_owned_subscriber_t sub;
406
+ bool declared;
407
+ bool liveliness;
408
+ zrb_session *owner; /* NULL once detached */
409
+ zrb_sub *next;
410
+ zch *ch;
411
+ };
412
+
413
+ struct zrb_qable {
414
+ z_owned_queryable_t qable;
415
+ bool declared;
416
+ zrb_session *owner;
417
+ zrb_qable *next;
418
+ zch *ch;
419
+ };
420
+
421
+ struct zrb_token {
422
+ z_owned_liveliness_token_t token;
423
+ bool declared;
424
+ zrb_session *owner;
425
+ zrb_token *next;
426
+ };
427
+
428
+ typedef struct {
429
+ zch *ch;
430
+ } zrb_get;
431
+
432
+ typedef struct {
433
+ z_owned_query_t query;
434
+ bool live;
435
+ } zrb_query;
436
+
437
+ /* ----------------------------------------------------------- subscriber */
438
+
439
+ static void zrb_sub_detach(zrb_sub *s) {
440
+ if (s->declared) {
441
+ z_drop(z_move(s->sub));
442
+ s->declared = false;
443
+ }
444
+ if (s->owner != NULL) {
445
+ zrb_sub **pp = &s->owner->subs;
446
+ while (*pp != NULL) {
447
+ if (*pp == s) {
448
+ *pp = s->next;
449
+ break;
450
+ }
451
+ pp = &(*pp)->next;
452
+ }
453
+ s->owner = NULL;
454
+ s->next = NULL;
455
+ }
456
+ }
457
+
458
+ static void zrb_sub_free(void *p) {
459
+ zrb_sub *s = (zrb_sub *)p;
460
+ zrb_sub_detach(s);
461
+ zch_unref(s->ch);
462
+ xfree(s);
463
+ }
464
+
465
+ static size_t zrb_sub_size(const void *p) { return sizeof(zrb_sub); }
466
+
467
+ static const rb_data_type_t zrb_sub_type = {
468
+ "Asterism::Zenoh::Subscriber", {NULL, zrb_sub_free, zrb_sub_size}, NULL, NULL, 0};
469
+
470
+ static zrb_sub *zrb_sub_get(VALUE self) {
471
+ zrb_sub *s;
472
+ TypedData_Get_Struct(self, zrb_sub, &zrb_sub_type, s);
473
+ return s;
474
+ }
475
+
476
+ /* Take out what is pending now, oldest first. Values that arrive while the
477
+ * block runs are left for the next call. */
478
+ static VALUE zrb_sub_each_pending(VALUE self) {
479
+ zrb_sub *s = zrb_sub_get(self);
480
+ bool collect = !rb_block_given_p();
481
+ VALUE out = collect ? rb_ary_new() : Qnil;
482
+ long taken = 0;
483
+ unsigned todo = atomic_load(&s->ch->queued);
484
+ while (todo > 0) {
485
+ todo--;
486
+ z_owned_sample_t sm;
487
+ if (zch_take_sample(s->ch, &sm) != Z_OK) {
488
+ break;
489
+ }
490
+ const z_loaned_sample_t *l = z_loan(sm);
491
+ VALUE vals[3];
492
+ int n;
493
+ vals[0] = zrb_key_str(z_sample_keyexpr(l));
494
+ if (s->liveliness) {
495
+ /* A token appearing is a PUT, one going away a DELETE. */
496
+ vals[1] = (z_sample_kind(l) == Z_SAMPLE_KIND_PUT) ? Qtrue : Qfalse;
497
+ n = 2;
498
+ } else {
499
+ vals[1] = zrb_str_from_bytes(z_sample_payload(l));
500
+ vals[2] = zrb_attachment_value(z_sample_attachment(l));
501
+ n = 3;
502
+ }
503
+ z_drop(z_move(sm));
504
+ taken++;
505
+ if (collect) {
506
+ rb_ary_push(out, rb_ary_new_from_values(n, vals));
507
+ } else {
508
+ rb_yield_values2(n, vals);
509
+ }
510
+ }
511
+ return collect ? out : LONG2NUM(taken);
512
+ }
513
+
514
+ static VALUE zrb_sub_pending(VALUE self) { return UINT2NUM(atomic_load(&zrb_sub_get(self)->ch->queued)); }
515
+ static VALUE zrb_sub_received(VALUE self) { return UINT2NUM(atomic_load(&zrb_sub_get(self)->ch->received)); }
516
+ static VALUE zrb_sub_dropped(VALUE self) { return UINT2NUM(atomic_load(&zrb_sub_get(self)->ch->dropped)); }
517
+
518
+ static VALUE zrb_sub_close(VALUE self) {
519
+ zrb_sub_detach(zrb_sub_get(self));
520
+ return Qnil;
521
+ }
522
+
523
+ static VALUE zrb_sub_closed_p(VALUE self) { return zrb_sub_get(self)->declared ? Qfalse : Qtrue; }
524
+
525
+ /* ------------------------------------------------------------ queryable */
526
+
527
+ static void zrb_qable_detach(zrb_qable *q) {
528
+ /* Unanswered queries first: their final replies go out while the
529
+ * session can still carry them. */
530
+ zch_finish_queries(q->ch);
531
+ if (q->declared) {
532
+ z_drop(z_move(q->qable));
533
+ q->declared = false;
534
+ }
535
+ if (q->owner != NULL) {
536
+ zrb_qable **pp = &q->owner->qables;
537
+ while (*pp != NULL) {
538
+ if (*pp == q) {
539
+ *pp = q->next;
540
+ break;
541
+ }
542
+ pp = &(*pp)->next;
543
+ }
544
+ q->owner = NULL;
545
+ q->next = NULL;
546
+ }
547
+ }
548
+
549
+ static void zrb_qable_free(void *p) {
550
+ zrb_qable *q = (zrb_qable *)p;
551
+ zrb_qable_detach(q);
552
+ zch_unref(q->ch);
553
+ xfree(q);
554
+ }
555
+
556
+ static size_t zrb_qable_size(const void *p) { return sizeof(zrb_qable); }
557
+
558
+ static const rb_data_type_t zrb_qable_type = {
559
+ "Asterism::Zenoh::Queryable", {NULL, zrb_qable_free, zrb_qable_size}, NULL, NULL, 0};
560
+
561
+ static zrb_qable *zrb_qable_get(VALUE self) {
562
+ zrb_qable *q;
563
+ TypedData_Get_Struct(self, zrb_qable, &zrb_qable_type, q);
564
+ return q;
565
+ }
566
+
567
+ /* ---------------------------------------------------------------- query */
568
+
569
+ static void zrb_query_finish(zrb_query *zq) {
570
+ if (zq->live) {
571
+ z_drop(z_move(zq->query));
572
+ zq->live = false;
573
+ }
574
+ }
575
+
576
+ static void zrb_query_free(void *p) {
577
+ zrb_query *zq = (zrb_query *)p;
578
+ zrb_query_finish(zq);
579
+ xfree(zq);
580
+ }
581
+
582
+ static size_t zrb_query_size(const void *p) { return sizeof(zrb_query); }
583
+
584
+ static const rb_data_type_t zrb_query_type = {
585
+ "Asterism::Zenoh::Query", {NULL, zrb_query_free, zrb_query_size}, NULL, NULL, 0};
586
+
587
+ static zrb_query *zrb_query_get(VALUE self) {
588
+ zrb_query *zq;
589
+ TypedData_Get_Struct(self, zrb_query, &zrb_query_type, zq);
590
+ return zq;
591
+ }
592
+
593
+ static zrb_query *zrb_query_get_live(VALUE self) {
594
+ zrb_query *zq = zrb_query_get(self);
595
+ if (!zq->live) {
596
+ rb_raise(eZenohError, "the query is finished");
597
+ }
598
+ return zq;
599
+ }
600
+
601
+ /* Wraps the query (moved in) in a Query object. */
602
+ static VALUE zrb_query_wrap(z_owned_query_t *q) {
603
+ zrb_query *zq;
604
+ VALUE obj = TypedData_Make_Struct(cQuery, zrb_query, &zrb_query_type, zq);
605
+ zq->query = *q;
606
+ zq->live = true;
607
+ z_internal_query_null(q);
608
+ return obj;
609
+ }
610
+
611
+ static VALUE zrb_query_yield_body(VALUE obj) { return rb_yield(obj); }
612
+
613
+ static VALUE zrb_query_yield_ensure(VALUE obj) {
614
+ zrb_query_finish(zrb_query_get(obj));
615
+ return Qnil;
616
+ }
617
+
618
+ /* qa.each_pending { |q| ... } -> Integer (queries taken). Each query is
619
+ * finished when the block returns (also when it raises).
620
+ * qa.each_pending -> Array of Query; each stays open until Query#finish or
621
+ * garbage collection. */
622
+ static VALUE zrb_qable_each_pending(VALUE self) {
623
+ zrb_qable *q = zrb_qable_get(self);
624
+ bool collect = !rb_block_given_p();
625
+ VALUE out = collect ? rb_ary_new() : Qnil;
626
+ long taken = 0;
627
+ unsigned todo = atomic_load(&q->ch->queued);
628
+ while (todo > 0) {
629
+ todo--;
630
+ z_owned_query_t oq;
631
+ if (zch_take_query(q->ch, &oq) != Z_OK) {
632
+ break;
633
+ }
634
+ VALUE obj = zrb_query_wrap(&oq);
635
+ taken++;
636
+ if (collect) {
637
+ rb_ary_push(out, obj);
638
+ } else {
639
+ rb_ensure(zrb_query_yield_body, obj, zrb_query_yield_ensure, obj);
640
+ }
641
+ }
642
+ return collect ? out : LONG2NUM(taken);
643
+ }
644
+
645
+ static VALUE zrb_qable_pending(VALUE self) { return UINT2NUM(atomic_load(&zrb_qable_get(self)->ch->queued)); }
646
+ static VALUE zrb_qable_received(VALUE self) { return UINT2NUM(atomic_load(&zrb_qable_get(self)->ch->received)); }
647
+ static VALUE zrb_qable_dropped(VALUE self) { return UINT2NUM(atomic_load(&zrb_qable_get(self)->ch->dropped)); }
648
+
649
+ static VALUE zrb_qable_close(VALUE self) {
650
+ zrb_qable_detach(zrb_qable_get(self));
651
+ return Qnil;
652
+ }
653
+
654
+ static VALUE zrb_qable_closed_p(VALUE self) { return zrb_qable_get(self)->declared ? Qfalse : Qtrue; }
655
+
656
+ static VALUE zrb_query_key(VALUE self) {
657
+ zrb_query *zq = zrb_query_get_live(self);
658
+ return zrb_key_str(z_query_keyexpr(z_loan(zq->query)));
659
+ }
660
+
661
+ static VALUE zrb_query_params(VALUE self) {
662
+ zrb_query *zq = zrb_query_get_live(self);
663
+ z_view_string_t ps;
664
+ z_query_parameters(z_loan(zq->query), &ps);
665
+ return zrb_str_from_view(z_loan(ps));
666
+ }
667
+
668
+ static VALUE zrb_query_payload(VALUE self) {
669
+ zrb_query *zq = zrb_query_get_live(self);
670
+ const z_loaned_bytes_t *b = z_query_payload(z_loan(zq->query));
671
+ if (b == NULL) {
672
+ return rb_str_new(NULL, 0);
673
+ }
674
+ return zrb_str_from_bytes(b);
675
+ }
676
+
677
+ static VALUE zrb_query_attachment(VALUE self) {
678
+ zrb_query *zq = zrb_query_get_live(self);
679
+ return zrb_attachment_value(z_query_attachment(z_loan(zq->query)));
680
+ }
681
+
682
+ /* q.reply(payload, attachment: nil) / q.reply(key, payload, attachment: nil)
683
+ * -> nil. The key defaults to the query's key; it must match the query's key
684
+ * expression. May be called more than once before the query is finished. */
685
+ static VALUE zrb_query_reply(int argc, VALUE *argv, VALUE self) {
686
+ VALUE a1, a2 = Qnil, opts = Qnil;
687
+ int n = rb_scan_args(argc, argv, "11:", &a1, &a2, &opts);
688
+ VALUE kw[1] = {Qundef};
689
+ if (!NIL_P(opts)) {
690
+ rb_get_kwargs(opts, &id_attachment, 0, 1, kw);
691
+ }
692
+ VALUE att = zrb_kw_attachment(kw[0]);
693
+ zrb_query *zq = zrb_query_get_live(self);
694
+ VALUE key_v = (n == 1) ? Qnil : a1;
695
+ VALUE payload = (n == 1) ? a1 : a2;
696
+ if (!RB_TYPE_P(payload, T_STRING)) {
697
+ rb_raise(rb_eTypeError, "payload must be a String");
698
+ }
699
+ z_view_keyexpr_t ke;
700
+ const z_loaned_keyexpr_t *kp;
701
+ if (NIL_P(key_v)) {
702
+ kp = z_query_keyexpr(z_loan(zq->query));
703
+ } else {
704
+ zrb_view_key(&ke, StringValueCStr(key_v));
705
+ kp = z_loan(ke);
706
+ }
707
+ z_query_reply_options_t ro;
708
+ z_query_reply_options_default(&ro);
709
+ z_owned_bytes_t att_bytes;
710
+ if (!NIL_P(att)) {
711
+ zrb_bytes_from_str(&att_bytes, att, "attachment");
712
+ ro.attachment = z_move(att_bytes);
713
+ }
714
+ z_owned_bytes_t bytes;
715
+ if (z_bytes_copy_from_buf(&bytes, (const uint8_t *)RSTRING_PTR(payload), (size_t)RSTRING_LEN(payload)) != Z_OK) {
716
+ if (!NIL_P(att)) {
717
+ z_drop(z_move(att_bytes));
718
+ }
719
+ rb_raise(eZenohError, "cannot allocate the payload");
720
+ }
721
+ z_result_t ret = z_query_reply(z_loan(zq->query), kp, z_move(bytes), &ro);
722
+ if (ret != Z_OK) {
723
+ rb_raise(eZenohError, "reply failed (%d)", (int)ret);
724
+ }
725
+ return Qnil;
726
+ }
727
+
728
+ static VALUE zrb_query_finish_m(VALUE self) {
729
+ zrb_query_finish(zrb_query_get(self));
730
+ return Qnil;
731
+ }
732
+
733
+ static VALUE zrb_query_finished_p(VALUE self) { return zrb_query_get(self)->live ? Qfalse : Qtrue; }
734
+
735
+ /* ------------------------------------------------------------------ get */
736
+
737
+ static void zrb_get_free(void *p) {
738
+ zrb_get *g = (zrb_get *)p;
739
+ zch_unref(g->ch); /* zenoh-c may still hold it until the get finishes */
740
+ xfree(g);
741
+ }
742
+
743
+ static size_t zrb_get_size(const void *p) { return sizeof(zrb_get); }
744
+
745
+ static const rb_data_type_t zrb_get_type = {"Asterism::Zenoh::Get", {NULL, zrb_get_free, zrb_get_size}, NULL, NULL, 0};
746
+
747
+ static zrb_get *zrb_get_get(VALUE self) {
748
+ zrb_get *g;
749
+ TypedData_Get_Struct(self, zrb_get, &zrb_get_type, g);
750
+ return g;
751
+ }
752
+
753
+ static VALUE zrb_get_new(zrb_get **out) {
754
+ zrb_get *g;
755
+ VALUE obj = TypedData_Make_Struct(cGet, zrb_get, &zrb_get_type, g);
756
+ g->ch = zch_new(ZCH_REPLY, ZRB_DEFAULT_DEPTH);
757
+ *out = g;
758
+ return obj;
759
+ }
760
+
761
+ /* g.each_reply { |key, payload, attachment| ... } -> Integer; without a
762
+ * block, Array of [key, payload, attachment]. Error replies are counted
763
+ * (errors), not yielded. */
764
+ static VALUE zrb_get_each_reply(VALUE self) {
765
+ zrb_get *g = zrb_get_get(self);
766
+ bool collect = !rb_block_given_p();
767
+ VALUE out = collect ? rb_ary_new() : Qnil;
768
+ long taken = 0;
769
+ unsigned todo = atomic_load(&g->ch->queued);
770
+ while (todo > 0) {
771
+ todo--;
772
+ z_owned_reply_t r;
773
+ if (zch_take_reply(g->ch, &r) != Z_OK) {
774
+ break;
775
+ }
776
+ VALUE vals[3];
777
+ const z_loaned_sample_t *sm = z_reply_ok(z_loan(r));
778
+ vals[0] = zrb_key_str(z_sample_keyexpr(sm));
779
+ vals[1] = zrb_str_from_bytes(z_sample_payload(sm));
780
+ vals[2] = zrb_attachment_value(z_sample_attachment(sm));
781
+ z_drop(z_move(r));
782
+ taken++;
783
+ if (collect) {
784
+ rb_ary_push(out, rb_ary_new_from_values(3, vals));
785
+ } else {
786
+ rb_yield_values2(3, vals);
787
+ }
788
+ }
789
+ return collect ? out : LONG2NUM(taken);
790
+ }
791
+
792
+ static VALUE zrb_get_done_p(VALUE self) { return atomic_load(&zrb_get_get(self)->ch->done) ? Qtrue : Qfalse; }
793
+ static VALUE zrb_get_pending(VALUE self) { return UINT2NUM(atomic_load(&zrb_get_get(self)->ch->queued)); }
794
+ static VALUE zrb_get_received(VALUE self) { return UINT2NUM(atomic_load(&zrb_get_get(self)->ch->received)); }
795
+ static VALUE zrb_get_dropped(VALUE self) { return UINT2NUM(atomic_load(&zrb_get_get(self)->ch->dropped)); }
796
+ static VALUE zrb_get_errors(VALUE self) { return UINT2NUM(atomic_load(&zrb_get_get(self)->ch->errors)); }
797
+
798
+ /* ---------------------------------------------------------------- token */
799
+
800
+ static void zrb_token_detach(zrb_token *t) {
801
+ if (t->declared) {
802
+ z_drop(z_move(t->token));
803
+ t->declared = false;
804
+ }
805
+ if (t->owner != NULL) {
806
+ zrb_token **pp = &t->owner->tokens;
807
+ while (*pp != NULL) {
808
+ if (*pp == t) {
809
+ *pp = t->next;
810
+ break;
811
+ }
812
+ pp = &(*pp)->next;
813
+ }
814
+ t->owner = NULL;
815
+ t->next = NULL;
816
+ }
817
+ }
818
+
819
+ static void zrb_token_free(void *p) {
820
+ zrb_token *t = (zrb_token *)p;
821
+ zrb_token_detach(t);
822
+ xfree(t);
823
+ }
824
+
825
+ static size_t zrb_token_size(const void *p) { return sizeof(zrb_token); }
826
+
827
+ static const rb_data_type_t zrb_token_type = {
828
+ "Asterism::Zenoh::LivelinessToken", {NULL, zrb_token_free, zrb_token_size}, NULL, NULL, 0};
829
+
830
+ static zrb_token *zrb_token_get(VALUE self) {
831
+ zrb_token *t;
832
+ TypedData_Get_Struct(self, zrb_token, &zrb_token_type, t);
833
+ return t;
834
+ }
835
+
836
+ static VALUE zrb_token_close(VALUE self) {
837
+ zrb_token_detach(zrb_token_get(self));
838
+ return Qnil;
839
+ }
840
+
841
+ static VALUE zrb_token_closed_p(VALUE self) { return zrb_token_get(self)->declared ? Qfalse : Qtrue; }
842
+
843
+ /* -------------------------------------------------------------- session */
844
+
845
+ /* Undeclares everything the session still has. Holds the GVL (the lists
846
+ * are only touched with it). */
847
+ static void zrb_session_detach_all(zrb_session *z) {
848
+ while (z->qables != NULL) {
849
+ zrb_qable_detach(z->qables); /* unlinks itself */
850
+ }
851
+ while (z->tokens != NULL) {
852
+ zrb_token_detach(z->tokens);
853
+ }
854
+ while (z->subs != NULL) {
855
+ zrb_sub_detach(z->subs);
856
+ }
857
+ }
858
+
859
+ /* Closes the zenoh-c session. Without the GVL when called from Ruby
860
+ * methods (it waits for zenoh's tasks), with it from the GC. */
861
+ static void *zrb_session_close_nogvl(void *p) {
862
+ zrb_session *z = (zrb_session *)p;
863
+ pthread_mutex_lock(&z->op_lock);
864
+ if (z->open) {
865
+ z->open = false;
866
+ z_close(z_loan_mut(z->session), NULL);
867
+ z_drop(z_move(z->session));
868
+ }
869
+ pthread_mutex_unlock(&z->op_lock);
870
+ return NULL;
871
+ }
872
+
873
+ static void zrb_session_shutdown(zrb_session *z, bool with_gvl_release) {
874
+ zrb_session_detach_all(z);
875
+ if (!z->open) {
876
+ return;
877
+ }
878
+ if (with_gvl_release) {
879
+ rb_thread_call_without_gvl(zrb_session_close_nogvl, z, RUBY_UBF_IO, NULL);
880
+ } else {
881
+ zrb_session_close_nogvl(z);
882
+ }
883
+ }
884
+
885
+ static void zrb_session_free(void *p) {
886
+ zrb_session *z = (zrb_session *)p;
887
+ zrb_session_shutdown(z, false);
888
+ pthread_mutex_destroy(&z->op_lock);
889
+ xfree(z);
890
+ }
891
+
892
+ static size_t zrb_session_size(const void *p) { return sizeof(zrb_session); }
893
+
894
+ static const rb_data_type_t zrb_session_type = {
895
+ "Asterism::Zenoh::Session", {NULL, zrb_session_free, zrb_session_size}, NULL, NULL, 0};
896
+
897
+ static zrb_session *zrb_session_get(VALUE self) {
898
+ zrb_session *z;
899
+ TypedData_Get_Struct(self, zrb_session, &zrb_session_type, z);
900
+ return z;
901
+ }
902
+
903
+ static void zrb_count_zid(const z_id_t *id, void *ctx) {
904
+ (void)id;
905
+ (*(int *)ctx)++;
906
+ }
907
+
908
+ /* Routers (client) or peers (peer mode) connected now. */
909
+ static int zrb_session_links(zrb_session *z) {
910
+ int n = 0;
911
+ z_owned_closure_zid_t cb;
912
+ z_closure(&cb, zrb_count_zid, NULL, &n);
913
+ if (z->peer) {
914
+ z_info_peers_zid(z_loan(z->session), z_move(cb));
915
+ } else {
916
+ z_info_routers_zid(z_loan(z->session), z_move(cb));
917
+ }
918
+ return n;
919
+ }
920
+
921
+ /* Close the session when its connection can no longer carry it (see the
922
+ * comment at the top). Returns true when the session is (now) closed. */
923
+ static bool zrb_session_check_link(zrb_session *z) {
924
+ if (!z->open) {
925
+ return true;
926
+ }
927
+ if (z_session_is_closed(z_loan(z->session))) {
928
+ zrb_session_shutdown(z, true);
929
+ return true;
930
+ }
931
+ if (z->peer && z->listening) {
932
+ return false;
933
+ }
934
+ if (zrb_session_links(z) == 0) {
935
+ zrb_session_shutdown(z, true);
936
+ return true;
937
+ }
938
+ return false;
939
+ }
940
+
941
+ static zrb_session *zrb_session_get_open(VALUE self) {
942
+ zrb_session *z = zrb_session_get(self);
943
+ if (zrb_session_check_link(z)) {
944
+ rb_raise(eZenohError, "session is closed");
945
+ }
946
+ return z;
947
+ }
948
+
949
+ /* A JSON5 string literal of s (for the configuration). */
950
+ static VALUE zrb_json_str(const char *s) {
951
+ VALUE out = rb_str_new_cstr("\"");
952
+ for (const char *p = s; *p; p++) {
953
+ if (*p == '"' || *p == '\\') {
954
+ rb_str_cat(out, "\\", 1);
955
+ }
956
+ rb_str_cat(out, p, 1);
957
+ }
958
+ rb_str_cat(out, "\"", 1);
959
+ return out;
960
+ }
961
+
962
+ static bool zrb_config_set(z_owned_config_t *config, const char *key, const char *json5) {
963
+ return zc_config_insert_json5(z_loan_mut(*config), key, json5) == Z_OK;
964
+ }
965
+
966
+ typedef struct {
967
+ zrb_session *z;
968
+ z_owned_config_t config;
969
+ z_result_t ret;
970
+ } zrb_open_args;
971
+
972
+ static void *zrb_open_nogvl(void *p) {
973
+ zrb_open_args *a = (zrb_open_args *)p;
974
+ a->ret = z_open(&a->z->session, z_move(a->config), NULL);
975
+ return NULL;
976
+ }
977
+
978
+ /* Asterism::Zenoh::Session.open(locator = nil, mode: :client, listen: nil) -> Session
979
+ * - client: connects to the router at locator.
980
+ * - peer: connects to the peer at locator (if given) and/or listens on
981
+ * listen (e.g. "tcp/0.0.0.0:7447"). At least one of them is needed.
982
+ * Raises Asterism::Zenoh::Error when the session cannot be opened (no
983
+ * router or peer answered within CONNECT_TIMEOUT_MS). */
984
+ static VALUE zrb_session_s_open(int argc, VALUE *argv, VALUE klass) {
985
+ VALUE locator_v = Qnil, opts = Qnil;
986
+ rb_scan_args(argc, argv, "01:", &locator_v, &opts);
987
+ VALUE kw[2] = {Qundef, Qundef};
988
+ if (!NIL_P(opts)) {
989
+ ID ids[2] = {id_mode, id_listen};
990
+ rb_get_kwargs(opts, ids, 0, 2, kw);
991
+ }
992
+ bool peer = false;
993
+ if (kw[0] != Qundef && !NIL_P(kw[0])) {
994
+ ID mode = zrb_sym_id(kw[0]);
995
+ if (mode == id_peer) {
996
+ peer = true;
997
+ } else if (mode != id_client) {
998
+ rb_raise(rb_eArgError, "mode must be :client or :peer");
999
+ }
1000
+ }
1001
+ const char *locator = NIL_P(locator_v) ? NULL : StringValueCStr(locator_v);
1002
+ VALUE listen_v = (kw[1] == Qundef) ? Qnil : kw[1];
1003
+ const char *listen = NIL_P(listen_v) ? NULL : StringValueCStr(listen_v);
1004
+ if (!peer && listen != NULL) {
1005
+ rb_raise(rb_eArgError, "listen needs mode: :peer");
1006
+ }
1007
+ if (locator == NULL && listen == NULL) {
1008
+ rb_raise(rb_eArgError, "a locator (or, for a peer, listen:) is needed");
1009
+ }
1010
+
1011
+ zrb_session *z;
1012
+ VALUE obj = TypedData_Make_Struct(klass, zrb_session, &zrb_session_type, z);
1013
+ pthread_mutex_init(&z->op_lock, NULL);
1014
+
1015
+ /* The configuration mirrors the mruby gem's zenoh-pico build: no
1016
+ * scouting (the locator is given), a connect-only peer does not listen,
1017
+ * and the session fails to open when nobody answers in time. */
1018
+ zrb_open_args a;
1019
+ a.z = z;
1020
+ if (z_config_default(&a.config) != Z_OK) {
1021
+ rb_raise(eZenohError, "cannot create the configuration");
1022
+ }
1023
+ char num[32];
1024
+ snprintf(num, sizeof(num), "%d", ZRB_CONNECT_TIMEOUT_MS);
1025
+ bool ok = zrb_config_set(&a.config, "mode", peer ? "\"peer\"" : "\"client\"") &&
1026
+ zrb_config_set(&a.config, "scouting/multicast/enabled", "false") &&
1027
+ zrb_config_set(&a.config, "scouting/gossip/enabled", "false") &&
1028
+ zrb_config_set(&a.config, "connect/timeout_ms", num) &&
1029
+ zrb_config_set(&a.config, "connect/exit_on_failure", "true");
1030
+ if (ok) {
1031
+ snprintf(num, sizeof(num), "%d", ZRB_SEND_TIMEOUT_MS * 1000);
1032
+ ok = zrb_config_set(&a.config, "transport/link/tx/queue/congestion_control/block/wait_before_close", num);
1033
+ }
1034
+ if (ok && locator != NULL) {
1035
+ VALUE ep = rb_str_concat(rb_str_concat(rb_str_new_cstr("["), zrb_json_str(locator)), rb_str_new_cstr("]"));
1036
+ ok = zrb_config_set(&a.config, "connect/endpoints", StringValueCStr(ep));
1037
+ }
1038
+ if (ok) {
1039
+ if (listen != NULL) {
1040
+ VALUE ep = rb_str_concat(rb_str_concat(rb_str_new_cstr("["), zrb_json_str(listen)), rb_str_new_cstr("]"));
1041
+ ok = zrb_config_set(&a.config, "listen/endpoints", StringValueCStr(ep));
1042
+ } else {
1043
+ ok = zrb_config_set(&a.config, "listen/endpoints", "[]");
1044
+ }
1045
+ }
1046
+ if (!ok) {
1047
+ z_drop(z_move(a.config));
1048
+ rb_raise(eZenohError, "invalid locator");
1049
+ }
1050
+ rb_thread_call_without_gvl(zrb_open_nogvl, &a, RUBY_UBF_IO, NULL);
1051
+ if (a.ret != Z_OK) {
1052
+ rb_raise(eZenohError, "cannot open a session to %s (%d)", locator != NULL ? locator : listen, (int)a.ret);
1053
+ }
1054
+ z->open = true;
1055
+ z->peer = peer;
1056
+ z->listening = (listen != NULL);
1057
+ return obj;
1058
+ }
1059
+
1060
+ typedef struct {
1061
+ zrb_session *z;
1062
+ z_owned_keyexpr_t key;
1063
+ z_owned_bytes_t payload;
1064
+ z_put_options_t opts;
1065
+ z_result_t ret;
1066
+ } zrb_put_args;
1067
+
1068
+ static void *zrb_put_nogvl(void *p) {
1069
+ zrb_put_args *a = (zrb_put_args *)p;
1070
+ pthread_mutex_lock(&a->z->op_lock);
1071
+ if (a->z->open) {
1072
+ a->ret = z_put(z_loan(a->z->session), z_loan(a->key), z_move(a->payload), &a->opts);
1073
+ } else {
1074
+ a->ret = Z_ESESSION_CLOSED;
1075
+ }
1076
+ pthread_mutex_unlock(&a->z->op_lock);
1077
+ return NULL;
1078
+ }
1079
+
1080
+ /* session.put(key, payload, attachment: nil) -> nil */
1081
+ static VALUE zrb_session_put(int argc, VALUE *argv, VALUE self) {
1082
+ VALUE key_v, payload, opts = Qnil;
1083
+ rb_scan_args(argc, argv, "2:", &key_v, &payload, &opts);
1084
+ VALUE kw[1] = {Qundef};
1085
+ if (!NIL_P(opts)) {
1086
+ rb_get_kwargs(opts, &id_attachment, 0, 1, kw);
1087
+ }
1088
+ const char *key = StringValueCStr(key_v);
1089
+ StringValue(payload);
1090
+ VALUE att = zrb_kw_attachment(kw[0]);
1091
+ zrb_session *z = zrb_session_get_open(self);
1092
+
1093
+ zrb_put_args a;
1094
+ a.z = z;
1095
+ a.ret = Z_OK;
1096
+ zrb_owned_key(&a.key, key);
1097
+ if (z_bytes_copy_from_buf(&a.payload, (const uint8_t *)RSTRING_PTR(payload), (size_t)RSTRING_LEN(payload)) !=
1098
+ Z_OK) {
1099
+ z_drop(z_move(a.key));
1100
+ rb_raise(eZenohError, "cannot allocate the payload");
1101
+ }
1102
+ z_put_options_default(&a.opts);
1103
+ z_owned_bytes_t att_bytes;
1104
+ if (!NIL_P(att)) {
1105
+ if (z_bytes_copy_from_buf(&att_bytes, (const uint8_t *)RSTRING_PTR(att), (size_t)RSTRING_LEN(att)) != Z_OK) {
1106
+ z_drop(z_move(a.payload));
1107
+ z_drop(z_move(a.key));
1108
+ rb_raise(eZenohError, "cannot allocate the attachment");
1109
+ }
1110
+ a.opts.attachment = z_move(att_bytes);
1111
+ }
1112
+ rb_thread_call_without_gvl(zrb_put_nogvl, &a, RUBY_UBF_IO, NULL);
1113
+ z_drop(z_move(a.key));
1114
+ if (zrb_session_check_link(z)) {
1115
+ rb_raise(eZenohError, "put failed: the connection is lost (%d)", (int)a.ret);
1116
+ }
1117
+ if (a.ret != Z_OK) {
1118
+ rb_raise(eZenohError, "put failed (%d)", (int)a.ret);
1119
+ }
1120
+ return Qnil;
1121
+ }
1122
+
1123
+ static VALUE zrb_sub_new(VALUE self, zrb_session *z, VALUE key_v, long depth, bool liveliness) {
1124
+ const char *key = StringValueCStr(key_v);
1125
+ z_view_keyexpr_t ke;
1126
+ zrb_view_key(&ke, key);
1127
+
1128
+ zrb_sub *s;
1129
+ VALUE obj = TypedData_Make_Struct(liveliness ? cWatch : cSubscriber, zrb_sub, &zrb_sub_type, s);
1130
+ s->liveliness = liveliness;
1131
+ s->ch = zch_new(ZCH_SAMPLE, (uint32_t)depth);
1132
+
1133
+ z_owned_closure_sample_t cb;
1134
+ zch_closure_sample(s->ch, &cb);
1135
+ z_result_t ret;
1136
+ if (liveliness) {
1137
+ /* history: the tokens alive now are reported first, as appearing. */
1138
+ z_liveliness_subscriber_options_t lo;
1139
+ z_liveliness_subscriber_options_default(&lo);
1140
+ lo.history = true;
1141
+ ret = z_liveliness_declare_subscriber(z_loan(z->session), &s->sub, z_loan(ke), z_move(cb), &lo);
1142
+ } else {
1143
+ /* Remote samples only: zenoh-pico (the mruby gem) does not deliver a
1144
+ * session's own puts to its own subscribers either. */
1145
+ z_subscriber_options_t so;
1146
+ z_subscriber_options_default(&so);
1147
+ so.allowed_origin = Z_LOCALITY_REMOTE;
1148
+ ret = z_declare_subscriber(z_loan(z->session), &s->sub, z_loan(ke), z_move(cb), &so);
1149
+ }
1150
+ if (ret != Z_OK) {
1151
+ rb_raise(eZenohError, "cannot subscribe to %s (%d)", key, (int)ret);
1152
+ }
1153
+ s->declared = true;
1154
+ s->owner = z;
1155
+ s->next = z->subs;
1156
+ z->subs = s;
1157
+ zrb_hold_session(obj, self, key_v);
1158
+ return obj;
1159
+ }
1160
+
1161
+ /* session.subscribe(key, depth = 16) -> Subscriber */
1162
+ static VALUE zrb_session_subscribe(int argc, VALUE *argv, VALUE self) {
1163
+ VALUE key_v, depth_v = Qnil;
1164
+ rb_scan_args(argc, argv, "11", &key_v, &depth_v);
1165
+ long depth = zrb_check_depth(depth_v);
1166
+ StringValueCStr(key_v);
1167
+ zrb_session *z = zrb_session_get_open(self);
1168
+ return zrb_sub_new(self, z, key_v, depth, false);
1169
+ }
1170
+
1171
+ /* session.liveliness_watch(key, depth = 16) -> LivelinessWatch */
1172
+ static VALUE zrb_session_liveliness_watch(int argc, VALUE *argv, VALUE self) {
1173
+ VALUE key_v, depth_v = Qnil;
1174
+ rb_scan_args(argc, argv, "11", &key_v, &depth_v);
1175
+ long depth = zrb_check_depth(depth_v);
1176
+ StringValueCStr(key_v);
1177
+ zrb_session *z = zrb_session_get_open(self);
1178
+ return zrb_sub_new(self, z, key_v, depth, true);
1179
+ }
1180
+
1181
+ /* session.queryable(key, depth = 16, complete: false) -> Queryable */
1182
+ static VALUE zrb_session_queryable(int argc, VALUE *argv, VALUE self) {
1183
+ VALUE key_v, depth_v = Qnil, opts = Qnil;
1184
+ rb_scan_args(argc, argv, "11:", &key_v, &depth_v, &opts);
1185
+ VALUE kw[1] = {Qundef};
1186
+ if (!NIL_P(opts)) {
1187
+ rb_get_kwargs(opts, &id_complete, 0, 1, kw);
1188
+ }
1189
+ bool complete = (kw[0] != Qundef) && RTEST(kw[0]);
1190
+ long depth = zrb_check_depth(depth_v);
1191
+ const char *key = StringValueCStr(key_v);
1192
+ zrb_session *z = zrb_session_get_open(self);
1193
+ z_view_keyexpr_t ke;
1194
+ zrb_view_key(&ke, key);
1195
+
1196
+ zrb_qable *q;
1197
+ VALUE obj = TypedData_Make_Struct(cQueryable, zrb_qable, &zrb_qable_type, q);
1198
+ q->ch = zch_new(ZCH_QUERY, (uint32_t)depth);
1199
+ z_owned_closure_query_t cb;
1200
+ zch_closure_query(q->ch, &cb);
1201
+ z_queryable_options_t qo;
1202
+ z_queryable_options_default(&qo);
1203
+ qo.complete = complete;
1204
+ /* Remote queries only, as with zenoh-pico (Asterism answers calls to
1205
+ * its own objects in place). */
1206
+ qo.allowed_origin = Z_LOCALITY_REMOTE;
1207
+ z_result_t ret = z_declare_queryable(z_loan(z->session), &q->qable, z_loan(ke), z_move(cb), &qo);
1208
+ if (ret != Z_OK) {
1209
+ rb_raise(eZenohError, "cannot declare a queryable on %s (%d)", key, (int)ret);
1210
+ }
1211
+ q->declared = true;
1212
+ q->owner = z;
1213
+ q->next = z->qables;
1214
+ z->qables = q;
1215
+ zrb_hold_session(obj, self, key_v);
1216
+ return obj;
1217
+ }
1218
+
1219
+ static z_query_target_t zrb_get_target(VALUE v) {
1220
+ if (v == Qundef || NIL_P(v)) {
1221
+ return Z_QUERY_TARGET_ALL;
1222
+ }
1223
+ ID t = zrb_sym_id(v);
1224
+ if (t == id_all) {
1225
+ return Z_QUERY_TARGET_ALL;
1226
+ }
1227
+ if (t == id_all_complete) {
1228
+ return Z_QUERY_TARGET_ALL_COMPLETE;
1229
+ }
1230
+ if (t == id_best_matching) {
1231
+ return Z_QUERY_TARGET_BEST_MATCHING;
1232
+ }
1233
+ rb_raise(rb_eArgError, "target must be :all, :all_complete or :best_matching");
1234
+ return Z_QUERY_TARGET_ALL; /* not reached */
1235
+ }
1236
+
1237
+ static z_query_consolidation_t zrb_get_consolidation(VALUE v) {
1238
+ if (v == Qundef || NIL_P(v)) {
1239
+ return z_query_consolidation_none();
1240
+ }
1241
+ ID c = zrb_sym_id(v);
1242
+ if (c == id_none) {
1243
+ return z_query_consolidation_none();
1244
+ }
1245
+ if (c == id_latest) {
1246
+ return z_query_consolidation_latest();
1247
+ }
1248
+ if (c == id_monotonic) {
1249
+ return z_query_consolidation_monotonic();
1250
+ }
1251
+ if (c == id_auto) {
1252
+ return z_query_consolidation_auto();
1253
+ }
1254
+ rb_raise(rb_eArgError, "consolidation must be :none, :latest, :monotonic or :auto");
1255
+ return z_query_consolidation_none(); /* not reached */
1256
+ }
1257
+
1258
+ typedef struct {
1259
+ zrb_session *z;
1260
+ z_owned_keyexpr_t key;
1261
+ char *params; /* malloc'd copy, or NULL */
1262
+ bool liveliness;
1263
+ z_owned_closure_reply_t cb;
1264
+ z_get_options_t opts;
1265
+ z_liveliness_get_options_t lopts;
1266
+ z_result_t ret;
1267
+ } zrb_get_args;
1268
+
1269
+ static void *zrb_get_nogvl(void *p) {
1270
+ zrb_get_args *a = (zrb_get_args *)p;
1271
+ pthread_mutex_lock(&a->z->op_lock);
1272
+ if (a->z->open) {
1273
+ if (a->liveliness) {
1274
+ a->ret = z_liveliness_get(z_loan(a->z->session), z_loan(a->key), z_move(a->cb), &a->lopts);
1275
+ } else {
1276
+ a->ret = z_get(z_loan(a->z->session), z_loan(a->key), a->params != NULL ? a->params : "", z_move(a->cb),
1277
+ &a->opts);
1278
+ }
1279
+ } else {
1280
+ a->ret = Z_ESESSION_CLOSED;
1281
+ }
1282
+ pthread_mutex_unlock(&a->z->op_lock);
1283
+ /* A closure that was not taken (the session was closed) is dropped
1284
+ * here, which releases the channel's reference. */
1285
+ z_drop(z_move(a->cb));
1286
+ return NULL;
1287
+ }
1288
+
1289
+ static VALUE zrb_run_get(VALUE self, zrb_session *z, zrb_get_args *a, VALUE obj, VALUE key_v, const char *what) {
1290
+ rb_thread_call_without_gvl(zrb_get_nogvl, a, RUBY_UBF_IO, NULL);
1291
+ z_drop(z_move(a->key));
1292
+ free(a->params);
1293
+ if (zrb_session_check_link(z)) {
1294
+ rb_raise(eZenohError, "%s failed: the connection is lost (%d)", what, (int)a->ret);
1295
+ }
1296
+ if (a->ret != Z_OK) {
1297
+ rb_raise(eZenohError, "%s failed (%d)", what, (int)a->ret);
1298
+ }
1299
+ zrb_hold_session(obj, self, key_v);
1300
+ return obj;
1301
+ }
1302
+
1303
+ /* session.get(key, timeout_ms = 2000, params = nil, payload = nil,
1304
+ * attachment: nil, target: :all, consolidation: :none) -> Get.
1305
+ * Returns at once; the replies come in on zenoh's threads. */
1306
+ static VALUE zrb_session_get_m(int argc, VALUE *argv, VALUE self) {
1307
+ VALUE key_v, timeout_v = Qnil, params_v = Qnil, payload = Qnil, opts = Qnil;
1308
+ rb_scan_args(argc, argv, "13:", &key_v, &timeout_v, &params_v, &payload, &opts);
1309
+ VALUE kw[3] = {Qundef, Qundef, Qundef};
1310
+ if (!NIL_P(opts)) {
1311
+ ID ids[3] = {id_attachment, id_target, id_consolidation};
1312
+ rb_get_kwargs(opts, ids, 0, 3, kw);
1313
+ }
1314
+ const char *key = StringValueCStr(key_v);
1315
+ long timeout_ms = zrb_check_timeout(timeout_v);
1316
+ const char *params = NIL_P(params_v) ? NULL : StringValueCStr(params_v);
1317
+ if (!NIL_P(payload) && !RB_TYPE_P(payload, T_STRING)) {
1318
+ rb_raise(rb_eTypeError, "payload must be a String");
1319
+ }
1320
+ VALUE att = zrb_kw_attachment(kw[0]);
1321
+ z_query_target_t target = zrb_get_target(kw[1]);
1322
+ z_query_consolidation_t consolidation = zrb_get_consolidation(kw[2]);
1323
+ zrb_session *z = zrb_session_get_open(self);
1324
+
1325
+ zrb_get_args a;
1326
+ memset(&a, 0, sizeof(a));
1327
+ a.z = z;
1328
+ zrb_owned_key(&a.key, key);
1329
+ zrb_get *g;
1330
+ VALUE obj = zrb_get_new(&g);
1331
+ z_get_options_default(&a.opts);
1332
+ a.opts.timeout_ms = (uint64_t)timeout_ms;
1333
+ a.opts.target = target;
1334
+ a.opts.consolidation = consolidation;
1335
+ /* Remote queryables only, as with zenoh-pico. */
1336
+ a.opts.allowed_destination = Z_LOCALITY_REMOTE;
1337
+ z_owned_bytes_t bytes;
1338
+ z_owned_bytes_t att_bytes;
1339
+ if (!NIL_P(payload)) {
1340
+ zrb_bytes_from_str(&bytes, payload, "payload");
1341
+ a.opts.payload = z_move(bytes);
1342
+ }
1343
+ if (!NIL_P(att)) {
1344
+ zrb_bytes_from_str(&att_bytes, att, "attachment");
1345
+ a.opts.attachment = z_move(att_bytes);
1346
+ }
1347
+ if (params != NULL) {
1348
+ a.params = strdup(params);
1349
+ }
1350
+ zch_closure_reply(g->ch, &a.cb);
1351
+ return zrb_run_get(self, z, &a, obj, key_v, "get");
1352
+ }
1353
+
1354
+ /* session.liveliness(key) -> LivelinessToken */
1355
+ static VALUE zrb_session_liveliness(VALUE self, VALUE key_v) {
1356
+ const char *key = StringValueCStr(key_v);
1357
+ zrb_session *z = zrb_session_get_open(self);
1358
+ z_view_keyexpr_t ke;
1359
+ zrb_view_key(&ke, key);
1360
+ zrb_token *t;
1361
+ VALUE obj = TypedData_Make_Struct(cToken, zrb_token, &zrb_token_type, t);
1362
+ z_result_t ret = z_liveliness_declare_token(z_loan(z->session), &t->token, z_loan(ke), NULL);
1363
+ if (ret != Z_OK) {
1364
+ rb_raise(eZenohError, "cannot declare a liveliness token on %s (%d)", key, (int)ret);
1365
+ }
1366
+ t->declared = true;
1367
+ t->owner = z;
1368
+ t->next = z->tokens;
1369
+ z->tokens = t;
1370
+ zrb_hold_session(obj, self, key_v);
1371
+ return obj;
1372
+ }
1373
+
1374
+ /* session.liveliness_get(key, timeout_ms = 2000) -> Get */
1375
+ static VALUE zrb_session_liveliness_get(int argc, VALUE *argv, VALUE self) {
1376
+ VALUE key_v, timeout_v = Qnil;
1377
+ rb_scan_args(argc, argv, "11", &key_v, &timeout_v);
1378
+ const char *key = StringValueCStr(key_v);
1379
+ long timeout_ms = zrb_check_timeout(timeout_v);
1380
+ zrb_session *z = zrb_session_get_open(self);
1381
+
1382
+ zrb_get_args a;
1383
+ memset(&a, 0, sizeof(a));
1384
+ a.z = z;
1385
+ a.liveliness = true;
1386
+ zrb_owned_key(&a.key, key);
1387
+ zrb_get *g;
1388
+ VALUE obj = zrb_get_new(&g);
1389
+ z_liveliness_get_options_default(&a.lopts);
1390
+ a.lopts.timeout_ms = (uint64_t)timeout_ms;
1391
+ zch_closure_reply(g->ch, &a.cb);
1392
+ return zrb_run_get(self, z, &a, obj, key_v, "liveliness_get");
1393
+ }
1394
+
1395
+ /* session.poll(steps = 8) -> true while the session is open, false once
1396
+ * closed (by close, or because the connection was lost). zenoh-c receives
1397
+ * on its own threads, so this only checks the connection; steps is
1398
+ * accepted for the mruby gem's signature. Never waits. */
1399
+ static VALUE zrb_session_poll(int argc, VALUE *argv, VALUE self) {
1400
+ VALUE steps_v = Qnil;
1401
+ rb_scan_args(argc, argv, "01", &steps_v);
1402
+ if (!NIL_P(steps_v)) {
1403
+ NUM2LONG(steps_v);
1404
+ }
1405
+ return zrb_session_check_link(zrb_session_get(self)) ? Qfalse : Qtrue;
1406
+ }
1407
+
1408
+ static VALUE zrb_session_closed_p(VALUE self) {
1409
+ return zrb_session_check_link(zrb_session_get(self)) ? Qtrue : Qfalse;
1410
+ }
1411
+
1412
+ static VALUE zrb_session_close(VALUE self) {
1413
+ zrb_session_shutdown(zrb_session_get(self), true);
1414
+ return Qnil;
1415
+ }
1416
+
1417
+ /* session.zid -> String: this session's Zenoh ID in hex. */
1418
+ static VALUE zrb_session_zid(VALUE self) {
1419
+ zrb_session *z = zrb_session_get_open(self);
1420
+ z_id_t id = z_info_zid(z_loan(z->session));
1421
+ z_owned_string_t str;
1422
+ z_id_to_string(&id, &str);
1423
+ VALUE out = zrb_str_from_view(z_loan(str));
1424
+ z_drop(z_move(str));
1425
+ return out;
1426
+ }
1427
+
1428
+ /* session.peers -> Integer: connected peers (peer mode), or routers (1
1429
+ * normally) for a client session; 0 once closed. */
1430
+ static VALUE zrb_session_peers(VALUE self) {
1431
+ zrb_session *z = zrb_session_get(self);
1432
+ if (zrb_session_check_link(z)) {
1433
+ return INT2FIX(0);
1434
+ }
1435
+ return INT2FIX(zrb_session_links(z));
1436
+ }
1437
+
1438
+ /* The default of zenoh-c's transport/unicast/max_sessions (MAX_PEERS). */
1439
+ static long zrb_default_max_sessions(void) {
1440
+ long n = -1;
1441
+ z_owned_config_t c;
1442
+ if (z_config_default(&c) != Z_OK) {
1443
+ return n;
1444
+ }
1445
+ z_owned_string_t s;
1446
+ if (zc_config_get_from_str(z_loan(c), "transport/unicast/max_sessions", &s) == Z_OK) {
1447
+ char buf[32];
1448
+ size_t len = z_string_len(z_loan(s));
1449
+ if (len < sizeof(buf)) {
1450
+ memcpy(buf, z_string_data(z_loan(s)), len);
1451
+ buf[len] = '\0';
1452
+ n = strtol(buf, NULL, 10);
1453
+ }
1454
+ z_drop(z_move(s));
1455
+ }
1456
+ z_drop(z_move(c));
1457
+ return n;
1458
+ }
1459
+
1460
+ /* ----------------------------------------------------------------- init */
1461
+
1462
+ void Init_asterism_zenoh(void) {
1463
+ /* Asterism::Zenoh. No top-level Zenoh (require "asterism/zenoh/global"). */
1464
+ mAsterism = rb_define_module("Asterism");
1465
+ mZenoh = rb_define_module_under(mAsterism, "Zenoh");
1466
+ eZenohError = rb_define_class_under(mZenoh, "Error", rb_eStandardError);
1467
+ rb_define_const(mZenoh, "C_VERSION", rb_str_freeze(rb_str_new_cstr(ZENOH_C)));
1468
+ rb_define_const(mZenoh, "CONNECT_TIMEOUT_MS", INT2FIX(ZRB_CONNECT_TIMEOUT_MS));
1469
+ rb_define_const(mZenoh, "SEND_TIMEOUT_MS", INT2FIX(ZRB_SEND_TIMEOUT_MS));
1470
+ rb_define_const(mZenoh, "PEER", Qtrue);
1471
+ rb_define_const(mZenoh, "MAX_PEERS", LONG2NUM(zrb_default_max_sessions()));
1472
+
1473
+ id_attachment = rb_intern("attachment");
1474
+ id_target = rb_intern("target");
1475
+ id_consolidation = rb_intern("consolidation");
1476
+ id_complete = rb_intern("complete");
1477
+ id_mode = rb_intern("mode");
1478
+ id_listen = rb_intern("listen");
1479
+ id_iv_session = rb_intern("@session");
1480
+ id_iv_key = rb_intern("@key");
1481
+ id_client = rb_intern("client");
1482
+ id_peer = rb_intern("peer");
1483
+ id_all = rb_intern("all");
1484
+ id_all_complete = rb_intern("all_complete");
1485
+ id_best_matching = rb_intern("best_matching");
1486
+ id_none = rb_intern("none");
1487
+ id_latest = rb_intern("latest");
1488
+ id_monotonic = rb_intern("monotonic");
1489
+ id_auto = rb_intern("auto");
1490
+
1491
+ cSession = rb_define_class_under(mZenoh, "Session", rb_cObject);
1492
+ zrb_no_new(cSession);
1493
+ rb_define_singleton_method(cSession, "open", zrb_session_s_open, -1);
1494
+ rb_define_method(cSession, "put", zrb_session_put, -1);
1495
+ rb_define_method(cSession, "subscribe", zrb_session_subscribe, -1);
1496
+ rb_define_method(cSession, "get", zrb_session_get_m, -1);
1497
+ rb_define_method(cSession, "queryable", zrb_session_queryable, -1);
1498
+ rb_define_method(cSession, "liveliness", zrb_session_liveliness, 1);
1499
+ rb_define_method(cSession, "liveliness_watch", zrb_session_liveliness_watch, -1);
1500
+ rb_define_method(cSession, "liveliness_get", zrb_session_liveliness_get, -1);
1501
+ rb_define_method(cSession, "poll", zrb_session_poll, -1);
1502
+ rb_define_method(cSession, "peers", zrb_session_peers, 0);
1503
+ rb_define_method(cSession, "zid", zrb_session_zid, 0);
1504
+ rb_define_method(cSession, "closed?", zrb_session_closed_p, 0);
1505
+ rb_define_method(cSession, "close", zrb_session_close, 0);
1506
+
1507
+ /* Subscriber and LivelinessWatch share one C structure. */
1508
+ cSubscriber = rb_define_class_under(mZenoh, "Subscriber", rb_cObject);
1509
+ cWatch = rb_define_class_under(mZenoh, "LivelinessWatch", rb_cObject);
1510
+ VALUE both[2] = {cSubscriber, cWatch};
1511
+ for (int i = 0; i < 2; i++) {
1512
+ zrb_no_new(both[i]);
1513
+ rb_define_method(both[i], "each_pending", zrb_sub_each_pending, 0);
1514
+ rb_define_method(both[i], "pending", zrb_sub_pending, 0);
1515
+ rb_define_method(both[i], "received", zrb_sub_received, 0);
1516
+ rb_define_method(both[i], "dropped", zrb_sub_dropped, 0);
1517
+ rb_define_method(both[i], "close", zrb_sub_close, 0);
1518
+ rb_define_method(both[i], "closed?", zrb_sub_closed_p, 0);
1519
+ }
1520
+
1521
+ cQueryable = rb_define_class_under(mZenoh, "Queryable", rb_cObject);
1522
+ zrb_no_new(cQueryable);
1523
+ rb_define_method(cQueryable, "each_pending", zrb_qable_each_pending, 0);
1524
+ rb_define_method(cQueryable, "pending", zrb_qable_pending, 0);
1525
+ rb_define_method(cQueryable, "received", zrb_qable_received, 0);
1526
+ rb_define_method(cQueryable, "dropped", zrb_qable_dropped, 0);
1527
+ rb_define_method(cQueryable, "close", zrb_qable_close, 0);
1528
+ rb_define_method(cQueryable, "closed?", zrb_qable_closed_p, 0);
1529
+
1530
+ cQuery = rb_define_class_under(mZenoh, "Query", rb_cObject);
1531
+ zrb_no_new(cQuery);
1532
+ rb_define_method(cQuery, "key", zrb_query_key, 0);
1533
+ rb_define_method(cQuery, "params", zrb_query_params, 0);
1534
+ rb_define_method(cQuery, "payload", zrb_query_payload, 0);
1535
+ rb_define_method(cQuery, "attachment", zrb_query_attachment, 0);
1536
+ rb_define_method(cQuery, "reply", zrb_query_reply, -1);
1537
+ rb_define_method(cQuery, "finish", zrb_query_finish_m, 0);
1538
+ rb_define_method(cQuery, "finished?", zrb_query_finished_p, 0);
1539
+
1540
+ cGet = rb_define_class_under(mZenoh, "Get", rb_cObject);
1541
+ zrb_no_new(cGet);
1542
+ rb_define_method(cGet, "each_reply", zrb_get_each_reply, 0);
1543
+ rb_define_method(cGet, "done?", zrb_get_done_p, 0);
1544
+ rb_define_method(cGet, "pending", zrb_get_pending, 0);
1545
+ rb_define_method(cGet, "received", zrb_get_received, 0);
1546
+ rb_define_method(cGet, "dropped", zrb_get_dropped, 0);
1547
+ rb_define_method(cGet, "errors", zrb_get_errors, 0);
1548
+
1549
+ cToken = rb_define_class_under(mZenoh, "LivelinessToken", rb_cObject);
1550
+ zrb_no_new(cToken);
1551
+ rb_define_method(cToken, "close", zrb_token_close, 0);
1552
+ rb_define_method(cToken, "closed?", zrb_token_closed_p, 0);
1553
+ }