Add Z85-encoding and -decoding.

Also:

- Bump the default Ruby to 3.2
- Fix/update the license statement
- Update my gem-signing cert
- Add coverage of Event#inspect
- Fix some miscellaneous warnings and spec issues
M .ruby-version +1 -1
@@ 1,1 1,1 @@ 
-3.0
+3.2

          
M LICENSE.txt +1 -1
@@ 1,4 1,4 @@ 
-Copyright (c) 2020 Ravn Group
+Copyright (c) 2020-2023 Ravn Inc
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the

          
M README.md +26 -2
@@ 28,7 28,7 @@ This example joins the Zyre network and 
     node = Zyre::Node.new
     node.join( 'global' )
     node.start
-    
+
     node.each_event do |event|
       event.print
     end

          
@@ 135,7 135,31 @@ development.
 
 ## License
 
-Copyright (c) 2020-2021, Ravn Group
+This project includes code adapted from the ZeroMQ RFC project, used under
+the following license:
+
+> Copyright (c) 2010-2013 iMatix Corporation and Contributors
+>
+> Permission is hereby granted, free of charge, to any person obtaining a copy of
+> this software and associated documentation files (the "Software"), to deal in
+> the Software without restriction, including without limitation the rights to
+> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+> the Software, and to permit persons to whom the Software is furnished to do so,
+> subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in all
+> copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Everything else is:
+
+Copyright (c) 2020-2023, Ravn Inc
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without

          
M certs/ged.pem +12 -12
@@ 1,7 1,7 @@ 
 -----BEGIN CERTIFICATE-----
-MIID+DCCAmCgAwIBAgIBAzANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdnZWQv
-REM9RmFlcmllTVVEL0RDPW9yZzAeFw0yMDEyMjQyMDU1MjlaFw0yMTEyMjQyMDU1
-MjlaMCIxIDAeBgNVBAMMF2dlZC9EQz1GYWVyaWVNVUQvREM9b3JnMIIBojANBgkq
+MIID+DCCAmCgAwIBAgIBBTANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdnZWQv
+REM9RmFlcmllTVVEL0RDPW9yZzAeFw0yMzAxMTYxNzE2MDlaFw0yNDAxMTYxNzE2
+MDlaMCIxIDAeBgNVBAMMF2dlZC9EQz1GYWVyaWVNVUQvREM9b3JnMIIBojANBgkq
 hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvyVhkRzvlEs0fe7145BYLfN6njX9ih5H
 L60U0p0euIurpv84op9CNKF9tx+1WKwyQvQP7qFGuZxkSUuWcP/sFhDXL1lWUuIl
 M4uHbGCRmOshDrF4dgnBeOvkHr1fIhPlJm5FO+Vew8tSQmlDsosxLUx+VB7DrVFO

          
@@ 12,13 12,13 @@ dXzdHqq+zbGZVSQ7pRYHYomD0IiDe1DbIouFnPWm
 ozilJg4aar2okb/RA6VS87o+d7g6LpDDMMQjH4G9OPnJENLdhu8KnPw/ivSVvQw7
 N2I4L/ZOIe2DIVuYH7aLHfjZDQv/mNgpAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYD
 VR0PBAQDAgSwMB0GA1UdDgQWBBRyjf55EbrHagiRLqt5YAd3yb8k4DANBgkqhkiG
