ccrpc 0.4.0 → 0.5.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: d74cc1cb91c2d6ae3a85ad925d8e94b317bcc9f13ecb51d7b32b8edfd1ef8d41
4
- data.tar.gz: f14c874d78b97fd52d87685d16dafe9d63685817c7bf759608319e42b60ab64d
3
+ metadata.gz: a3f1a9ac22e449534ede2f1d3f94efcc11becf3d65d7e868ce1f6334f580ba54
4
+ data.tar.gz: 70a5849b9f6594090a5069b13d566b39cf53baf69bf5ee743ead1fa56d7bd1a3
5
5
  SHA512:
6
- metadata.gz: 04f3fc642a27bb61f4f279783b1ad4bfe97120804d039c8e2d7c866aee1c3aee56ac430fe1826ea493b781af7b5cef9dd54c4aeadaa6c818ff768e79e95e95ac
7
- data.tar.gz: 33b4035c9a686261c34fd0f7ed245c6f1744b69b76998c7be60afdd13c6887785a526ffe3c5f7de7472d434c6d18dff6752a154ae70db4427da38908f6f7067d
6
+ metadata.gz: 31e497800794295aafed95c1f75b8d2cc866aff6255d94914f0377eb04d7ad3a0009da355bc6f240466a25cd5131de46a56d8845c345f4a67b8896303e79a5e2
7
+ data.tar.gz: c457015d25201324c856a7fea9b0bc3ccdc0b5a1f9819f7c0758e45f27f3cf4c732b4028fa3e8f9af955a2e14319bde9c6ff6da9e3a3cae09b3cc560e8109520
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -1,15 +1,16 @@
1
- # Ccrpc - A minimalistic RPC library for Ruby
1
+ # Ccrpc - A minimalist RPC library for Ruby
2
2
 
3
3
  Features:
4
- * Simple human readable wire protocol
5
- * Works on arbitrary ruby IO objects (Pipes, Sockets, STDIN, STDOUT) even Windows CR/LF converting IOs
4
+
5
+ * Simple human readable wire protocol and optionally a faster binary protocol
6
+ * Works on arbitrary IO like objects (Pipes, Sockets, STDIN, STDOUT, OpenSSL) even Windows CR/LF converting IOs
6
7
  * No object definitions - only plain string transfers (so no issues with undefined classes or garbage collection like in DRb)
7
8
  * Each call transfers a function name and a list of parameters in form of a Hash<String=>String>
8
9
  * Each response equally transfers a list of parameters
9
- * Similar to closures, it's possible to respond to a particular call as a call_back
10
- * Fully asynchronous, either by use of multiple threads or by using lazy_answers, so that arbitrary calls in both directions can be mixed simultaneously without blocking each other
10
+ * Similar to closures, it's possible to respond to a particular call as a `call_back`
11
+ * Fully asynchronous, either by use of multiple threads or by using `lazy_answers`, so that arbitrary calls in both directions can be mixed simultaneously without blocking each other
11
12
  * Fully thread safe, but doesn't use additional internal threads
12
- * Each call_back arrives in the thread of the caller
13
+ * Each `call_back` arrives in the thread of the caller
13
14
  * Only dedicated functions can be called (not arbitrary as in DRb)
14
15
  * No dependencies
15
16
 
@@ -72,7 +73,7 @@ The following example invokes the call in the opposite direction, from the subpr
72
73
  require 'ccrpc'
73
74
  # Create the receiver side of the connection
74
75
  # Use a copy of STDOUT because...
75
- rpc = Ccrpc::RpcConnection.new(STDIN, STDOUT.dup)
76
+ rpc = Ccrpc::RpcConnection.new(STDIN.binmode, STDOUT.dup.binmode)
76
77
  # .. STDOUT is now redirected to STDERR, so that pp prints to STDERR
77
78
  STDOUT.reopen(STDERR)
78
79
  # Call function "hello" with param {"who" => "world"}
@@ -84,10 +85,10 @@ The following example invokes the call in the opposite direction, from the subpr
84
85
  tf.write(code)
85
86
  tf.flush
86
87
  # Execute the temp file in a subprocess
87
- io = IO.popen(['ruby', tf.path], "w+")
88
+ io = IO.popen(['ruby', tf.path], "wb+")
88
89
 
