amq-protocol 2.3.4 → 2.4.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.
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,
@@ -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
 
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
@@ -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.
@@ -130,7 +134,7 @@ This functionality is part of the https://github.com/ruby-amqp/amq-client librar
130
134
  end # final?
131
135
 
132
136
  def decode_payload
133
- self.method_class.decode(@payload[4..-1])
137
+ self.method_class.decode(@payload.byteslice(4..-1))
134
138
  end
135
139
  end
136
140
 
@@ -167,8 +171,8 @@ This functionality is part of the https://github.com/ruby-amqp/amq-client librar
167
171
  # the total size of the content body, that is, the sum of the body sizes for the
168
172
  # following content body frames. Zero indicates that there are no content body frames.
169
173
  # 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]
174
+ @body_size = @payload.byteslice(4, 8).unpack1(PACK_UINT64_BE)
175
+ @data = @payload.byteslice(12..-1)
172
176
  @properties = Basic.decode_properties(@data)
173
177
  end
174
178
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: binary
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "amq/protocol/type_constants"
4
5
  require "amq/protocol/table_value_encoder"
@@ -14,6 +15,8 @@ module AMQ
14
15
 
15
16
  include TypeConstants
16
17
 
18
+ # Pack format string
19
+ PACK_UINT32_BE = 'N'.freeze
17
20
 
18
21
  #
19
22
  # API
@@ -27,16 +30,16 @@ module AMQ
27
30
 
28
31
 
29
32
  def self.encode(table)
30
- buffer = String.new
33
+ buffer = +''
31
34
 
32
35
  table ||= {}
33
36
 
34
37
  table.each do |key, value|
35
38
  key = key.to_s # it can be a symbol as well
36
- buffer << key.bytesize.chr + key
39
+ buffer << key.bytesize.chr << key
37
40
 
38
41
  case value
39
- when Hash then
42
+ when Hash
40
43
  buffer << TYPE_HASH
41
44
  buffer << self.encode(value)
42
45
  else
@@ -44,15 +47,15 @@ module AMQ
44
47
  end
45
48
  end
46
49
 
47
- [buffer.bytesize].pack(PACK_UINT32).force_encoding(buffer.encoding) + buffer
50
+ [buffer.bytesize].pack(PACK_UINT32_BE) << buffer
48
51
  end
49
52
 
50
53
 
51
54
 
52
55
 
53
56
  def self.decode(data)
54
- table = Hash.new
55
- table_length = data.unpack(PACK_UINT32).first
57
+ table = {}
58
+ table_length = data.unpack1(PACK_UINT32_BE)
56
59
 
57
60
  return table if table_length.zero?
58
61
 
@@ -86,19 +89,19 @@ module AMQ
86
89
  when TYPE_BOOLEAN
87
90
  v, offset = TableValueDecoder.decode_boolean(data, offset)
88
91
  v
89
- when TYPE_BYTE then
92
+ when TYPE_BYTE
90
93
  v, offset = TableValueDecoder.decode_byte(data, offset)
91
94
  v
92
- when TYPE_SIGNED_16BIT then
95
+ when TYPE_SIGNED_16BIT
93
96
  v, offset = TableValueDecoder.decode_short(data, offset)
94
97
  v
95
- when TYPE_SIGNED_64BIT then
98
+ when TYPE_SIGNED_64BIT
96
99
  v, offset = TableValueDecoder.decode_long(data, offset)
97
100
  v
98
- when TYPE_32BIT_FLOAT then
101
+ when TYPE_32BIT_FLOAT
99
102
  v, offset = TableValueDecoder.decode_32bit_float(data, offset)
100
103
  v
101
- when TYPE_64BIT_FLOAT then
104
+ when TYPE_64BIT_FLOAT
102
105
  v, offset = TableValueDecoder.decode_64bit_float(data, offset)
103
106
  v
104
107
  when TYPE_VOID
@@ -112,11 +115,11 @@ module AMQ
112
115
  end
113
116
 
114
117
  table
115
- end # self.decode
118
+ end
116
119
 
117
120
 
118
121
  def self.length(data)
119
- data.unpack(PACK_UINT32).first
122
+ data.unpack1(PACK_UINT32_BE)
120
123
  end
121
124
 
122
125
 
@@ -128,17 +131,17 @@ module AMQ
128
131
  end
129
132
 
130
133
  acc
131
- end # self.hash_size(value)
134
+ end
132
135
 