-9w0BAQsFAAOCAYEAMYegZanJi8zq7QKPT7wqXefX4C88I5JWeBHR3PvvWK0CwyMV
-peyiu5I13w/lYX+HUZjE4qsSpJMJFXWl4WZCOo+AMprOcf0PxfuJpxCej5D4tavf
-vRfhahSw7XJrcZih/3J+/UgoH7R05MJ+8LTcy3HGrB3a0vTafjm8OY7Xpa0LJDoN
-JDqxK321VIHyTibbKeA1hWSE6ljlQDvFbTqiCj3Ulp1jTv3TOlvRl8fqcfhxUJI0
-+5Q82jJODjEN+GaWs0V+NlrbU94cXwS2PH5dXogftB5YYA5Ex8A0ikZ73xns4Hdo
-XxdLdd92F5ovxA23j/rKe/IDwqr6FpDkU3nPXH/Qp0TVGv9zZnVJc/Z6ChkuWj8z
-pW7JAyyiiHZgKKDReDrA2LA7Zs3o/7KA6UtUH0FHf8LYhcK+pfHk6RtjRe65ffw+
-MCh97sQ/Z/MOusb5+QddBmB+k8EicXyGNl4b5L4XpL7fIQu+Y96TB3JEJlShxFD9
-k9FjI4d9EP54gS/4
+9w0BAQsFAAOCAYEARYCeUVBWARNKqF0cvNnLJvFf4hdW2+Rtc7NfC5jQvX9a1oom
+sfVvS96eER/9cbrphu+vc59EELw4zT+RY3/IesnoE7CaX6zIOFmSmG7K61OHsSLR
+KqMygcWwyuPXT2JG7JsGHuxbzgaRWe29HbSjBbLYxiMH8Zxh4tKutxzKF7jb0Ggq
+KAf9MH5LwG8IHVIfV5drT14PvgR3tcvmrn1timPyJl+eZ3LNnm9ofOCweuZCq1cy
+4Q8LV3vP2Cofy9q+az3DHdaUGlmMiZZZqKixDr1KSS9nvh0ZrKMOUL1sWj/IaxrQ
+RV3y6td14q49t+xnbj00hPlbW7uE2nLJLt2NAoXiE1Nonndz1seB2c6HL79W9fps
+E/O12pQjCp/aPUZMt8/8tKW31RIy/KP8XO6OTJNWA8A/oNEI0g5p/LmmEtJKWYr1
+WmEdESlpWhzFECctefIF2lsN9vaOuof57RM77t2otrtcscDtNarIqjZsIyqtDvtL
+DttITiit0Vwz7bY0
 -----END CERTIFICATE-----

          
M ext/zyre_ext/extconf.rb +7 -0
@@ 9,11 9,15 @@ have_library( 'zyre' ) or
 	abort "No zyre library!"
 have_library( 'czmq' ) or
 	abort "No czmq library!"
+have_library( 'zmq' ) or
+	abort "No zmq library!"
 
 have_header( 'zyre.h' ) or
 	abort "No zyre.h header!"
 have_header( 'czmq.h' ) or
 	abort "No czmq.h header!"
+have_header( 'zmq.h' ) or
+	abort "No zmq.h header!"
 have_header( 'ruby/thread.h' ) or
 	abort "Your Ruby is too old!"
 

          
@@ 23,6 27,9 @@ have_func( 'zyre_set_beacon_peer_port', 
 have_func( 'zyre_set_contest_in_group', 'zyre.h' )
 have_func( 'zyre_set_zcert', 'zyre.h' )
 
+have_func( 'zmq_z85_encode', 'zmq.h' )
+have_func( 'zmq_z85_decode', 'zmq.h' )
+
 have_func( 'zcert_unset_meta', 'czmq.h' )
 
 create_header()

          
M ext/zyre_ext/zyre_ext.c +87 -1
@@ 5,6 5,12 @@ 
  *  Authors:
  *    * Michael Granger <ged@FaerieMUD.org>
  *
+ *  Refs:
+ *  - https://github.com/zeromq/zyre#api-summary
+ *  - http://api.zeromq.org/master:zmq-z85-decode
+ *  - http://api.zeromq.org/master:zmq-z85-encode
+ *
+ *
  */
 
 #include "zyre_ext.h"

          
@@ 148,7 154,7 @@ rzyre_s_zyre_version()
 
 	version = zyre_version();
 
-	return INT2NUM( version );
+	return LONG2NUM( version );
 }
 
 

          
@@ 180,6 186,7 @@ rzyre_s_interfaces( VALUE module )
 		const char *netmask_s = ziflist_netmask( iflist );
 		const VALUE info_hash = rb_hash_new();
 
+		rzyre_log( "debug", "Getting info for %s", address_s );
 		rb_hash_aset( info_hash, ID2SYM(rb_intern("address")), rb_usascii_str_new_cstr(address_s) );
 		rb_hash_aset( info_hash, ID2SYM(rb_intern("broadcast")), rb_usascii_str_new_cstr(broadcast_s) );
 		rb_hash_aset( info_hash, ID2SYM(rb_intern("netmask")), rb_usascii_str_new_cstr(netmask_s) );

          
@@ 211,6 218,77 @@ rzyre_s_disable_zsys_handler( VALUE modu
 }
 
 