89
90
  # Create the caller side of the connection
90
- rpc = Ccrpc::RpcConnection.new(io, io)
91
+ rpc = Ccrpc::RpcConnection.new(io, io, protocol: :binary)
91
92
  # Wait for calls
92
93
  rpc.call do |call|
93
94
  # Print the received call data to STDERR
data/lib/ccrpc/escape.rb CHANGED
@@ -5,7 +5,7 @@ module Ccrpc
5
5
  module Escape
6
6
  def self.escape(data)
7
7
  data = data.b if data.frozen? || data.encoding != Encoding::BINARY
8
- data.gsub(/([\a\r\n\t\\])/n){ "\\x" + $1.unpack("H2")[0] }
8
+ data.gsub(/([\a\r\n\t\\])/n){ "\\x" + $1.unpack1("H2") }
9
9
  end
10
10
 
11
11
  def self.unescape(data)
@@ -76,8 +76,11 @@ class RpcConnection
76
76
  end
77
77
 
78
78
  CallbackReceiver = Struct.new :meth, :callbacks
79
+ ReceivedCallData = Struct.new :cbfunc, :id, :recv_id
79
80
 
81
+ # The kind of +IO+ object used to receive calls and answers. Set by {initialize}.
80
82
  attr_accessor :read_io
83
+ # The kind of +IO+ object used to send calls and answers. Set by {initialize}.
81
84
  attr_accessor :write_io
82
85
 
83
86
  # Create a RPC connection
@@ -88,11 +91,28 @@ class RpcConnection
88
91
  # If enabled the return value of #call is always a Ccrpc::Promise object.
89
92
  # It behaves like an ordinary +nil+ or Hash object, but the actual IO blocking operation is delayed to the first method call on the Promise object.
90
93
  # See {#call} for more description.
91
- def initialize(read_io, write_io, lazy_answers: false)
94
+ # @param [Symbol] protocol Select the protocol which is used to send calls.
95
+ # * The +:text+ protocol is the classic default.
96
+ # * The +:binary+ protocol is faster, but not so readable for human.
97
+ # * The +:prefer_binary+ is the same as :binary, but with an initial round-trip to check that the other end is binary-capable (means ccrpc >= 0.5).
98
+ # The protocol used to receive calls is selected by the *protocol* option on the other end.
99
+ # A connection could use different protocols for both directions, although this has no advantage.
100
+ def initialize(read_io, write_io, lazy_answers: false, protocol: :text)
92
101
  super()
93
102
 
94
103
  @read_io = read_io
95
104
  @write_io = write_io
105
+ @read_binary = false
106
+ @write_binary = case protocol
107
+ when :binary
108
+ true
109
+ when :text, :only_text # only_text is to simulate ccrpc-0.4.0 peer
110
+ false
111
+ when :prefer_binary
112
+ nil
113
+ else
114
+ raise ArgumentError, "invalid protocol: #{protocol.inspect}"
115
+ end
96
116
  if lazy_answers
97
117
  require 'ccrpc/lazy'
98
118
  alias maybe_lazy do_lazy
@@ -104,27 +124,100 @@ class RpcConnection
104
124
  @write_io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
105
125
  end
106
126
 
107
- # A random number as start call ID is not technically required, but makes transferred data more readable.
108
- @id = rand(1000)
109
127
  @id_mutex = Mutex.new
110
128
  @read_mutex = Mutex.new
111
129
  @write_mutex = Mutex.new
112
130
  @answers = {}
113
131
  @receivers = {}
114
132
  @answers_mutex = Mutex.new
133
+ @proto_ack_mutex = Mutex.new
115
134
  @new_answer = ConditionVariable.new
116
135
 
117
136
  @read_enum = Enumerator.new do |y|
118
137
  begin
119
138
  while @read_enum
