amqp-client 1.0.2 → 1.1.3
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/.github/workflows/main.yml +2 -2
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +29 -12
- data/CHANGELOG.md +22 -0
- data/Gemfile +4 -0
- data/README.md +62 -27
- data/Rakefile +3 -1
- data/amqp-client.gemspec +1 -1
- data/lib/amqp/client/channel.rb +11 -10
- data/lib/amqp/client/connection.rb +93 -56
- data/lib/amqp/client/{frames.rb → frame_bytes.rb} +34 -36
- data/lib/amqp/client/message.rb +101 -44
- data/lib/amqp/client/properties.rb +143 -76
- data/lib/amqp/client/table.rb +51 -32
- data/lib/amqp/client/version.rb +1 -1
- data/lib/amqp/client.rb +19 -7
- data/sig/amqp-client.rbs +264 -0
- metadata +5 -4
data/lib/amqp/client/message.rb
CHANGED
@@ -3,52 +3,109 @@
|
|
3
3
|
module AMQP
|
4
4
|
class Client
|
5
5
|
# A message delivered from the broker
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# @return [String] The message body
|
18
|
-
# @!attribute redelivered
|
19
|
-
# @return [Boolean] True if the message have been delivered before
|
20
|
-
# @!attribute consumer_tag
|
21
|
-
# @return [String] The tag of the consumer the message was deliviered to
|
22
|
-
# @return [nil] Nil if the message was polled and not deliviered to a consumer
|
23
|
-
Message =
|
24
|
-
Struct.new(:channel, :delivery_tag, :exchange_name, :routing_key, :properties, :body, :redelivered, :consumer_tag) do
|
25
|
-
# Acknowledge the message
|
26
|
-
# @return [nil]
|
27
|
-
def ack
|
28
|
-
channel.basic_ack(delivery_tag)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Reject the message
|
32
|
-
# @param requeue [Boolean] If true the message will be put back into the queue again, ready to be redelivered
|
33
|
-
# @return [nil]
|
34
|
-
def reject(requeue: false)
|
35
|
-
channel.basic_reject(delivery_tag, requeue: requeue)
|
36
|
-
end
|
6
|
+
class Message
|
7
|
+
# @api private
|
8
|
+
def initialize(channel, consumer_tag, delivery_tag, exchange, routing_key, redelivered)
|
9
|
+
@channel = channel
|
10
|
+
@consumer_tag = consumer_tag
|
11
|
+
@delivery_tag = delivery_tag
|
12
|
+
@exchange = exchange
|
13
|
+
@routing_key = routing_key
|
14
|
+
@redelivered = redelivered
|
15
|
+
@properties = nil
|
16
|
+
@body = ""
|
37
17
|
end
|
38
18
|
|
19
|
+
# The channel the message was deliviered to
|
20
|
+
# @return [Connection::Channel]
|
21
|
+
attr_reader :channel
|
22
|
+
|
23
|
+
# The tag of the consumer the message was deliviered to
|
24
|
+
# @return [String]
|
25
|
+
# @return [nil] If the message was polled and not deliviered to a consumer
|
26
|
+
attr_reader :consumer_tag
|
27
|
+
|
28
|
+
# The delivery tag of the message, used for acknowledge or reject the message
|
29
|
+
# @return [Integer]
|
30
|
+
attr_reader :delivery_tag
|
31
|
+
|
32
|
+
# Name of the exchange the message was published to
|
33
|
+
# @return [String]
|
34
|
+
attr_reader :exchange
|
35
|
+
|
36
|
+
# The routing key the message was published with
|
37
|
+
# @return [String]
|
38
|
+
attr_reader :routing_key
|
39
|
+
|
40
|
+
# True if the message have been delivered before
|
41
|
+
# @return [Boolean]
|
42
|
+
attr_reader :redelivered
|
43
|
+
|
44
|
+
# Message properties
|
45
|
+
# @return [Properties]
|
46
|
+
attr_accessor :properties
|
47
|
+
|
48
|
+
# The message body
|
49
|
+
# @return [String]
|
50
|
+
attr_accessor :body
|
51
|
+
|
52
|
+
# Acknowledge the message
|
53
|
+
# @return [nil]
|
54
|
+
def ack
|
55
|
+
@channel.basic_ack(@delivery_tag)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Reject the message
|
59
|
+
# @param requeue [Boolean] If true the message will be put back into the queue again, ready to be redelivered
|
60
|
+
# @return [nil]
|
61
|
+
def reject(requeue: false)
|
62
|
+
@channel.basic_reject(@delivery_tag, requeue: requeue)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @see #exchange
|
66
|
+
# @deprecated
|
67
|
+
# @!attribute [r] exchange_name
|
68
|
+
# @return [String]
|
69
|
+
def exchange_name
|
70
|
+
@exchange
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
39
74
|
# A published message returned by the broker due to some error
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
75
|
+
class ReturnMessage
|
76
|
+
# @api private
|
77
|
+
def initialize(reply_code, reply_text, exchange, routing_key)
|
78
|
+
@reply_code = reply_code
|
79
|
+
@reply_text = reply_text
|
80
|
+
@exchange = exchange
|
81
|
+
@routing_key = routing_key
|
82
|
+
@properties = nil
|
83
|
+
@body = ""
|
84
|
+
end
|
85
|
+
|
86
|
+
# Error code
|
87
|
+
# @return [Integer]
|
88
|
+
attr_reader :reply_code
|
89
|
+
|
90
|
+
# Description on why the message was returned
|
91
|
+
# @return [String]
|
92
|
+
attr_reader :reply_text
|
93
|
+
|
94
|
+
# Name of the exchange the message was published to
|
95
|
+
# @return [String]
|
96
|
+
attr_reader :exchange
|
97
|
+
|
98
|
+
# The routing key the message was published with
|
99
|
+
# @return [String]
|
100
|
+
attr_reader :routing_key
|
101
|
+
|
102
|
+
# Message properties
|
103
|
+
# @return [Properties]
|
104
|
+
attr_accessor :properties
|
105
|
+
|
106
|
+
# The message body
|
107
|
+
# @return [String]
|
108
|
+
attr_accessor :body
|
109
|
+
end
|
53
110
|
end
|
54
111
|
end
|
@@ -5,44 +5,111 @@ require_relative "./table"
|
|
5
5
|
module AMQP
|
6
6
|
class Client
|
7
7
|
# Encode/decode AMQP Properties
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
8
|
+
class Properties
|
9
|
+
# rubocop:disable Metrics/ParameterLists
|
10
|
+
def initialize(content_type: nil, content_encoding: nil, headers: nil, delivery_mode: nil,
|
11
|
+
priority: nil, correlation_id: nil, reply_to: nil, expiration: nil,
|
12
|
+
message_id: nil, timestamp: nil, type: nil, user_id: nil, app_id: nil)
|
13
|
+
@content_type = content_type
|
14
|
+
@content_encoding = content_encoding
|
15
|
+
@headers = headers
|
16
|
+
@delivery_mode = delivery_mode
|
17
|
+
@priority = priority
|
18
|
+
@correlation_id = correlation_id
|
19
|
+
@reply_to = reply_to
|
20
|
+
@expiration = expiration
|
21
|
+
@message_id = message_id
|
22
|
+
@timestamp = timestamp
|
23
|
+
@type = type
|
24
|
+
@user_id = user_id
|
25
|
+
@app_id = app_id
|
26
|
+
end
|
27
|
+
# rubocop:enable Metrics/ParameterLists
|
28
|
+
|
29
|
+
# Properties as a Hash
|
30
|
+
# @return [Hash] Properties
|
31
|
+
def to_h
|
32
|
+
{
|
33
|
+
content_type: content_type,
|
34
|
+
content_encoding: content_encoding,
|
35
|
+
headers: headers,
|
36
|
+
delivery_mode: delivery_mode,
|
37
|
+
priority: priority,
|
38
|
+
correlation_id: correlation_id,
|
39
|
+
reply_to: reply_to,
|
40
|
+
expiration: expiration,
|
41
|
+
message_id: message_id,
|
42
|
+
timestamp: timestamp,
|
43
|
+
type: type,
|
44
|
+
user_id: user_id,
|
45
|
+
app_id: app_id
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
alias to_hash to_h
|
50
|
+
|
51
|
+
# Returns true if two Property objects holds the same information
|
52
|
+
# @return [Boolean]
|
53
|
+
def ==(other)
|
54
|
+
return false unless other.is_a? self.class
|
55
|
+
|
56
|
+
instance_variables.all? { |v| instance_variable_get(v) == other.instance_variable_get(v) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Content type of the message body
|
60
|
+
# @return [String, nil]
|
61
|
+
attr_accessor :content_type
|
62
|
+
# Content encoding of the body
|
63
|
+
# @return [String, nil]
|
64
|
+
attr_accessor :content_encoding
|
65
|
+
# Headers, for applications and header exchange routing
|
66
|
+
# @return [Hash<String, Object>, nil]
|
67
|
+
attr_accessor :headers
|
68
|
+
# Message persistent level
|
69
|
+
# @note The exchange and queue have to durable as well for the message to be persistent
|
70
|
+
# @return [1] Transient message
|
71
|
+
# @return [2] Persistent message
|
72
|
+
# @return [nil] Not specified (implicitly transient)
|
73
|
+
attr_accessor :delivery_mode
|
74
|
+
# A priority of the message (between 0 and 255)
|
75
|
+
# @return [Integer, nil]
|
76
|
+
attr_accessor :priority
|
77
|
+
# Message correlation id, commonly used to correlate RPC requests and responses
|
78
|
+
# @return [String, nil]
|
79
|
+
attr_accessor :correlation_id
|
80
|
+
# Queue to reply RPC responses to
|
81
|
+
# @return [String, nil]
|
82
|
+
attr_accessor :reply_to
|
83
|
+
# Number of seconds the message will stay in the queue
|
84
|
+
# @return [String, nil]
|
85
|
+
attr_accessor :expiration
|
86
|
+
# Application message identifier
|
87
|
+
# @return [String, nil]
|
88
|
+
attr_accessor :message_id
|
89
|
+
# Message timestamp, often indicates when the message was originally generated
|
90
|
+
# @return [Date, nil]
|
91
|
+
attr_accessor :timestamp
|
92
|
+
# Message type name
|
93
|
+
# @return [String, nil]
|
94
|
+
attr_accessor :type
|
95
|
+
# The user that published the message
|
96
|
+
# @return [String, nil]
|
97
|
+
attr_accessor :user_id
|
98
|
+
# Name of application that generated the message
|
99
|
+
# @return [String, nil]
|
100
|
+
attr_accessor :app_id
|
101
|
+
|
37
102
|
# Encode properties into a byte array
|
103
|
+
# @param properties [Hash]
|
38
104
|
# @return [String] byte array
|
39
|
-
def encode
|
105
|
+
def self.encode(properties)
|
106
|
+
return "\x00\x00" if properties.empty?
|
107
|
+
|
40
108
|
flags = 0
|
41
109
|
arr = [flags]
|
42
|
-
fmt =
|
43
|
-
fmt.pos = 2
|
110
|
+
fmt = String.new("S>", capacity: 37)
|
44
111
|
|
45
|
-
if content_type
|
112
|
+
if (content_type = properties[:content_type])
|
46
113
|
content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
|
47
114
|
|
48
115
|
flags |= (1 << 15)
|
@@ -50,7 +117,7 @@ module AMQP
|
|
50
117
|
fmt << "Ca*"
|
51
118
|
end
|
52
119
|
|
53
|
-
if content_encoding
|
120
|
+
if (content_encoding = properties[:content_encoding])
|
54
121
|
content_encoding.is_a?(String) || raise(ArgumentError, "content_encoding must be a string")
|
55
122
|
|
56
123
|
flags |= (1 << 14)
|
@@ -58,7 +125,7 @@ module AMQP
|
|
58
125
|
fmt << "Ca*"
|
59
126
|
end
|
60
127
|
|
61
|
-
if headers
|
128
|
+
if (headers = properties[:headers])
|
62
129
|
headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
|
63
130
|
|
64
131
|
flags |= (1 << 13)
|
@@ -67,31 +134,31 @@ module AMQP
|
|
67
134
|
fmt << "L>a*"
|
68
135
|
end
|
69
136
|
|
70
|
-
if delivery_mode
|
137
|
+
if (delivery_mode = properties[:delivery_mode])
|
71
138
|
delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
|
72
|
-
delivery_mode.between?(0, 2) || raise(ArgumentError, "delivery_mode must be be between 0 and 2")
|
73
139
|
|
74
140
|
flags |= (1 << 12)
|
75
141
|
arr << delivery_mode
|
76
142
|
fmt << "C"
|
77
143
|
end
|
78
144
|
|
79
|
-
if priority
|
145
|
+
if (priority = properties[:priority])
|
80
146
|
priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
|
147
|
+
|
81
148
|
flags |= (1 << 11)
|
82
149
|
arr << priority
|
83
150
|
fmt << "C"
|
84
151
|
end
|
85
152
|
|
86
|
-
if correlation_id
|
87
|
-
|
153
|
+
if (correlation_id = properties[:correlation_id])
|
154
|
+
correlation_id.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
|
88
155
|
|
89
156
|
flags |= (1 << 10)
|
90
157
|
arr << correlation_id.bytesize << correlation_id
|
91
158
|
fmt << "Ca*"
|
92
159
|
end
|
93
160
|
|
94
|
-
if reply_to
|
161
|
+
if (reply_to = properties[:reply_to])
|
95
162
|
reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
|
96
163
|
|
97
164
|
flags |= (1 << 9)
|
@@ -99,8 +166,8 @@ module AMQP
|
|
99
166
|
fmt << "Ca*"
|
100
167
|
end
|
101
168
|
|
102
|
-
if expiration
|
103
|
-
|
169
|
+
if (expiration = properties[:expiration])
|
170
|
+
expiration = expiration.to_s if expiration.is_a?(Integer)
|
104
171
|
expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
|
105
172
|
|
106
173
|
flags |= (1 << 8)
|
@@ -108,7 +175,7 @@ module AMQP
|
|
108
175
|
fmt << "Ca*"
|
109
176
|
end
|
110
177
|
|
111
|
-
if message_id
|
178
|
+
if (message_id = properties[:message_id])
|
112
179
|
message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
|
113
180
|
|
114
181
|
flags |= (1 << 7)
|
@@ -116,7 +183,7 @@ module AMQP
|
|
116
183
|
fmt << "Ca*"
|
117
184
|
end
|
118
185
|
|
119
|
-
if timestamp
|
186
|
+
if (timestamp = properties[:timestamp])
|
120
187
|
timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
|
121
188
|
|
122
189
|
flags |= (1 << 6)
|
@@ -124,7 +191,7 @@ module AMQP
|
|
124
191
|
fmt << "Q>"
|
125
192
|
end
|
126
193
|
|
127
|
-
if type
|
194
|
+
if (type = properties[:type])
|
128
195
|
type.is_a?(String) || raise(ArgumentError, "type must be a string")
|
129
196
|
|
130
197
|
flags |= (1 << 5)
|
@@ -132,7 +199,7 @@ module AMQP
|
|
132
199
|
fmt << "Ca*"
|
133
200
|
end
|
134
201
|
|
135
|
-
if user_id
|
202
|
+
if (user_id = properties[:user_id])
|
136
203
|
user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
|
137
204
|
|
138
205
|
flags |= (1 << 4)
|
@@ -140,7 +207,7 @@ module AMQP
|
|
140
207
|
fmt << "Ca*"
|
141
208
|
end
|
142
209
|
|
143
|
-
if app_id
|
210
|
+
if (app_id = properties[:app_id])
|
144
211
|
app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
|
145
212
|
|
146
213
|
flags |= (1 << 3)
|
@@ -149,87 +216,87 @@ module AMQP
|
|
149
216
|
end
|
150
217
|
|
151
218
|
arr[0] = flags
|
152
|
-
arr.pack(fmt
|
219
|
+
arr.pack(fmt)
|
153
220
|
end
|
154
221
|
|
155
222
|
# Decode a byte array
|
156
223
|
# @return [Properties]
|
157
|
-
def self.decode(bytes)
|
158
|
-
|
159
|
-
flags = bytes.unpack1("S>")
|
160
|
-
pos
|
224
|
+
def self.decode(bytes, pos = 0)
|
225
|
+
p = new
|
226
|
+
flags = bytes.byteslice(pos, 2).unpack1("S>")
|
227
|
+
pos += 2
|
161
228
|
if (flags & 0x8000).positive?
|
162
|
-
len = bytes
|
229
|
+
len = bytes.getbyte(pos)
|
163
230
|
pos += 1
|
164
|
-
|
231
|
+
p.content_type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
165
232
|
pos += len
|
166
233
|
end
|
167
234
|
if (flags & 0x4000).positive?
|
168
|
-
len = bytes
|
235
|
+
len = bytes.getbyte(pos)
|
169
236
|
pos += 1
|
170
|
-
|
237
|
+
p.content_encoding = bytes.byteslice(pos, len).force_encoding("utf-8")
|
171
238
|
pos += len
|
172
239
|
end
|
173
240
|
if (flags & 0x2000).positive?
|
174
241
|
len = bytes.byteslice(pos, 4).unpack1("L>")
|
175
242
|
pos += 4
|
176
|
-
|
243
|
+
p.headers = Table.decode(bytes.byteslice(pos, len))
|
177
244
|
pos += len
|
178
245
|
end
|
179
246
|
if (flags & 0x1000).positive?
|
180
|
-
|
247
|
+
p.delivery_mode = bytes.getbyte(pos)
|
181
248
|
pos += 1
|
182
249
|
end
|
183
250
|
if (flags & 0x0800).positive?
|
184
|
-
|
251
|
+
p.priority = bytes.getbyte(pos)
|
185
252
|
pos += 1
|
186
253
|
end
|
187
254
|
if (flags & 0x0400).positive?
|
188
|
-
len = bytes
|
255
|
+
len = bytes.getbyte(pos)
|
189
256
|
pos += 1
|
190
|
-
|
257
|
+
p.correlation_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
191
258
|
pos += len
|
192
259
|
end
|
193
260
|
if (flags & 0x0200).positive?
|
194
|
-
len = bytes
|
261
|
+
len = bytes.getbyte(pos)
|
195
262
|
pos += 1
|
196
|
-
|
263
|
+
p.reply_to = bytes.byteslice(pos, len).force_encoding("utf-8")
|
197
264
|
pos += len
|
198
265
|
end
|
199
266
|
if (flags & 0x0100).positive?
|
200
|
-
len = bytes
|
267
|
+
len = bytes.getbyte(pos)
|
201
268
|
pos += 1
|
202
|
-
|
269
|
+
p.expiration = bytes.byteslice(pos, len).force_encoding("utf-8")
|
203
270
|
pos += len
|
204
271
|
end
|
205
272
|
if (flags & 0x0080).positive?
|
206
|
-
len = bytes
|
273
|
+
len = bytes.getbyte(pos)
|
207
274
|
pos += 1
|
208
|
-
|
275
|
+
p.message_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
209
276
|
pos += len
|
210
277
|
end
|
211
278
|
if (flags & 0x0040).positive?
|
212
|
-
|
279
|
+
p.timestamp = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
|
213
280
|
pos += 8
|
214
281
|
end
|
215
282
|
if (flags & 0x0020).positive?
|
216
|
-
len = bytes
|
283
|
+
len = bytes.getbyte(pos)
|
217
284
|
pos += 1
|
218
|
-
|
285
|
+
p.type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
219
286
|
pos += len
|
220
287
|
end
|
221
288
|
if (flags & 0x0010).positive?
|
222
|
-
len = bytes
|
289
|
+
len = bytes.getbyte(pos)
|
223
290
|
pos += 1
|
224
|
-
|
291
|
+
p.user_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
225
292
|
pos += len
|
226
293
|
end
|
227
294
|
if (flags & 0x0008).positive?
|
228
|
-
len = bytes
|
295
|
+
len = bytes.getbyte(pos)
|
229
296
|
pos += 1
|
230
|
-
|
297
|
+
p.app_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
231
298
|
end
|
232
|
-
|
299
|
+
p
|
233
300
|
end
|
234
301
|
end
|
235
302
|
end
|
data/lib/amqp/client/table.rb
CHANGED
@@ -5,32 +5,34 @@ module AMQP
|
|
5
5
|
# Encode and decode an AMQP table to/from hash, only used internally
|
6
6
|
# @api private
|
7
7
|
module Table
|
8
|
-
module_function
|
9
|
-
|
10
8
|
# Encodes a hash into a byte array
|
9
|
+
# @param hash [Hash]
|
11
10
|
# @return [String] Byte array
|
12
|
-
def encode(hash)
|
13
|
-
|
14
|
-
|
11
|
+
def self.encode(hash)
|
12
|
+
return "" if hash.empty?
|
13
|
+
|
14
|
+
arr = []
|
15
|
+
fmt = String.new
|
16
|
+
hash.each do |k, value|
|
15
17
|
key = k.to_s
|
16
|
-
|
17
|
-
|
18
|
+
arr.push(key.bytesize, key)
|
19
|
+
fmt << "Ca*"
|
20
|
+
encode_field(value, arr, fmt)
|
18
21
|
end
|
19
|
-
|
22
|
+
arr.pack(fmt)
|
20
23
|
end
|
21
24
|
|
22
25
|
# Decodes an AMQP table into a hash
|
23
26
|
# @return [Hash<String, Object>]
|
24
|
-
def decode(bytes)
|
27
|
+
def self.decode(bytes)
|
25
28
|
hash = {}
|
26
29
|
pos = 0
|
27
30
|
while pos < bytes.bytesize
|
28
|
-
key_len = bytes
|
31
|
+
key_len = bytes.getbyte(pos)
|
29
32
|
pos += 1
|
30
33
|
key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
|
31
34
|
pos += key_len
|
32
|
-
|
33
|
-
len, value = decode_field(rest)
|
35
|
+
len, value = decode_field(bytes, pos)
|
34
36
|
pos += len + 1
|
35
37
|
hash[key] = value
|
36
38
|
end
|
@@ -38,43 +40,58 @@ module AMQP
|
|
38
40
|
end
|
39
41
|
|
40
42
|
# Encoding a single value in a table
|
43
|
+
# @return [nil]
|
41
44
|
# @api private
|
42
|
-
def encode_field(value)
|
45
|
+
def self.encode_field(value, arr, fmt)
|
43
46
|
case value
|
44
47
|
when Integer
|
45
48
|
if value > 2**31
|
46
|
-
|
49
|
+
arr.push("l", value)
|
50
|
+
fmt << "aq>"
|
47
51
|
else
|
48
|
-
|
52
|
+
arr.push("I", value)
|
53
|
+
fmt << "al>"
|
49
54
|
end
|
50
55
|
when Float
|
51
|
-
|
56
|
+
arr.push("d", value)
|
57
|
+
fmt << "aG"
|
52
58
|
when String
|
53
|
-
|
59
|
+
arr.push("S", value.bytesize, value)
|
60
|
+
fmt << "aL>a*"
|
54
61
|
when Time
|
55
|
-
|
62
|
+
arr.push("T", value.to_i)
|
63
|
+
fmt << "aQ>"
|
56
64
|
when Array
|
57
|
-
|
58
|
-
|
65
|
+
value_arr = []
|
66
|
+
value_fmt = String.new
|
67
|
+
value.each { |e| encode_field(e, value_arr, value_fmt) }
|
68
|
+
bytes = value_arr.pack(value_fmt)
|
69
|
+
arr.push("A", bytes.bytesize, bytes)
|
70
|
+
fmt << "aL>a*"
|
59
71
|
when Hash
|
60
72
|
bytes = Table.encode(value)
|
61
|
-
|
73
|
+
arr.push("F", bytes.bytesize, bytes)
|
74
|
+
fmt << "aL>a*"
|
62
75
|
when true
|
63
|
-
|
76
|
+
arr.push("t", 1)
|
77
|
+
fmt << "aC"
|
64
78
|
when false
|
65
|
-
|
79
|
+
arr.push("t", 0)
|
80
|
+
fmt << "aC"
|
66
81
|
when nil
|
67
|
-
|
82
|
+
arr << "V"
|
83
|
+
fmt << "a"
|
68
84
|
else raise ArgumentError, "unsupported table field type: #{value.class}"
|
69
85
|
end
|
86
|
+
nil
|
70
87
|
end
|
71
88
|
|
72
89
|
# Decodes a single value
|
73
|
-
# @return [Array<Integer, Object>] Bytes read and the parsed
|
90
|
+
# @return [Array<Integer, Object>] Bytes read and the parsed value
|
74
91
|
# @api private
|
75
|
-
def decode_field(bytes)
|
76
|
-
type = bytes[
|
77
|
-
pos
|
92
|
+
def self.decode_field(bytes, pos)
|
93
|
+
type = bytes[pos]
|
94
|
+
pos += 1
|
78
95
|
case type
|
79
96
|
when "S"
|
80
97
|
len = bytes.byteslice(pos, 4).unpack1("L>")
|
@@ -86,15 +103,17 @@ module AMQP
|
|
86
103
|
[4 + len, decode(bytes.byteslice(pos, len))]
|
87
104
|
when "A"
|
88
105
|
len = bytes.byteslice(pos, 4).unpack1("L>")
|
106
|
+
pos += 4
|
107
|
+
array_end = pos + len
|
89
108
|
a = []
|
90
|
-
while pos <
|
91
|
-
length, value = decode_field(bytes
|
109
|
+
while pos < array_end
|
110
|
+
length, value = decode_field(bytes, pos)
|
92
111
|
pos += length + 1
|
93
112
|
a << value
|
94
113
|
end
|
95
114
|
[4 + len, a]
|
96
115
|
when "t"
|
97
|
-
[1, bytes
|
116
|
+
[1, bytes.getbyte(pos) == 1]
|
98
117
|
when "b"
|
99
118
|
[1, bytes.byteslice(pos, 1).unpack1("c")]
|
100
119
|
when "B"
|
@@ -114,7 +133,7 @@ module AMQP
|
|
114
133
|
when "d"
|
115
134
|
[8, bytes.byteslice(pos, 8).unpack1("G")]
|
116
135
|
when "D"
|
117
|
-
scale = bytes
|
136
|
+
scale = bytes.getbyte(pos)
|
118
137
|
pos += 1
|
119
138
|
value = bytes.byteslice(pos, 4).unpack1("L>")
|
120
139
|
d = value / 10**scale
|