133
136
 
134
137
  def self.decode_table_key(data, offset)
135
- key_length = data.slice(offset, 1).unpack(PACK_CHAR).first
138
+ key_length = data.getbyte(offset)
136
139
  offset += 1
137
- key = data.slice(offset, key_length)
140
+ key = data.byteslice(offset, key_length)
138
141
  offset += key_length
139
142
 
140
143
  [key, offset]
141
- end # self.decode_table_key(data, offset)
144
+ end
142
145
 
143
146
 
144
147
 
@@ -1,6 +1,6 @@
1
1
  # encoding: binary
2
+ # frozen_string_literal: true
2
3
 
3
- require "amq/endianness"
4
4
  require "amq/protocol/type_constants"
5
5
  require "amq/protocol/float_32bit"
6
6
 
@@ -15,15 +15,23 @@ module AMQ
15
15
 
16
16
  include TypeConstants
17
17
 
18
+ # Pack format strings use explicit endianness (available as of Ruby 1.9.3)
19
+ PACK_UINT32_BE = 'N'.freeze
20
+ PACK_INT64_BE = 'q>'.freeze
21
+ PACK_INT16_BE = 's>'.freeze
22
+ PACK_UINT64_BE = 'Q>'.freeze
23
+ PACK_FLOAT32 = 'f'.freeze # single precision float (native endian, matches encoder)
24
+ PACK_FLOAT64 = 'G'.freeze # big-endian double precision float
25
+ PACK_UCHAR_UINT32 = 'CN'.freeze
18
26
 
19
27
  #
20
28
  # API
21
29
  #
22
30
 
23
31
  def self.decode_array(data, initial_offset)
24
- array_length = data.slice(initial_offset, 4).unpack(PACK_UINT32).first
32
+ array_length = data.byteslice(initial_offset, 4).unpack1(PACK_UINT32_BE)
25
33
 
26
- ary = Array.new
34
+ ary = []
27
35
  offset = initial_offset + 4
28
36
 
29
37
  while offset <= (initial_offset + array_length)
@@ -54,25 +62,25 @@ module AMQ
54
62
  when TYPE_BOOLEAN
55
63
  v, offset = decode_boolean(data, offset)
56
64
  v
57
- when TYPE_BYTE then
65
+ when TYPE_BYTE
58
66
  v, offset = decode_byte(data, offset)
59
67
  v
60
- when TYPE_SIGNED_16BIT then
68
+ when TYPE_SIGNED_16BIT
61
69
  v, offset = decode_short(data, offset)
62
70
  v
63
- when TYPE_SIGNED_64BIT then
71
+ when TYPE_SIGNED_64BIT
64
72
  v, offset = decode_long(data, offset)
65
73
  v
66
- when TYPE_32BIT_FLOAT then
74
+ when TYPE_32BIT_FLOAT
67
75
  v, offset = decode_32bit_float(data, offset)
68
76
  v
69
- when TYPE_64BIT_FLOAT then
77
+ when TYPE_64BIT_FLOAT
70
78
  v, offset = decode_64bit_float(data, offset)
71
79
  v
72
80
  when TYPE_VOID
73
81
  nil
74
82
  when TYPE_ARRAY
75
- v, offset = TableValueDecoder.decode_array(data, offset)
83
+ v, offset = decode_array(data, offset)
76
84
  v
77
85
  else
78
86
  raise ArgumentError.new("unsupported type in a table value: #{type.inspect}, do not know how to decode!")
@@ -83,113 +91,101 @@ module AMQ
83
91
 
84
92
 
85
93
  [ary, initial_offset + array_length + 4]
86
- end # self.decode_array(data, initial_offset)
94
+ end
87
95
 
88
96
 
89
97
  def self.decode_string(data, offset)
90
- length = data.slice(offset, 4).unpack(PACK_UINT32).first
98
+ length = data.byteslice(offset, 4).unpack1(PACK_UINT32_BE)
91
99
  offset += 4
92
- v = data.slice(offset, length)
100
+ v = data.byteslice(offset, length)
93
101
  offset += length
94
102
 
95
103
  [v, offset]
96
- end # self.decode_string(data, offset)
104
+ end
97
105
 
98
106
 
99
107
  def self.decode_integer(data, offset)
100
- v = data.slice(offset, 4).unpack(PACK_UINT32).first
108
+ v = data.byteslice(offset, 4).unpack1(PACK_UINT32_BE)
101
109
  offset += 4