120
- l = @read_io.gets&.force_encoding(Encoding::BINARY)
121
- break if l.nil?
122
- y << l
139
+ if @read_binary
140
+ t = @read_io.read(1)&.getbyte(0)
141
+ # p @read_io=>t
142
+ case t
143
+ when 1
144
+ keysize, valsize = @read_io.read(8).unpack("NN")
145
+ key = @read_io.read(keysize).force_encoding(Encoding::UTF_8)
146
+ value = @read_io.read(valsize).force_encoding(Encoding::UTF_8)
147
+ y << [key, value]
148
+ when 2
149
+ id, funcsize = @read_io.read(8).unpack("NN")
150
+ func = @read_io.read(funcsize)
151
+ y << ReceivedCallData.new(func.force_encoding(Encoding::UTF_8), id)
152
+ when 3
153
+ id, recv_id, funcsize = @read_io.read(12).unpack("NNN")
154
+ func = @read_io.read(funcsize)
155
+ y << ReceivedCallData.new(func.force_encoding(Encoding::UTF_8), id, recv_id)
156
+ when 4
157
+ id = @read_io.read(4).unpack1("N")
158
+ y << id
159
+ when 79 # "O"
160
+ l = @read_io.read(6)
161
+ unless l == "\tK\n\a1\n"
162
+ raise InvalidResponse, "invalid binary response #{l.inspect}"
163
+ end
164
+ y << ["O", "K"]
165
+ y << 1
166
+
167
+ when NilClass
168
+ # connection closed
169
+ break
170
+
171
+ else
172
+ raise InvalidResponse, "invalid binary response #{t.inspect}"
173
+ end
174
+
175
+ else
176
+
177
+ l = @read_io.gets&.force_encoding(Encoding::BINARY)
178
+ # p @read_io=>l
179
+ case
180
+ when l=="\r\0\a1\n" && protocol != :only_text
181
+ @read_binary = true
182
+ when l=="\r\1\a1\n" && protocol != :only_text
183
+ @read_binary = true
184
+ send_answer({O: :K}, 1)
185
+
186
+ when l=~/\A([^\t\a\n]+)\t(.*?)\r?\n\z/mn
187
+ # received key/value pair used for either callback parameters or return values
188
+ y << [Escape.unescape($1).force_encoding(Encoding::UTF_8), Escape.unescape($2.force_encoding(Encoding::UTF_8))]
189
+
190
+ when l=~/\A([^\t\a\n]+)(?:\a(\d+))?(?:\a(\d+))?\r?\n\z/mn
191
+ # received callback
192
+ y << ReceivedCallData.new(Escape.unescape($1.force_encoding(Encoding::UTF_8)), $2&.to_i, $3&.to_i)
193
+
194
+ when l=~/\A\a(\d+)\r?\n\z/mn
195
+ # received return event
196
+ y << $1.to_i
197
+
198
+ when l.nil?
199
+ # connection closed
200
+ break
201
+
202
+ else
203
+ raise InvalidResponse, "invalid text response #{l.inspect}"
204
+ end
205
+ end
123
206
  end
124
207
  rescue => err
125
208
  y << err
126
209
  end
127
210
  end
211
+
212
+ if @write_binary == true # immediate binary mode
213
+ # Use ID 1 for proto change request to have a fixed string over the wire
214
+ register_call("\r", 1)
215
+ @write_io.write "\r\0\a1\n"
216
+ @write_io.flush
217
+ end
218
+
219
+ # A random number as start call ID is not technically required, but makes transferred data more readable.
220
+ @id = rand(1000) + 1
128
221
  end
129
222
 
130
223
  private def do_lazy(&block)
@@ -134,11 +227,11 @@ class RpcConnection
134
227
  yield
135
228
  end
136
229
 
137
- # Disable reception of data from the read_io object.
230
+ # Disable reception of data from the {read_io} object.
138
231
  #
139
- # This function doesn't close the IO objects.
232
+ # This function doesn't close the +IO+ objects.
140
233
  # A waiting reception is not aborted by this call.
141
- # It can be aborted by calling IO#close on the underlying read_io and write_io objects.
234
+ # It can be aborted by calling +IO#close+ on the underlying {read_io} and {write_io} objects.
142
235
  def detach
143
236
  @read_enum = nil
144
237
  end
@@ -146,9 +239,9 @@ class RpcConnection
146
239
  # Do a RPC call and/or wait for a RPC call from the other side.
147
240
  #
148
241
  # {#call} must be called with either a function name (and optional parameters) or with a block or with both.