+/*
+ * call-seq:
+ *    Zyre.z85_encode( data )   -> string
+ *
+ * Return the specified binary +data+ as a Z85-encoded binary string. The size of the data must
+ * be divisible by 4. If there is a problem encoding the data, returns +nil+.
+ *
+ */
+static VALUE
+rzyre_s_z85_encode( VALUE module, VALUE data )
+{
+#if HAVE_ZMQ_Z85_ENCODE
+	const char *data_str = StringValuePtr( data );
+	const long len = RSTRING_LEN( data );
+	const long res_len = (len * 1.25) + 1;
+	char *encoded = NULL;
+	VALUE result = Qnil;
+
+	if ( len % 4 ) return Qnil;
+
+	encoded = RB_ZALLOC_N( char, res_len );
+	zmq_z85_encode( encoded, (unsigned char *)data_str, len );
+
+	if ( encoded != NULL ) {
+		result = rb_usascii_str_new( encoded, res_len - 1 );
+	}
+
+	ruby_xfree( encoded );
+
+	return result;
+#else
+	rb_notimplement();
+#endif
+}
+
+
+/*
+ * call-seq:
+ *    Zyre.z85_decode( string )   -> data
+ *
+ * Return the data decoded from the specified Z85-encoded binary +string+. If there is a
+ * problem decoding the string, returns +nil+.
+ *
+ */
+static VALUE
+rzyre_s_z85_decode( VALUE module, VALUE string )
+{
+#if HAVE_ZMQ_Z85_DECODE
+	const char *data_str = StringValueCStr( string );
+	const long len = RSTRING_LEN( string );
+	const long res_len = (len * 0.8) + 1;
+	char *decoded = NULL;
+	VALUE result = Qnil;
+
+	if ( len % 5 ) return Qnil;
+
+	decoded = RB_ZALLOC_N( char, res_len );
+	zmq_z85_decode( (unsigned char *)decoded, data_str );
+
+	if ( decoded != NULL ) {
+		result = rb_str_new( decoded, res_len - 1 );
+	}
+
+	ruby_xfree( decoded );
+
+	return result;
+#else
+	rb_notimplement();
+#endif
+}
+
 
 static VALUE
 rzyre_s_start_authenticator( VALUE module )

          
