garaio_bunny 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +231 -0
  3. data/lib/amq/protocol/extensions.rb +16 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +55 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
  7. data/lib/bunny/channel.rb +2055 -0
  8. data/lib/bunny/channel_id_allocator.rb +82 -0
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
  10. data/lib/bunny/concurrent/condition.rb +66 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +62 -0
  12. data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
  13. data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
  14. data/lib/bunny/consumer.rb +128 -0
  15. data/lib/bunny/consumer_tag_generator.rb +23 -0
  16. data/lib/bunny/consumer_work_pool.rb +122 -0
  17. data/lib/bunny/cruby/socket.rb +110 -0
  18. data/lib/bunny/cruby/ssl_socket.rb +118 -0
  19. data/lib/bunny/delivery_info.rb +93 -0
  20. data/lib/bunny/exceptions.rb +269 -0
  21. data/lib/bunny/exchange.rb +275 -0
  22. data/lib/bunny/framing.rb +56 -0
  23. data/lib/bunny/get_response.rb +83 -0
  24. data/lib/bunny/heartbeat_sender.rb +71 -0
  25. data/lib/bunny/jruby/socket.rb +57 -0
  26. data/lib/bunny/jruby/ssl_socket.rb +58 -0
  27. data/lib/bunny/message_properties.rb +119 -0
  28. data/lib/bunny/queue.rb +393 -0
  29. data/lib/bunny/reader_loop.rb +158 -0
  30. data/lib/bunny/return_info.rb +74 -0
  31. data/lib/bunny/session.rb +1483 -0
  32. data/lib/bunny/socket.rb +14 -0
  33. data/lib/bunny/ssl_socket.rb +14 -0
  34. data/lib/bunny/test_kit.rb +41 -0
  35. data/lib/bunny/timeout.rb +7 -0
  36. data/lib/bunny/transport.rb +526 -0
  37. data/lib/bunny/version.rb +6 -0
  38. data/lib/bunny/versioned_delivery_tag.rb +28 -0
  39. data/lib/bunny.rb +92 -0
  40. metadata +127 -0