149
- # If {#call} is called with a function name, the block on the other side of the RPC connection is called with that function name.
150
- # If {#call} is called with a block only, than it receives these kind of calls, which are called anonymous callbacks.
151
- # If {#call} is called with a function name and a block, then the RPC function on the other side is called and it is possible to call back to this dedicated block by invoking {Call#call_back} .
242
+ # * If called with a function name, the block on the other end of the RPC connection is called with that function name.
243
+ # * If called with a block only, than it receives these kind of calls, which are called anonymous callbacks.
244
+ # * If called with a function name and a block, then the RPC function on the other side is called and it is possible to call back to this dedicated block by invoking {Call#call_back} .
152
245
  #
153
246
  # @param func [String, Symbol] The RPC function to be called on the other side.
154
247
  # The other side must wait for calls through {#call} without arguments but with a block.
@@ -165,9 +258,9 @@ class RpcConnection
165
258
  # This type of answers can be enabled by +RpcConnection#new(lazy_answers: true)+
166
259
  # The Promise object is returned as soon as the RPC call is sent and a callback receiver is registered, but before waiting for the corresponding answer.
167
260
  # This way several calls can be send in parallel without using threads.
168
- # As soon as a method is called on the Promise object, this method is blocked until the RPC answer was received.
169
- # The Promise object then behaves like a Hash or +nil+ object.
170
- # It is recommended to use Promise#itself to trigger waiting for call answers or callbacks (although any other method triggers waiting as well).
261
+ # As soon as any method is called on the Promise object, this method is blocked until the RPC answer was received.
262
+ # When the RPC answer has been received, the Promise object then behaves like an ordinary Hash object or +nil+ in case of connection end.
263
+ # It is recommended to use +Promise#itself+ to trigger explicit waiting for call answers or callbacks (although any other method triggers waiting as well).
171
264
  # @return [NilClass] Waiting for further answers was stopped gracefully by either returning +[hash, true]+ from the block or because the connection was closed.
172
265
  def call(func=nil, params={}, &block)
173
266
  call_intern(func, params, &block)
@@ -175,15 +268,16 @@ class RpcConnection
175
268
 
176
269
  protected
177
270
 
178
- def call_intern(func, params={}, recv_id=nil, &block)
179
- id = next_id if func
271
+ def register_call(func, id, &block)
272
+ id ||= next_id if func
180
273
 
181
274
  @answers_mutex.synchronize do
182
- @receivers[id] = CallbackReceiver.new(block_given? ? nil : caller[3], [])
275
+ @receivers[id] = CallbackReceiver.new(block_given? ? nil : caller[4], [])
183
276
  end
277
+ id
278
+ end
184
279
 
185
- send_call(func, params, id, recv_id) if func
186
-
280
+ def wait_for_return(id, &block)
187
281
  maybe_lazy do
188
282
  @answers_mutex.synchronize do
189
283
  res = loop do
@@ -191,6 +285,7 @@ class RpcConnection
191
285
  if cb=@receivers[id].callbacks.shift
192
286
  @answers_mutex.unlock
193
287
  begin
288
+ # invoke the user block
194
289
  rets, exit = yield(cb)
195
290
  if rets
196
291
  cb.answer = rets
@@ -229,47 +324,88 @@ class RpcConnection
229
324
  end
230
325
  end
231
326
 
327
+ def call_intern(func, params={}, recv_id=nil, &block)
328
+ id = register_call(func, nil, &block)
329
+ send_call(func, params, id, recv_id) if func
330
+ wait_for_return(id, &block)
331
+ end
332
+
232
333
  def next_id
233
334
  @id_mutex.synchronize do
234
335
  @id = (@id + 1) & 0xffffffff
235
336
  end
236
337
  end
237
338
 
238
- def send_call(func, params, id, recv_id=nil)
339
+ def wait_for_proto_ack
340
+ @proto_ack_mutex.synchronize do
341
+ if @write_binary.nil? # acknowledge text/binary mode
342
+ register_call("\r", 1)
343
+ @write_mutex.synchronize do
344
+ @write_io.write "\r\1\a1\n"
345
+ @write_io.flush
346
+ end
347
+ # wait until protocol is acknowledged
348
+ res = wait_for_return(1)
349
+ if res == {"O" => "K"}
350
+ @write_binary = true
351
+ else
352
+ @write_binary = false
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ def send_call_or_answer(params)
239
359
  to_send = String.new
240
360
  @write_mutex.synchronize do
241
361
  params.compact.each do |key, value|
