amq-protocol 2.3.3 → 2.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +85 -2
  3. data/LICENSE +1 -1
  4. data/README.md +2 -7
  5. data/lib/amq/bit_set.rb +2 -1
  6. data/lib/amq/endianness.rb +2 -0
  7. data/lib/amq/int_allocator.rb +3 -3
  8. data/lib/amq/pack.rb +33 -42
  9. data/lib/amq/protocol/channel_close.rb +24 -0
  10. data/lib/amq/protocol/client.rb +31 -40
  11. data/lib/amq/protocol/constants.rb +2 -0
  12. data/lib/amq/protocol/exceptions.rb +3 -1
  13. data/lib/amq/protocol/float_32bit.rb +2 -0
  14. data/lib/amq/protocol/frame.rb +16 -10
  15. data/lib/amq/protocol/table.rb +20 -17
  16. data/lib/amq/protocol/table_value_decoder.rb +49 -53
  17. data/lib/amq/protocol/table_value_encoder.rb +2 -1
  18. data/lib/amq/protocol/type_constants.rb +1 -0
  19. data/lib/amq/protocol/version.rb +1 -1
  20. data/lib/amq/protocol.rb +1 -0
  21. data/lib/amq/settings.rb +1 -0
  22. data/lib/amq/uri.rb +30 -17
  23. metadata +5 -45
  24. data/.github/ISSUE_TEMPLATE.md +0 -18
  25. data/.github/workflows/ci.yml +0 -31
  26. data/.gitignore +0 -18
  27. data/.gitmodules +0 -3
  28. data/.rspec +0 -1
  29. data/.travis.yml +0 -17
  30. data/Gemfile +0 -22
  31. data/Rakefile +0 -55
  32. data/amq-protocol.gemspec +0 -27
  33. data/benchmarks/int_allocator.rb +0 -34
  34. data/benchmarks/pure/body_framing_with_256k_payload.rb +0 -28
  35. data/benchmarks/pure/body_framing_with_2k_payload.rb +0 -28
  36. data/codegen/__init__.py +0 -0
  37. data/codegen/amqp_0.9.1_changes.json +0 -1
  38. data/codegen/codegen.py +0 -151
  39. data/codegen/codegen_helpers.py +0 -162
  40. data/codegen/protocol.rb.pytemplate +0 -320
  41. data/generate.rb +0 -24
  42. data/profiling/README.md +0 -9
  43. data/profiling/stackprof/body_framing_with_2k_payload.rb +0 -33
  44. data/spec/amq/bit_set_spec.rb +0 -227
  45. data/spec/amq/int_allocator_spec.rb +0 -113
  46. data/spec/amq/pack_spec.rb +0 -68
  47. data/spec/amq/protocol/basic_spec.rb +0 -325
  48. data/spec/amq/protocol/blank_body_encoding_spec.rb +0 -9
  49. data/spec/amq/protocol/channel_spec.rb +0 -127
  50. data/spec/amq/protocol/confirm_spec.rb +0 -41
  51. data/spec/amq/protocol/connection_spec.rb +0 -146
  52. data/spec/amq/protocol/constants_spec.rb +0 -10
  53. data/spec/amq/protocol/exchange_spec.rb +0 -106
  54. data/spec/amq/protocol/frame_spec.rb +0 -92
  55. data/spec/amq/protocol/method_spec.rb +0 -43
  56. data/spec/amq/protocol/queue_spec.rb +0 -126
  57. data/spec/amq/protocol/table_spec.rb +0 -259
  58. data/spec/amq/protocol/tx_spec.rb +0 -55
  59. data/spec/amq/protocol/value_decoder_spec.rb +0 -86
  60. data/spec/amq/protocol/value_encoder_spec.rb +0 -140
  61. data/spec/amq/protocol_spec.rb +0 -812
  62. data/spec/amq/settings_spec.rb +0 -22
  63. data/spec/amq/uri_parsing_spec.rb +0 -280
  64. data/spec/spec_helper.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49d61c5e0d929d97678b8c9f5b01c147c74f874a70481c7c24de865331ad8278
