amq-protocol 0.7.0.beta2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -25,4 +25,5 @@ end
25
25
  group(:test) do
26
26
  gem "rspec", ">=2.0.0"
27
27
  gem "simplecov", :platform => :mri_19
28
+ gem "effin_utf8"
28
29
  end
@@ -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 # TODO: encoding support
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
- # We don't want to require these libraries.
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
- def self.length(data)
89
- data.unpack(PACK_UINT32).first
90
- end
54
+
55
+
91
56
 
92
57
  def self.decode(data)
93
- table = Hash.new
94
- size = data.unpack(PACK_UINT32).first
95
- offset = 4
96
- while offset < size
97
- key_length = data.slice(offset, 1).unpack(PACK_CHAR).first
98
- offset += 1
99
- key = data.slice(offset, key_length)
100
- offset += key_length
101
- type = data.slice(offset, 1)
102
- offset += 1
103
-
104
- case type
105
- when TYPE_STRING
106
- length = data.slice(offset, 4).unpack(PACK_UINT32).first
107
- offset += 4
108
- value = data.slice(offset, length)
109
- offset += length
110
- when TYPE_INTEGER
111
- value = data.slice(offset, 4).unpack(PACK_UINT32).first
112
- offset += 4
113
- when TYPE_DECIMAL
114
- decimals, raw = data.slice(offset, 5).unpack(PACK_UCHAR_UINT32)
115
- offset += 5
116
- value = BigDecimal.new(raw.to_s) * (BigDecimal.new(TEN) ** -decimals)
117
- when TYPE_TIME
118
- timestamp = data.slice(offset, 8).unpack(PACK_UINT32_X2).last
119
- value = Time.at(timestamp)
120
- offset += 8
121
- when TYPE_HASH
122
- length = data.slice(offset, 4).unpack(PACK_UINT32).first
123
- value = self.decode(data.slice(offset, length + 4))
124
- offset += 4 + length
125
- when TYPE_BOOLEAN
126
- value = data.slice(offset, 2)
127
- integer = value.unpack(PACK_CHAR).first # 0 or 1
128
- value = (integer == 1)
129
- offset += 1
130
- value
131
- when TYPE_SIGNED_8BIT then raise NotImplementedError.new
132
- when TYPE_SIGNED_16BIT then raise NotImplementedError.new
133
- when TYPE_SIGNED_64BIT then raise NotImplementedError.new
134
- when TYPE_32BIT_FLOAT then
135
- value = data.slice(offset, 4).unpack(PACK_32BIT_FLOAT).first
136
- offset += 4
137
- when TYPE_64BIT_FLOAT then
138
- value = data.slice(offset, 8).unpack(PACK_64BIT_FLOAT).first
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
- end
152
- end
153
- end
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
@@ -1,5 +1,5 @@
1
1
  module AMQ
2
2
  module Protocol
3
- VERSION = "0.7.0.beta2"
3
+ VERSION = "0.7.0"
4
4
  end # Protocol
5
5
  end # AMQ
@@ -84,7 +84,7 @@ module AMQ
84
84
  end # DATA.each
85
85
 
86
86
 
87
- it "is capable of decoding booleans" do
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: 62196439
5
- prerelease: 6
4
+ hash: 3
5
+ prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
9
  - 0
10
- - beta
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-14 00:00:00 Z
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: 25
100
+ hash: 3
96
101
  segments:
97
- - 1
98
- - 3
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.8.5
107
+ rubygems_version: 1.6.2
105
108
  signing_key:
106
109
  specification_version: 3
107
110
  summary: AMQP 0.9.1 encoder & decoder.