lib/amqp/client/table.rb in amqp-client-1.0.0 vs lib/amqp/client/table.rb in amqp-client-1.0.1

- old
+ new

@@ -1,123 +1,134 @@ # frozen_string_literal: true module AMQP - # Encode/decode AMQP Tables - module Table - module_function + class Client + # Encode and decode an AMQP table to/from hash, only used internally + # @api private + module Table + module_function - def encode(hash) - tbl = "" - hash.each do |k, v| - key = k.to_s - tbl += [key.bytesize, key, encode_field(v)].pack("C a* a*") + # Encodes a hash into a byte array + # @return [String] Byte array + def encode(hash) + tbl = StringIO.new + hash.each do |k, v| + key = k.to_s + tbl.write [key.bytesize, key].pack("Ca*") + tbl.write encode_field(v) + end + tbl.string end - tbl - end - def decode(bytes) - h = {} - pos = 0 - - while pos < bytes.bytesize - key_len = bytes[pos].ord - 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) - pos += len + 1 - h[key] = value + # Decodes an AMQP table into a hash + # @return [Hash<String, Object>] + def decode(bytes) + hash = {} + pos = 0 + while pos < bytes.bytesize + key_len = bytes[pos].ord + 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) + pos += len + 1 + hash[key] = value + end + hash end - h - end - def encode_field(value) - case value - when Integer - if value > 2**31 - ["l", value].pack("a q>") - else - ["I", value].pack("a l>") + # Encoding a single value in a table + # @api private + def encode_field(value) + case value + when Integer + if value > 2**31 + ["l", value].pack("a q>") + else + ["I", value].pack("a l>") + end + when Float + ["d", value].pack("a G") + when String + ["S", value.bytesize, value].pack("a L> a*") + when Time + ["T", value.to_i].pack("a Q>") + when Array + bytes = value.map { |e| encode_field(e) }.join + ["A", bytes.bytesize, bytes].pack("a L> a*") + when Hash + bytes = Table.encode(value) + ["F", bytes.bytesize, bytes].pack("a L> a*") + when true + ["t", 1].pack("a C") + when false + ["t", 0].pack("a C") + when nil + ["V"].pack("a") + else raise ArgumentError, "unsupported table field type: #{value.class}" end - when Float - ["d", value].pack("a G") - when String - ["S", value.bytesize, value].pack("a L> a*") - when Time - ["T", value.to_i].pack("a Q>") - when Array - bytes = value.map { |e| encode_field(e) }.join - ["A", bytes.bytesize, bytes].pack("a L> a*") - when Hash - bytes = Table.encode(value) - ["F", bytes.bytesize, bytes].pack("a L> a*") - when true - ["t", 1].pack("a C") - when false - ["t", 0].pack("a C") - when nil - ["V"].pack("a") - else raise "unsupported table field type: #{value.class}" end - end - # returns [length of field including type, value of field] - def decode_field(bytes) - type = bytes[0] - 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")] - when "F" - 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>") - a = [] - while pos < len - length, value = decode_field(bytes.byteslice(pos, -1)) - pos += length + 1 - a << value + # Decodes a single value + # @return [Array<Integer, Object>] Bytes read and the parsed object + # @api private + def decode_field(bytes) + type = bytes[0] + 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")] + when "F" + 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>") + a = [] + while pos < len + length, value = decode_field(bytes.byteslice(pos, -1)) + pos += length + 1 + a << value + end + [4 + len, a] + when "t" + [1, bytes[pos].ord == 1] + when "b" + [1, bytes.byteslice(pos, 1).unpack1("c")] + when "B" + [1, bytes.byteslice(pos, 1).unpack1("C")] + when "s" + [2, bytes.byteslice(pos, 2).unpack1("s")] + when "u" + [2, bytes.byteslice(pos, 2).unpack1("S")] + when "I" + [4, bytes.byteslice(pos, 4).unpack1("l>")] + when "i" + [4, bytes.byteslice(pos, 4).unpack1("L>")] + when "l" + [8, bytes.byteslice(pos, 8).unpack1("q>")] + when "f" + [4, bytes.byteslice(pos, 4).unpack1("g")] + when "d" + [8, bytes.byteslice(pos, 8).unpack1("G")] + when "D" + scale = bytes[pos].ord + pos += 1 + value = bytes.byteslice(pos, 4).unpack1("L>") + d = value / 10**scale + [5, d] + when "x" + len = bytes.byteslice(pos, 4).unpack1("L>") + [4 + len, bytes.byteslice(pos, len)] + when "T" + [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))] + when "V" + [0, nil] + else raise ArgumentError, "unsupported table field type: #{type}" end - [4 + len, a] - when "t" - [1, bytes[pos].ord == 1] - when "b" - [1, bytes.byteslice(pos, 1).unpack1("c")] - when "B" - [1, bytes.byteslice(pos, 1).unpack1("C")] - when "s" - [2, bytes.byteslice(pos, 2).unpack1("s")] - when "u" - [2, bytes.byteslice(pos, 2).unpack1("S")] - when "I" - [4, bytes.byteslice(pos, 4).unpack1("l>")] - when "i" - [4, bytes.byteslice(pos, 4).unpack1("L>")] - when "l" - [8, bytes.byteslice(pos, 8).unpack1("q>")] - when "f" - [4, bytes.byteslice(pos, 4).unpack1("g")] - when "d" - [8, bytes.byteslice(pos, 8).unpack1("G")] - when "D" - scale = bytes[pos].ord - pos += 1 - value = bytes.byteslice(pos, 4).unpack1("L>") - d = value / 10**scale - [5, d] - when "x" - len = bytes.byteslice(pos, 4).unpack1("L>") - [4 + len, bytes.byteslice(pos, len)] - when "T" - [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))] - when "V" - [0, nil] - else raise "unsupported table field type: #{type}" end end end end