242
- to_send << Escape.escape(key.to_s) << "\t" <<
243
- Escape.escape(value.to_s) << "\n"
362
+ if @write_binary
363
+ k = key.to_s
364
+ v = value.to_s
365
+ to_send << [1, k.bytesize, v.bytesize, k, v].pack("CNNa*a*")
366
+ else
367
+ to_send << Escape.escape(key.to_s) << "\t" <<
368
+ Escape.escape(value.to_s) << "\n"
369
+ end
244
370
  if to_send.bytesize > 9999
245
371
  @write_io.write to_send
246
372
  to_send = String.new
247
373
  end
248
374
  end
249
- to_send << Escape.escape(func.to_s) << "\a#{id}"
250
- to_send << "\a#{recv_id}" if recv_id
251
- @write_io.write(to_send << "\n")
375
+ yield(to_send)
376
+ @write_io.write(to_send)
252
377
  @write_io.flush
253
378
  end
254
379
  after_write
255
380
  end
256
381
 
257
- def send_answer(answer, id)
258
- to_send = String.new
259
- @write_mutex.synchronize do
260
- answer.compact.each do |key, value|
261
- to_send << Escape.escape(key.to_s) << "\t" <<
262
- Escape.escape(value.to_s) << "\n"
263
- if to_send.bytesize > 9999
264
- @write_io.write to_send
265
- to_send = String.new
382
+ def send_call(func, params, id, recv_id)
383
+ wait_for_proto_ack unless recv_id
384
+ send_call_or_answer(params) do |to_send|
385
+ if @write_binary
386
+ f = func.to_s
387
+ if recv_id
388
+ to_send << [3, id, recv_id, f.bytesize, f].pack("CNNNa*")
389
+ else
390
+ to_send << [2, id, f.bytesize, f].pack("CNNa*")
266
391
  end
392
+ else
393
+ to_send << Escape.escape(func.to_s) << "\a#{id}"
394
+ to_send << "\a#{recv_id}" if recv_id
395
+ to_send << "\n"
396
+ end
397
+ end
398
+ end
399
+
400
+ def send_answer(answer, id)
401
+ send_call_or_answer(answer) do |to_send|
402
+ if @write_binary
403
+ to_send << [4, id].pack("CN")
404
+ else
405
+ to_send << "\a#{id}" if id
406
+ to_send << "\n"
267
407
  end
268
- to_send << "\a#{id}" if id
269
- @write_io.write(to_send << "\n")
270
- @write_io.flush
271
408
  end
272
- after_write
273
409
  end
274
410
 
275
411
  def receive_answers
@@ -278,27 +414,25 @@ class RpcConnection
278
414
  case l
279
415
  when Exception
280
416
  raise l
281
- when /\A([^\t\a\n]+)\t(.*?)\r?\n\z/mn
282
- # received key/value pair used for either callback parameters or return values
283
- rets[Escape.unescape($1).force_encoding(Encoding::UTF_8)] ||= Escape.unescape($2.force_encoding(Encoding::UTF_8))
284
417
 
285
- when /\A([^\t\a\n]+)(?:\a(\d+))?(?:\a(\d+))?\r?\n\z/mn
286
- # received callback
287
- cbfunc, id, recv_id = $1, $2&.to_i, $3&.to_i
418
+ when Array
419
+ rets[l[0]] ||= l[1]
288
420
 
289
- callback = Call.new(self, Escape.unescape(cbfunc.force_encoding(Encoding::UTF_8)).to_sym, rets, id)
421
+ when ReceivedCallData
422
+ # received callback
423
+ callback = Call.new(self, l.cbfunc.to_sym, rets, l.id)
290
424
 
291
425
  @answers_mutex.synchronize do
292
- receiver = @receivers[recv_id]
426
+ receiver = @receivers[l.recv_id]
293
427
 
294
428
  if !receiver
295
- if recv_id
296
- raise NoCallbackDefined, "call_back to #{cbfunc.inspect} was received, but corresponding call returned already"
429
+ if l.recv_id
430
+ raise NoCallbackDefined, "call_back to #{l.cbfunc.inspect} was received, but corresponding call returned already"
297
431
  else
