lib/amqp/client/table.rb in amqp-client-1.1.0 vs lib/amqp/client/table.rb in amqp-client-1.1.1

- old
+ new

@@ -4,19 +4,24 @@ class Client # Encode and decode an AMQP table to/from hash, only used internally # @api private module Table # Encodes a hash into a byte array + # @param hash [Hash] # @return [String] Byte array def self.encode(hash) - tbl = StringIO.new - hash.each do |k, v| + return "" if hash.empty? + + arr = [] + fmt = String.new + hash.each do |k, value| key = k.to_s - tbl.write [key.bytesize, key].pack("Ca*") - tbl.write encode_field(v) + arr.push(key.bytesize, key) + fmt << "Ca*" + encode_field(value, arr, fmt) end - tbl.string + arr.pack(fmt) end # Decodes an AMQP table into a hash # @return [Hash<String, Object>] def self.decode(bytes) @@ -25,56 +30,70 @@ while pos < bytes.bytesize key_len = bytes.getbyte(pos) pos += 1 key = bytes.byteslice(pos, key_len).force_encoding("utf-8") pos += key_len - rest = bytes.byteslice(pos, bytes.bytesize - pos) - len, value = decode_field(rest) + len, value = decode_field(bytes, pos) pos += len + 1 hash[key] = value end hash end # Encoding a single value in a table + # @return [nil] # @api private - def self.encode_field(value) + def self.encode_field(value, arr, fmt) case value when Integer if value > 2**31 - ["l", value].pack("a q>") + arr.push("l", value) + fmt << "aq>" else - ["I", value].pack("a l>") + arr.push("I", value) + fmt << "al>" end when Float - ["d", value].pack("a G") + arr.push("d", value) + fmt << "aG" when String - ["S", value.bytesize, value].pack("a L> a*") + arr.push("S", value.bytesize, value) + fmt << "aL>a*" when Time - ["T", value.to_i].pack("a Q>") + arr.push("T", value.to_i) + fmt << "aQ>" when Array - bytes = value.map { |e| encode_field(e) }.join - ["A", bytes.bytesize, bytes].pack("a L> a*") + value_arr = [] + value_fmt = String.new + value.each { |e| encode_field(e, value_arr, value_fmt) } + bytes = value_arr.pack(value_fmt) + arr.push("A", bytes.bytesize, bytes) + fmt << "aL>a*" when Hash bytes = Table.encode(value) - ["F", bytes.bytesize, bytes].pack("a L> a*") + arr.push("F", bytes.bytesize, bytes) + fmt << "aL>a*" when true - ["t", 1].pack("a C") + arr.push("t", 1) + fmt << "aC" when false - ["t", 0].pack("a C") + arr.push("t", 0) + fmt << "aC" when nil - ["V"].pack("a") + arr << "V" + fmt << "a" else raise ArgumentError, "unsupported table field type: #{value.class}" end + nil end # Decodes a single value - # @return [Array<Integer, Object>] Bytes read and the parsed object + # @return [Array<Integer, Object>] Bytes read and the parsed value # @api private - def self.decode_field(bytes) - type = bytes[0] - pos = 1 + def self.decode_field(bytes, pos) + type = bytes[pos] + pos += 1 case type when "S" len = bytes.byteslice(pos, 4).unpack1("L>") pos += 4 [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")] @@ -82,12 +101,14 @@ len = bytes.byteslice(pos, 4).unpack1("L>") pos += 4 [4 + len, decode(bytes.byteslice(pos, len))] when "A" len = bytes.byteslice(pos, 4).unpack1("L>") + pos += 4 + array_end = pos + len a = [] - while pos < len - length, value = decode_field(bytes.byteslice(pos, -1)) + while pos < array_end + length, value = decode_field(bytes, pos) pos += length + 1 a << value end [4 + len, a] when "t"