amq-protocol 2.3.4 → 2.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 +4 -4
- data/AGENTS.md +23 -0
- data/CLAUDE.md +1 -0
- data/ChangeLog.md +30 -3
- data/GEMINI.md +1 -0
- data/Gemfile +5 -0
- data/README.md +2 -7
- data/benchmarks/frame_encoding.rb +75 -0
- data/benchmarks/method_encoding.rb +198 -0
- data/benchmarks/pack_unpack.rb +158 -0
- data/benchmarks/run_all.rb +64 -0
- data/benchmarks/table_encoding.rb +110 -0
- data/lib/amq/bit_set.rb +1 -0
- data/lib/amq/endianness.rb +2 -0
- data/lib/amq/int_allocator.rb +1 -0
- data/lib/amq/pack.rb +33 -42
- data/lib/amq/protocol/client.rb +30 -37
- data/lib/amq/protocol/constants.rb +2 -0
- data/lib/amq/protocol/exceptions.rb +3 -1
- data/lib/amq/protocol/float_32bit.rb +2 -0
- data/lib/amq/protocol/frame.rb +9 -3
- data/lib/amq/protocol/table.rb +20 -17
- data/lib/amq/protocol/table_value_decoder.rb +48 -52
- data/lib/amq/protocol/table_value_encoder.rb +1 -0
- data/lib/amq/protocol/type_constants.rb +1 -0
- data/lib/amq/protocol/version.rb +1 -1
- data/lib/amq/settings.rb +1 -0
- data/spec/amq/bit_set_spec.rb +22 -0
- data/spec/amq/endianness_spec.rb +23 -0
- data/spec/amq/int_allocator_spec.rb +26 -3
- data/spec/amq/pack_spec.rb +14 -24
- data/spec/amq/protocol/exceptions_spec.rb +70 -0
- data/spec/amq/protocol/float_32bit_spec.rb +27 -0
- data/spec/amq/protocol/frame_spec.rb +64 -0
- data/spec/amq/protocol/table_spec.rb +32 -0
- data/spec/amq/protocol/value_decoder_spec.rb +97 -0
- data/spec/amq/protocol/value_encoder_spec.rb +21 -0
- data/spec/amq/settings_spec.rb +37 -1
- data/spec/amq/uri_parsing_spec.rb +7 -0
- metadata +14 -3
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
6
|
+
|
|
7
|
+
require "amq/protocol/client"
|
|
8
|
+
require "benchmark/ips"
|
|
9
|
+
|
|
10
|
+
puts
|
|
11
|
+
puts "-" * 80
|
|
12
|
+
puts "Table Encoding/Decoding Benchmarks on #{RUBY_DESCRIPTION}"
|
|
13
|
+
puts "-" * 80
|
|
14
|
+
|
|
15
|
+
# Test data - various table sizes and types
|
|
16
|
+
EMPTY_TABLE = {}
|
|
17
|
+
|
|
18
|
+
SIMPLE_TABLE = {
|
|
19
|
+
"key1" => "value1",
|
|
20
|
+
"key2" => 42,
|
|
21
|
+
"key3" => true
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
TYPICAL_HEADERS = {
|
|
25
|
+
"content_type" => "application/json",
|
|
26
|
+
"content_encoding" => "utf-8",
|
|
27
|
+
"x-custom-header" => "some-value",
|
|
28
|
+
"x-retry-count" => 3,
|
|
29
|
+
"x-timestamp" => Time.now.to_i
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
COMPLEX_TABLE = {
|
|
33
|
+
"string" => "hello world",
|
|
34
|
+
"integer" => 123456789,
|
|
35
|
+
"float" => 3.14159,
|
|
36
|
+
"boolean_true" => true,
|
|
37
|
+
"boolean_false" => false,
|
|
38
|
+
"nested" => {
|
|
39
|
+
"inner_key" => "inner_value",
|
|
40
|
+
"inner_number" => 999
|
|
41
|
+
},
|
|
42
|
+
"array" => [1, 2, 3, "four", true]
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
LARGE_TABLE = (1..50).to_h { |i| ["key_#{i}", "value_#{i}"] }.freeze
|
|
46
|
+
|
|
47
|
+
# Pre-encode tables for decode benchmarks
|
|
48
|
+
ENCODED_EMPTY = AMQ::Protocol::Table.encode(EMPTY_TABLE)
|
|
49
|
+
ENCODED_SIMPLE = AMQ::Protocol::Table.encode(SIMPLE_TABLE)
|
|
50
|
+
ENCODED_TYPICAL = AMQ::Protocol::Table.encode(TYPICAL_HEADERS)
|
|
51
|
+
ENCODED_COMPLEX = AMQ::Protocol::Table.encode(COMPLEX_TABLE)
|
|
52
|
+
ENCODED_LARGE = AMQ::Protocol::Table.encode(LARGE_TABLE)
|
|
53
|
+
|
|
54
|
+
puts "Table sizes (bytes): empty=#{ENCODED_EMPTY.bytesize}, simple=#{ENCODED_SIMPLE.bytesize}, typical=#{ENCODED_TYPICAL.bytesize}, complex=#{ENCODED_COMPLEX.bytesize}, large=#{ENCODED_LARGE.bytesize}"
|
|
55
|
+
puts
|
|
56
|
+
|
|
57
|
+
puts "=== Table Encoding ==="
|
|
58
|
+
Benchmark.ips do |x|
|
|
59
|
+
x.config(time: 5, warmup: 2)
|
|
60
|
+
|
|
61
|
+
x.report("encode empty") do
|
|
62
|
+
AMQ::Protocol::Table.encode(EMPTY_TABLE)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
x.report("encode simple (3 keys)") do
|
|
66
|
+
AMQ::Protocol::Table.encode(SIMPLE_TABLE)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
x.report("encode typical headers (5 keys)") do
|
|
70
|
+
AMQ::Protocol::Table.encode(TYPICAL_HEADERS)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
x.report("encode complex (nested/array)") do
|
|
74
|
+
AMQ::Protocol::Table.encode(COMPLEX_TABLE)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
x.report("encode large (50 keys)") do
|
|
78
|
+
AMQ::Protocol::Table.encode(LARGE_TABLE)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
x.compare!
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
puts
|
|
85
|
+
puts "=== Table Decoding ==="
|
|
86
|
+
Benchmark.ips do |x|
|
|
87
|
+
x.config(time: 5, warmup: 2)
|
|
88
|
+
|
|
89
|
+
x.report("decode empty") do
|
|
90
|
+
AMQ::Protocol::Table.decode(ENCODED_EMPTY)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
x.report("decode simple (3 keys)") do
|
|
94
|
+
AMQ::Protocol::Table.decode(ENCODED_SIMPLE)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
x.report("decode typical headers (5 keys)") do
|
|
98
|
+
AMQ::Protocol::Table.decode(ENCODED_TYPICAL)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
x.report("decode complex (nested/array)") do
|
|
102
|
+
AMQ::Protocol::Table.decode(ENCODED_COMPLEX)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
x.report("decode large (50 keys)") do
|
|
106
|
+
AMQ::Protocol::Table.decode(ENCODED_LARGE)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
x.compare!
|
|
110
|
+
end
|
data/lib/amq/bit_set.rb
CHANGED
data/lib/amq/endianness.rb
CHANGED
data/lib/amq/int_allocator.rb
CHANGED
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
|
-
#
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
data/lib/amq/protocol/client.rb
CHANGED
|
@@ -1569,30 +1569,31 @@ module AMQ
|
|
|
1569
1569
|
0x0004,
|
|
1570
1570
|
]
|
|
1571
1571
|
|
|
1572
|
+
# Optimized decode_properties using getbyte and unpack1
|
|
1572
1573
|
def self.decode_properties(data)
|
|
1573
1574
|
offset, data_length, properties = 0, data.bytesize, {}
|
|
1574
1575
|
|
|
1575
|
-
compressed_index = data
|
|
1576
|
+
compressed_index = data.byteslice(offset, 2).unpack1(PACK_UINT16)
|
|
1576
1577
|
offset += 2
|
|
1577
1578
|
while data_length > offset
|
|
1578
1579
|
DECODE_PROPERTIES_KEYS.each do |key|
|
|
1579
1580
|
next unless compressed_index >= key
|
|
1580
1581
|
compressed_index -= key
|
|
1581
|
-
name = DECODE_PROPERTIES[key] || raise(RuntimeError.new("No property found for index #{
|
|
1582
|
+
name = DECODE_PROPERTIES[key] || raise(RuntimeError.new("No property found for index #{key.inspect}!"))
|
|
1582
1583
|
case DECODE_PROPERTIES_TYPE[key]
|
|
1583
1584
|
when :shortstr
|
|
1584
|
-
size = data
|
|
1585
|
+
size = data.getbyte(offset)
|
|
1585
1586
|
offset += 1
|
|
1586
|
-
result = data
|
|
1587
|
+
result = data.byteslice(offset, size)
|
|
1587
1588
|
when :octet
|
|
1588
1589
|
size = 1
|
|
1589
|
-
result = data
|
|
1590
|
+
result = data.getbyte(offset)
|
|
1590
1591
|
when :timestamp
|
|
1591
1592
|
size = 8
|
|
1592
|
-
result = Time.at(data
|
|
1593
|
+
result = Time.at(data.byteslice(offset, 8).unpack1(PACK_UINT64_BE))
|
|
1593
1594
|
when :table
|
|
1594
|
-
size = 4 + data
|
|
1595
|
-
result = Table.decode(data
|
|
1595
|
+
size = 4 + data.byteslice(offset, 4).unpack1(PACK_UINT32)
|
|
1596
|
+
result = Table.decode(data.byteslice(offset, size))
|
|
1596
1597
|
end
|
|
1597
1598
|
properties[name] = result
|
|
1598
1599
|
offset += size
|
|
@@ -1688,13 +1689,11 @@ module AMQ
|
|
|
1688
1689
|
@index = 0x003C0015 # 60, 21, 3932181
|
|
1689
1690
|
@packed_indexes = [60, 21].pack(PACK_UINT16_X2).freeze
|
|
1690
1691
|
|
|
1692
|
+
# Optimized decode using getbyte
|
|
1691
1693
|
# @return
|
|
1692
1694
|
def self.decode(data)
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
offset += 1
|
|
1696
|
-
consumer_tag = data[offset, length]
|
|
1697
|
-
offset += length
|
|
1695
|
+
length = data.getbyte(0)
|
|
1696
|
+
consumer_tag = data.byteslice(1, length)
|
|
1698
1697
|
self.new(consumer_tag)
|
|
1699
1698
|
end
|
|
1700
1699
|
|
|
@@ -1866,26 +1865,25 @@ module AMQ
|
|
|
1866
1865
|
@index = 0x003C003C # 60, 60, 3932220
|
|
1867
1866
|
@packed_indexes = [60, 60].pack(PACK_UINT16_X2).freeze
|
|
1868
1867
|
|
|
1868
|
+
# Optimized decode using getbyte and unpack1 for better performance
|
|
1869
1869
|
# @return
|
|
1870
1870
|
def self.decode(data)
|
|
1871
|
-
offset =
|
|
1872
|
-
length = data
|
|
1871
|
+
offset = 0
|
|
1872
|
+
length = data.getbyte(offset)
|
|
1873
1873
|
offset += 1
|
|
1874
|
-
consumer_tag = data
|
|
1874
|
+
consumer_tag = data.byteslice(offset, length)
|
|
1875
1875
|
offset += length
|
|
1876
|
-
delivery_tag =
|
|
1876
|
+
delivery_tag = data.byteslice(offset, 8).unpack1(PACK_UINT64_BE)
|
|
1877
1877
|
offset += 8
|
|
1878
|
-
|
|
1878
|
+
redelivered = (data.getbyte(offset) & 1) != 0
|
|
1879
1879
|
offset += 1
|
|
1880
|
-
|
|
1881
|
-
length = data[offset, 1].unpack(PACK_CHAR).first
|
|
1880
|
+
length = data.getbyte(offset)
|
|
1882
1881
|
offset += 1
|
|
1883
|
-
exchange = data
|
|
1882
|
+
exchange = data.byteslice(offset, length)
|
|
1884
1883
|
offset += length
|
|
1885
|
-
length = data
|
|
1884
|
+
length = data.getbyte(offset)
|
|
1886
1885
|
offset += 1
|
|
1887
|
-
routing_key = data
|
|
1888
|
-
offset += length
|
|
1886
|
+
routing_key = data.byteslice(offset, length)
|
|
1889
1887
|
self.new(consumer_tag, delivery_tag, redelivered, exchange, routing_key)
|
|
1890
1888
|
end
|
|
1891
1889
|
|
|
@@ -2009,14 +2007,11 @@ module AMQ
|
|
|
2009
2007
|
@index = 0x003C0050 # 60, 80, 3932240
|
|
2010
2008
|
@packed_indexes = [60, 80].pack(PACK_UINT16_X2).freeze
|
|
2011
2009
|
|
|
2010
|
+
# Optimized decode using unpack1 and getbyte
|
|
2012
2011
|
# @return
|
|
2013
2012
|
def self.decode(data)
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
offset += 8
|
|
2017
|
-
bit_buffer = data[offset, 1].unpack(PACK_CHAR).first
|
|
2018
|
-
offset += 1
|
|
2019
|
-
multiple = (bit_buffer & (1 << 0)) != 0
|
|
2013
|
+
delivery_tag = data.byteslice(0, 8).unpack1(PACK_UINT64_BE)
|
|
2014
|
+
multiple = (data.getbyte(8) & 1) != 0
|
|
2020
2015
|
self.new(delivery_tag, multiple)
|
|
2021
2016
|
end
|
|
2022
2017
|
|
|
@@ -2141,15 +2136,13 @@ module AMQ
|
|
|
2141
2136
|
@index = 0x003C0078 # 60, 120, 3932280
|
|
2142
2137
|
@packed_indexes = [60, 120].pack(PACK_UINT16_X2).freeze
|
|
2143
2138
|
|
|
2139
|
+
# Optimized decode using unpack1 and getbyte
|
|
2144
2140
|
# @return
|
|
2145
2141
|
def self.decode(data)
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
offset += 1
|
|
2151
|
-
multiple = (bit_buffer & (1 << 0)) != 0
|
|
2152
|
-
requeue = (bit_buffer & (1 << 1)) != 0
|
|
2142
|
+
delivery_tag = data.byteslice(0, 8).unpack1(PACK_UINT64_BE)
|
|
2143
|
+
bit_buffer = data.getbyte(8)
|
|
2144
|
+
multiple = (bit_buffer & 1) != 0
|
|
2145
|
+
requeue = (bit_buffer & 2) != 0
|
|
2153
2146
|
self.new(delivery_tag, multiple, requeue)
|
|
2154
2147
|
end
|
|
2155
2148
|
|
|
@@ -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 #{
|
|
46
|
+
super("Argument #{argument} has to be #{expected.inspect}, was #{actual.inspect}")
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
|
data/lib/amq/protocol/frame.rb
CHANGED
|
@@ -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.
|
|
@@ -56,9 +60,11 @@ This functionality is part of the https://github.com/ruby-amqp/amq-client librar
|
|
|
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
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
70
|
raise FrameTypeError.new(TYPES_OPTIONS) unless type
|
|
@@ -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
|
|
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 =
|
|
171
|
-
@data = @payload
|
|
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
|
data/lib/amq/protocol/table.rb
CHANGED
|
@@ -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 =
|
|
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
|
|
39
|
+
buffer << key.bytesize.chr << key
|
|
37
40
|
|
|
38
41
|
case value
|
|
39
|
-
when Hash
|
|
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(
|
|
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 =
|
|
55
|
-
table_length = data.
|
|
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
|
-
|
|
92
|
+
when TYPE_BYTE
|
|
90
93
|
v, offset = TableValueDecoder.decode_byte(data, offset)
|
|
91
94
|
v
|
|
92
|
-
when TYPE_SIGNED_16BIT
|
|
95
|
+
when TYPE_SIGNED_16BIT
|
|
93
96
|
v, offset = TableValueDecoder.decode_short(data, offset)
|
|
94
97
|
v
|
|
95
|
-
when TYPE_SIGNED_64BIT
|
|
98
|
+
when TYPE_SIGNED_64BIT
|
|
96
99
|
v, offset = TableValueDecoder.decode_long(data, offset)
|
|
97
100
|
v
|
|
98
|
-
when TYPE_32BIT_FLOAT
|
|
101
|
+
when TYPE_32BIT_FLOAT
|
|
99
102
|
v, offset = TableValueDecoder.decode_32bit_float(data, offset)
|
|
100
103
|
v
|
|
101
|
-
when TYPE_64BIT_FLOAT
|
|
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
|
|
118
|
+
end
|
|
116
119
|
|
|
117
120
|
|
|
118
121
|
def self.length(data)
|
|
119
|
-
data.
|
|
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
|
|
134
|
+
end
|
|
132
135
|
|
|
133
136
|
|
|
134
137
|
def self.decode_table_key(data, offset)
|
|
135
|
-
key_length = data.
|
|
138
|
+
key_length = data.getbyte(offset)
|
|
136
139
|
offset += 1
|
|
137
|
-
key = data.
|
|
140
|
+
key = data.byteslice(offset, key_length)
|
|
138
141
|
offset += key_length
|
|
139
142
|
|
|
140
143
|
[key, offset]
|
|
141
|
-
end
|
|
144
|
+
end
|
|
142
145
|
|
|
143
146
|
|
|
144
147
|
|