amqp-client 1.0.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,52 +3,109 @@
3
3
  module AMQP
4
4
  class Client
5
5
  # A message delivered from the broker
6
- # @!attribute channel
7
- # @return [Connection::Channel] The channel the message was deliviered to
8
- # @!attribute delivery_tag
9
- # @return [Integer] The delivery tag of the message, used for acknowledge or reject the message
10
- # @!attribute exchange_name
11
- # @return [String] Name of the exchange the message was published to
12
- # @!attribute routing_key
13
- # @return [String] The routing key the message was published with
14
- # @!attribute properties
15
- # @return [Properties]
16
- # @!attribute body
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
- # @!attribute reply_code
41
- # @return [Integer] Error code
42
- # @!attribute reply_text
43
- # @return [String] Description on why the message was returned
44
- # @!attribute exchange
45
- # @return [String] Name of the exchange the message was published to
46
- # @!attribute routing_key
47
- # @return [String] The routing key the message was published with
48
- # @!attribute properties
49
- # @return [Properties]
50
- # @!attribute body
51
- # @return [String] The message body
52
- ReturnMessage = Struct.new(:reply_code, :reply_text, :exchange, :routing_key, :properties, :body)
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
- # @!attribute content_type
9
- # @return [String] Content type of the message body
10
- # @!attribute content_encoding
11
- # @return [String] Content encoding of the body
12
- # @!attribute headers
13
- # @return [Hash<String, Object>] Custom headers
14
- # @!attribute delivery_mode
15
- # @return [Integer] 2 for persisted message, transient messages for all other values
16
- # @!attribute priority
17
- # @return [Integer] A priority of the message (between 0 and 255)
18
- # @!attribute correlation_id
19
- # @return [Integer] A correlation id, most often used used for RPC communication
20
- # @!attribute reply_to
21
- # @return [String] Queue to reply RPC responses to
22
- # @!attribute expiration
23
- # @return [Integer, String] Number of seconds the message will stay in the queue
24
- # @!attribute message_id
25
- # @return [String]
26
- # @!attribute timestamp
27
- # @return [Date] User-definable, but often used for the time the message was originally generated
28
- # @!attribute type
29
- # @return [String] User-definable, but can can indicate what kind of message this is
30
- # @!attribute user_id
31
- # @return [String] User-definable, but can be used to verify that this is the user that published the message
32
- # @!attribute app_id
33
- # @return [String] User-definable, but often indicates which app that generated the message
34
- Properties = Struct.new(:content_type, :content_encoding, :headers, :delivery_mode, :priority, :correlation_id,
35
- :reply_to, :expiration, :message_id, :timestamp, :type, :user_id, :app_id,
36
- keyword_init: true) do
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 = StringIO.new(String.new("S>", capacity: 35))
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
- priority.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
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
- self.expiration = expiration.to_s if expiration.is_a?(Integer)
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.string)
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
- h = new
159
- flags = bytes.unpack1("S>")
160
- pos = 2
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[pos].ord
229
+ len = bytes.getbyte(pos)
163
230
  pos += 1
164
- h[:content_type] = bytes.byteslice(pos, len).force_encoding("utf-8")
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[pos].ord
235
+ len = bytes.getbyte(pos)
169
236
  pos += 1
170
- h[:content_encoding] = bytes.byteslice(pos, len).force_encoding("utf-8")
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
- h[:headers] = Table.decode(bytes.byteslice(pos, len))
243
+ p.headers = Table.decode(bytes.byteslice(pos, len))
177
244
  pos += len
178
245
  end
179
246
  if (flags & 0x1000).positive?
180
- h[:delivery_mode] = bytes[pos].ord
247
+ p.delivery_mode = bytes.getbyte(pos)
181
248
  pos += 1
182
249
  end
183
250
  if (flags & 0x0800).positive?
184
- h[:priority] = bytes[pos].ord
251
+ p.priority = bytes.getbyte(pos)
185
252
  pos += 1
186
253
  end
187
254
  if (flags & 0x0400).positive?
188
- len = bytes[pos].ord
255
+ len = bytes.getbyte(pos)
189
256
  pos += 1
190
- h[:correlation_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
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[pos].ord
261
+ len = bytes.getbyte(pos)
195
262
  pos += 1
196
- h[:reply_to] = bytes.byteslice(pos, len).force_encoding("utf-8")
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[pos].ord
267
+ len = bytes.getbyte(pos)
201
268
  pos += 1
202
- h[:expiration] = bytes.byteslice(pos, len).force_encoding("utf-8")
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[pos].ord
273
+ len = bytes.getbyte(pos)
207
274
  pos += 1
208
- h[:message_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
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
- h[:timestamp] = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
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[pos].ord
283
+ len = bytes.getbyte(pos)
217
284
  pos += 1
218
- h[:type] = bytes.byteslice(pos, len).force_encoding("utf-8")
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[pos].ord
289
+ len = bytes.getbyte(pos)
223
290
  pos += 1
224
- h[:user_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
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[pos].ord
295
+ len = bytes.getbyte(pos)
229
296
  pos += 1
230
- h[:app_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
297
+ p.app_id = bytes.byteslice(pos, len).force_encoding("utf-8")
231
298
  end
232
- h
299
+ p
233
300
  end
234
301
  end
235
302
  end
@@ -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
- tbl = StringIO.new
14
- hash.each do |k, v|
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
- tbl.write [key.bytesize, key].pack("Ca*")
17
- tbl.write encode_field(v)
18
+ arr.push(key.bytesize, key)
19
+ fmt << "Ca*"
20
+ encode_field(value, arr, fmt)
18
21
  end
19
- tbl.string
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[pos].ord
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
- rest = bytes.byteslice(pos, bytes.bytesize - pos)
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
- ["l", value].pack("a q>")
49
+ arr.push("l", value)
50
+ fmt << "aq>"
47
51
  else
48
- ["I", value].pack("a l>")
52
+ arr.push("I", value)
53
+ fmt << "al>"
49
54
  end
50
55
  when Float
51
- ["d", value].pack("a G")
56
+ arr.push("d", value)
57
+ fmt << "aG"
52
58
  when String
53
- ["S", value.bytesize, value].pack("a L> a*")
59
+ arr.push("S", value.bytesize, value)
60
+ fmt << "aL>a*"
54
61
  when Time
55
- ["T", value.to_i].pack("a Q>")
62
+ arr.push("T", value.to_i)
63
+ fmt << "aQ>"
56
64
  when Array
57
- bytes = value.map { |e| encode_field(e) }.join
58
- ["A", bytes.bytesize, bytes].pack("a L> a*")
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
- ["F", bytes.bytesize, bytes].pack("a L> a*")
73
+ arr.push("F", bytes.bytesize, bytes)
74
+ fmt << "aL>a*"
62
75
  when true
63
- ["t", 1].pack("a C")
76
+ arr.push("t", 1)
77
+ fmt << "aC"
64
78
  when false
65
- ["t", 0].pack("a C")
79
+ arr.push("t", 0)
80
+ fmt << "aC"
66
81
  when nil
67
- ["V"].pack("a")
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 object
90
+ # @return [Array<Integer, Object>] Bytes read and the parsed value
74
91
  # @api private
75
- def decode_field(bytes)
76
- type = bytes[0]
77
- pos = 1
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 < len
91
- length, value = decode_field(bytes.byteslice(pos, -1))
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[pos].ord == 1]
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[pos].ord
136
+ scale = bytes.getbyte(pos)
118
137
  pos += 1
119
138
  value = bytes.byteslice(pos, 4).unpack1("L>")
120
139
  d = value / 10**scale