class Aws::Cbor::Decoder
Pure Ruby implementation of CBOR Decoder
def decode
def decode return nil if @buffer.nil? || @buffer.empty? val = decode_item return val unless @pos != @buffer.size raise ExtraBytesError.new(@pos, @buffer.size) end
def decode_item
high level, generic decode. Based on the next type. Consumes and returns
def decode_item case (next_type = peek_type) when :array read_array.times.map { decode_item } when :map read_map.times.map { [read_string, decode_item] }.to_h when :indefinite_array read_start_indefinite_array value = [] value << decode_item until peek_type == :break_stop_code read_end_indefinite_collection value when :indefinite_map read_start_indefinite_map value = {} value[read_string] = decode_item until peek_type == :break_stop_code read_end_indefinite_collection value when :indefinite_binary_string read_info value = String.new value << read_binary_string until peek_type == :break_stop_code read_end_indefinite_collection value when :indefinite_string read_info value = String.new value << read_string until peek_type == :break_stop_code read_end_indefinite_collection value.force_encoding(Encoding::UTF_8) when :tag case (tag = read_tag) when TAG_TYPE_EPOCH item = decode_item Time.at(item) when TAG_TYPE_BIGNUM, TAG_TYPE_NEG_BIGNUM read_bignum(tag) when TAG_TYPE_BIGDEC read_big_decimal else Tagged.new(tag, decode_item) end when :break_stop_code raise UnexpectedBreakCodeError else send("read_#{next_type}") end end
def initialize(bytes)
def initialize(bytes) @buffer = bytes @pos = 0 end
def peek(n_bytes)
def peek(n_bytes) return @buffer[@pos, n_bytes] if (@pos + n_bytes) <= @buffer.bytesize raise OutOfBytesError.new(n_bytes, @buffer.bytesize - @pos) end
def peek_type
def peek_type ib = peek(1).ord add_info = ib & FIVE_BIT_MASK major_type = ib >> 5 case major_type when 0, 1 then :integer when 2 add_info == 31 ? :indefinite_binary_string : :binary_string when 3 add_info == 31 ? :indefinite_string : :string when 4 add_info == 31 ? :indefinite_array : :array when 5 add_info == 31 ? :indefinite_map : :map when 6 then :tag when 7 # simple or float case add_info when 20, 21 then :boolean when 22 then :nil when 23 then :undefined # for smithy, this should be parsed as nil when 25 then :half when 26 then :float when 27 then :double when 31 then :break_stop_code else :reserved_undefined end end end
def read_array
def read_array _major_type, add_info = read_info read_count(add_info) end
def read_big_decimal
an exponent e and a mantissa m
that contains exactly two integer numbers:
A decimal fraction or a bigfloat is represented as a tagged array
def read_big_decimal unless (s = read_array) == 2 raise Error, "Expected array of length 2 but length is: #{s}" end e = read_integer m = read_integer BigDecimal(m) * (BigDecimal(10)**BigDecimal(e)) end
def read_bignum(tag_value)
def read_bignum(tag_value) _major_type, add_info = read_info bstr = take(read_count(add_info)) v = bstr.bytes.inject(0) do |sum, b| sum <<= 8 sum + b end case tag_value when 2 then v when 3 then -1 - v else raise Error, 'Invalid Tag value for BigNum, ' \ "expected 2 or 3, got: #{tag_value}" end end
def read_binary_string
def read_binary_string _major_type, add_info = read_info take(read_count(add_info)).force_encoding(Encoding::BINARY) end
def read_boolean
def read_boolean _major_type, add_info = read_info case add_info when 20 then false when 21 then true else raise Error, 'Invalid Boolean simple type, expected add_info of 20 or 21, ' \ "got: #{add_info}" end end
def read_break_stop_code
def read_break_stop_code read_info :break_stop_code end
def read_count(add_info)
def read_count(add_info) case add_info when 0..23 then add_info when 24 then take(1).ord when 25 then take(2).unpack1('n') when 26 then take(4).unpack1('N') when 27 then take(8).unpack1('Q>') when 28 then take(16).unpack1('Q>') when 29 then take(32).unpack1('Q>') else raise UnexpectedAdditionalInformationError, add_info end end
def read_double
def read_double read_info take(8).unpack1('G') end
def read_end_indefinite_collection
def read_end_indefinite_collection read_info end
def read_float
def read_float read_info take(4).unpack1('g') end
def read_half
exponent - 5 bits
sign - 1 bit
format:
Support decoding only
16 bit IEEE 754 half-precision floats
def read_half read_info b16 = take(2).unpack1('n') exp = (b16 >> 10) & 0x1f mant = b16 & 0x3ff val = case exp when 0 Math.ldexp(mant, -24) when 31 mant.zero? ? Float::INFINITY : Float::NAN else # exp bias is 15, but to use ldexp we divide by 1024 (2^10) to get # exp-15-10 Math.ldexp(1024 + mant, exp - 25) end if (b16[15]).zero? val else -val end end
def read_info
def read_info ib = take(1).ord [ib >> 5, ib & FIVE_BIT_MASK] end
def read_integer
def read_integer major_type, add_info = read_info val = read_count(add_info) case major_type when 0 then val when 1 then -1 - val else raise Error, "Expected Integer (0,1) got major type: #{major_type}" end end
def read_map
def read_map _major_type, add_info = read_info read_count(add_info) end
def read_nil
def read_nil read_info nil end
def read_reserved_undefined
def read_reserved_undefined _major_type, add_info = read_info raise Error, "Undefined reserved additional information: #{add_info}" end
def read_start_indefinite_array
returns nothing but consumes and checks the type/info.
def read_start_indefinite_array read_info end
def read_start_indefinite_map
returns nothing but consumes and checks the type/info.
def read_start_indefinite_map read_info end
def read_string
def read_string _major_type, add_info = read_info take(read_count(add_info)).force_encoding(Encoding::UTF_8) end
def read_tag
def read_tag _major_type, add_info = read_info read_count(add_info) end
def read_undefined
def read_undefined read_info :undefined end
def take(n_bytes)
def take(n_bytes) opos = @pos @pos += n_bytes return @buffer[opos, n_bytes] if @pos <= @buffer.bytesize raise OutOfBytesError.new(n_bytes, @buffer.bytesize - @pos) end