amq-protocol 0.7.0.beta2 → 0.7.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/Gemfile +1 -0
- data/lib/amq/protocol/table.rb +99 -113
- data/lib/amq/protocol/table_value_decoder.rb +151 -0
- data/lib/amq/protocol/table_value_encoder.rb +117 -0
- data/lib/amq/protocol/type_constants.rb +26 -0
- data/lib/amq/protocol/version.rb +1 -1
- data/spec/amq/protocol/table_spec.rb +121 -1
- data/spec/amq/protocol/value_decoder_spec.rb +66 -0
- data/spec/amq/protocol/value_encoder_spec.rb +138 -0
- metadata +16 -13
data/Gemfile
CHANGED
data/lib/amq/protocol/table.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# encoding: binary
|
2
2
|
|
3
3
|
require "amq/protocol/client"
|
4
|
+
require "amq/protocol/type_constants"
|
5
|
+
require "amq/protocol/table_value_encoder"
|
6
|
+
require "amq/protocol/table_value_decoder"
|
4
7
|
|
5
8
|
# We will need to introduce concept of mappings, because
|
6
9
|
# AMQP 0.9, 0.9.1 and RabbitMQ uses different letters for entities
|
@@ -8,146 +11,129 @@ require "amq/protocol/client"
|
|
8
11
|
module AMQ
|
9
12
|
module Protocol
|
10
13
|
class Table
|
14
|
+
|
15
|
+
#
|
16
|
+
# Behaviors
|
17
|
+
#
|
18
|
+
|
19
|
+
include TypeConstants
|
20
|
+
|
21
|
+
|
22
|
+
#
|
23
|
+
# API
|
24
|
+
#
|
25
|
+
|
11
26
|
class InvalidTableError < StandardError
|
12
27
|
def initialize(key, value)
|
13
28
|
super("Invalid table value on key #{key}: #{value.inspect} (#{value.class})")
|
14
29
|
end
|
15
30
|
end
|
16
31
|
|
17
|
-
TYPE_STRING = 'S'.freeze
|
18
|
-
TYPE_INTEGER = 'I'.freeze
|
19
|
-
TYPE_HASH = 'F'.freeze
|
20
|
-
TYPE_TIME = 'T'.freeze
|
21
|
-
TYPE_DECIMAL = 'D'.freeze
|
22
|
-
TYPE_BOOLEAN = 't'.freeze
|
23
|
-
TYPE_SIGNED_8BIT = 'b'.freeze
|
24
|
-
TYPE_SIGNED_16BIT = 's'.freeze
|
25
|
-
TYPE_SIGNED_64BIT = 'l'.freeze
|
26
|
-
TYPE_32BIT_FLOAT = 'f'.freeze
|
27
|
-
TYPE_64BIT_FLOAT = 'd'.freeze
|
28
|
-
TYPE_VOID = 'V'.freeze
|
29
|
-
TYPE_BYTE_ARRAY = 'x'.freeze
|
30
|
-
TEN = '10'.freeze
|
31
|
-
|
32
|
-
BOOLEAN_TRUE = "\x01".freeze
|
33
|
-
BOOLEAN_FALSE = "\x00".freeze
|
34
|
-
|
35
32
|
|
36
33
|
def self.encode(table)
|
37
34
|
buffer = String.new
|
35
|
+
|
38
36
|
table ||= {}
|
37
|
+
|
39
38
|
table.each do |key, value|
|
40
39
|
key = key.to_s # it can be a symbol as well
|
41
40
|
buffer << key.bytesize.chr + key
|
42
41
|
|
43
42
|
case value
|
44
|
-
when String then
|
45
|
-
buffer << TYPE_STRING
|
46
|
-
buffer << [value.bytesize].pack(PACK_UINT32)
|
47
|
-
buffer << value
|
48
|
-
when Integer then
|
49
|
-
buffer << TYPE_INTEGER
|
50
|
-
buffer << [value].pack(PACK_UINT32)
|
51
|
-
when Float then
|
52
|
-
buffer << TYPE_64BIT_FLOAT
|
53
|
-
buffer << [value].pack(PACK_64BIT_FLOAT)
|
54
|
-
when true, false then
|
55
|
-
buffer << TYPE_BOOLEAN
|
56
|
-
buffer << (value ? BOOLEAN_TRUE : BOOLEAN_FALSE)
|
57
43
|
when Hash then
|
58
|
-
buffer << TYPE_HASH
|
44
|
+
buffer << TYPE_HASH
|
59
45
|
buffer << self.encode(value)
|
60
|
-
when Time then
|
61
|
-
buffer << TYPE_TIME
|
62
|
-
buffer << [value.to_i].pack(PACK_INT64).reverse # FIXME: there has to be a more efficient way
|
63
|
-
when nil then
|
64
|
-
buffer << TYPE_VOID
|
65
46
|
else
|
66
|
-
|
67
|
-
if defined?(BigDecimal) && value.is_a?(BigDecimal)
|
68
|
-
buffer << TYPE_DECIMAL
|
69
|
-
if value.exponent < 0
|
70
|
-
decimals = -value.exponent
|
71
|
-
# p [value.exponent] # normalize
|
72
|
-
raw = (value * (decimals ** 10)).to_i
|
73
|
-
#pieces.append(struct.pack('>cBI', 'D', decimals, raw)) # byte integer
|
74
|
-
buffer << [decimals + 1, raw].pack(PACK_UCHAR_UINT32) # somewhat like floating point
|
75
|
-
else
|
76
|
-
# per spec, the "decimals" octet is unsigned (!)
|
77
|
-
buffer << [0, value.to_i].pack(PACK_UCHAR_UINT32)
|
78
|
-
end
|
79
|
-
else
|
80
|
-
raise InvalidTableError.new(key, value)
|
81
|
-
end
|
47
|
+
buffer << TableValueEncoder.encode(value)
|
82
48
|
end
|
83
49
|
end
|
84
50
|
|
85
51
|
[buffer.bytesize].pack(PACK_UINT32) + buffer
|
86
52
|
end
|
87
53
|
|
88
|
-
|
89
|
-
|
90
|
-
end
|
54
|
+
|
55
|
+
|
91
56
|
|
92
57
|
def self.decode(data)
|
93
|
-
table
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
offset
|
101
|
-
type =
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
offset += 8
|
140
|
-
when TYPE_VOID
|
141
|
-
value = nil
|
142
|
-
when TYPE_BYTE_ARRAY
|
143
|
-
else
|
144
|
-
raise "Not a valid type: #{type.inspect}\nData: #{data.inspect}\nUnprocessed data: #{data[offset..-1].inspect}\nOffset: #{offset}\nTotal size: #{size}\nProcessed data: #{table.inspect}"
|
145
|
-
end
|
146
|
-
table[key] = value
|
58
|
+
table = Hash.new
|
59
|
+
table_length = data.unpack(PACK_UINT32).first
|
60
|
+
|
61
|
+
return table if table_length.zero?
|
62
|
+
|
63
|
+
offset = 4
|
64
|
+
while offset <= table_length
|
65
|
+
key, offset = decode_table_key(data, offset)
|
66
|
+
type, offset = TableValueDecoder.decode_value_type(data, offset)
|
67
|
+
|
68
|
+
table[key] = case type
|
69
|
+
when TYPE_STRING
|
70
|
+
v, offset = TableValueDecoder.decode_string(data, offset)
|
71
|
+
v
|
72
|
+
when TYPE_INTEGER
|
73
|
+
v, offset = TableValueDecoder.decode_integer(data, offset)
|
74
|
+
v
|
75
|
+
when TYPE_DECIMAL
|
76
|
+
v, offset = TableValueDecoder.decode_big_decimal(data, offset)
|
77
|
+
v
|
78
|
+
when TYPE_TIME
|
79
|
+
v, offset = TableValueDecoder.decode_time(data, offset)
|
80
|
+
v
|
81
|
+
when TYPE_HASH
|
82
|
+
v, offset = TableValueDecoder.decode_hash(data, offset)
|
83
|
+
v
|
84
|
+
when TYPE_BOOLEAN
|
85
|
+
v, offset = TableValueDecoder.decode_boolean(data, offset)
|
86
|
+
v
|
87
|
+
when TYPE_SIGNED_8BIT then raise NotImplementedError.new
|
88
|
+
when TYPE_SIGNED_16BIT then raise NotImplementedError.new
|
89
|
+
when TYPE_SIGNED_64BIT then raise NotImplementedError.new
|
90
|
+
when TYPE_32BIT_FLOAT then
|
91
|
+
v, offset = TableValueDecoder.decode_32bit_float(data, offset)
|
92
|
+
v
|
93
|
+
when TYPE_64BIT_FLOAT then
|
94
|
+
v, offset = TableValueDecoder.decode_64bit_float(data, offset)
|
95
|
+
v
|
96
|
+
when TYPE_VOID
|
97
|
+
nil
|
98
|
+
when TYPE_ARRAY
|
99
|
+
v, offset = TableValueDecoder.decode_array(data, offset)
|
100
|
+
v
|
101
|
+
else
|
102
|
+
raise ArgumentError, "Not a valid type: #{type.inspect}\nData: #{data.inspect}\nUnprocessed data: #{data[offset..-1].inspect}\nOffset: #{offset}\nTotal size: #{table_length}\nProcessed data: #{table.inspect}"
|
103
|
+
end
|
147
104
|
end
|
148
105
|
|
149
106
|
table
|
107
|
+
end # self.decode
|
108
|
+
|
109
|
+
|
110
|
+
def self.length(data)
|
111
|
+
data.unpack(PACK_UINT32).first
|
150
112
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
113
|
+
|
114
|
+
|
115
|
+
def self.hash_size(value)
|
116
|
+
acc = 0
|
117
|
+
value.each do |k, v|
|
118
|
+
acc += (1 + k.to_s.bytesize)
|
119
|
+
acc += TableValueEncoder.field_value_size(v)
|
120
|
+
end
|
121
|
+
|
122
|
+
acc
|
123
|
+
end # self.hash_size(value)
|
124
|
+
|
125
|
+
|
126
|
+
def self.decode_table_key(data, offset)
|
127
|
+
key_length = data.slice(offset, 1).unpack(PACK_CHAR).first
|
128
|
+
offset += 1
|
129
|
+
key = data.slice(offset, key_length)
|
130
|
+
offset += key_length
|
131
|
+
|
132
|
+
[key, offset]
|
133
|
+
end # self.decode_table_key(data, offset)
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
end # Table
|
138
|
+
end # Protocol
|
139
|
+
end # AMQ
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
require "amq/protocol/client"
|
4
|
+
require "amq/protocol/type_constants"
|
5
|
+
require "amq/protocol/table"
|
6
|
+
|
7
|
+
module AMQ
|
8
|
+
module Protocol
|
9
|
+
|
10
|
+
class TableValueDecoder
|
11
|
+
|
12
|
+
#
|
13
|
+
# Behaviors
|
14
|
+
#
|
15
|
+
|
16
|
+
include TypeConstants
|
17
|
+
|
18
|
+
|
19
|
+
#
|
20
|
+
# API
|
21
|
+
#
|
22
|
+
|
23
|
+
def self.decode_array(data, initial_offset)
|
24
|
+
array_length = data.slice(initial_offset, 4).unpack(PACK_UINT32).first
|
25
|
+
|
26
|
+
ary = Array.new
|
27
|
+
offset = initial_offset + 4
|
28
|
+
|
29
|
+
while offset <= (initial_offset + array_length)
|
30
|
+
type, offset = decode_value_type(data, offset)
|
31
|
+
|
32
|
+
i = case type
|
33
|
+
when TYPE_STRING
|
34
|
+
v, offset = decode_string(data, offset)
|
35
|
+
v
|
36
|
+
when TYPE_INTEGER
|
37
|
+
v, offset = decode_integer(data, offset)
|
38
|
+
v
|
39
|
+
when TYPE_DECIMAL
|
40
|
+
v, offset = decode_big_decimal(data, offset)
|
41
|
+
v
|
42
|
+
when TYPE_TIME
|
43
|
+
v, offset = decode_time(data, offset)
|
44
|
+
v
|
45
|
+
when TYPE_HASH
|
46
|
+
v, offset = decode_hash(data, offset)
|
47
|
+
v
|
48
|
+
when TYPE_BOOLEAN
|
49
|
+
v, offset = decode_boolean(data, offset)
|
50
|
+
v
|
51
|
+
when TYPE_SIGNED_8BIT then raise NotImplementedError.new
|
52
|
+
when TYPE_SIGNED_16BIT then raise NotImplementedError.new
|
53
|
+
when TYPE_SIGNED_64BIT then raise NotImplementedError.new
|
54
|
+
when TYPE_32BIT_FLOAT then
|
55
|
+
v, offset = decode_32bit_float(data, offset)
|
56
|
+
v
|
57
|
+
when TYPE_64BIT_FLOAT then
|
58
|
+
v, offset = decode_64bit_float(data, offset)
|
59
|
+
v
|
60
|
+
when TYPE_VOID
|
61
|
+
nil
|
62
|
+
when TYPE_ARRAY
|
63
|
+
v, offset = TableValueDecoder.decode_array(data, offset)
|
64
|
+
v
|
65
|
+
else
|
66
|
+
raise ArgumentError.new("unsupported type: #{type.inspect}")
|
67
|
+
end
|
68
|
+
|
69
|
+
ary << i
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
[ary, initial_offset + array_length + 4]
|
74
|
+
end # self.decode_array(data, initial_offset)
|
75
|
+
|
76
|
+
|
77
|
+
def self.decode_string(data, offset)
|
78
|
+
length = data.slice(offset, 4).unpack(PACK_UINT32).first
|
79
|
+
offset += 4
|
80
|
+
v = data.slice(offset, length)
|
81
|
+
offset += length
|
82
|
+
|
83
|
+
[v, offset]
|
84
|
+
end # self.decode_string(data, offset)
|
85
|
+
|
86
|
+
|
87
|
+
def self.decode_integer(data, offset)
|
88
|
+
v = data.slice(offset, 4).unpack(PACK_UINT32).first
|
89
|
+
offset += 4
|
90
|
+
|
91
|
+
[v, offset]
|
92
|
+
end # self.decode_integer(data, offset)
|
93
|
+
|
94
|
+
|
95
|
+
def self.decode_big_decimal(data, offset)
|
96
|
+
decimals, raw = data.slice(offset, 5).unpack(PACK_UCHAR_UINT32)
|
97
|
+
offset += 5
|
98
|
+
v = BigDecimal.new(raw.to_s) * (BigDecimal.new(TEN) ** -decimals)
|
99
|
+
|
100
|
+
[v, offset]
|
101
|
+
end # self.decode_big_decimal(data, offset)
|
102
|
+
|
103
|
+
|
104
|
+
def self.decode_time(data, offset)
|
105
|
+
timestamp = data.slice(offset, 8).unpack(PACK_UINT32_X2).last
|
106
|
+
v = Time.at(timestamp)
|
107
|
+
offset += 8
|
108
|
+
|
109
|
+
[v, offset]
|
110
|
+
end # self.decode_time(data, offset)
|
111
|
+
|
112
|
+
|
113
|
+
def self.decode_boolean(data, offset)
|
114
|
+
integer = data.slice(offset, 2).unpack(PACK_CHAR).first # 0 or 1
|
115
|
+
offset += 1
|
116
|
+
[(integer == 1), offset]
|
117
|
+
end # self.decode_boolean(data, offset)
|
118
|
+
|
119
|
+
|
120
|
+
def self.decode_32bit_float(data, offset)
|
121
|
+
v = data.slice(offset, 4).unpack(PACK_32BIT_FLOAT).first
|
122
|
+
offset += 4
|
123
|
+
|
124
|
+
[v, offset]
|
125
|
+
end # self.decode_32bit_float(data, offset)
|
126
|
+
|
127
|
+
|
128
|
+
def self.decode_64bit_float(data, offset)
|
129
|
+
v = data.slice(offset, 8).unpack(PACK_64BIT_FLOAT).first
|
130
|
+
offset += 8
|
131
|
+
|
132
|
+
[v, offset]
|
133
|
+
end # self.decode_64bit_float(data, offset)
|
134
|
+
|
135
|
+
|
136
|
+
def self.decode_value_type(data, offset)
|
137
|
+
[data.slice(offset, 1), offset + 1]
|
138
|
+
end # self.decode_value_type(data, offset)
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
def self.decode_hash(data, offset)
|
143
|
+
length = data.slice(offset, 4).unpack(PACK_UINT32).first
|
144
|
+
v = Table.decode(data.slice(offset, length + 4))
|
145
|
+
offset += 4 + length
|
146
|
+
|
147
|
+
[v, offset]
|
148
|
+
end # self.decode_hash(data, offset)
|
149
|
+
end # TableValueDecoder
|
150
|
+
end # Protocol
|
151
|
+
end # AMQ
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
require "amq/protocol/client"
|
4
|
+
require "amq/protocol/type_constants"
|
5
|
+
require "amq/protocol/table"
|
6
|
+
|
7
|
+
module AMQ
|
8
|
+
module Protocol
|
9
|
+
|
10
|
+
class TableValueEncoder
|
11
|
+
|
12
|
+
#
|
13
|
+
# Behaviors
|
14
|
+
#
|
15
|
+
|
16
|
+
include TypeConstants
|
17
|
+
|
18
|
+
#
|
19
|
+
# API
|
20
|
+
#
|
21
|
+
|
22
|
+
def self.encode(value)
|
23
|
+
accumulator = String.new
|
24
|
+
|
25
|
+
case value
|
26
|
+
when String then
|
27
|
+
accumulator << TYPE_STRING
|
28
|
+
accumulator << [value.bytesize].pack(PACK_UINT32)
|
29
|
+
accumulator << value
|
30
|
+
when Symbol then
|
31
|
+
v = value.to_s
|
32
|
+
accumulator << TYPE_STRING
|
33
|
+
accumulator << [v.bytesize].pack(PACK_UINT32)
|
34
|
+
accumulator << v
|
35
|
+
when Integer then
|
36
|
+
accumulator << TYPE_INTEGER
|
37
|
+
accumulator << [value].pack(PACK_UINT32)
|
38
|
+
when Float then
|
39
|
+
accumulator << TYPE_64BIT_FLOAT
|
40
|
+
accumulator << [value].pack(PACK_64BIT_FLOAT)
|
41
|
+
when true, false then
|
42
|
+
accumulator << TYPE_BOOLEAN
|
43
|
+
accumulator << (value ? BOOLEAN_TRUE : BOOLEAN_FALSE)
|
44
|
+
when Time then
|
45
|
+
accumulator << TYPE_TIME
|
46
|
+
accumulator << [value.to_i].pack(PACK_INT64).reverse # FIXME: there has to be a more efficient way
|
47
|
+
when nil then
|
48
|
+
accumulator << TYPE_VOID
|
49
|
+
when Array then
|
50
|
+
accumulator << TYPE_ARRAY
|
51
|
+
accumulator << [self.array_size(value)].pack(PACK_UINT32)
|
52
|
+
|
53
|
+
value.each { |v| accumulator << self.encode(v) }
|
54
|
+
when Hash then
|
55
|
+
accumulator << TYPE_HASH
|
56
|
+
accumulator << AMQ::Protocol::Table.encode(value)
|
57
|
+
else
|
58
|
+
# We don't want to require these libraries.
|
59
|
+
if defined?(BigDecimal) && value.is_a?(BigDecimal)
|
60
|
+
accumulator << TYPE_DECIMAL
|
61
|
+
if value.exponent < 0
|
62
|
+
decimals = -value.exponent
|
63
|
+
raw = (value * (decimals ** 10)).to_i
|
64
|
+
accumulator << [decimals + 1, raw].pack(PACK_UCHAR_UINT32) # somewhat like floating point
|
65
|
+
else
|
66
|
+
# per spec, the "decimals" octet is unsigned (!)
|
67
|
+
accumulator << [0, value.to_i].pack(PACK_UCHAR_UINT32)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ArgumentError.new("Unsupported value #{value.inspect} of type #{value.class.name}")
|
71
|
+
end # if
|
72
|
+
end # case
|
73
|
+
|
74
|
+
accumulator
|
75
|
+
end # self.encode(value)
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
def self.field_value_size(value)
|
81
|
+
# the type tag takes 1 byte
|
82
|
+
acc = 1
|
83
|
+
|
84
|
+
case value
|
85
|
+
when String then
|
86
|
+
acc += (value.bytesize + 4)
|
87
|
+
when Integer then
|
88
|
+
acc += 4
|
89
|
+
when Float then
|
90
|
+
acc += 8
|
91
|
+
when Time, DateTime then
|
92
|
+
acc += 8
|
93
|
+
when true, false then
|
94
|
+
acc += 1
|
95
|
+
when nil then
|
96
|
+
# nothing, type tag alone is enough
|
97
|
+
when Hash then
|
98
|
+
acc += (4 + Table.hash_size(value))
|
99
|
+
when Array then
|
100
|
+
acc += (4 + self.array_size(value))
|
101
|
+
end
|
102
|
+
|
103
|
+
acc
|
104
|
+
end # self.field_value_size(value)
|
105
|
+
|
106
|
+
|
107
|
+
def self.array_size(value)
|
108
|
+
acc = 0
|
109
|
+
value.each { |v| acc += self.field_value_size(v) }
|
110
|
+
|
111
|
+
acc
|
112
|
+
end # self.array_size(value)
|
113
|
+
|
114
|
+
end # TableValueEncoder
|
115
|
+
|
116
|
+
end # Protocol
|
117
|
+
end # AMQ
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
module AMQ
|
4
|
+
module Protocol
|
5
|
+
module TypeConstants
|
6
|
+
TYPE_STRING = 'S'.freeze
|
7
|
+
TYPE_INTEGER = 'I'.freeze
|
8
|
+
TYPE_HASH = 'F'.freeze
|
9
|
+
TYPE_TIME = 'T'.freeze
|
10
|
+
TYPE_DECIMAL = 'D'.freeze
|
11
|
+
TYPE_BOOLEAN = 't'.freeze
|
12
|
+
TYPE_SIGNED_8BIT = 'b'.freeze
|
13
|
+
TYPE_SIGNED_16BIT = 's'.freeze
|
14
|
+
TYPE_SIGNED_64BIT = 'l'.freeze
|
15
|
+
TYPE_32BIT_FLOAT = 'f'.freeze
|
16
|
+
TYPE_64BIT_FLOAT = 'd'.freeze
|
17
|
+
TYPE_VOID = 'V'.freeze
|
18
|
+
TYPE_BYTE_ARRAY = 'x'.freeze
|
19
|
+
TYPE_ARRAY = 'A'.freeze
|
20
|
+
TEN = '10'.freeze
|
21
|
+
|
22
|
+
BOOLEAN_TRUE = "\x01".freeze
|
23
|
+
BOOLEAN_FALSE = "\x00".freeze
|
24
|
+
end # TypeConstants
|
25
|
+
end # Protocol
|
26
|
+
end # AMQ
|
data/lib/amq/protocol/version.rb
CHANGED
@@ -84,7 +84,7 @@ module AMQ
|
|
84
84
|
end # DATA.each
|
85
85
|
|
86
86
|
|
87
|
-
it "is capable of decoding
|
87
|
+
it "is capable of decoding boolean table values" do
|
88
88
|
input1 = { "boolval" => true }
|
89
89
|
Table.decode(Table.encode(input1)).should == input1
|
90
90
|
|
@@ -95,6 +95,76 @@ module AMQ
|
|
95
95
|
|
96
96
|
|
97
97
|
|
98
|
+
it "is capable of decoding string table values" do
|
99
|
+
input = { "stringvalue" => "string" }
|
100
|
+
Table.decode(Table.encode(input)).should == input
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
it "is capable of decoding integer table values" do
|
106
|
+
input = { "intvalue" => 10 }
|
107
|
+
Table.decode(Table.encode(input)).should == input
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
it "is capable of decoding long table values" do
|
113
|
+
input = { "longvalue" => 912598613 }
|
114
|
+
Table.decode(Table.encode(input)).should == input
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
it "is capable of decoding float table values" do
|
120
|
+
input = { "floatvalue" => 100.0 }
|
121
|
+
Table.decode(Table.encode(input)).should == input
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
it "is capable of decoding time table values" do
|
127
|
+
input = { "intvalue" => Time.parse("2011-07-14 01:17:46 +0400") }
|
128
|
+
Table.decode(Table.encode(input)).should == input
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
it "is capable of decoding empty hash table values" do
|
134
|
+
input = { "hashvalue" => Hash.new }
|
135
|
+
Table.decode(Table.encode(input)).should == input
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
xit "is capable of decoding empty array table values" do
|
141
|
+
input = { "arrayvalue" => Array.new }
|
142
|
+
Table.decode(Table.encode(input)).should == input
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
xit "is capable of decoding single string value array table values" do
|
147
|
+
input = { "arrayvalue" => ["amq-protocol"] }
|
148
|
+
Table.decode(Table.encode(input)).should == input
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
it "is capable of decoding simple nested hash table values" do
|
154
|
+
input = { "hashvalue" => { "a" => "b" } }
|
155
|
+
Table.decode(Table.encode(input)).should == input
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
it "is capable of decoding nil table values" do
|
161
|
+
input = { "nil" => nil }
|
162
|
+
Table.decode(Table.encode(input)).should == input
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
|
98
168
|
it "is capable of decoding tables" do
|
99
169
|
input = {
|
100
170
|
"boolval" => true,
|
@@ -108,6 +178,56 @@ module AMQ
|
|
108
178
|
Table.decode(Table.encode(input)).should == input
|
109
179
|
end
|
110
180
|
|
181
|
+
|
182
|
+
|
183
|
+
it "is capable of decoding deeply nested tables" do
|
184
|
+
input = {
|
185
|
+
"hashval" => {
|
186
|
+
"protocol" => {
|
187
|
+
"name" => "AMQP",
|
188
|
+
"major" => 0,
|
189
|
+
"minor" => "9",
|
190
|
+
"rev" => 1.0,
|
191
|
+
"spec" => {
|
192
|
+
"url" => "http://bit.ly/hw2ELX",
|
193
|
+
"utf8" => "à bientôt"
|
194
|
+
}
|
195
|
+
},
|
196
|
+
"true" => true,
|
197
|
+
"false" => false,
|
198
|
+
"nil" => nil
|
199
|
+
}
|
200
|
+
}
|
201
|
+
Table.decode(Table.encode(input)).should == input
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
it "is capable of decoding array values in tables" do
|
207
|
+
input1 = {
|
208
|
+
"arrayval1" => [198, 3, 77, 8.0, ["inner", "array", { "oh" => "well", "it" => "should work", "3" => 6 }], "two", { "a" => "value", "is" => nil }],
|
209
|
+
"arrayval2" => [198, 3, 77, "two", { "a" => "value", "is" => nil }, 8.0, ["inner", "array", { "oh" => "well", "it" => "should work", "3" => 6 }]]
|
210
|
+
}
|
211
|
+
Table.decode(Table.encode(input1)).should == input1
|
212
|
+
|
213
|
+
|
214
|
+
input2 = {
|
215
|
+
"coordinates" => {
|
216
|
+
"latitude" => 59.35,
|
217
|
+
"longitude" => 18.066667
|
218
|
+
},
|
219
|
+
"time" => @now,
|
220
|
+
"participants" => 11,
|
221
|
+
"venue" => "Stockholm",
|
222
|
+
"true_field" => true,
|
223
|
+
"false_field" => false,
|
224
|
+
"nil_field" => nil,
|
225
|
+
"ary_field" => ["one", 2.0, 3]
|
226
|
+
}
|
227
|
+
|
228
|
+
Table.decode(Table.encode(input2)).should == input2
|
229
|
+
end
|
230
|
+
|
111
231
|
end # describe
|
112
232
|
end
|
113
233
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
require 'time'
|
5
|
+
require "amq/protocol/table_value_decoder"
|
6
|
+
|
7
|
+
module AMQ
|
8
|
+
module Protocol
|
9
|
+
describe TableValueDecoder do
|
10
|
+
|
11
|
+
it "is capable of decoding basic arrays TableValueEncoder encodes" do
|
12
|
+
input1 = [1, 2, 3]
|
13
|
+
|
14
|
+
value, offset = described_class.decode_array(TableValueEncoder.encode(input1), 1)
|
15
|
+
value.size.should == 3
|
16
|
+
value.first.should == 1
|
17
|
+
value.should == input1
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
input2 = ["one", 2, "three"]
|
22
|
+
|
23
|
+
value, offset = described_class.decode_array(TableValueEncoder.encode(input2), 1)
|
24
|
+
value.size.should == 3
|
25
|
+
value.first.should == "one"
|
26
|
+
value.should == input2
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
input3 = ["one", 2, "three", 4.0, 5000000.0]
|
31
|
+
|
32
|
+
value, offset = described_class.decode_array(TableValueEncoder.encode(input3), 1)
|
33
|
+
value.size.should == 5
|
34
|
+
value.last.should == 5000000.0
|
35
|
+
value.should == input3
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
it "is capable of decoding arrays TableValueEncoder encodes" do
|
41
|
+
input1 = [{ "one" => 2 }, 3]
|
42
|
+
data1 = TableValueEncoder.encode(input1)
|
43
|
+
|
44
|
+
# puts(TableValueEncoder.encode({ "one" => 2 }).inspect)
|
45
|
+
# puts(TableValueEncoder.encode(input1).inspect)
|
46
|
+
|
47
|
+
|
48
|
+
value, offset = described_class.decode_array(data1, 1)
|
49
|
+
value.size.should == 2
|
50
|
+
value.first.should == Hash["one" => 2]
|
51
|
+
value.should == input1
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
input2 = ["one", 2, { "three" => { "four" => 5.0 } }]
|
56
|
+
|
57
|
+
value, offset = described_class.decode_array(TableValueEncoder.encode(input2), 1)
|
58
|
+
value.size.should == 3
|
59
|
+
value.last["three"]["four"].should == 5.0
|
60
|
+
value.should == input2
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
require 'time'
|
5
|
+
require "amq/protocol/table_value_encoder"
|
6
|
+
|
7
|
+
module AMQ
|
8
|
+
module Protocol
|
9
|
+
describe TableValueEncoder do
|
10
|
+
|
11
|
+
|
12
|
+
it "calculates size of string field values" do
|
13
|
+
described_class.field_value_size("amqp").should == 9
|
14
|
+
described_class.encode("amqp").bytesize.should == 9
|
15
|
+
|
16
|
+
described_class.field_value_size("amq-protocol").should == 17
|
17
|
+
described_class.encode("amq-protocol").bytesize.should == 17
|
18
|
+
|
19
|
+
described_class.field_value_size("à bientôt").should == 16
|
20
|
+
described_class.encode("à bientôt").bytesize.should == 16
|
21
|
+
end
|
22
|
+
|
23
|
+
it "calculates size of integer field values" do
|
24
|
+
described_class.field_value_size(10).should == 5
|
25
|
+
described_class.encode(10).bytesize.should == 5
|
26
|
+
end
|
27
|
+
|
28
|
+
it "calculates size of float field values (considering them to be 64-bit)" do
|
29
|
+
described_class.field_value_size(10.0).should == 9
|
30
|
+
described_class.encode(10.0).bytesize.should == 9
|
31
|
+
|
32
|
+
described_class.field_value_size(120000.0).should == 9
|
33
|
+
described_class.encode(120000.0).bytesize.should == 9
|
34
|
+
end
|
35
|
+
|
36
|
+
it "calculates size of boolean field values" do
|
37
|
+
described_class.field_value_size(true).should == 2
|
38
|
+
described_class.encode(true).bytesize.should == 2
|
39
|
+
|
40
|
+
described_class.field_value_size(false).should == 2
|
41
|
+
described_class.encode(false).bytesize.should == 2
|
42
|
+
end
|
43
|
+
|
44
|
+
it "calculates size of void field values" do
|
45
|
+
described_class.field_value_size(nil).should == 1
|
46
|
+
described_class.encode(nil).bytesize.should == 1
|
47
|
+
end
|
48
|
+
|
49
|
+
it "calculates size of time field values" do
|
50
|
+
t = Time.parse("2011-07-14 01:17:46 +0400")
|
51
|
+
|
52
|
+
described_class.field_value_size(t).should == 9
|
53
|
+
described_class.encode(t).bytesize.should == 9
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
it "calculates size of basic table field values" do
|
58
|
+
input1 = { "key" => "value" }
|
59
|
+
described_class.field_value_size(input1).should == 19
|
60
|
+
described_class.encode(input1).bytesize.should == 19
|
61
|
+
|
62
|
+
|
63
|
+
input2 = { "intval" => 1 }
|
64
|
+
described_class.field_value_size(input2).should == 17
|
65
|
+
described_class.encode(input2).bytesize.should == 17
|
66
|
+
|
67
|
+
|
68
|
+
input3 = { "intval" => 1, "key" => "value" }
|
69
|
+
described_class.field_value_size(input3).should == 31
|
70
|
+
described_class.encode(input3).bytesize.should == 31
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
it "calculates size of table field values" do
|
75
|
+
input1 = {
|
76
|
+
"hashval" => {
|
77
|
+
"protocol" => {
|
78
|
+
"name" => "AMQP",
|
79
|
+
"major" => 0,
|
80
|
+
"minor" => "9",
|
81
|
+
"rev" => 1.0,
|
82
|
+
"spec" => {
|
83
|
+
"url" => "http://bit.ly/hw2ELX",
|
84
|
+
"utf8" => one_point_eight? ? "à bientôt" : "à bientôt".force_encoding(::Encoding::ASCII_8BIT)
|
85
|
+
}
|
86
|
+
},
|
87
|
+
"true" => true,
|
88
|
+
"false" => false,
|
89
|
+
"nil" => nil
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
described_class.field_value_size(input1).should == 162
|
94
|
+
# puts(described_class.encode(input1).inspect)
|
95
|
+
described_class.encode(input1).bytesize.should == 162
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
input2 = {
|
100
|
+
"boolval" => true,
|
101
|
+
"intval" => 1,
|
102
|
+
"strval" => "Test",
|
103
|
+
"timestampval" => Time.parse("2011-07-14 01:17:46 +0400"),
|
104
|
+
"floatval" => 3.14,
|
105
|
+
"longval" => 912598613,
|
106
|
+
"hashval" => { "protocol" => "AMQP091", "true" => true, "false" => false, "nil" => nil }
|
107
|
+
}
|
108
|
+
|
109
|
+
described_class.field_value_size(input2).should == 150
|
110
|
+
described_class.encode(input2).bytesize.should == 150
|
111
|
+
end
|
112
|
+
|
113
|
+
it "calculates size of basic array field values" do
|
114
|
+
input1 = [1, 2, 3]
|
115
|
+
|
116
|
+
described_class.field_value_size(input1).should == 20
|
117
|
+
described_class.encode(input1).bytesize.should == 20
|
118
|
+
|
119
|
+
|
120
|
+
input2 = ["one", "two", "three"]
|
121
|
+
described_class.field_value_size(input2).should == 31
|
122
|
+
described_class.encode(input2).bytesize.should == 31
|
123
|
+
|
124
|
+
|
125
|
+
input3 = ["one", 2, "three"]
|
126
|
+
described_class.field_value_size(input3).should == 28
|
127
|
+
described_class.encode(input3).bytesize.should == 28
|
128
|
+
|
129
|
+
|
130
|
+
input4 = ["one", 2, "three", ["four", 5, [6.0]]]
|
131
|
+
described_class.field_value_size(input4).should == 61
|
132
|
+
described_class.encode(input4).bytesize.should == 61
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
end # TableValueEncoder
|
137
|
+
end # Protocol
|
138
|
+
end # AMQ
|
metadata
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amq-protocol
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 3
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 7
|
9
9
|
- 0
|
10
|
-
|
11
|
-
- 2
|
12
|
-
version: 0.7.0.beta2
|
10
|
+
version: 0.7.0
|
13
11
|
platform: ruby
|
14
12
|
authors:
|
15
13
|
- Jakub Stastny
|
@@ -20,7 +18,8 @@ autorequire:
|
|
20
18
|
bindir: bin
|
21
19
|
cert_chain: []
|
22
20
|
|
23
|
-
date: 2011-07-
|
21
|
+
date: 2011-07-17 00:00:00 +04:00
|
22
|
+
default_executable:
|
24
23
|
dependencies: []
|
25
24
|
|
26
25
|
description: " amq-protocol is an AMQP 0.9.1 serialization library for Ruby. It is not an\n AMQP client: amq-protocol only handles serialization and deserialization.\n If you want to write your own AMQP client, this gem can help you with that.\n"
|
@@ -53,6 +52,9 @@ files:
|
|
53
52
|
- lib/amq/protocol/client.rb
|
54
53
|
- lib/amq/protocol/frame.rb
|
55
54
|
- lib/amq/protocol/table.rb
|
55
|
+
- lib/amq/protocol/table_value_decoder.rb
|
56
|
+
- lib/amq/protocol/table_value_encoder.rb
|
57
|
+
- lib/amq/protocol/type_constants.rb
|
56
58
|
- lib/amq/protocol/version.rb
|
57
59
|
- post-processing.rb
|
58
60
|
- protocol.rb.pytemplate
|
@@ -67,9 +69,12 @@ files:
|
|
67
69
|
- spec/amq/protocol/queue_spec.rb
|
68
70
|
- spec/amq/protocol/table_spec.rb
|
69
71
|
- spec/amq/protocol/tx_spec.rb
|
72
|
+
- spec/amq/protocol/value_decoder_spec.rb
|
73
|
+
- spec/amq/protocol/value_encoder_spec.rb
|
70
74
|
- spec/amq/protocol_spec.rb
|
71
75
|
- spec/spec_helper.rb
|
72
76
|
- tasks.rb
|
77
|
+
has_rdoc: true
|
73
78
|
homepage: http://github.com/ruby-amqp/amq-protocol
|
74
79
|
licenses: []
|
75
80
|
|
@@ -90,18 +95,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
96
|
none: false
|
92
97
|
requirements:
|
93
|
-
- - "
|
98
|
+
- - ">="
|
94
99
|
- !ruby/object:Gem::Version
|
95
|
-
hash:
|
100
|
+
hash: 3
|
96
101
|
segments:
|
97
|
-
-
|
98
|
-
|
99
|
-
- 1
|
100
|
-
version: 1.3.1
|
102
|
+
- 0
|
103
|
+
version: "0"
|
101
104
|
requirements: []
|
102
105
|
|
103
106
|
rubyforge_project: amq-protocol
|
104
|
-
rubygems_version: 1.
|
107
|
+
rubygems_version: 1.6.2
|
105
108
|
signing_key:
|
106
109
|
specification_version: 3
|
107
110
|
summary: AMQP 0.9.1 encoder & decoder.
|