102
110
 
103
111
  [v, offset]
104
- end # self.decode_integer(data, offset)
105
-
112
+ end
106
113
 
107
- if AMQ::Endianness.big_endian?
108
- def self.decode_long(data, offset)
109
- v = data.slice(offset, 8).unpack(PACK_INT64)
110
114
 
111
- offset += 8
112
- [v, offset]
113
- end
114
- else
115
- def self.decode_long(data, offset)
116
- slice = data.slice(offset, 8).bytes.to_a.reverse.map(&:chr).join
117
- v = slice.unpack(PACK_INT64).first
118
-
119
- offset += 8
120
- [v, offset]
121
- end
115
+ def self.decode_long(data, offset)
116
+ v = data.byteslice(offset, 8).unpack1(PACK_INT64_BE)
117
+ offset += 8
118
+ [v, offset]
122
119
  end
123
120
 
124
121
 
125
122
  def self.decode_big_decimal(data, offset)
126
- decimals, raw = data.slice(offset, 5).unpack(PACK_UCHAR_UINT32)
123
+ decimals, raw = data.byteslice(offset, 5).unpack(PACK_UCHAR_UINT32)
127
124
  offset += 5
128
125
  v = BigDecimal(raw.to_s) * (BigDecimal(TEN) ** -decimals)
129
126
 
130
127
  [v, offset]
131
- end # self.decode_big_decimal(data, offset)
128
+ end
132
129
 
133
130
 
134
131
  def self.decode_time(data, offset)
135
- timestamp = data.slice(offset, 8).unpack(PACK_UINT64_BE).last
132
+ timestamp = data.byteslice(offset, 8).unpack1(PACK_UINT64_BE)
136
133
  v = Time.at(timestamp)
137
134
  offset += 8
138
135
 
139
136
  [v, offset]
140
- end # self.decode_time(data, offset)
137
+ end
141
138
 
142
139
 
143
140
  def self.decode_boolean(data, offset)
144
- integer = data.slice(offset, 2).unpack(PACK_CHAR).first # 0 or 1
141
+ byte = data.getbyte(offset)
145
142
  offset += 1
146
- [(integer == 1), offset]
147
- end # self.decode_boolean(data, offset)
143
+ [(byte == 1), offset]
144
+ end
148
145
 
149
146
 
150
147
  def self.decode_32bit_float(data, offset)
151
- v = data.slice(offset, 4).unpack(PACK_32BIT_FLOAT).first
148
+ v = data.byteslice(offset, 4).unpack1(PACK_FLOAT32)
152
149
  offset += 4
153
150
 
154
151
  [v, offset]
155
- end # self.decode_32bit_float(data, offset)
152
+ end
156
153
 
157
154
 
158
155
  def self.decode_64bit_float(data, offset)
159
- v = data.slice(offset, 8).unpack(PACK_64BIT_FLOAT).first
156
+ v = data.byteslice(offset, 8).unpack1(PACK_FLOAT64)
160
157
  offset += 8
161
158
 
162
159
  [v, offset]
163
- end # self.decode_64bit_float(data, offset)
160
+ end
164
161
 
165
162
 
166
163
  def self.decode_value_type(data, offset)
167
- [data.slice(offset, 1), offset + 1]
168
- end # self.decode_value_type(data, offset)
169
-
164
+ [data.byteslice(offset, 1), offset + 1]
165
+ end
170
166
 
171
167
 
172
168
  def self.decode_hash(data, offset)
173
- length = data.slice(offset, 4).unpack(PACK_UINT32).first
174
- v = Table.decode(data.slice(offset, length + 4))
169
+ length = data.byteslice(offset, 4).unpack1(PACK_UINT32_BE)
170
+ v = Table.decode(data.byteslice(offset, length + 4))
175
171
  offset += 4 + length
176
172
 
177
173
  [v, offset]
178
- end # self.decode_hash(data, offset)
174
+ end
179
175
 
180
176
 
181
177
  # Decodes/Converts a byte value from the data at the provided offset.
182
178
  #
183
- # @param [Array] data - A big-endian ordered array of bytes.
184
- # @param [Fixnum] offset - The offset which bytes the byte is consumed.
185
- # @return [Array] - The Fixnum value and new offset pair.
179
+ # @param [String] data - Binary data string
180
+ # @param [Integer] offset - The offset from which to read the byte
181
+ # @return [Array] - The Integer value and new offset pair
186
182
  def self.decode_byte(data, offset)
