zyre 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dec6f1a89c5683ac88460dc3cc156b79156e318052a7ea81494336e0686ec522
4
- data.tar.gz: f1fd6cb1ba63bf1ea8054823faa26afaa0646f063da940ee3e6e1e12ead7161a
3
+ metadata.gz: e5ed839568df209af8c75bddfe0be453af7219319c31848556261888e3b6ee39
4
+ data.tar.gz: 778239dec2e83ac60efbccdccffa55916ff6365f82a6bc9869a796b6e0484223
5
5
  SHA512:
6
- metadata.gz: f98b88495f675531470868a0c4e7f7ec94970fd3e9415d771a3ad0d1c27bea34c7badd3e7e3a084e147611871fc45439bbb495abda72f5a6962eaf11beeb7992
7
- data.tar.gz: a442f583bd320bf642794d6b0136057473d8a9abafef5fd1570213ee4d564dc4aa1bca48c4f3d36696c534cdba51b0ba645c68926b6f2eea072bf21a0623e818
6
+ metadata.gz: 6bbf929517d6375ed0f27b7eac8b413a200fda2af90d0493e2d246a4e232862bcd97cb00961b7118ff617bc00c278b92af2ffeba978756d64fb56d6171a2342f
7
+ data.tar.gz: 7613fd948161ec855ab2042401304e381866b7eef9aeaf166898912e24c9fa0ede81c91a35f54075f269855322219880e972d3a712b498935786e66b48c17860
checksums.yaml.gz.sig CHANGED
@@ -1 +1,4 @@
1
- \���wO):��"YPw)}f(q��N�
1
+ oiibS��Sy��
2
+ �km��%0��t���1�l �g��hbq��2��<<�q�|�`wRkQZ��1��l�A^7D���1���/��#m�&�t;=U��b�D��f��j*��EEݕ����-[BȽ�Ä�-��h�U"rp+}k`�-S"�{sK�19/�?���j;@�&q��q�n�4��k��:N i�6r�S<�,��!�Y]*&Sς��~V�@*���e�N��+��^�w
3
+ ����2+��2�v�_���W�����{l6)^UJ��;� ��r�IHM�(���х�9�����ɼw[+{�9W� 2l^NWg7Y����F7L�j�Z\*c��ͮV� ����J
4
+ ��0v�V������` �l���� �
data/Authentication.md CHANGED
@@ -1,30 +1,30 @@
1
1
  # Authenticated \Zyre
2
2
 
3
- Note that authentication requires API that is still in Draft, so it requires
4
- that your libzyre be built with --enable-drafts. Also, since draft APIs are
5
- subject to change, this documentation may be out of date. A good place to check
6
- for the latest info is the built-in test suites in the Zyre source itself.
3
+ Note that authentication requires a Draft API, so it requires that your libzyre
4
+ be built with `--enable-drafts`. Also, since draft APIs are subject to change,
5
+ this documentation may be out of date. A good place to check for the latest
6
+ info is the built-in test suites in the Zyre source itself.
7
7
 
8
- Authentication isn't done yet, but when it is it'll look something like:
8
+ Authentication in Zyre is done using the mechanism built into ZeroMQ. To enable it, you start the authenticator actor ([zauth][]) and then configure the authentication you wish to use.
9
9
 
10
- cert = Zyre::Cert.new
11
- cert.save( "/usr/local/var/certs/server" )
10
+ ## Curve Authentication
12
11
 
13
- Zyre.allow( "127.0.0.1", "10.0.12.2" )
14
- Zyre.start_authenticator( :CURVE, "/usr/local/var/certs" )
12
+ To enable secure connections, you tell Zyre to enable Curve. You can either call this method with no argument, in which case any node that presents a valid cert will be allowed to connect, or you pass in the path to a [zcertstore][] that contains all the certificates of nodes which will be allowed to connect.
15
13
 
16
- node = Zyre::Node.new
17
- node.zap_domain = 'application_name'
18
- node.zcert = cert
19
- node.start
14
+ To document:
20
15
 
21
- # later...
22
-
23
- node.stop
24
- Zyre.stop_authenticator
16
+ - Creating a certstore
17
+ - Enabling the authenticator
18
+ - Enabling curve authentication
25
19
 
26
20
 
27
21
  ## References
28
22
 
29
23
  - ZAP (ZeroMQ Authentication Protocol) - https://rfc.zeromq.org/spec/27/
30
24
  - Using ZeroMQ Security (Part 2) - https://jaxenter.com/using-zeromq-security-part-2-119353.html
25
+
26
+
27
+ [zauth]: http://api.zeromq.org/czmq3-0:zauth
28
+ [zcertstore]: http://api.zeromq.org/czmq3-0:zcertstore
29
+
30
+
data/History.md CHANGED
@@ -1,6 +1,24 @@
1
1
  # Release History for zyre
2
2
 
3
3
  ---
4
+ ## v0.8.0 [2026-02-02] Michael Granger <ged@faeriemud.org>
5
+
6
+ Improvements:
7
+
8
+ - Add Zyre::Certstore for managing zcertstores
9
+ - Add convenience method for making public certs
10
+
11
+
12
+ ## v0.7.0 [2025-08-05] Michael Granger <ged@faeriemud.org>
13
+
14
+ Improvements:
15
+
16
+ - Add ZAUTH
17
+
18
+ Fixes:
19
+
20
+ - Move the interface method setting to the Zyre toplevel since it's actually global
21
+
4
22
 
5
23
  ## v0.6.0 [2023-08-08] Michael Granger <ged@faeriemud.org>
6
24
 
data/ext/zyre_ext/cert.c CHANGED
@@ -15,6 +15,14 @@ VALUE rzyre_cZyreCert;
15
15
  static void rzyre_cert_free( void *ptr );
16
16
 
17
17
 
18
+ static const byte EMPTY_KEY[32] = {
19
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
20
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
21
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
23
+ static const char *Z85_EMPTY_KEY = "0000000000000000000000000000000000000000";
24
+
25
+
18
26
  static const rb_data_type_t rzyre_cert_t = {
19
27
  .wrap_struct_name = "Zyre::Cert",
20
28
  .function = {
@@ -68,6 +76,24 @@ rzyre_get_cert( VALUE self )
68
76
  }
69
77
 
70
78
 
79
+
80
+ /*
81
+ * Wrap a zcert_t in a Zyre::Cert object.
82
+ */
83
+ VALUE
84
+ rzyre_wrap_cert( zcert_t *ptr )
85
+ {
86
+ VALUE wrapper = rzyre_cert_alloc( rzyre_cZyreCert );
87
+ zcert_t *copy = zcert_dup( ptr );
88
+
89
+ RTYPEDDATA_DATA( wrapper ) = copy;
90
+
91
+ return wrapper;
92
+ }
93
+
94
+
95
+
96
+
71
97
  /*
72
98
  * call-seq:
73
99
  * Zyre::Cert.from( public_key, secret_key ) -> cert
@@ -104,6 +130,42 @@ rzyre_cert_s_from( VALUE class, VALUE public_key, VALUE secret_key )
104
130
  }
105
131
 
106
132
 
133
+
134
+ /*
135
+ * call-seq:
136
+ * Zyre::Cert.from_public( public_key ) -> cert
137
+ *
138
+ * Create a public certificate from a +public_key+ string.
139
+ *
140
+ */
141
+ static VALUE
142
+ rzyre_cert_s_from_public( VALUE class, VALUE public_key )
143
+ {
144
+ VALUE self = rzyre_cert_alloc( class );
145
+ zcert_t *ptr = NULL;
146
+ const char *pub_str = StringValuePtr( public_key );
147
+
148
+ if ( RSTRING_LEN(public_key) == 32 ) {
149
+ ptr = zcert_new_from( (const byte *)pub_str, EMPTY_KEY );
150
+ } else if ( RSTRING_LEN(public_key) == 40 ) {
151
+ #ifdef HAVE_ZCERT_NEW_FROM_TXT
152
+ ptr = zcert_new_from_txt( pub_str, Z85_EMPTY_KEY );
153
+ #else
154
+ rb_raise( rb_eNotImpError,
155
+ "can't create a key from encoded keys: Czmq is too old!" );
156
+ #endif
157
+ }
158
+
159
+ if ( !ptr ) {
160
+ rb_raise( rb_eArgError, "invalid key" );
161
+ }
162
+
163
+ RTYPEDDATA_DATA( self ) = ptr;
164
+
165
+ return self;
166
+ }
167
+
168
+
107
169
  /*
108
170
  * call-seq:
109
171
  * Zyre::Cert.load( filename ) -> cert
@@ -262,7 +324,7 @@ rzyre_cert_meta( VALUE self, VALUE name )
262
324
  const char *name_str = StringValuePtr( name );
263
325
  const char *value = zcert_meta( ptr, name_str );
264
326
 
265
- if ( value ) rval = rb_str_new_cstr( value );
327
+ if ( value ) rval = rb_utf8_str_new_cstr( value );
266
328
 
267
329
  return rval;
268
330
  }
@@ -357,7 +419,8 @@ static VALUE
357
419
  rzyre_cert_save_public( VALUE self, VALUE filename )
358
420
  {
359
421
  zcert_t *ptr = rzyre_get_cert( self );
360
- const char *filename_str = StringValueCStr( filename );
422
+ VALUE filename_s = rb_funcall( filename, rb_intern("to_s"), 0 );
423
+ const char *filename_str = StringValueCStr( filename_s );
361
424
  int result;
362
425
 
363
426
  result = zcert_save_public( ptr, filename_str );
@@ -477,6 +540,7 @@ rzyre_init_cert( void ) {
477
540
  rb_define_alloc_func( rzyre_cZyreCert, rzyre_cert_alloc );
478
541
 
479
542
  rb_define_singleton_method( rzyre_cZyreCert, "from", rzyre_cert_s_from, 2 );
543
+ rb_define_singleton_method( rzyre_cZyreCert, "from_public", rzyre_cert_s_from_public, 1 );
480
544
  rb_define_singleton_method( rzyre_cZyreCert, "load", rzyre_cert_s_load, 1 );
481
545
 
482
546
  rb_define_protected_method( rzyre_cZyreCert, "initialize", rzyre_cert_initialize, 0 );
@@ -0,0 +1,208 @@
1
+ /*
2
+ * certstore.c - A curve certificate store for use with Zyre.
3
+ * $Id$
4
+ *
5
+ * Authors:
6
+ * * Michael Granger <ged@FaerieMUD.org>
7
+ *
8
+ */
9
+
10
+ #include "zyre_ext.h"
11
+
12
+ VALUE rzyre_cZyreCertstore;
13
+
14
+ // Forward declarations
15
+ static void rzyre_certstore_free( void *ptr );
16
+
17
+
18
+ static const rb_data_type_t rzyre_certstore_t = {
19
+ .wrap_struct_name = "Zyre::Certstore",
20
+ .function = {
21
+ .dmark = NULL,
22
+ .dfree = rzyre_certstore_free,
23
+ },
24
+ .data = NULL,
25
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
26
+ };
27
+
28
+
29
+ /*
30
+ * Free function
31
+ */
32
+ static void
33
+ rzyre_certstore_free( void *ptr )
34
+ {
35
+ if ( ptr ) {
36
+ zcertstore_destroy( (zcertstore_t **)&ptr );
37
+ }
38
+ }
39
+
40
+
41
+ /*
42
+ * Alloc function
43
+ */
44
+ static VALUE
45
+ rzyre_certstore_alloc( VALUE klass )
46
+ {
47
+ return TypedData_Wrap_Struct( klass, &rzyre_certstore_t, NULL );
48
+ }
49
+
50
+
51
+ /*
52
+ * Fetch the data pointer and check it for sanity.
53
+ */
54
+ inline zcertstore_t *
55
+ rzyre_get_certstore( VALUE self )
56
+ {
57
+ zcertstore_t *ptr;
58
+
59
+ if ( !IsZyreCertstore(self) ) {
60
+ rb_raise( rb_eTypeError, "wrong argument type %s (expected Zyre::Certstore)",
61
+ rb_class2name(CLASS_OF( self )) );
62
+ }
63
+
64
+ ptr = DATA_PTR( self );
65
+ assert( ptr );
66
+
67
+ return ptr;
68
+ }
69
+
70
+
71
+
72
+ /*
73
+ * call-seq:
74
+ * Zyre::Certstore.new -> certstore
75
+ * Zyre::Certstore.new( directory ) -> certstore
76
+ *
77
+ * Create a new certificate store. If not +directory+ is given, creates an
78
+ * in-memory store.
79
+ *
80
+ */
81
+ static VALUE
82
+ rzyre_certstore_initialize( int argc, VALUE *argv, VALUE self )
83
+ {
84
+ VALUE directory = Qnil;
85
+ zcertstore_t *ptr;
86
+
87
+ TypedData_Get_Struct( self, zcertstore_t, &rzyre_certstore_t, ptr );
88
+ if ( !ptr ) {
89
+ rb_scan_args( argc, argv, "01", &directory );
90
+
91
+ if ( RTEST(directory) ) {
92
+ VALUE dir_string = rb_funcall( directory, rb_intern("to_s"), 0 );
93
+ const char *location = StringValueCStr( dir_string );
94
+ ptr = zcertstore_new( location );
95
+ } else {
96
+ ptr = zcertstore_new( NULL );
97
+ }
98
+
99
+ assert( ptr );
100
+ RTYPEDDATA_DATA( self ) = ptr;
101
+ }
102
+
103
+ return self;
104
+ }
105
+
106
+
107
+
108
+ /*
109
+ * call-seq:
110
+ * certstore.lookup( public_key )
111
+ *
112
+ * Look up certificate by public key, returns Zyre::Cert object if found,
113
+ * else returns `nil`. The +public_key+ should be a String in Z85 text format.
114
+ *
115
+ */
116
+ static VALUE
117
+ rzyre_certstore_lookup( VALUE self, VALUE public_key )
118
+ {
119
+ zcertstore_t *ptr = rzyre_get_certstore( self );
120
+ const char *key_txt = StringValueCStr( public_key );
121
+ zcert_t *cert = zcertstore_lookup( ptr, key_txt );
122
+
123
+ if ( cert ) {
124
+ VALUE zyre_cert = rzyre_wrap_cert( cert );
125
+ return zyre_cert;
126
+ }
127
+
128
+ return Qnil;
129
+ }
130
+
131
+
132
+
133
+ /*
134
+ * call-seq:
135
+ * certstore.insert( certificate )
136
+ *
137
+ * Insert +certificate+ (a Zyre::Cert) into certificate store in memory. Note
138
+ * that this does not save the certificate to disk. To do that, use Zyre::Cert#save
139
+ * directly on the certificate.
140
+ *
141
+ */
142
+ static VALUE
143
+ rzyre_certstore_insert( VALUE self, VALUE cert )
144
+ {
145
+ zcertstore_t *ptr = rzyre_get_certstore( self );
146
+ zcert_t *zcert = rzyre_get_cert( cert );
147
+ zcert_t *zcert_owned = zcert_dup( zcert );
148
+
149
+ assert( zcert_owned );
150
+ zcertstore_insert( ptr, &zcert_owned );
151
+
152
+ return Qtrue;
153
+ }
154
+
155
+
156
+ /*
157
+ * call-seq:
158
+ * certstore.print
159
+ *
160
+ * Print list of certificates in store to logging facility.
161
+ *
162
+ */
163
+ static VALUE
164
+ rzyre_certstore_print( VALUE self )
165
+ {
166
+ zcertstore_t *ptr = rzyre_get_certstore( self );
167
+
168
+ zcertstore_print( ptr );
169
+
170
+ return Qtrue;
171
+ }
172
+
173
+
174
+
175
+ /*
176
+ * Initialize the Cert class.
177
+ */
178
+ void
179
+ rzyre_init_certstore( void ) {
180
+
181
+ #ifdef FOR_RDOC
182
+ rb_cData = rb_define_class( "Data" );
183
+ rzyre_mZyre = rb_define_module( "Zyre" );
184
+ #endif
185
+
186
+ /*
187
+ * Document-class: Zyre::Certstore
188
+ *
189
+ * A certificate store for Zyre curve authentication.
190
+ *
191
+ * Refs:
192
+ * - http://api.zeromq.org/czmq4-0:zcertstore
193
+ *
194
+ */
195
+ rzyre_cZyreCertstore = rb_define_class_under( rzyre_mZyre, "Certstore", rb_cObject );
196
+
197
+ rb_define_alloc_func( rzyre_cZyreCertstore, rzyre_certstore_alloc );
198
+
199
+ rb_define_protected_method( rzyre_cZyreCertstore, "initialize", rzyre_certstore_initialize, -1 );
200
+
201
+ rb_define_method( rzyre_cZyreCertstore, "lookup", rzyre_certstore_lookup, 1 );
202
+ rb_define_method( rzyre_cZyreCertstore, "insert", rzyre_certstore_insert, 1 );
203
+
204
+ rb_define_method( rzyre_cZyreCertstore, "print", rzyre_certstore_print, 0 );
205
+
206
+ rb_require( "zyre/certstore" );
207
+ }
208
+
@@ -31,6 +31,7 @@ have_func( 'zmq_z85_encode', 'zmq.h' )
31
31
  have_func( 'zmq_z85_decode', 'zmq.h' )
32
32
 
33
33
  have_func( 'zcert_unset_meta', 'czmq.h' )
34
+ have_func( 'zcert_new_from_txt', 'czmq.h' )
34
35
 
35
36
  create_header()
36
37
  create_makefile( 'zyre_ext' )
data/ext/zyre_ext/node.c CHANGED
@@ -286,6 +286,7 @@ rzyre_node_interface_eq( VALUE self, VALUE interface )
286
286
  zyre_t *ptr = rzyre_get_node( self );
287
287
  const char *interface_str = StringValueCStr( interface );
288
288
 
289
+ rb_warn( "Setting #interface on a Node sets it globally." );
289
290
  zyre_set_interface( ptr, interface_str );
290
291
 
291
292
  return Qtrue;
@@ -17,6 +17,8 @@
17
17
 
18
18
  VALUE rzyre_mZyre;
19
19
 
20
+ static zactor_t *auth;
21
+
20
22
 
21
23
  /* --------------------------------------------------------------
22
24
  * Logging Functions
@@ -148,7 +150,7 @@ rzyre_make_zmsg_from( VALUE messages )
148
150
  *
149
151
  */
150
152
  static VALUE
151
- rzyre_s_zyre_version()
153
+ rzyre_s_zyre_version( VALUE _mod )
152
154
  {
153
155
  static uint64_t version;
154
156
 
@@ -293,10 +295,172 @@ rzyre_s_z85_decode( VALUE module, VALUE string )
293
295
  }
294
296
 
295
297
 
298
+
299
+ /*
300
+ * call-seq:
301
+ * Zyre.start_authenticator -> true
302
+ *
303
+ * Start the ZAUTH authenticator actor. If it's already running, this
304
+ * call is silently ignored.
305
+ *
306
+ */
296
307
  static VALUE
297
308
  rzyre_s_start_authenticator( VALUE module )
298
309
  {
299
- return Qnil;
310
+ if ( !auth ) {
311
+ rzyre_log_obj( rzyre_mZyre, "info", "starting up the ZAUTH actor." );
312
+ auth = zactor_new( zauth, NULL );
313
+ assert( auth );
314
+ }
315
+
316
+ return Qtrue;
317
+ }
318
+
319
+
320
+ /*
321
+ * call-seq:
322
+ * Zyre.authenticator_started? -> true or false
323
+ *
324
+ * Returns `true` if the ZAUTH authenticator actor is running.
325
+ *
326
+ */
327
+ static VALUE
328
+ rzyre_s_authenticator_started_p( VALUE module )
329
+ {
330
+ if ( auth ) {
331
+ return Qtrue;
332
+ } else {
333
+ return Qfalse;
334
+ }
335
+ }
336
+
337
+
338
+ /*
339
+ * call-seq:
340
+ * Zyre.stop_authenticator -> true
341
+ *
342
+ * Stop the ZAUTH authenticator actor if it is running. If it's not running, this
343
+ * call is silently ignored.
344
+ *
345
+ */
346
+ static VALUE
347
+ rzyre_s_stop_authenticator( VALUE module )
348
+ {
349
+ if ( auth ) {
350
+ rzyre_log_obj( rzyre_mZyre, "info", "shutting down the ZAUTH actor." );
351
+ zactor_destroy( &auth );
352
+ }
353
+
354
+ return Qtrue;
355
+ }
356
+
357
+
358
+ /*
359
+ * Async wait function; called without the GVL.
360
+ */
361
+ static void *
362
+ rzyre_wait_for_auth_without_gvl( void *_unused )
363
+ {
364
+ zsock_wait( auth );
365
+ return NULL;
366
+ }
367
+
368
+
369
+ /*
370
+ * call-seq:
371
+ * Zyre.verbose_auth!
372
+ *
373
+ * Enable the ZAUTH actor's verbose logging.
374
+ *
375
+ */
376
+ static VALUE
377
+ rzyre_s_verbose_auth_bang( VALUE module )
378
+ {
379
+ if ( auth ) {
380
+ zstr_sendx( auth, "VERBOSE", NULL );
381
+ rb_thread_call_without_gvl2( rzyre_wait_for_auth_without_gvl, 0, RUBY_UBF_IO, 0 );
382
+ } else {
383
+ rb_raise( rb_eRuntimeError, "can't enable verbose auth: authenticator is not started." );
384
+ }
385
+
386
+ return Qtrue;
387
+ }
388
+
389
+
390
+ /*
391
+ * call-seq:
392
+ * Zyre.enable_curve_auth( cert_dir=nil )
393
+ *
394
+ * Enable CURVE authentication, using the specified +cert_dir+ for allowed
395
+ * public certificates. If no +cert_dir+ is given, any connection presenting a valid CURVE
396
+ * certificate will be allowed.
397
+ *
398
+ */
399
+ static VALUE
400
+ rzyre_s_enable_curve_auth( int argc, VALUE *argv, VALUE module )
401
+ {
402
+ VALUE cert_dir = Qnil;
403
+ char *cert_dir_s;
404
+
405
+ rb_scan_args( argc, argv, "01", &cert_dir );
406
+
407
+ if ( argc ) {
408
+ cert_dir_s = StringValueCStr( cert_dir );
409
+ zstr_sendx( auth, "CURVE", cert_dir_s, NULL );
410
+ } else {
411
+ zstr_sendx( auth, "CURVE", CURVE_ALLOW_ANY, NULL );
412
+ }
413
+
414
+ rb_thread_call_without_gvl2( rzyre_wait_for_auth_without_gvl, 0, RUBY_UBF_IO, 0 );
415
+
416
+ return Qtrue;
417
+ }
418
+
419
+
420
+ /*
421
+ * call-seq:
422
+ * Zyre.interface -> string
423
+ *
424
+ * Return network interface to use for broadcasts, or nil if none was set.
425
+ *
426
+ */
427
+ static VALUE
428
+ rzyre_s_interface( VALUE module )
429
+ {
430
+ const char *interface = zsys_interface();
431
+
432
+ if ( strnlen(interface, 1) == 0 ) {
433
+ return Qnil;
434
+ }
435
+
436
+ return rb_utf8_str_new_cstr( interface );
437
+ }
438
+
439
+
440
+ /*
441
+ * call-seq:
442
+ * Zyre.interface = string
443
+ *
444
+ * Set network interface name to use for broadcasts.
445
+ *
446
+ * This lets the interface be configured for test environments where required.
447
+ * For example, on Mac OS X, zbeacon cannot bind to 255.255.255.255 which is
448
+ * the default when there is no specified interface. If the environment
449
+ * variable ZSYS_INTERFACE is set, use that as the default interface name.
450
+ * Setting the interface to "*" means "use all available interfaces".
451
+ *
452
+ */
453
+ static VALUE
454
+ rzyre_s_interface_eq( VALUE module, VALUE new_interface )
455
+ {
456
+ if ( NIL_P(new_interface) ) {
457
+ zsys_set_interface( "" );
458
+ } else {
459
+ const char *new_interface_s = StringValueCStr( new_interface );
460
+ zsys_set_interface( new_interface_s );
461
+ }
462
+
463
+ return Qtrue;
300
464
  }
301
465
 
302
466
 
@@ -326,15 +490,23 @@ Init_zyre_ext()
326
490
 
327
491
  rb_define_singleton_method( rzyre_mZyre, "zyre_version", rzyre_s_zyre_version, 0 );
328
492
  rb_define_singleton_method( rzyre_mZyre, "interfaces", rzyre_s_interfaces, 0 );
329
- rb_define_singleton_method( rzyre_mZyre, "disable_zsys_handler", rzyre_s_disable_zsys_handler, 0 );
493
+ rb_define_singleton_method( rzyre_mZyre, "disable_zsys_handler",
494
+ rzyre_s_disable_zsys_handler, 0 );
330
495
 
331
496
  rb_define_singleton_method( rzyre_mZyre, "z85_encode", rzyre_s_z85_encode, 1 );
332
497
  rb_define_singleton_method( rzyre_mZyre, "z85_decode", rzyre_s_z85_decode, 1 );
333
498
 
334
- // :TODO: Allow for startup of the zauth agent. This will require enough of a
335
- // subset of CZMQ that I hesitate to do it in Zyre. Maybe better to just write a
336
- // decent CZMQ library instead?
337
- rb_define_singleton_method( rzyre_mZyre, "start_authenticator", rzyre_s_start_authenticator, 0 );
499
+ rb_define_singleton_method( rzyre_mZyre, "start_authenticator",
500
+ rzyre_s_start_authenticator, 0 );
501
+ rb_define_singleton_method( rzyre_mZyre, "authenticator_started?",
502
+ rzyre_s_authenticator_started_p, 0 );
503
+ rb_define_singleton_method( rzyre_mZyre, "stop_authenticator", rzyre_s_stop_authenticator, 0 );
504
+
505
+ rb_define_singleton_method( rzyre_mZyre, "verbose_auth!", rzyre_s_verbose_auth_bang, 0 );
506
+ rb_define_singleton_method( rzyre_mZyre, "enable_curve_auth", rzyre_s_enable_curve_auth, -1 );
507
+
508
+ rb_define_singleton_method( rzyre_mZyre, "interface", rzyre_s_interface, 0 );
509
+ rb_define_singleton_method( rzyre_mZyre, "interface=", rzyre_s_interface_eq, 1 );
338
510
 
339
511
  // :TODO: set up zsys_set_logsender()
340
512
 
@@ -342,5 +514,6 @@ Init_zyre_ext()
342
514
  rzyre_init_event();
343
515
  rzyre_init_poller();
344
516
  rzyre_init_cert();
517
+ rzyre_init_certstore();
345
518
  }
346
519
 
@@ -87,6 +87,7 @@ extern VALUE rzyre_cZyreNode;
87
87
  extern VALUE rzyre_cZyreEvent;
88
88
  extern VALUE rzyre_cZyrePoller;
89
89
  extern VALUE rzyre_cZyreCert;
90
+ extern VALUE rzyre_cZyreCertstore;
90
91
 
91
92
  extern zactor_t *auth_actor;
92
93
 
@@ -99,6 +100,7 @@ extern zactor_t *auth_actor;
99
100
  #define IsZyreEvent( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyreEvent )
100
101
  #define IsZyrePoller( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyrePoller )
101
102
  #define IsZyreCert( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyreCert )
103
+ #define IsZyreCertstore( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyreCertstore )
102
104
 
103
105
  /* --------------------------------------------------------------
104
106
  * Utility functions
@@ -115,9 +117,11 @@ extern void rzyre_init_node _(( void ));
115
117
  extern void rzyre_init_event _(( void ));
116
118
  extern void rzyre_init_poller _(( void ));
117
119
  extern void rzyre_init_cert _(( void ));
120
+ extern void rzyre_init_certstore _(( void ));
118
121
 
119
122
  extern zyre_t * rzyre_get_node _(( VALUE ));
120
123
  extern zcert_t * rzyre_get_cert _(( VALUE ));
124
+ extern VALUE rzyre_wrap_cert _(( zcert_t * ));
121
125
 
122
126
  #endif /* end of include guard: ZYRE_EXT_H_90322ABD */
123
127
 
data/lib/zyre/cert.rb CHANGED
@@ -13,6 +13,7 @@ class Zyre::Cert
13
13
 
14
14
  # The placeholder key that is set as the secret key for a public certificate.
15
15
  EMPTY_KEY = "\x00" * 32
16
+ Z85_EMPTY_KEY = Zyre.z85_encode( EMPTY_KEY )
16
17
 
17
18
 
18
19
  # Use the Zyre module's logger
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'loggability'
4
+
5
+ require 'zyre' unless defined?( Zyre )
6
+
7
+
8
+ #--
9
+ # See also: ext/zyre_ext/certstore.c
10
+ class Zyre::Certstore
11
+ extend Loggability
12
+
13
+
14
+ end # class Zyre::Certstore
data/lib/zyre.rb CHANGED
@@ -13,7 +13,7 @@ module Zyre
13
13
 
14
14
 
15
15
  # Gem version (semver)
16
- VERSION = '0.6.0'
16
+ VERSION = '0.8.0'
17
17
 
18
18
  # Set up a logger for Zyre classes
19
19
  log_as :zyre
@@ -27,6 +27,17 @@ RSpec.describe( Zyre::Cert ) do
27
27
  end
28
28
 
29
29
 
30
+ it "can be created from public key" do
31
+ cert = described_class.new
32
+
33
+ result = described_class.from_public( cert.public_key )
34
+
35
+ expect( result ).to be_a( described_class )
36
+ expect( result.public_txt ).to eq( cert.public_txt )
37
+ expect( result.secret_txt ).to eq( Zyre::Cert::Z85_EMPTY_KEY )
38
+ end
39
+
40
+
30
41
  it "can be saved to and loaded from a file" do
31
42
  cert = described_class.new
32
43
  cert.save( cert_file.path )
@@ -46,7 +57,7 @@ RSpec.describe( Zyre::Cert ) do
46
57
 
47
58
  expect( result ).to be_a( described_class )
48
59
  expect( result.public_key ).to eq( cert.public_key )
49
- expect( result.secret_key ).to eq( Zyre::Cert::EMPTY_KEY )
60
+ expect( result.secret_key ).to eq( described_class::EMPTY_KEY )
50
61
  end
51
62
 
52
63
 
@@ -197,7 +208,7 @@ RSpec.describe( Zyre::Cert ) do
197
208
 
198
209
  it "can be applied to a Zyre node", :draft_apis do
199
210
  node = instance_double( Zyre::Node )
200
- cert = Zyre::Cert.new
211
+ cert = described_class.new
201
212
 
202
213
  expect( node ).to receive( :zcert= ).with( cert )
203
214
  cert.apply( node )
@@ -212,6 +223,8 @@ RSpec.describe( Zyre::Cert ) do
212
223
  other = cert.dup
213
224
 
214
225
  expect( other.object_id ).not_to eq( cert.object_id )
226
+ expect( other[:node_id] ).to eq( cert[:node_id] )
227
+ expect( other[:node_order] ).to eq( cert[:node_order] )
215
228
  end
216
229
 
217
230
 
@@ -0,0 +1,60 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'pathname'
6
+ require 'tmpdir'
7
+ require 'zyre/certstore'
8
+
9
+
10
+ RSpec.describe( Zyre::Certstore ) do
11
+
12
+ let( :certstore_location ) { Pathname(Dir::Tmpname.create(['zyre-cerstore-', '-spec']) {}) }
13
+
14
+
15
+ it "can be created as an in-memory store" do
16
+ instance = described_class.new
17
+
18
+ expect( instance ).to be_a( described_class )
19
+ end
20
+
21
+
22
+ it "can be created with a directory name" do
23
+ instance = described_class.new( certstore_location )
24
+
25
+ expect( instance ).to be_a( described_class )
26
+ end
27
+
28
+
29
+ it "can have certs added to it" do
30
+ instance = described_class.new
31
+
32
+ cert = Zyre::Cert.new
33
+ cert[:name] = 'test1'
34
+
35
+ instance.insert( cert )
36
+
37
+ res = instance.lookup( cert.public_txt )
38
+ expect( res ).to be_a( Zyre::Cert )
39
+ expect( res.public_txt ).to eq( cert.public_txt )
40
+ expect( res[:name] ).to eq( 'test1' )
41
+ end
42
+
43
+
44
+ it "can lookup certs added to a directory certstore" do
45
+ certstore_location.mkpath
46
+ instance = described_class.new( certstore_location )
47
+
48
+ cert = Zyre::Cert.new
49
+ cert[:name] = 'test23'
50
+ cert.save_public( certstore_location / 'mykey.txt' )
51
+
52
+ res = instance.lookup( cert.public_txt )
53
+
54
+ expect( res ).to be_a( Zyre::Cert )
55
+ expect( res.public_txt ).to eq( cert.public_txt )
56
+ expect( res[:name] ).to eq( 'test23' )
57
+ end
58
+
59
+ end
60
+
@@ -26,6 +26,14 @@ RSpec.describe( Zyre::Node ) do
26
26
  TEST_BINARY_STRING = TEST_BINARY_STRING_PARTS.join( '' )
27
27
 
28
28
 
29
+ before( :each ) do
30
+ @interface = Zyre.interface
31
+ end
32
+ after( :each ) do
33
+ Zyre.interface = @interface
34
+ end
35
+
36
+
29
37
  it "can be created anonymously" do
30
38
  node = described_class.new
31
39
 
@@ -108,11 +116,11 @@ RSpec.describe( Zyre::Node ) do
108
116
  end
109
117
 
110
118
 
111
- it "can be set to communicate on a particular network interface" do
119
+ it "has a backward-compatible method for setting the interface on a Node" do
112
120
  node = described_class.new
113
121
  expect {
114
122
  node.interface = 'lo0'
115
- }.to_not raise_error
123
+ }.to change { Zyre.interface }.to( 'lo0' )
116
124
  end
117
125
 
118
126
 
@@ -530,8 +538,6 @@ RSpec.describe( Zyre::Node ) do
530
538
  end
531
539
 
532
540
 
533
- it "supports CURVE authentication"
534
-
535
541
  it "supports advertised_endpoint="
536
542
  it "supports gossip_connect_curve"
537
543
  it "supports gossip_unpublish"
data/spec/zyre_spec.rb CHANGED
@@ -7,6 +7,14 @@ require 'zyre'
7
7
 
8
8
  RSpec.describe( Zyre ) do
9
9
 
10
+ before( :each ) do
11
+ @interface = described_class.interface
12
+ end
13
+ after( :each ) do
14
+ described_class.interface = @interface
15
+ described_class.stop_authenticator
16
+ end
17
+
10
18
 
11
19
  # Pattern for matching IPv4 addresses
12
20
  IPV4_ADDRESS = /^((?:(?:^|\.)(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])){4})$/
@@ -64,6 +72,21 @@ RSpec.describe( Zyre ) do
64
72
  end
65
73
 
66
74
 
75
+ it "can set the interface used by all nodes" do
76
+ expect {
77
+ described_class.interface = 'LENNY'
78
+ }.to change { described_class.interface }.to( 'LENNY' )
79
+ end
80
+
81
+
82
+ it "clears the interface setting if set to `nil`" do
83
+ described_class.interface = "eth0"
84
+ expect {
85
+ described_class.interface = nil
86
+ }.to change { described_class.interface }.to( nil )
87
+ end
88
+
89
+
67
90
  describe "Z85 encoding/decoding" do
68
91
 
69
92
  # From the 32/Z85 reference code:
@@ -126,5 +149,95 @@ RSpec.describe( Zyre ) do
126
149
 
127
150
  end
128
151
 
152
+
153
+ describe "ZAUTH authenticator" do
154
+
155
+ it "can be started" do
156
+ expect {
157
+ described_class.start_authenticator
158
+ }.to change { described_class.authenticator_started? }.to( true )
159
+ end
160
+
161
+
162
+ it "ignores call to start if it's already started" do
163
+ described_class.start_authenticator
164
+
165
+ expect {
166
+ described_class.start_authenticator
167
+ }.not_to raise_error
168
+ end
169
+
170
+
171
+ it "can be stopped" do
172
+ described_class.start_authenticator
173
+
174
+ expect {
175
+ described_class.stop_authenticator
176
+ }.to change { described_class.authenticator_started? }.to( false )
177
+ end
178
+
179
+
180
+ it "ignores calls to stop if it's not started" do
181
+ expect {
182
+ described_class.stop_authenticator
183
+ }.not_to raise_error
184
+ end
185
+
186
+
187
+ it "can enable verbose auth logging" do
188
+ described_class.start_authenticator
189
+ expect {
190
+ described_class.verbose_auth!
191
+ }.not_to raise_error
192
+ end
193
+
194
+
195
+ it "raises an error if verbose auth logging is enabled before the authenticator is started" do
196
+ expect {
197
+ described_class.verbose_auth!
198
+ }.to raise_error( /authenticator is not started/i )
199
+ end
200
+
201
+
202
+ it "supports any-cert style authentication" do
203
+ described_class.start_authenticator
204
+ # described_class.verbose_auth!
205
+ described_class.enable_curve_auth
206
+
207
+ server_cert = Zyre::Cert.new
208
+ server = Zyre::Node.new( 'server' )
209
+ # server.verbose!
210
+ server.zap_domain = 'TEST'
211
+ server.zcert = server_cert
212
+ server.set_header( 'X-PUBLICKEY', server_cert.public_txt )
213
+ server.join( 'TEST' )
214
+ server.start
215
+
216
+ client_cert = Zyre::Cert.new
217
+ authed_client = Zyre::Node.new( 'authed_client' )
218
+ # authed_client.verbose!
219
+ authed_client.zap_domain = 'TEST'
220
+ authed_client.zcert = client_cert
221
+ authed_client.set_header( 'X-PUBLICKEY', client_cert.public_txt )
222
+ authed_client.join( 'TEST' )
223
+ authed_client.start
224
+ authed_client.shout( 'TEST', 'authed_client hello' )
225
+
226
+ result = server.wait_for( :JOIN, timeout: 0.2 )
227
+ expect( result ).to be_a( Zyre::Event::Join )
228
+
229
+ noauth_client = Zyre::Node.new( 'noauth_client' )
230
+ # noauth_client.verbose!
231
+ noauth_client.zap_domain = 'TEST'
232
+ noauth_client.join( 'TEST' )
233
+ noauth_client.start
234
+ noauth_client.shout( 'TEST', 'noauth_client hello' )
235
+
236
+ result = server.wait_for( :JOIN, timeout: 0.2 )
237
+ expect( result ).to be_nil
238
+ end
239
+
240
+ end
241
+
129
242
  end
130
243
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,39 +1,39 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zyre
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Granger
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain:
11
10
  - |
12
11
  -----BEGIN CERTIFICATE-----
13
- MIID+DCCAmCgAwIBAgIBBTANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdnZWQv
14
- REM9RmFlcmllTVVEL0RDPW9yZzAeFw0yMzAxMTYxNzE2MDlaFw0yNDAxMTYxNzE2
15
- MDlaMCIxIDAeBgNVBAMMF2dlZC9EQz1GYWVyaWVNVUQvREM9b3JnMIIBojANBgkq
16
- hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvyVhkRzvlEs0fe7145BYLfN6njX9ih5H
17
- L60U0p0euIurpv84op9CNKF9tx+1WKwyQvQP7qFGuZxkSUuWcP/sFhDXL1lWUuIl
18
- M4uHbGCRmOshDrF4dgnBeOvkHr1fIhPlJm5FO+Vew8tSQmlDsosxLUx+VB7DrVFO
19
- 5PU2AEbf04GGSrmqADGWXeaslaoRdb1fu/0M5qfPTRn5V39sWD9umuDAF9qqil/x
20
- Sl6phTvgBrG8GExHbNZpLARd3xrBYLEFsX7RvBn2UPfgsrtvpdXjsHGfpT3IPN+B
21
- vQ66lts4alKC69TE5cuKasWBm+16A4aEe3XdZBRNmtOu/g81gvwA7fkJHKllJuaI
22
- dXzdHqq+zbGZVSQ7pRYHYomD0IiDe1DbIouFnPWmagaBnGHwXkDT2bKKP+s2v21m
23
- ozilJg4aar2okb/RA6VS87o+d7g6LpDDMMQjH4G9OPnJENLdhu8KnPw/ivSVvQw7
24
- N2I4L/ZOIe2DIVuYH7aLHfjZDQv/mNgpAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYD
25
- VR0PBAQDAgSwMB0GA1UdDgQWBBRyjf55EbrHagiRLqt5YAd3yb8k4DANBgkqhkiG
26
- 9w0BAQsFAAOCAYEARYCeUVBWARNKqF0cvNnLJvFf4hdW2+Rtc7NfC5jQvX9a1oom
27
- sfVvS96eER/9cbrphu+vc59EELw4zT+RY3/IesnoE7CaX6zIOFmSmG7K61OHsSLR
28
- KqMygcWwyuPXT2JG7JsGHuxbzgaRWe29HbSjBbLYxiMH8Zxh4tKutxzKF7jb0Ggq
29
- KAf9MH5LwG8IHVIfV5drT14PvgR3tcvmrn1timPyJl+eZ3LNnm9ofOCweuZCq1cy
30
- 4Q8LV3vP2Cofy9q+az3DHdaUGlmMiZZZqKixDr1KSS9nvh0ZrKMOUL1sWj/IaxrQ
31
- RV3y6td14q49t+xnbj00hPlbW7uE2nLJLt2NAoXiE1Nonndz1seB2c6HL79W9fps
32
- E/O12pQjCp/aPUZMt8/8tKW31RIy/KP8XO6OTJNWA8A/oNEI0g5p/LmmEtJKWYr1
33
- WmEdESlpWhzFECctefIF2lsN9vaOuof57RM77t2otrtcscDtNarIqjZsIyqtDvtL
34
- DttITiit0Vwz7bY0
12
+ MIIEMDCCApigAwIBAgIBAjANBgkqhkiG9w0BAQsFADA+MQwwCgYDVQQDDANnZWQx
13
+ GTAXBgoJkiaJk/IsZAEZFglGYWVyaWVNVUQxEzARBgoJkiaJk/IsZAEZFgNvcmcw
14
+ HhcNMjYwMjAyMjExMjQ0WhcNMjcwMjAyMjExMjQ0WjA+MQwwCgYDVQQDDANnZWQx
15
+ GTAXBgoJkiaJk/IsZAEZFglGYWVyaWVNVUQxEzARBgoJkiaJk/IsZAEZFgNvcmcw
16
+ ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC/JWGRHO+USzR97vXjkFgt
17
+ 83qeNf2KHkcvrRTSnR64i6um/ziin0I0oX23H7VYrDJC9A/uoUa5nGRJS5Zw/+wW
18
+ ENcvWVZS4iUzi4dsYJGY6yEOsXh2CcF46+QevV8iE+UmbkU75V7Dy1JCaUOyizEt
19
+ TH5UHsOtUU7k9TYARt/TgYZKuaoAMZZd5qyVqhF1vV+7/Qzmp89NGflXf2xYP26a
20
+ 4MAX2qqKX/FKXqmFO+AGsbwYTEds1mksBF3fGsFgsQWxftG8GfZQ9+Cyu2+l1eOw
21
+ cZ+lPcg834G9DrqW2zhqUoLr1MTly4pqxYGb7XoDhoR7dd1kFE2a067+DzWC/ADt
22
+ +QkcqWUm5oh1fN0eqr7NsZlVJDulFgdiiYPQiIN7UNsii4Wc9aZqBoGcYfBeQNPZ
23
+ soo/6za/bWajOKUmDhpqvaiRv9EDpVLzuj53uDoukMMwxCMfgb04+ckQ0t2G7wqc
24
+ /D+K9JW9DDs3Yjgv9k4h7YMhW5gftosd+NkNC/+Y2CkCAwEAAaM5MDcwCQYDVR0T
25
+ BAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFHKN/nkRusdqCJEuq3lgB3fJvyTg
26
+ MA0GCSqGSIb3DQEBCwUAA4IBgQAQwvsRIJ8FV1bYadOnOg2jcSzZXjt0FVPbOQG9
27
+ eQ9UM7+bTPU0eRCGZuOPrEp8ROc1s5zwZNFv7qHWv/zWWC6sIj4gEnCZkUugK03k
28
+ bYDsSlkyjiQ4ApVV00+h/Vhxcw+RLIZuqy2QFl8YAfMJm+JS8G7SuoqpFjbv3UUF
29
+ vrObIiO7LbhJxpYzTGkGzMFigcnm6vIGMex4AgtArc2RpWOvtAXQrU7CZHUkcdQM
30
+ 4n5eVK+m3IZlD7dd4HoPT2jF2cOGmk8XFclT0GcPVYmEWFFmotN2aiMj4sqVm/Ob
31
+ V+FmyTUy4gVtcNOFO/sBnFRiQW0fxV9/97Dog9ciYesfiAhm4EpcuTmatdAuMrZR
32
+ WBmf+jXIub5S7RBw+m0mk2xhmcSkg1vY5w6IEIhSo2e2gE9rA0rIkZO5wP4sCouE
33
+ XdaRMEnt/AVHUzroBR3CWAz/6ZnDF8GS7EK1bOfM+YWqJL30g7PoxpT+UJTqWJtM
34
+ CCC1LSoBD7OEUiaIMWUo4h7xWIs=
35
35
  -----END CERTIFICATE-----
36
- date: 2023-08-08 00:00:00.000000000 Z
36
+ date: 2026-02-02 00:00:00.000000000 Z
37
37
  dependencies:
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: loggability
@@ -168,6 +168,7 @@ files:
168
168
  - LICENSE.txt
169
169
  - README.md
170
170
  - ext/zyre_ext/cert.c
171
+ - ext/zyre_ext/certstore.c
171
172
  - ext/zyre_ext/event.c
172
173
  - ext/zyre_ext/extconf.rb
173
174
  - ext/zyre_ext/node.c
@@ -177,6 +178,7 @@ files:
177
178
  - lib/observability/instrumentation/zyre.rb
178
179
  - lib/zyre.rb
179
180
  - lib/zyre/cert.rb
181
+ - lib/zyre/certstore.rb
180
182
  - lib/zyre/event.rb
181
183
  - lib/zyre/event/enter.rb
182
184
  - lib/zyre/event/evasive.rb
@@ -194,6 +196,7 @@ files:
194
196
  - spec/observability/instrumentation/zyre_spec.rb
195
197
  - spec/spec_helper.rb
196
198
  - spec/zyre/cert_spec.rb
199
+ - spec/zyre/certstore_spec.rb
197
200
  - spec/zyre/event_spec.rb
198
201
  - spec/zyre/node_spec.rb
199
202
  - spec/zyre/poller_spec.rb
@@ -207,7 +210,6 @@ metadata:
207
210
  documentation_uri: https://deveiate.org/code/zyre
208
211
  changelog_uri: https://deveiate.org/code/zyre/History_md.html
209
212
  source_uri: https://gitlab.com/ravngroup/open-source/ruby-zyre/-/tree/master
210
- post_install_message:
211
213
  rdoc_options: []
212
214
  require_paths:
213
215
  - lib
@@ -222,8 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
224
  - !ruby/object:Gem::Version
223
225
  version: '0'
224
226
  requirements: []
225
- rubygems_version: 3.4.10
226
- signing_key:
227
+ rubygems_version: 4.0.3
227
228
  specification_version: 4
228
229
  summary: A ZRE library for Ruby.
229
230
  test_files: []
metadata.gz.sig CHANGED
Binary file