@@ -0,0 +1,14 @@
1
+ # See #165. MK.
2
+ if defined?(JRUBY_VERSION)
3
+ require "bunny/jruby/socket"
4
+
5
+ module Bunny
6
+ SocketImpl = JRuby::Socket
7
+ end
8
+ else
9
+ require "bunny/cruby/socket"
10
+
11
+ module Bunny
12
+ SocketImpl = Socket
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # See #165. MK.
2
+ if defined?(JRUBY_VERSION)
3
+ require "bunny/jruby/ssl_socket"
4
+
5
+ module Bunny
6
+ SSLSocketImpl = JRuby::SSLSocket
7
+ end
8
+ else
9
+ require "bunny/cruby/ssl_socket"
10
+
11
+ module Bunny
12
+ SSLSocketImpl = SSLSocket
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "timeout"
4
+
5
+ module Bunny
6
+ # Unit, integration and stress testing toolkit
7
+ class TestKit
8
+ class << self
9
+
10
+ def poll_while(timeout = 60, &probe)
11
+ Timeout.timeout(timeout) {
12
+ sleep 0.1 while probe.call
13
+ }
14
+ end
15
+ def poll_until(timeout = 60, &probe)
16
+ Timeout.timeout(timeout) {
17
+ sleep 0.1 until probe.call
18
+ }
19
+ end
20
+
21
+ # @return [Integer] Random integer in the range of [a, b]
22
+ # @api private
23
+ def random_in_range(a, b)
24
+ Range.new(a, b).to_a.sample
25
+ end
26
+
27
+ # @param [Integer] a Lower bound of message size, in KB
28
+ # @param [Integer] b Upper bound of message size, in KB
29
+ # @param [Integer] i Random number to use in message generation
30
+ # @return [String] Message payload of length in the given range, with non-ASCII characters
31
+ # @api public
32
+ def message_in_kb(a, b, i)
33
+ s = "Ю#{i}"
34
+ n = random_in_range(a, b) / s.bytesize
35
+
36
+ s * n * 1024
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ module Bunny
2
+ Timeout = ::Timeout
3
+
4
+ # Backwards compatibility
5
+ # @private
6
+ Timer = Timeout
7
+ end
@@ -0,0 +1,526 @@
1
+ require "socket"
2
+ require "thread"
3
+ require "monitor"
4
+
5
+ begin
6
+ require "openssl"
7
+ rescue LoadError => _le
8
+ $stderr.puts "Could not load OpenSSL"
9
+ end
10
+
11
+ require "bunny/exceptions"
12
+ require "bunny/socket"
13
+
14
+ module Bunny
15
+ # @private
16
+ class Transport
17
+
18
+ #
19
+ # API
20
+ #
21
+
22
+ # Default TCP connection timeout
23
+ DEFAULT_CONNECTION_TIMEOUT = 30.0
24
+
25
+ DEFAULT_READ_TIMEOUT = 30.0
26
+ DEFAULT_WRITE_TIMEOUT = 30.0
27
+
28
+ attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
29
+ attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
30
+
31
+ def read_timeout=(v)
32
+ @read_timeout = v
33
+ @read_timeout = nil if @read_timeout == 0
34
+ end
35
+
36
+ def initialize(session, host, port, opts)
37
+ @session = session
38
+ @session_error_handler = opts[:session_error_handler]
39
+ @host = host
40
+ @port = port
41
+ @opts = opts
42
+
43
+ @logger = session.logger
44
+ @tls_enabled = tls_enabled?(opts)
45
+
46
+ @read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
47
+ @read_timeout = nil if @read_timeout == 0
48
+
49
+ @write_timeout = opts[:socket_timeout] # Backwards compatability
50
+
51
+ @write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
52
+ @write_timeout = nil if @write_timeout == 0
53
+
54
+ @connect_timeout = self.timeout_from(opts)
55
+ @connect_timeout = nil if @connect_timeout == 0
56
+ @disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
57
+
58
+ @writes_mutex = @session.mutex_impl.new
59
+
60
+ @socket = nil
61
+
62
+ prepare_tls_context(opts) if @tls_enabled
63
+ end
64
+
65
+ def hostname
66
+ @host
67
+ end
68
+
69
+ def local_address
70
+ @socket.local_address
71
+ end
72
+
73
+ def uses_tls?
74
+ @tls_enabled
75
+ end
76
+ alias tls? uses_tls?
77
+
78
+ def uses_ssl?
79
+ @tls_enabled
80
+ end
81
+ alias ssl? uses_ssl?
82
+
83
+
84
+ def connect
85
+ if uses_tls?
86
+ begin
87
+ @socket.connect
88
+ rescue OpenSSL::SSL::SSLError => e
89
+ @logger.error { "TLS connection failed: #{e.message}" }
90
+ raise e
91
+ end
92
+
93
+ log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert)
94
+ log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain)
95
+
96
+ begin
97
+ @socket.post_connection_check(host) if @verify_peer
98
+ rescue OpenSSL::SSL::SSLError => e
99
+ @logger.error do
100
+ msg = "Peer verification of target server failed: #{e.message}. "
101
+ msg += "Target hostname: #{hostname}, see peer certificate chain details below."
102
+ msg
103
+ end
104
+ log_peer_certificate_info(Logger::ERROR, @socket.peer_cert)
105
+ log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain)
106
+
107
+ raise e
108
+ end
109
+
110
+ @status = :connected
111
+
112
+ @socket
113
+ else
114
+ # no-op
115
+ end
116
+ end
117
+
118
+ def connected?
119
+ :connected == @status && open?
120
+ end
121
+
122
+ def configure_socket(&block)
123
+ block.call(@socket) if @socket
124
+ end
125
+
126
+ def configure_tls_context(&block)
127
+ block.call(@tls_context) if @tls_context
128
+ end
129
+
130
+ if defined?(JRUBY_VERSION)
131
+ # Writes data to the socket.
132
+ def write(data)
133
+ return write_without_timeout(data) unless @write_timeout
134
+
135
+ begin
136
+ if open?
137
+ @writes_mutex.synchronize do
138
+ @socket.write(data)
139
+ end
140
+ end
141
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
142
+ @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
143
+ close
144
+ @status = :not_connected
145
+
146
+ if @session.automatically_recover?
147
+ @session.handle_network_failure(e)
148
+ else
149
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
150
+ end
151
+ end
152
+ end
153
+ else
154
+ # Writes data to the socket. If read/write timeout was specified the operation will return after that
155
+ # amount of time has elapsed waiting for the socket.
156
+ def write(data)
157
+ return write_without_timeout(data) unless @write_timeout
158
+
159
+ begin
160
+ if open?
161
+ @writes_mutex.synchronize do
162
+ @socket.write_nonblock_fully(data, @write_timeout)
163
+ end
164
+ end
165
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
166
+ @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
167
+ close
168
+ @status = :not_connected
169
+
170
+ if @session.automatically_recover?
171
+ @session.handle_network_failure(e)
172
+ else
173
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ # Writes data to the socket without timeout checks
180
+ def write_without_timeout(data, raise_exceptions = false)
181
+ begin
182
+ @writes_mutex.synchronize { @socket.write(data) }
183
+ @socket.flush
184
+ rescue SystemCallError, Bunny::ConnectionError, IOError => e
185
+ close
186
+ raise e if raise_exceptions
187
+
188
+ if @session.automatically_recover?
189
+ @session.handle_network_failure(e)
190
+ else
191
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
192
+ end
193
+ end
194
+ end
195
+
196
+ # Sends frame to the peer.
197
+ #
198
+ # @raise [ConnectionClosedError]
199
+ # @private
200
+ def send_frame(frame)
201
+ if closed?
202
+ @session.handle_network_failure(ConnectionClosedError.new(frame))
203
+ else
204
+ write(frame.encode)
205
+ end
206
+ end
207
+
208
+ # Sends frame to the peer without timeout control.
209
+ #
210
+ # @raise [ConnectionClosedError]
211
+ # @private
212
+ def send_frame_without_timeout(frame)
213
+ if closed?
214
+ @session.handle_network_failure(ConnectionClosedError.new(frame))
215
+ else
216
+ write_without_timeout(frame.encode)
217
+ end
218
+ end
219
+
220
+
221
+ def close(reason = nil)
222
+ @socket.close if open?
223
+ end
224
+
225
+ def open?
226
+ @socket && !@socket.closed?
227
+ end
228
+
229
+ def closed?
230
+ !open?
231
+ end
232
+
233
+ def flush
234
+ @socket.flush if @socket
235
+ end
236
+
237
+ def read_fully(count)
238
+ begin
239
+ @socket.read_fully(count, @read_timeout)
240
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
241
+ @logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
242
+ close
243
+ @status = :not_connected
244
+
245
+ if @session.automatically_recover?
246
+ raise
247
+ else
248
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
249
+ end
250
+ end
251
+ end
252
+
253
+ def read_ready?(timeout = nil)
254
+ io = IO.select([@socket].compact, nil, nil, timeout)
255
+ io && io[0].include?(@socket)
256
+ end
257
+
258
+ # Exposed primarily for Bunny::Channel
259
+ # @private
260
+ def read_next_frame(opts = {})
261
+ header = read_fully(7)
262
+ type, channel, size = AMQ::Protocol::Frame.decode_header(header)
263
+ payload = if size > 0
264
+ read_fully(size)
265
+ else
266
+ ''
267
+ end
268
+ frame_end = read_fully(1)
269
+
270
+ # 1) the size is miscalculated
271
+ if payload.bytesize != size
272
+ raise BadLengthError.new(size, payload.bytesize)
273
+ end
274
+
275
+ # 2) the size is OK, but the string doesn't end with FINAL_OCTET
276
+ raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
277
+ AMQ::Protocol::Frame.new(type, payload, channel)
278
+ end
279
+
280
+
281
+ def self.reacheable?(host, port, timeout)
282
+ begin
283
+ s = Bunny::SocketImpl.open(host, port,
284
+ :connect_timeout => timeout)
285
+
286
+ true
287
+ rescue SocketError, Timeout::Error => _e
288
+ false
289
+ ensure
290
+ s.close if s
291
+ end
292
+ end
293
+
294
+ def self.ping!(host, port, timeout)
295
+ raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reacheable?(host, port, timeout)
296
+ end
297
+
298
+ def initialize_socket
299
+ begin
300
+ @socket = Bunny::SocketImpl.open(@host, @port,
301
+ :keepalive => @opts[:keepalive],
302
+ :connect_timeout => @connect_timeout)
303
+ rescue StandardError, ClientTimeout => e
304
+ @status = :not_connected
305
+ raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
306
+ end
307
+
308
+ @socket
309
+ end
310
+
311
+ def maybe_initialize_socket
312
+ initialize_socket if !@socket || closed?
313
+ end
314
+
315
+ def post_initialize_socket
316
+ @socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl)
317
+ wrap_in_tls_socket(@socket)
318
+ else
319
+ @socket
320
+ end
321
+ end
322
+
323
+ protected
324
+
325
+ def tls_enabled?(opts)
326
+ return !!opts[:tls] unless opts[:tls].nil?
327
+ return !!opts[:ssl] unless opts[:ssl].nil?
328
+ (opts[:port] == AMQ::Protocol::TLS_PORT) || false
329
+ end
330
+
331
+ def tls_ca_certificates_paths_from(opts)
332
+ Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates])
333
+ end
334
+
335
+ def tls_certificate_path_from(opts)
336
+ opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
337
+ end
338
+
339
+ def tls_key_path_from(opts)
340
+ opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
341
+ end
342
+
343
+ def tls_certificate_from(opts)
344
+ begin
345
+ read_client_certificate!
346
+ rescue MissingTLSCertificateFile => _e
347
+ inline_client_certificate_from(opts)
348
+ end
349
+ end
350
+
351
+ def tls_key_from(opts)
352
+ begin
353
+ read_client_key!
354
+ rescue MissingTLSKeyFile => _e
355
+ inline_client_key_from(opts)
356
+ end
357
+ end
358
+
359
+ def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate")
360
+ exts = peer_cert.extensions.map { |x| x.value }
361
+ # Subject Alternative Names
362
+ sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") }
363
+
364
+ msg = "#{prefix} subject: #{peer_cert.subject}, "
365
+ msg += "subject alternative names: #{sans.join(', ')}, "
366
+ msg += "issuer: #{peer_cert.issuer}, "
367
+ msg += "not valid after: #{peer_cert.not_after}, "
368
+ msg += "X.509 usage extensions: #{exts.join(', ')}"
369
+
370
+ msg
371
+ end
372
+
373
+ def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate")
374
+ @logger.add(severity) { peer_certificate_info(peer_cert, prefix) }
375
+ end
376
+
377
+ def log_peer_certificate_chain_info(severity, chain)
378
+ chain.each do |cert|
379
+ self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry")
380
+ end
381
+ end
382
+
383
+ def inline_client_certificate_from(opts)
384
+ opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
385
+ end
386
+
387
+ def inline_client_key_from(opts)
388
+ opts[:tls_key] || opts[:ssl_key_string]
389
+ end
390
+
391
+ def prepare_tls_context(opts)
392
+ if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?)
393
+ opts[:verify_peer] = true
394
+ end
395
+
396
+ # client cert/key paths
397
+ @tls_certificate_path = tls_certificate_path_from(opts)
398
+ @tls_key_path = tls_key_path_from(opts)
399
+ # client cert/key
400
+ @tls_certificate = tls_certificate_from(opts)
401
+ @tls_key = tls_key_from(opts)
402
+ @tls_certificate_store = opts[:tls_certificate_store]
403
+
404
+ @verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify])
405
+
406
+ @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
407
+ end
408
+
409
+ def as_boolean(val)
410
+ case val
411
+ when true then true
412
+ when false then false
413
+ when "true" then true
414
+ when "false" then false
415
+ else
416
+ !!val
417
+ end
418
+ end
419
+
420
+ def wrap_in_tls_socket(socket)
421
+ raise ArgumentError, "cannot wrap nil into TLS socket, @tls_context is nil. This is a Bunny bug." unless socket
422
+ raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context
423
+
424
+ s = Bunny::SSLSocketImpl.new(socket, @tls_context)
425
+
426
+ # always set the SNI server name if possible since RFC 3546 and RFC 6066 both state
427
+ # that TLS clients supporting the extensions can talk to TLS servers that do not
428
+ s.hostname = @host if s.respond_to?(:hostname)
429
+
430
+ s.sync_close = true
431
+ s
432
+ end
433
+
434
+ def check_local_certificate_path!(s)
435
+ raise MissingTLSCertificateFile, "cannot read client TLS certificate from #{s}" unless File.file?(s) && File.readable?(s)
436
+ end
437
+
438
+ def check_local_key_path!(s)
439
+ raise MissingTLSKeyFile, "cannot read client TLS private key from #{s}" unless File.file?(s) && File.readable?(s)
440
+ end
441
+
442
+ def read_client_certificate!
443
+ if @tls_certificate_path
444
+ check_local_certificate_path!(@tls_certificate_path)
445
+ @tls_certificate = File.read(@tls_certificate_path)
446
+ end
447
+ end
448
+
449
+ def read_client_key!
450
+ if @tls_key_path
451
+ check_local_key_path!(@tls_key_path)
452
+ @tls_key = File.read(@tls_key_path)
453
+ end
454
+ end
455
+
456
+ def initialize_tls_context(ctx, opts = {})
457
+ ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate
458
+ ctx.key = OpenSSL::PKey.read(@tls_key) if @tls_key
459
+ ctx.cert_store = if @tls_certificate_store
460
+ @tls_certificate_store
461
+ else
462
+ # this ivar exists so that this value can be exposed in the API
463
+ @tls_ca_certificates = tls_ca_certificates_paths_from(opts)
464
+ initialize_tls_certificate_store(@tls_ca_certificates)
465
+ end
466
+ should_silence_warnings = opts.fetch(:tls_silence_warnings, false)
467
+
468
+ if !@tls_certificate && !should_silence_warnings
469
+ @logger.warn <<-MSG
470
+ Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer
471
+ certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html
472
+ MSG
473
+ end
474
+ if @tls_certificate && !@tls_key
475
+ @logger.warn "Using TLS but no client private key is provided!"
476
+ end
477
+
478
+ verify_mode = if @verify_peer
479
+ OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
480
+ else
481
+ OpenSSL::SSL::VERIFY_NONE
482
+ end
483
+ @logger.debug { "Will use peer verification mode #{verify_mode}" }
484
+ ctx.verify_mode = verify_mode
485
+
486
+ if !@verify_peer && !should_silence_warnings
487
+ @logger.warn <<-MSG
488
+ Using TLS but peer hostname verification is disabled. This is convenient for local development
489
+ but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html
490
+ MSG
491
+ end
492
+
493
+ ssl_version = opts[:tls_protocol] || opts[:ssl_version] || :TLSv1_2
494
+ ctx.ssl_version = ssl_version if ssl_version
495
+
496
+ ctx
497
+ end
498
+
499
+ def initialize_tls_certificate_store(certs)
500
+ cert_files = []
501
+ cert_inlines = []
502
+ certs.each do |cert|
503
+ # if it starts with / or C:/ then it's a file path that may or may not
504
+ # exist (e.g. a default OpenSSL path). MK.
505
+ if File.readable?(cert) || cert =~ /\A([a-z]:?)?\//i
506
+ cert_files.push(cert)
507
+ else
508
+ cert_inlines.push(cert)
509
+ end
510
+ end
511
+ @logger.debug { "Using CA certificates at #{cert_files.join(', ')}" }
512
+ @logger.debug { "Using #{cert_inlines.count} inline CA certificates" }
513
+ OpenSSL::X509::Store.new.tap do |store|
514
+ store.set_default_paths
515
+ cert_files.select { |path| File.readable?(path) }.
516
+ each { |path| store.add_file(path) }
517
+ cert_inlines.
518
+ each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) }
519
+ end
520
+ end
521
+
522
+ def timeout_from(options)
523
+ options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT
524
+ end
525
+ end
526
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ module Bunny
4
+ # @return [String] Version of the library
5
+ VERSION = "2.19.1"
6
+ end
@@ -0,0 +1,28 @@
1
+ module Bunny
2
+ # Wraps a delivery tag (which is an integer) so that {Bunny::Channel} could
3
+ # detect stale tags after connection recovery.
4
+ #
5
+ # @private
6
+ class VersionedDeliveryTag
7
+ attr_reader :tag
8
+ attr_reader :version
9
+
10
+ def initialize(tag, version)
11
+ raise ArgumentError.new("tag cannot be nil") unless tag
12
+ raise ArgumentError.new("version cannot be nil") unless version
13
+
14
+ @tag = tag.to_i
15
+ @version = version.to_i
16
+ end
17
+
18
+ def to_i
19
+ @tag
20
+ end
21
+
22
+ def stale?(version)
23
+ raise ArgumentError.new("version cannot be nil") unless version
24
+
25
+ @version < version.to_i
26
+ end
27
+ end
28
+ end