187
- [data.slice(offset, 1).unpack(PACK_CHAR).first, offset += 1]
183
+ [data.getbyte(offset), offset + 1]
188
184
  end
189
185
 
190
186
 
191
187
  def self.decode_short(data, offset)
192
- v = AMQ::Hacks.unpack_int16_big_endian(data.slice(offset, 2)).first
188
+ v = data.byteslice(offset, 2).unpack1(PACK_INT16_BE)
193
189
  offset += 2
194
190
  [v, offset]
195
191
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: binary
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "amq/protocol/type_constants"
4
5
  require "date"
@@ -1,4 +1,5 @@
1
1
  # encoding: binary
2
+ # frozen_string_literal: true
2
3
 
3
4
  module AMQ
4
5
  module Protocol
@@ -1,5 +1,5 @@
1
1
  module AMQ
2
2
  module Protocol
3
- VERSION = "2.3.4"
3
+ VERSION = "2.4.0"
4
4
  end # Protocol
5
5
  end # AMQ
data/lib/amq/settings.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "amq/protocol/client"
4
5
  require "amq/uri"
@@ -223,5 +223,27 @@ RSpec.describe AMQ::BitSet do
223
223
  subject.unset(254)
224
224
  expect(subject.get(254)).to be_falsey
225
225
  end # it
226
+
227
+ it "returns -1 when all bits are set" do
228
+ bs = described_class.new(64)
229
+ 0.upto(63) { |i| bs.set(i) }
230
+ expect(bs.next_clear_bit).to eq(-1)
231
+ end
232
+ end # describe
233
+
234
+ describe "#to_s" do
235
+ it "returns a string representation of the bit set" do
236
+ bs = described_class.new(64)
237
+ result = bs.to_s
238
+ expect(result).to be_a(String)
239
+ expect(result).to include(":")
240
+ end
241
+
242
+ it "shows set bits" do
243
+ bs = described_class.new(64)
244
+ bs.set(0)
245
+ result = bs.to_s
246
+ expect(result).to end_with("1:")
247
+ end
226
248
  end # describe
227
249
  end
@@ -0,0 +1,23 @@
1
+ # encoding: binary
2
+
3
+ require "amq/endianness"
4
+
5
+ RSpec.describe AMQ::Endianness do
6
+ describe ".big_endian?" do
7
+ it "returns a boolean" do
8
+ expect([true, false]).to include(described_class.big_endian?)
9
+ end
10
+ end
11
+
12
+ describe ".little_endian?" do
13
+ it "returns the opposite of big_endian?" do
14
+ expect(described_class.little_endian?).to eq(!described_class.big_endian?)
15
+ end
16
+ end
17
+
18
+ describe "BIG_ENDIAN constant" do
19
+ it "is a boolean" do
20
+ expect([true, false]).to include(AMQ::Endianness::BIG_ENDIAN)
21
+ end
22
+ end
23
+ end
@@ -11,13 +11,17 @@ RSpec.describe AMQ::IntAllocator do
11
11
  end
12
12
 
13
13
 
14
- # ...
15
-
16
-
17
14
  #
18
15
  # Examples
19
16
  #
20
17
 
18
+ describe "#initialize" do
19
+ it "raises ArgumentError when hi <= lo" do
20
+ expect { described_class.new(5, 5) }.to raise_error(ArgumentError)
21
+ expect { described_class.new(10, 5) }.to raise_error(ArgumentError)
22
+ end
23
+ end
24
+
21
25
  describe "#number_of_bits" do
22
26
  it "returns number of bits available for allocation" do
23
27
  expect(subject.number_of_bits).to eq(4)
@@ -110,4 +114,23 @@ RSpec.describe AMQ::IntAllocator do
110
114
  end
111
115
  end
112
116
  end
117
+
118
+ describe "#release" do
119
+ it "is an alias for #free" do
120
+ subject.allocate
121
+ expect(subject.allocated?(1)).to be_truthy
122
+ subject.release(1)
123
+ expect(subject.allocated?(1)).to be_falsey
124
+ end
125
+ end
126
+
127
+ describe "#reset" do
128
+ it "releases all allocations" do
129
+ 4.times { subject.allocate }
130
+ expect(subject.allocate).to eq(-1)
131
+
132
+ subject.reset
133
+ expect(subject.allocate).to eq(1)
134
+ end
135
+ end
113
136
  end