298
- raise NoCallbackDefined, "call to #{cbfunc.inspect} was received, but there is no #{self.class}#call running"
432
+ raise NoCallbackDefined, "call to #{l.cbfunc.inspect} was received, but there is no #{self.class}#call running"
299
433
  end
300
434
  elsif meth=receiver.meth
301
- raise NoCallbackDefined, "call_back to #{cbfunc.inspect} was received, but corresponding call was called without a block in #{meth}"
435
+ raise NoCallbackDefined, "call_back to #{l.cbfunc.inspect} was received, but corresponding call was called without a block in #{meth}"
302
436
  end
303
437
 
304
438
  receiver.callbacks << callback
@@ -306,9 +440,10 @@ class RpcConnection
306
440
  end
307
441
  return
308
442
 
309
- when /\A\a(\d+)\r?\n\z/mn
443
+ when Integer
310
444
  # received return event
311
- id = $1.to_i
445
+ id = l
446
+
312
447
  @answers_mutex.synchronize do
313
448
  @answers[id] = rets
314
449
  @new_answer.broadcast
data/lib/ccrpc/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ccrpc
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,37 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ccrpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Kanis
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain:
11
10
  - |
12
11
  -----BEGIN CERTIFICATE-----
13
- MIIDljCCAn6gAwIBAgIBATANBgkqhkiG9w0BAQsFADBIMRMwEQYDVQQDDApsYXJz
14
- LmthbmlzMRwwGgYKCZImiZPyLGQBGRYMc2luY25vdmF0aW9uMRMwEQYKCZImiZPy
15
- LGQBGRYDY29tMB4XDTIzMTAyNzIwNTc0MloXDTI0MTAyNjIwNTc0MlowSDETMBEG
16
- A1UEAwwKbGFycy5rYW5pczEcMBoGCgmSJomT8ixkARkWDHNpbmNub3ZhdGlvbjET
17
- MBEGCgmSJomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
18
- ggEBAMJ4t2fVdVUixHSsWWXudvO7uCNKchxmrocupP5CNIvhkr0dYoJX+Hkghhh3
19
- CFvo2gm8Oolfbl8Zvt/tBvJ/DhIaLWelhLpx7+gwoXkmrMvzOrJx1Wn4WdBSbKbX
20
- 0b7kbY9XKtZPp0e+5xCGDZRwGgdvEJrQqu//lgKdnuS5jMMqx4agqE0vZXmJ6CpV
21
- FmUZ111nUmqUXO1lAJPrbYgx7PuUh+/KuaLTXe1TFuQKIK5HJoLQPRHSrleqRzD2
22
- U7CagEy8gu8Ct9jD35QrxnBju0UyQlPlOB1v8wgfv50t8106Anke0NytTAH2uGKU
23
- 5fimmqnLQnFg8AzZnuaevliscYcCAwEAAaOBijCBhzAJBgNVHRMEAjAAMAsGA1Ud
24
- DwQEAwIEsDAdBgNVHQ4EFgQU8WIevudyI+2h11gae0Sr2GzINeswJgYDVR0RBB8w
25
- HYEbbGFycy5rYW5pc0BzaW5jbm92YXRpb24uY29tMCYGA1UdEgQfMB2BG2xhcnMu
26
- a2FuaXNAc2luY25vdmF0aW9uLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAaD5tg+U+
27
- /oolmp1/q6kCGqSaN4BIa40zDVFBaPsU02TXP7tpc+MD+WXwATMJAQ7MoIdFV2Rn
28
- lCnMpKga9c+YvY3sKNeto9w+KvgIWSizHmUxqQmBpq74Xxz+fbBqZS/RboS0nY2p
29
- ggnJJUsPbRXFFG09KPSNEGgsl5gipbX3k4HUlH/EaGrXCKswEr5WP5GwVm8t4J2r
30
- t9W3c/nKB0fn3ouhpxl+AQU0dKqEanlH9rs7UNtVIgQtrH+xLMScdwgRzIVrwKVC
31
- UAcSEq6uV4ESW20hEz8XvDK007nIk7R18hD6l5B9nqRN96+VO/5mex0iHV7lf79h
32
- 17EsHXVAFAb3dA==
12
+ MIIEBDCCAmygAwIBAgIBAzANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDDB1sYXJz
13
+ L0RDPWdyZWl6LXJlaW5zZG9yZi9EQz1kZTAeFw0yNDEyMjkxOTU2NTZaFw0yNTEy
14
+ MjkxOTU2NTZaMCgxJjAkBgNVBAMMHWxhcnMvREM9Z3JlaXotcmVpbnNkb3JmL0RD
15
+ PWRlMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAwum6Y1KznfpzXOT/
16
+ mZgJTBbxZuuZF49Fq3K0WA67YBzNlDv95qzSp7V/7Ek3NCcnT7G+2kSuhNo1FhdN
17
+ eSDO/moYebZNAcu3iqLsuzuULXPLuoU0GsMnVMqV9DZPh7cQHE5EBZ7hlzDBK7k/
18
+ 8nBMvR0mHo77kIkapHc26UzVq/G0nKLfDsIHXVylto3PjzOumjG6GhmFN4r3cP6e
19
+ SDfl1FSeRYVpt4kmQULz/zdSaOH3AjAq7PM2Z91iGwQvoUXMANH2v89OWjQO/NHe
20
+ JMNDFsmHK/6Ji4Kk48Z3TyscHQnipAID5GhS1oD21/WePdj7GhmbF5gBzkV5uepd
21
+ eJQPgWGwrQW/Z2oPjRuJrRofzWfrMWqbOahj9uth6WSxhNexUtbjk6P8emmXOJi5
22
+ chQPnWX+N3Gj+jjYxqTFdwT7Mj3pv1VHa+aNUbqSPpvJeDyxRIuo9hvzDaBHb/Cg
23
+ 9qRVcm8a96n4t7y2lrX1oookY6bkBaxWOMtWlqIprq8JZXM9AgMBAAGjOTA3MAkG
24
+ A1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQ4h1tIyvdUWtMI739xMzTR
25
+ 7EfMFzANBgkqhkiG9w0BAQsFAAOCAYEAoZZWzNV2XXaoSmvyamSSN+Wt/Ia+DNrU
26
+ 2pc3kMEqykH6l1WiVPszr6HavQ//2I2UcSRSS5AGDdiSXcfyFmHtMBdtJHhTPcn7
27
+ 4DLliB0szpvwG+ltGD8PI8eWkLaTQeFzs+0QCTavgKV+Zw56Q0J5zZvHHUMrLkUD
28
+ qhwKjdTdkrRTn9Sqi0BrIRRZGTUDdrt8qoWm35aES5arKZzytgrRD/kXfFW2LCg0
29
+ FzgTKibR4/3g8ph94kQLg/D2SMlVPkQ3ECi036mZxDC2n8V6u3rDkG5923wmrRZB
30
+ J6cqz475Q8HYORQCB68OPzkWMfC7mBo3vpSsIqRoNs1FE4FJu4FGwZG8fBSrDC4H
31
+ bZe+GtyS3e2SMjgT65zp35gLO9I7MquzYN9P6V2u1iBpTycchk5z9R1ghxzZSBT8
32
+ DrkJ9tVlPQtJB0LqT0tvBap4upnwT1xYq721b5dwH6AF4Pi6iz/dc5vnq1/MH8bV
33
+ 8VbbBzzeE7MsvgkP3sHlLmY8PtuyViJ8
33
34
  -----END CERTIFICATE-----
34
- date: 2024-10-18 00:00:00.000000000 Z
35
+ date: 1980-01-02 00:00:00.000000000 Z
35
36
  dependencies: []
36
37
  description: Simple bidirectional and thread safe RPC protocol. Works on arbitrary
37
38
  Ruby IO objects.
@@ -63,7 +64,6 @@ licenses:
63
64
  metadata:
64
65
  homepage_uri: https://github.com/larskanis/ccrpc
65
66
  documentation_uri: https://rubydoc.info/gems/ccrpc
66
- post_install_message:
67
67
  rdoc_options: []
68
68
  require_paths:
69
69
  - lib
@@ -78,8 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
78
  - !ruby/object:Gem::Version
79
79
  version: '0'
80
80
  requirements: []
81
- rubygems_version: 3.5.11
82
- signing_key:
81
+ rubygems_version: 3.6.9
83
82
  specification_version: 4
84
83
  summary: Simple bidirectional RPC protocol
85
84
  test_files: []
metadata.gz.sig CHANGED
Binary file