@@ 225,6 303,11 @@ rzyre_s_start_authenticator( VALUE modul
 void
 Init_zyre_ext()
 {
+	/*
+	 * Document-module: Zyre
+	 *
+	 * The top level namespace for Zyre classes.
+	 */
 	rzyre_mZyre = rb_define_module( "Zyre" );
 
 #ifdef CZMQ_BUILD_DRAFT_API

          
@@ 242,6 325,9 @@ Init_zyre_ext()
 	rb_define_singleton_method( rzyre_mZyre, "interfaces", rzyre_s_interfaces, 0 );
 	rb_define_singleton_method( rzyre_mZyre, "disable_zsys_handler", rzyre_s_disable_zsys_handler, 0 );
 
+	rb_define_singleton_method( rzyre_mZyre, "z85_encode", rzyre_s_z85_encode, 1 );
+	rb_define_singleton_method( rzyre_mZyre, "z85_decode", rzyre_s_z85_decode, 1 );
+
 	// :TODO: Allow for startup of the zauth agent. This will require enough of a
 	// subset of CZMQ that I hesitate to do it in Zyre. Maybe better to just write a
 	// decent CZMQ library instead?

          
M ext/zyre_ext/zyre_ext.h +1 -0
@@ 27,6 27,7 @@ 
 
 #include "zyre.h"
 #include "czmq.h"
+#include "zmq.h"
 
 #ifndef TRUE
 # define TRUE    1

          
M lib/zyre/event/exit.rb +0 -1
@@ 11,7 11,6 @@ class Zyre::Event::Exit < Zyre::Event
 		return "%s (%s) has left the network" % [
 			self.peer_uuid,
 			self.peer_name,
-			self.headers,
 		]
 	end
 

          
M spec/spec_helper.rb +1 -1
@@ 49,7 49,7 @@ RSpec.configure do |config|
 	config.profile_examples = 5
 	config.run_all_when_everything_filtered = true
 	config.shared_context_metadata_behavior = :apply_to_host_groups
-	# config.warnings = true
+	config.warnings = true
 
 	config.before( :suite ) do
 		Zyre::Testing.check_fdmax

          
M spec/zyre/node_spec.rb +2 -2
@@ 44,7 44,7 @@ RSpec.describe( Zyre::Node ) do
 
 
 	it "can set headers" do
-		node1 = started_node do |n|
+		_node1 = started_node do |n|
 			n.set_header( 'Protocol-version', '2' )
 		end
 		node2 = started_node

          
@@ 56,7 56,7 @@ RSpec.describe( Zyre::Node ) do
 
 
 	it "can set headers from a hash" do
-		node1 = started_node do |n|
+		_node1 = started_node do |n|
 			n.headers = {
 				protocol_version: 2,
 				content_type: 'application/messagepack'

          
M spec/zyre_spec.rb +64 -1
@@ 5,7 5,7 @@ require_relative 'spec_helper'
 require 'zyre'
 
 
-RSpec.describe Zyre do
+RSpec.describe( Zyre ) do
 
 
 	# Pattern for matching IPv4 addresses

          
@@ 63,5 63,68 @@ RSpec.describe Zyre do
 		expect( described_class.has_draft_apis? ).to eq( true ).or( eq false )
 	end
 
+
+	describe "Z85 encoding/decoding" do
+
+		# From the 32/Z85 reference code:
+		# https://github.com/zeromq/rfc/blob/master/src/spec_32.c
+		let( :test_data_1 ) do
+			[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B].pack('C*')
+		end
+		let( :test_data_2 ) do
+			[
+				0x8E, 0x0B, 0xDD, 0x69, 0x76, 0x28, 0xB9, 0x1D,
+				0x8F, 0x24, 0x55, 0x87, 0xEE, 0x95, 0xC5, 0xB0,
+				0x4D, 0x48, 0x96, 0x3F, 0x79, 0x25, 0x98, 0x77,
+				0xB4, 0x9C, 0xD9, 0x06, 0x3A, 0xEA, 0xD3, 0xB7
+			].pack('C*')
+		end
+
+
+		it "can round-trip an empty string" do
+			result = described_class.z85_decode( described_class.z85_encode(''.b) )
+			expect( result ).to eq( ''.b )
+		end
+
+
+		it "fails if the data to be encoded is not bounded at 4 bytes" do
+			expect( described_class.z85_encode(test_data_1[0, 3]) ).to be_nil
+		end
+
+
+		it "can round-trip 4-byte-bounded data" do
+			encoded = described_class.z85_encode( test_data_1 )
+
+			expect( encoded ).to eq( 'HelloWorld' )
+			expect( encoded.encoding ).to eq( Encoding::US_ASCII )
+
+			decoded = described_class.z85_decode( encoded )
+
+			expect( decoded ).to eq( test_data_1 )
+			expect( decoded.encoding ).to eq( Encoding::ASCII_8BIT )
+		end
+
+
+		it "can round-trip a ZMQ Curve test key" do
+			encoded = described_class.z85_encode( test_data_2 )
+
+			expect( encoded ).to eq( 'JTKVSB%%)wK0E.X)V>+}o?pNmC{O&4W4b!Ni{Lh6' )
+			expect( encoded.encoding ).to eq( Encoding::US_ASCII )
+
+			decoded = described_class.z85_decode( encoded )
+
+			expect( decoded ).to eq( test_data_2 )
+			expect( decoded.encoding ).to eq( Encoding::ASCII_8BIT )
+		end
+
+
+		it "fails if the data to be decoded is not bounded at 5 bytes" do
+			encoded = 'JTKVSB%%)wK0E.X)V>+}o?pNmC{O&4W4b!Ni{Lh6'
+
+			expect( described_class.z85_decode(encoded[0..-2]) ).to be_nil
+		end
+
+	end
+
 end
 

          
M zyre.gemspec +13 -26
@@ 1,46 1,33 @@ 
 # -*- encoding: utf-8 -*-
-# stub: zyre 0.5.0.pre.20210316161024 ruby lib
+# stub: zyre 0.5.0.pre.20230119183244 ruby lib
 # stub: ext/zyre_ext/extconf.rb
 
 Gem::Specification.new do |s|
   s.name = "zyre".freeze
-  s.version = "0.5.0.pre.20210316161024"
+  s.version = "0.5.0.pre.20230119183244"
 
   s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version=
   s.metadata = { "changelog_uri" => "https://deveiate.org/code/zyre/History_md.html", "documentation_uri" => "https://deveiate.org/code/zyre", "homepage_uri" => "https://gitlab.com/ravngroup/open-source/ruby-zyre", "source_uri" => "https://gitlab.com/ravngroup/open-source/ruby-zyre/-/tree/master" } if s.respond_to? :metadata=
   s.require_paths = ["lib".freeze]
   s.authors = ["Michael Granger".freeze]
-  s.date = "2021-03-16"
+  s.date = "2023-01-19"
   s.description = "A ZRE library for Ruby. This is a Ruby (MRI) binding for the Zyre library for reliable group messaging over local area networks, an implementation of the ZeroMQ Realtime Exchange protocol.".freeze
   s.email = ["ged@faeriemud.org".freeze]
   s.extensions = ["ext/zyre_ext/extconf.rb".freeze]
   s.files = ["Authentication.md".freeze, "History.md".freeze, "LICENSE.txt".freeze, "README.md".freeze, "ext/zyre_ext/cert.c".freeze, "ext/zyre_ext/event.c".freeze, "ext/zyre_ext/extconf.rb".freeze, "ext/zyre_ext/node.c".freeze, "ext/zyre_ext/poller.c".freeze, "ext/zyre_ext/zyre_ext.c".freeze, "ext/zyre_ext/zyre_ext.h".freeze, "lib/observability/instrumentation/zyre.rb".freeze, "lib/zyre.rb".freeze, "lib/zyre/cert.rb".freeze, "lib/zyre/event.rb".freeze, "lib/zyre/event/enter.rb".freeze, "lib/zyre/event/evasive.rb".freeze, "lib/zyre/event/exit.rb".freeze, "lib/zyre/event/join.rb".freeze, "lib/zyre/event/leader.rb".freeze, "lib/zyre/event/leave.rb".freeze, "lib/zyre/event/shout.rb".freeze, "lib/zyre/event/silent.rb".freeze, "lib/zyre/event/stop.rb".freeze, "lib/zyre/event/whisper.rb".freeze, "lib/zyre/node.rb".freeze, "lib/zyre/poller.rb".freeze, "lib/zyre/testing.rb".freeze, "spec/observability/instrumentation/zyre_spec.rb".freeze, "spec/spec_helper.rb".freeze, "spec/zyre/cert_spec.rb".freeze, "spec/zyre/event_spec.rb".freeze, "spec/zyre/node_spec.rb".freeze, "spec/zyre/poller_spec.rb".freeze, "spec/zyre/testing_spec.rb".freeze, "spec/zyre_spec.rb".freeze]
   s.homepage = "https://gitlab.com/ravngroup/open-source/ruby-zyre".freeze
   s.licenses = ["BSD-3-Clause".freeze]
-  s.rubygems_version = "3.2.3".freeze
+  s.rubygems_version = "3.4.2".freeze
   s.summary = "A ZRE library for Ruby.".freeze
 
-  if s.respond_to? :specification_version then
-    s.specification_version = 4
-  end
+  s.specification_version = 4
 
-  if s.respond_to? :add_runtime_dependency then
-    s.add_runtime_dependency(%q<loggability>.freeze, ["~> 0.18"])
-    s.add_development_dependency(%q<rake-deveiate>.freeze, ["~> 0.15", ">= 0.15.1"])
-    s.add_development_dependency(%q<rake-compiler>.freeze, ["~> 1.1"])
-    s.add_development_dependency(%q<rubocop>.freeze, ["~> 0.91"])
-    s.add_development_dependency(%q<rspec_junit_formatter>.freeze, ["~> 0.4"])
-    s.add_development_dependency(%q<simplecov-cobertura>.freeze, ["~> 1.4"])
-    s.add_development_dependency(%q<observability>.freeze, ["~> 0.3"])
-    s.add_development_dependency(%q<rspec-wait>.freeze, ["~> 0.0"])
-  else
-    s.add_dependency(%q<loggability>.freeze, ["~> 0.18"])
-    s.add_dependency(%q<rake-deveiate>.freeze, ["~> 0.15", ">= 0.15.1"])
-    s.add_dependency(%q<rake-compiler>.freeze, ["~> 1.1"])
-    s.add_dependency(%q<rubocop>.freeze, ["~> 0.91"])
-    s.add_dependency(%q<rspec_junit_formatter>.freeze, ["~> 0.4"])
-    s.add_dependency(%q<simplecov-cobertura>.freeze, ["~> 1.4"])
-    s.add_dependency(%q<observability>.freeze, ["~> 0.3"])
-    s.add_dependency(%q<rspec-wait>.freeze, ["~> 0.0"])
-  end
+  s.add_runtime_dependency(%q<loggability>.freeze, ["~> 0.18", ">= 0.18.2"])
+  s.add_development_dependency(%q<rake-deveiate>.freeze, ["~> 0.15", ">= 0.15.1"])
+  s.add_development_dependency(%q<rake-compiler>.freeze, ["~> 1.1"])
+  s.add_development_dependency(%q<rubocop>.freeze, ["~> 0.91"])
+  s.add_development_dependency(%q<rspec_junit_formatter>.freeze, ["~> 0.4"])
+  s.add_development_dependency(%q<simplecov-cobertura>.freeze, ["~> 1.4"])
+  s.add_development_dependency(%q<observability>.freeze, ["~> 0.3"])
+  s.add_development_dependency(%q<rspec-wait>.freeze, ["~> 0.0"])
 end