4
- data.tar.gz: efaeca2672afb7b1cd8eceb809bd8be9fcbfbecceb2e1893187b6fc9dcd2d5ff
3
+ metadata.gz: 7cd695fb0f7f7cc8c9d80109f349fdba92c500b5b8eea4ff6ea2c5e6dfdddd8d
4
+ data.tar.gz: 929be34e6f1bd9c2f84fded2933e9f3775286e19babbabac45ce0ccab1b3c38d
5
5
  SHA512:
6
- metadata.gz: 12444b7175daf2d75f021e91eccb8782ebd11ec60855229bf82fa3e29792ba1ca8c96283723b8b5ffeaaf16ef6586b0b74948f605fb3526bed4146cff4030b0a
7
- data.tar.gz: 7d8c26b0e7a501515c1a130410c66d54ad7811c4e7cee15b02d894a8f46feacdc08f8456672e13b35df1f1ca738223c49721e545829c088079e28f96ebada1d8
6
+ metadata.gz: 990e85acbfc80b8dbfa1be036705afdef1f96fdae2b1c8270ff00c972483925361d9dbb7807309fca6a0db55c3d795f8da34d3255f153ff463cda2d12ceed918
7
+ data.tar.gz: 62f8b49269c978f162a21aced9eb22702ae676e28589da94f66b1560083c66878584a1999fa0611f4be1f205a402539b228f0d165107dc930d7a7112801cb8aa
data/ChangeLog.md CHANGED
@@ -1,14 +1,97 @@
1
+ ## Changes between 2.8.0 and 2.9.0 (unreleased)
2
+
3
+ No changes yet.
4
+
5
+ ## Changes between 2.7.0 and 2.8.0 (Apr 25, 2026)
6
+
7
+ ### Performance Improvements in Frame Decoding and Encoding
8
+
9
+ Replacing `x == nil` with `x.nil?` in the frame layer hot path yields a
10
+ consistent **+15–18% throughput improvement** in `Frame.decode_header`
11
+ (called on every received frame) and **+12–14%** in `HeartbeatFrame.encode`,
12
+ across Ruby 3.3, 3.4, and 4.0.
13
+
14
+ See benchmarks/BENCHMARKS.md for instructions on how to reproduce these numbers on your machine.
15
+
16
+ Contributed by @eglitobias.
17
+
18
+ GitHub issue: [#86](https://github.com/ruby-amqp/amq-protocol/pull/86)
19
+
20
+
21
+ ## Changes between 2.6.0 and 2.7.0 (Mar 31, 2026)
22
+
23
+ ### Channel.Close Predicates Now Return True Boolean
24
+
25
+ And not just truthy values (values that are not `nil` or `false`).
26
+
27
+
28
+ ## Changes between 2.5.1 and 2.6.0 (Mar 30, 2026)
29
+
30
+ ### Channel.Close Predicate Methods
31
+
32
+ `Channel::Close` now provides predicate methods for identifying common
33
+ channel closure reasons by reply code and text:
34
+
35
+ * `#delivery_ack_timeout?`: consumer [delivery acknowledgement timeout](https://www.rabbitmq.com/docs/consumers#acknowledgement-timeout)
36
+ * `#unknown_delivery_tag?`: unknown delivery tag (e.g. [double ack](https://www.rabbitmq.com/docs/channels#error-handling))
37
+ * `#message_too_large?`: message exceeded the configured max size
38
+
39
+
40
+ ## Changes between 2.5.0 and 2.5.1 (Jan 19, 2026)
41
+
42
+ ### Windows Installation Fixes
43
+
44
+ `2.4.0` and `2.5.0` versions unintentionally included files that were
45
+ symlinks, which caused installation on Windows to fail.
46
+
47
+
48
+ ## Changes between 2.4.0 and 2.5.0 (Dec 31, 2025)
49
+
50
+ ### Additional Consumer Hot Path Optimizations
51
+
52
+ A few more decode method optimizations for consumer delivery code paths.
53
+
54
+
55
+ ## Changes between 2.3.4 and 2.4.0 (Dec 30, 2025)
56
+
57
+ ### Performance Improvements
58
+
59
+ Optimized encoding and decoding hot paths:
60
+
61
+ * Built-in `Q>`/`q>` packing/unpacking directives are 6-7x faster than the original implementation (that originally targeted Ruby 1.8.x)
62
+ * Switched to `unpack1` instead of `unpack().first` throughout
63
+ * Use `byteslice` instead of `slice` for binary string operations
64
+ * Use `getbyte` for single byte access (4x faster than alternatives)
65
+ * Adopted the `frozen_string_literal` pragma
66
+
67
+ The improvements on Ruby 3.4 are very meaningful:
68
+
69
+ * `AMQ::Pack.pack_uint64_big_endian`: about 6.6x faster
70
+ * `AMQ::Pack.unpack_uint64_big_endian`: about 7.2x faster
71
+ * `Basic.Deliver.decode`: about 1.7x faster
72
+ * `Basic.Ack/Nack/Reject.encode`: about 2.5x faster
73
+
74
+
75
+ ## Changes between 2.3.3 and 2.3.4 (May 12, 2025)
76
+
77
+ ### (Forward) Compatibility with Ruby 3.5
78
+
79
+ Contributed by @Earlopain.
80
+
81
+ GitHub issue: [#80](https://github.com/ruby-amqp/amq-protocol/pull/80)
82
+
83
+
1
84
  ## Changes between 2.3.2 and 2.3.3 (February 17, 2025)
2
85
 
3
86
  ### Improved Compatibility with Ruby 3.4
4
87
 
5
- Contribiuted by @BenTalagan.
88
+ Contributed by @BenTalagan.
6
89
 
7
90
  GitHub issue: [#79](https://github.com/ruby-amqp/amq-protocol/pull/79)
8
91
 
9
92
  ### Support Binary-Encoded Frozen Strings for Payloads
10
93
 
11
- Contribiuted by @djrodgerspryor.
94
+ Contributed by @djrodgerspryor.
12
95
 
13
96
  GitHub issue: [#78](https://github.com/ruby-amqp/amq-protocol/pull/78)
14
97
 
data/LICENSE CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2010 – 2011 Jakub Šťastný aka Botanicus
2
- Copyright (c) 2011 – 2016 Michael S. Klishin <michael@defprotocol.org>
2
+ Copyright (c) 2011 – 2026 Michael S. Klishin <michael@defprotocol.org>
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining
5
5
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -9,6 +9,8 @@ needs for you, including RabbitMQ extensions to AMQP 0.9.1.
9
9
 
10
10
  ## Supported Ruby Versions
11
11
 
12
+ * amq-protocol `2.3.4` has fixes for Ruby 3.5 compatibility
13
+ * amq-protocol `2.3.3` has fixes for Ruby 3.4 compatibility
12
14
  * amq-protocol `2.3.0` only supports Ruby 2.2+.
13
15
  * amq-protocol `2.0.0` through `2.2.0` and later supports Ruby 2.0+.
14
16
  * amq-protocol `1.9.2` was the last version to support Ruby 1.8 and 1.9.
@@ -45,21 +47,14 @@ To run tests, use
45
47
  amq-protocol is maintained by [Michael Klishin](https://github.com/michaelklishin).
46
48
 
47
49
 
48
- ## CI Status
49
-
50
- [![Build Status](https://secure.travis-ci.org/ruby-amqp/amq-protocol.svg)](https://travis-ci.org/ruby-amqp/amq-protocol)
51
-
52
-
53
50
  ## Issues
54
51
 
55
52
  Please report any issues you may find to our [Issue tracker](http://github.com/ruby-amqp/amq-protocol/issues) on GitHub.
56
53
 
57
-
58
54
  ## Mailing List
59
55
 
60
56
  Any questions you may have should be sent to the [Ruby AMQP mailing list](http://groups.google.com/group/ruby-amqp).
61
57
 
62
-
63
58
  ## License
64
59
 
65
60
  MIT (see LICENSE in the repository root).
data/lib/amq/bit_set.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module AMQ
4
5
  # Very minimalistic, pure Ruby implementation of bit set. Inspired by java.util.BitSet,
@@ -112,7 +113,7 @@ module AMQ
112
113
 
113
114
  def check_range(i)
114
115
  if i < 0 || i >= @nbits
115
- raise IndexError.new("Cannot access bit #{i} from a BitSet with #{@nbits} bits")
116
+ raise IndexError, "Cannot access bit #{i} from a BitSet with #{@nbits} bits"
116
117
  end
117
118
  end # check_range
118
119
  end # BitSet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AMQ
2
4
  module Endianness
3
5
  BIG_ENDIAN = ([1].pack("s") == "\x00\x01")
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "amq/bit_set"
4
5
 
@@ -25,13 +26,12 @@ module AMQ
25
26
  # @param [Integer] hi Upper boundary of the integer range available for allocation
26
27
  # @raise [ArgumentError] if upper boundary is not greater than the lower one
27
28
  def initialize(lo, hi)
28
- raise ArgumentError.new "upper boundary must be greater than the lower one (given: hi = #{hi}, lo = #{lo})" unless hi > lo
29
+ raise ArgumentError, "upper boundary must be greater than the lower one (given: hi = #{hi}, lo = #{lo})" unless hi > lo
29
30
 
30
31
  @hi = hi
31
32
  @lo = lo
32
33
 
33
34
  @number_of_bits = hi - lo
34
- @range = Range.new(1, @number_of_bits)
35
35
  @free_set = BitSet.new(@number_of_bits)
36
36
  end # initialize(hi, lo)
37
37
 
@@ -46,7 +46,7 @@ module AMQ
46
46
 
47
47
  if n = @free_set.next_clear_bit
48
48
 
49
- if n < @hi - 1 then
49
+ if n < @hi - 1
50
50
  @free_set.set(n)
51
51
  n + 1
52
52
  else
data/lib/amq/pack.rb CHANGED
@@ -1,54 +1,45 @@
1
1
  # encoding: binary
2
-
3
- require 'amq/endianness'
2
+ # frozen_string_literal: true
4
3
 
5
4
  module AMQ
6
- # Implements pack to/unpack from 64bit string in network byte order
7
- # compatible with Ruby 1.8+.
5
+ # Implements pack to/unpack from 64bit string in network byte order.
6
+ # Uses native Ruby pack directives with explicit endianness (Ruby 1.9.3+).
8
7
  module Pack
9
- UINT64 = "Q".freeze
10
- UINT16_BE = "n".freeze
11
- INT16 = "c".freeze
12
-
13
- if Endianness.big_endian?
14
- def self.pack_uint64_big_endian(long_long)
15
- [long_long].pack(UINT64)
16
- end
17
-
18
- def self.unpack_uint64_big_endian(data)
19
- data.unpack(UINT64)
20
- end
21
-
22
- def self.pack_int16_big_endian(short)
23
- [long_long].pack(INT16)
24
- end
25
-
26
- def self.unpack_int16_big_endian(data)
27
- data.unpack(INT16)
28
- end
29
- else
30
- def self.pack_uint64_big_endian(long_long)
31
- result = [long_long].pack(UINT64)
32
- result.bytes.to_a.reverse.map(&:chr).join
33
- end
8
+ # Pack format strings - frozen for performance
9
+ UINT64_BE = 'Q>'.freeze # 64-bit unsigned, big-endian
10
+ INT64_BE = 'q>'.freeze # 64-bit signed, big-endian
11
+ INT16_BE = 's>'.freeze # 16-bit signed, big-endian
12
+ UINT16_BE = 'n'.freeze # 16-bit unsigned, big-endian (network order)
13
+
14
+ # Packs a 64-bit unsigned integer to big-endian binary string.
15
+ # @param long_long [Integer] The value to pack
16
+ # @return [String] 8-byte binary string in big-endian order
17
+ def self.pack_uint64_big_endian(long_long)
18
+ [long_long].pack(UINT64_BE)
19
+ end
34
20
 
35
- def self.unpack_uint64_big_endian(data)
36
- data = data.bytes.to_a.reverse.map(&:chr).join
37
- data.unpack(UINT64)
38
- end
21
+ # Unpacks a big-endian binary string to a 64-bit unsigned integer.
22
+ # @param data [String] 8-byte binary string in big-endian order
23
+ # @return [Array<Integer>] Single-element array containing the unpacked value
24
+ def self.unpack_uint64_big_endian(data)
25
+ data.unpack(UINT64_BE)
26
+ end
39
27
 
40
- def self.pack_int16_big_endian(short)
41
- result = [long_long].pack(INT16)
42
- result.bytes.to_a.reverse.map(&:chr).join
43
- end
28
+ # Packs a 16-bit signed integer to big-endian binary string.
29
+ # @param short [Integer] The value to pack
30
+ # @return [String] 2-byte binary string in big-endian order
31
+ def self.pack_int16_big_endian(short)
32
+ [short].pack(INT16_BE)
33
+ end
44
34
 
45
- def self.unpack_int16_big_endian(data)
46
- value = data.bytes.to_a.map(&:chr).join.unpack(UINT16_BE)[0]
47
- [(value & ~(1 << 15)) - (value & (1 << 15))]
48
- end
35
+ # Unpacks a big-endian binary string to a 16-bit signed integer.
36
+ # @param data [String] 2-byte binary string in big-endian order
37
+ # @return [Array<Integer>] Single-element array containing the unpacked value
38
+ def self.unpack_int16_big_endian(data)
39
+ data.unpack(INT16_BE)
49
40
  end
50
41
  end
51
42
 
52
- # Backwards compatibility
43
+ # Backwards compatibility alias
53
44
  Hacks = Pack
54
45
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AMQ
4
+ module Protocol
5
+ class Channel
6
+ class Close
7
+ # @return [Boolean] true if the channel was closed due to a consumer delivery acknowledgement timeout
8
+ def delivery_ack_timeout?
9
+ reply_code == 406 && reply_text.match?(/delivery acknowledgement on channel \d+ timed out/)
10
+ end
11
+
12
+ # @return [Boolean] true if the channel was closed due to an unknown delivery tag (e.g. double ack)
13
+ def unknown_delivery_tag?
14
+ reply_code == 406 && reply_text.match?(/unknown delivery tag/)
15
+ end
16
+
17
+ # @return [Boolean] true if the channel was closed because a message exceeded the configured max size
18
+ def message_too_large?
19
+ reply_code == 406 && reply_text.match?(/larger than configured max size/)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -173,9 +173,7 @@ module AMQ
173
173
  limit = frame_size - 8
174
174
  return [BodyFrame.new(body, channel)] if body.bytesize < limit
175
175
 
176
- # Otherwise String#slice on 1.9 will operate with code points,
177
- # and we need bytes. MK.
178
- body.force_encoding("ASCII-8BIT") if RUBY_VERSION.to_f >= 1.9 && body.encoding != Encoding::BINARY
176
+ body.force_encoding("ASCII-8BIT") if body.encoding != Encoding::BINARY
179
177
 
180
178
  array = Array.new
181
179
  while body && !body.empty?
@@ -1569,30 +1567,31 @@ module AMQ
1569
1567
  0x0004,
1570
1568
  ]
1571
1569
 
1570
+ # Optimized decode_properties using getbyte and unpack1
1572
1571
  def self.decode_properties(data)
1573
1572
  offset, data_length, properties = 0, data.bytesize, {}
1574
1573
 
1575
- compressed_index = data[offset, 2].unpack(PACK_UINT16)[0]
1574
+ compressed_index = data.byteslice(offset, 2).unpack1(PACK_UINT16)
1576
1575
  offset += 2
1577
1576
  while data_length > offset
1578
1577
  DECODE_PROPERTIES_KEYS.each do |key|
1579
1578
  next unless compressed_index >= key
1580
1579
  compressed_index -= key
1581
- name = DECODE_PROPERTIES[key] || raise(RuntimeError.new("No property found for index #{index.inspect}!"))
1580
+ name = DECODE_PROPERTIES[key] || raise(RuntimeError.new("No property found for index #{key.inspect}!"))
1582
1581
  case DECODE_PROPERTIES_TYPE[key]
1583
1582
  when :shortstr
1584
- size = data[offset, 1].unpack(PACK_CHAR)[0]
1583
+ size = data.getbyte(offset)
1585
1584
  offset += 1
1586
- result = data[offset, size]
1585
+ result = data.byteslice(offset, size)
1587
1586
  when :octet
1588
1587
  size = 1
1589
- result = data[offset, size].unpack(PACK_CHAR).first
1588
+ result = data.getbyte(offset)
1590
1589
  when :timestamp
1591
1590
  size = 8
1592
- result = Time.at(data[offset, size].unpack(PACK_UINT64_BE).last)
1591
+ result = Time.at(data.byteslice(offset, 8).unpack1(PACK_UINT64_BE))
1593
1592
  when :table
1594
- size = 4 + data[offset, 4].unpack(PACK_UINT32)[0]
1595
- result = Table.decode(data[offset, size])
1593
+ size = 4 + data.byteslice(offset, 4).unpack1(PACK_UINT32)
1594
+ result = Table.decode(data.byteslice(offset, size))
1596
1595
  end
1597
1596
  properties[name] = result
1598
1597
  offset += size
@@ -1688,13 +1687,11 @@ module AMQ
1688
1687
  @index = 0x003C0015 # 60, 21, 3932181
1689
1688
  @packed_indexes = [60, 21].pack(PACK_UINT16_X2).freeze
1690
1689
 
1690
+ # Optimized decode using getbyte
1691
1691
  # @return
1692
1692
  def self.decode(data)
1693
- offset = offset = 0 # self-assigning offset to eliminate "assigned but unused variable" warning even if offset is not used in this method
1694
- length = data[offset, 1].unpack(PACK_CHAR).first
1695
- offset += 1
1696
- consumer_tag = data[offset, length]
1697
- offset += length
1693
+ length = data.getbyte(0)
1694
+ consumer_tag = data.byteslice(1, length)
1698
1695
  self.new(consumer_tag)
1699
1696
  end
1700
1697
 
@@ -1866,26 +1863,25 @@ module AMQ
1866
1863
  @index = 0x003C003C # 60, 60, 3932220
1867
1864
  @packed_indexes = [60, 60].pack(PACK_UINT16_X2).freeze
1868
1865
 
1866
+ # Optimized decode using getbyte and unpack1 for better performance
1869
1867
  # @return
1870
1868
  def self.decode(data)
1871
- offset = offset = 0 # self-assigning offset to eliminate "assigned but unused variable" warning even if offset is not used in this method
1872
- length = data[offset, 1].unpack(PACK_CHAR).first
1869
+ offset = 0
1870
+ length = data.getbyte(offset)
1873
1871
  offset += 1
1874
- consumer_tag = data[offset, length]
1872
+ consumer_tag = data.byteslice(offset, length)
1875
1873
  offset += length
1876
- delivery_tag = AMQ::Pack.unpack_uint64_big_endian(data[offset, 8]).first
1874
+ delivery_tag = data.byteslice(offset, 8).unpack1(PACK_UINT64_BE)
1877
1875
  offset += 8
1878
- bit_buffer = data[offset, 1].unpack(PACK_CHAR).first
1876
+ redelivered = (data.getbyte(offset) & 1) != 0
1879
1877
  offset += 1
1880
- redelivered = (bit_buffer & (1 << 0)) != 0
1881
- length = data[offset, 1].unpack(PACK_CHAR).first
1878
+ length = data.getbyte(offset)
1882
1879
  offset += 1
1883
- exchange = data[offset, length]
1880
+ exchange = data.byteslice(offset, length)
1884
1881
  offset += length
1885
- length = data[offset, 1].unpack(PACK_CHAR).first
1882
+ length = data.getbyte(offset)
1886
1883
  offset += 1
1887
- routing_key = data[offset, length]
1888
- offset += length
1884
+ routing_key = data.byteslice(offset, length)
1889
1885
  self.new(consumer_tag, delivery_tag, redelivered, exchange, routing_key)
1890
1886
  end
1891
1887
 
@@ -2009,14 +2005,11 @@ module AMQ
2009
2005
  @index = 0x003C0050 # 60, 80, 3932240
2010
2006
  @packed_indexes = [60, 80].pack(PACK_UINT16_X2).freeze
2011
2007
 
2008
+ # Optimized decode using unpack1 and getbyte
2012
2009
  # @return
2013
2010
  def self.decode(data)
2014
- offset = offset = 0 # self-assigning offset to eliminate "assigned but unused variable" warning even if offset is not used in this method
2015
- delivery_tag = AMQ::Pack.unpack_uint64_big_endian(data[offset, 8]).first
2016
- offset += 8
2017
- bit_buffer = data[offset, 1].unpack(PACK_CHAR).first
2018
- offset += 1
2019
- multiple = (bit_buffer & (1 << 0)) != 0
2011
+ delivery_tag = data.byteslice(0, 8).unpack1(PACK_UINT64_BE)
2012
+ multiple = (data.getbyte(8) & 1) != 0
2020
2013
  self.new(delivery_tag, multiple)
2021
2014
  end
2022
2015
 
@@ -2141,15 +2134,13 @@ module AMQ
2141
2134
  @index = 0x003C0078 # 60, 120, 3932280
2142
2135
  @packed_indexes = [60, 120].pack(PACK_UINT16_X2).freeze
2143
2136
 
2137
+ # Optimized decode using unpack1 and getbyte
2144
2138
  # @return
2145
2139
  def self.decode(data)
2146
- offset = offset = 0 # self-assigning offset to eliminate "assigned but unused variable" warning even if offset is not used in this method
2147
- delivery_tag = AMQ::Pack.unpack_uint64_big_endian(data[offset, 8]).first
2148
- offset += 8
2149
- bit_buffer = data[offset, 1].unpack(PACK_CHAR).first
2150
- offset += 1
2151
- multiple = (bit_buffer & (1 << 0)) != 0
2152
- requeue = (bit_buffer & (1 << 1)) != 0
2140
+ delivery_tag = data.byteslice(0, 8).unpack1(PACK_UINT64_BE)
2141
+ bit_buffer = data.getbyte(8)
2142
+ multiple = (bit_buffer & 1) != 0
2143
+ requeue = (bit_buffer & 2) != 0
2153
2144
  self.new(delivery_tag, multiple, requeue)
2154
2145
  end
2155
2146
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AMQ
2
4
  module Protocol
3
5
  TLS_PORT = 5671
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AMQ
2
4
  module Protocol
3
5
  class Error < StandardError
@@ -41,7 +43,7 @@ module AMQ
41
43
 
42
44
  class BadResponseError < Protocol::Error
43
45
  def initialize(argument, expected, actual)
44
- super("Argument #{argument} has to be #{expected.inspect}, was #{data.inspect}")
46
+ super("Argument #{argument} has to be #{expected.inspect}, was #{actual.inspect}")
45
47
  end
46
48
  end
47
49
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AMQ
2
4
  module Protocol
3
5
  # Allows distinguishing between 32-bit and 64-bit floats in Ruby.
@@ -1,4 +1,5 @@
1
1
  # encoding: binary
2
+ # frozen_string_literal: true
2
3
 
3
4
  module AMQ
4
5
  module Protocol
@@ -9,6 +10,9 @@ module AMQ
9
10
  CHANNEL_RANGE = (0..65535).freeze
10
11
  FINAL_OCTET = "\xCE".freeze # 206
11
12
 
13
+ # Pack format for 64-bit unsigned big-endian
14
+ PACK_UINT64_BE = 'Q>'.freeze
15
+
12
16
  def self.encoded_payload(payload)
13
17
  if payload.respond_to?(:force_encoding) && payload.encoding.name != 'BINARY'
14
18
  # Only copy if we have to.
@@ -19,8 +23,8 @@ module AMQ
19
23
 
20
24
  # The channel number is 0 for all frames which are global to the connection and 1-65535 for frames that refer to specific channels.
21
25
  def self.encode_to_array(type, payload, channel)
22
- raise RuntimeError.new("Channel has to be 0 or an integer in range 1..65535 but was #{channel.inspect}") unless CHANNEL_RANGE.include?(channel)
23
- raise RuntimeError.new("Payload can't be nil") if payload.nil?
26
+ raise RuntimeError, "Channel has to be 0 or an integer in range 1..65535 but was #{channel.inspect}" unless CHANNEL_RANGE.include?(channel)
27
+ raise RuntimeError, "Payload can't be nil" if payload.nil?
24
28
  components = []
25
29
  components << [find_type(type), channel, payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
26
30
  components << encoded_payload(payload)
@@ -43,25 +47,27 @@ module AMQ
43
47
  end
44
48
 
45
49
  def self.find_type(type)
46
- type_id = if Symbol === type then TYPES[type] else type end
47
- raise FrameTypeError.new(TYPES_OPTIONS) if type == nil || !TYPES_REVERSE.has_key?(type_id)
50
+ type_id = type.is_a?(Symbol) ? TYPES[type] : type
51
+ raise FrameTypeError, TYPES_OPTIONS if type.nil? || !TYPES_REVERSE.key?(type_id)
48
52
  type_id
49
53
  end
50
54
 
51
55
  def self.decode(*)
52
- raise NotImplementedError.new <<-EOF
56
+ raise NotImplementedError, <<-EOF
53
57
  You are supposed to redefine this method, because it's dependent on used IO adapter.
54
58
 
55
59
  This functionality is part of the https://github.com/ruby-amqp/amq-client library.
56
60
  EOF
57
61
  end
58
62
 
63
+ # Optimized header decode using unpack1 for single values where appropriate
59
64
  def self.decode_header(header)
60
- raise EmptyResponseError if header == nil || header.empty?
65
+ raise EmptyResponseError if header.nil? || header.empty?
61
66
 
67
+ # Use unpack for multiple values - this is the optimal approach
62
68
  type_id, channel, size = header.unpack(PACK_CHAR_UINT16_UINT32)
63
69
  type = TYPES_REVERSE[type_id]
64
- raise FrameTypeError.new(TYPES_OPTIONS) unless type
70
+ raise FrameTypeError, TYPES_OPTIONS unless type
65
71
  [type, channel, size]
66
72
  end
67
73
 
@@ -130,7 +136,7 @@ This functionality is part of the https://github.com/ruby-amqp/amq-client librar
130
136
  end # final?
131
137
 
132
138
  def decode_payload
133
- self.method_class.decode(@payload[4..-1])
139
+ self.method_class.decode(@payload.byteslice(4..-1))
134
140
  end
135
141
  end
136
142
 
@@ -167,8 +173,8 @@ This functionality is part of the https://github.com/ruby-amqp/amq-client librar
167
173
  # the total size of the content body, that is, the sum of the body sizes for the
168
174
  # following content body frames. Zero indicates that there are no content body frames.
169
175
  # So this is NOT related to this very header frame!
170
- @body_size = AMQ::Hacks.unpack_uint64_big_endian(@payload[4..11]).first
171
- @data = @payload[12..-1]
176
+ @body_size = @payload.byteslice(4, 8).unpack1(PACK_UINT64_BE)
177
+ @data = @payload.byteslice(12..-1)
172
178
  @properties = Basic.decode_properties(@data)
173
179
  end
174
180
  end