6PY2VELQNZ5LQUKWIYKEZ4QEDGKEN24ESFUIXUBCWFTU3GWIXSSQC # frozen_string_literal: truerequire 'cbor/item/unsigned_integer'require 'cbor/item/negative_integer'require 'cbor/item/byte_string'require 'cbor/item/text_string'require 'cbor/item/array'require 'cbor/item/map'require 'cbor/item/tagged'require 'cbor/item/simple'require 'cbor/item/half_float'require 'cbor/item/single_float'require 'cbor/item/double_float'require 'cbor/item/break'module CBOR# A stream of CBOR items; can be read from or written to.## Abstract class.class Streamattr_reader :rpos, :posdef initialize@rpos = 0@wpos = 0enddef decode(expected: nil)byte = readbyteunless expected.nil? || expected.member?(byte)raise decode_error("Unexpected item, expected byte in #{expected.inspect}")endcase bytewhen 0x00..0x1b then Item::UnsignedInteger.decode(self, byte)when 0x20..0x3b then Item::NegativeInteger.new(self, byte - 0x20)when 0x40..0x5f then Item::ByteString.decode(self, byte - 0x40)when 0x60..0x7f then Item::TextString.decode(self, byte - 0x60)when 0x80..0x9f then Item::Array.decode(self, byte - 0x80)when 0xa0..0xbf then Item::Map.decode(self, byte - 0xa0)when 0xc0..0xdb then Item::Tagged.decode(self, byte - 0xc0)when 0xe0..0xf8 then Item::Simple.decode(self, byte - 0xe0)when 0xf9 then Item::HalfFloat.decode(self)when 0xfa then Item::SingleFloat.decode(self)when 0xfb then Item::DoubleFloat.decode(self)when 0xff then Item::Break.decode(self)elseraise decode_error('Malformed item')endenddef <<(item)str = item.to_cborwrite(str)enddef readbytebyte = read(1)byte.bytes.firstenddef read_string(byte)length = read_uint(byte)read(length)enddef read_indefinite(expected: nil)items = []loop doit = decode(expected: expected)break if Item::Break === ititems << itenditemsenddef read_unpack1(nbytes, format)data = read(nbytes)@rpos += nbytesdata.unpack1(format)enddef read_uint(byte)case bytewhen 0x00..0x17 then bytewhen 0x18 then read_unpack1(1, 'C')when 0x19 then read_unpack1(2, 'S>')when 0x1a then read_unpack1(4, 'L>')when 0x1b then read_unpack1(8, 'Q>')endenddef read(_count)raise 'abstract'enddef write(_str)raise 'abstract'endprivatedef decode_error(msg)MalformedCBOR.new(msg, pos: @rpos)endend# Stream of CBOR items based on an IO object.class IOStream < Streamdef initialize(io)@io = iosuper()enddef read(count)str = @io.read(count)@rpos += countstrenddef write(str)@wpos += str.bytesize@io.write(str)endend# Stream of CBOR items based on a String object.class StringStream < Streamdef initialize(str)@str = strsuper()enddef read(count)substr = @str[@rpos, count]@rpos += substr.bytesizesubstrenddef write(substr)@wpos += substr.bytesize@str += substrendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# An unsigned integer between 0 and 2**64 - 1class UnsignedInteger < Datadef integer?trueenddef self.decode(stream, byte)n = stream.read_uint(byte)new(n)enddef to_cborencode_uint(0, @value)endendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# An UTF-8 encoded textclass TextString < Datadef initialize(value)text = value.encode(Encoding::UTF_8)super(text)endclass << selfdef decode(stream, byte)return decode_indefinite(stream) if byte == 0x1fstring = stream.read_string(byte)new(string)endprivatedef decode_indefinite(stream)chunks = stream.read_indefinite(expected: 0x60..0x7b)string = chunks.map(&:value).joinnew(string)endenddef to_cborencode_uint(3, @value.bytesize) + @value.bendendendend
# frozen_string_literal: truerequire 'base64'require 'bigdecimal'require 'json'require 'mail'require 'time'require 'cbor/stream'require 'cbor/item/data'require 'cbor/item/byte_string'require 'cbor/item/text_string'require 'cbor/item/array'module CBORmodule Item# Generic tagged item.class Tagged < Dataattr_reader :tag, :itemdef initialize(tag, item, value)@tag = tag@item = itemsuper(value)enddef to_cborencode_uint(6, tag) + @item.to_cborenddef to_json(*arg)case @tagwhen 21 then Base64.urlsafe_encode64(@value)when 22 then Base64.strict_encode64(@value)when 23 then @value.bytes.map { |b| b.to_s(16) }.joinelse@value.to_json(*arg)endendclass << selfdef decode(stream, byte)tag = stream.read_uint(byte)item = stream.decodevalue = decode_value(tag, item, stream.rpos)new(tag, item, value)enddef create_text_datetime(time)new(0, TextString.new(time.iso8601), time)enddef create_epoch_datetime(time)new(1, UnsignedInteger.new(time.to_i), time)enddef create_bignum(num)positive = num.negative? ? false : truen = positive ? num : -num + 1bytes = []while n.positive?n, b = n.divmod(256)bytes << bendstring = bytes.reverse.joinitem = ByteString.new(string)tag = positive ? 3 : 4new(tag, item, num)enddef create_decimal_fraction(bigdecimal)a, b = bigdecimal.to_s.split('e', 2)c = a.split('.', 2).lastexp = Integer(b) - c.lengthmant = Integer(c)item = Array.new([exp, mant])new(4, item, bigdecimal)enddef create_expconv_b64url(str)item = ByteString.new(str)new(21, item, str)enddef create_expconv_b64(str)item = ByteString.new(str)new(22, item, str)enddef create_expconv_b16(str)item = ByteString.new(str)new(23, item, str)enddef create_encoded_cbor(item)str = item.to_cbornew(24, ByteString.new(str), str)enddef create_uri(uri)str = uri.to_snew(32, TextString.new(str), uri)enddef create_b64url(binstr)str = Base64.urlsafe_encode64(binstr)new(33, TextString.new(str), uri)enddef create_b64(binstr)str = Base64.strict_encode64(binstr)new(34, TextString.new(str), uri)enddef create_mime_message(email)str = email.to_snew(35, TextString.new(str), uri)enddef create_self_described(item)new(0xd9_d9_f7, item, item)endprivatedef decode_value(tag, item, pos)case tagwhen 0 then decode_text_datetime(item, pos)when 1 then decode_epoch_datetime(item, pos)when 2 then decode_unsigned_bignum(item, pos)when 3 then decode_negative_bignum(item, pos)when 4 then decode_decimal_fraction(item, pos)when 5 then decode_bigfloat(item, pos)when 21 then decode_expected_conversion(item, pos)when 22 then decode_expected_conversion(item, pos)when 23 then decode_expected_conversion(item, pos)when 24 then decode_encoded_cbor(item, pos)when 32 then decode_uri(item, pos)when 33 then decode_b64url(item, pos)when 34 then decode_b64(item, pos)when 36 then decode_mime_message(item, pos)when 55_799 then decode_self_described_cbor(item, pos)elseitem.valueendenddef decode_text_datetime(item, pos)error!('Expected text string item', pos) unless TextString === itemTime.iso8601(item.value)enddef decode_epoch_datetime(item, pos)error!('Expected number item', pos) unless item.number?Time.at(item.value)enddef decode_unsigned_bignum(item, pos)error!('Expected byte string item', pos) unless ByteString === itemunpack_bignum(item.value)enddef decode_negative_bignum(item, pos)error!('Expected byte string item', pos) unless ByteString === item-1 - unpack_bignum(item.value)enddef decode_decimal_fraction(item, pos)exp, mant = unpack_fraction(item, pos)BigDecimal(mant) * BigDecimal(10)**BigDecimal(exp)enddef decode_bigfloat(item, pos)exp, mant = unpack_fraction(item, pos)BigDecimal(mant) * BigDecimal(2)**BigDecimal(exp)end# FIXME: should accept any item data :-(## https://www.rfc-editor.org/rfc/rfc8949.html#convexpectdef decode_expected_conversion(item, pos)error!('Expected byte string item', pos) unless ByteString === itemitem.valueend# FIXME: provide a way to decode embedded CBOR item.def decode_encoded_cbor(item, pos)error!('Expected byte string item', pos) unless ByteString === itemitem.valueenddef decode_uri(item, pos)error!('Expected text string item', pos) unless TextString === itemURI.parse(item.value)enddef decode_b64url(item, pos)error!('Expected text string item', pos) unless TextString === itemBase64.urlsafe_decode64(item.value)enddef decode_b64(item, pos)error!('Expected text string item', pos) unless TextString === itemBase64.strict_decode64(item.value)enddef decode_mime_message(item, pos)error!('Expected text string item', pos) unless TextString === itemMail.read(item.value)enddef decode_self_described_cbor(item, pos)error!('Expected data item', pos) unless Data === itemitemenddef unpack_fraction(item, pos)unless Array === item && item.value.size == 2 && item.value[0].integer? && item.value[1].integer?error!('Expected two elements array item', pos)endexp = item.value[0].valuemant = item.value[1].value[exp, mant]enddef unpack_bignum(str)res = 0exp = 0str.bytes.reverse.each do |b|res += b * 2**expexp += 8endresenddef error!(msg, pos)raise MalformedCBOR.new(msg, pos: pos)endendendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# A float with single precisionclass SingleFloat < Datadef number?trueenddef self.decode(stream)float = stream.read_unpack1(4, 'g')new(float)enddef to_cborencode_type(7, 26) + [@value].pack('g')endendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# Parent class for simple values.class Simple < Datadef self.decode(stream, byte)n = stream.read_uint(byte)case nwhen 20 then False.newwhen 21 then True.newwhen 22 then Null.newwhen 23 then Undefined.newelseMalformedCBORendenddef to_cborn = case @valuewhen FalseClass then 20when TrueClass then 21when NilClass then 22when :undefined then 23endencode_uint(7, n)endend# The boolean "false"class False < Simpledef initializesuper(false)endend# The boolean "true"class True < Simpledef initializesuper(true)endend# The "null" valueclass Null < Simpledef initializesuper(nil)endend# An undefined valueclass Undefined < Simpledef initializesuper(:undefined)endendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# A negative integer between -2**64 and -1class NegativeInteger < Datadef integer?trueenddef self.decode(stream, byte)n = stream.read_uint(byte)new(-1 - n)enddef to_cborencode_uint(1, -@value + 1)endendendend
# frozen_string_literal: truerequire 'cbor/error'require 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# A map/hash of CBOR data itemsclass Map < Dataclass << selfdef decode(stream, byte)items = byte == 0x1f ? decode_indefinite(stream) : decode_definite(stream, byte)raise decode_error('Odd number of items in map', pos: stream.rpos) unless items.size.even?new(items.each_slice(2).to_h)endprivatedef decode_definite(stream, byte)count = stream.read_uint(byte)(1..count).map { stream.decode }enddef decode_indefinite(stream)stream.read_indefiniteendenddef to_cborencode_uint(5, @value.size) + encode_pairs(@value)endprotecteddef encode_pairs(hash)pairs = hash.map { |k, v| [k.to_cbor, v.to_cbor] }sorted_pairs = pairs.sort_by(&:first)sorted_pairs.flatten.joinendendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'require 'cbor/half_float'module CBORmodule Item# A float with half precisionclass HalfFloat < Datadef number?trueenddef self.decode(stream)float = CBOR::HalfFloat.decode(stream.read(2))new(float)enddef to_cborencode_type(7, 25) + CBOR::HalfFloat.encode(@value)endendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# A float with double precisionclass DoubleFloat < Datadef number?trueenddef self.decode(stream)float = stream.read_unpack1(8, 'G')new(float)enddef to_cborencode_type(7, 27) + [@value].pack('G')endendendend
# frozen_string_literal: truemodule CBOR# Namespace for CBOR data items.module Item# Asbtract parent class for CBOR data itemsclass Dataattr_reader :valuedef initialize(value)@value = valueenddef integer?falseenddef number?integer?enddef break?falseendclass << selfdef decode(_stream, _first_byte)raise 'abstract'endendprotecteddef encode_type(major, additional)type = (major << 5) + additional[type].pack('C')enddef encode_uint(major, value)if value <= 0x17encode_type(major, value)elsif value < 256encode_type(major, 0x18) + [value].pack('C')elsif value < 2**16encode_type(major, 0x19) + [value].pack('S>')elsif value < 2**32encode_type(major, 0x1a) + [value].pack('L>')elseencode_type(major, 0x1b) + [value].pack('Q>')endenddef decode_error(message, pos:)MalformedCBOR.new(message, pos: pos)endendclass << selfdef from_object(obj)case objwhen ::Time, ::DateTime then Tagged.create_epoch_datetime(obj.to_time)when ::BigDecimal then Tagged.create_decimal_fraction(obj)when ::URI then Tagged.create_uri(obj)when ::Integer then create_integer(obj)when ::NilClass then Null.newwhen ::FalseClass then False.newwhen ::TrueClass then True.newwhen :undefined then Undefined.newwhen ::Array then Array.new(obj.map { |o| from_object(o) })when ::Hash then Map.new(obj.map { |k, v| [from_object(k), from_object(v)] }.to_h)when ::String then create_string(obj)when ::Float then create_float(obj)elseraise 'FIXME: implement'endendprivatedef create_float(float)[HalfFloat, SingleFloat].each do |klass|it = klass.new(float)s = StringStream.new(it.to_cbor)return it if s.decode.value == floatendDoubleFloat.new(float)enddef create_string(str)if str.encoding == Encoding::UTF_8TextString.new(str)elseByteString.new(str)endenddef create_integer(int)if int.abs >= 2**64Tagged.create_bignum(int)elsif int.negative?NegativeInteger.new(-int + 1)elseUnsignedInteger.new(int)endendendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# A binary string of bytesclass ByteString < Dataclass << selfdef decode(stream, byte)return decode_indefinite(stream) if byte == 0x1fstring = stream.read_string(byte)new(string)endprivatedef decode_indefinite(stream)chunks = stream.read_indefinite(expected: 0x40..0x5b)string = chunks.map(&:value).joinnew(string)endenddef to_cborencode_uint(2, @value.bytesize) + @valueendendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# Break code for indefinite length arrays, maps and strings.class Break < Datadef initializesuper(:break)enddef break?trueenddef self.decode(_stream)newendprotecteddef encode(stream)stream.write_uint(7, 31)endendendend
# frozen_string_literal: truerequire 'cbor/stream'require 'cbor/item/data'module CBORmodule Item# An array of CBOR data itemsclass Array < Dataclass << selfdef decode(stream, byte)return decode_indefinite(stream) if byte == 0x1fcount = stream.read_uint(byte)items = (1..count).map { stream.decode }new(items)endprivatedef decode_indefinite(stream)items = stream.read_indefinitenew(items)endenddef to_cborencode_uint(4, @value.size) + @value.map(&:to_cbor).joinendendendend
# frozen_string_literal: truemodule CBOR# Convert to and from half-precision floating point numbers.module HalfFloatclass << selfdef encode(float)[encode_to_uint16(float)].pack('S>')enddef decode(bytes)half = bytes.unpack1('S>')valu = (half & 0x7fff) << 13 | (half & 0x8000) << 16if (half & 0x7c00) != 0x7c00Math.ldexp(decode_single(valu), 112)elsedecode_single(valu | 0x7f800000)endendprivatedef encode_to_uint16(float)positive = float.phase.zero?if float.infinite?positive ? 0b0_11111_0000000000 : 0b1_11111_0000000000elsif float.zero?positive ? 0b0_00000_0000000000 : 0b1_00000_0000000000elsefrac, exp = Math.frexp(float)single = Math.ldexp(frac, exp - 112)valu = [single].pack('g').unpack1('L>')(valu & (0x8000 << 16)) >> 16 | (valu & (0x7fff << 13)) >> 13endenddef decode_single(single)[single].pack('L>').unpack1('g')endendendend
# frozen_string_literal: truemodule CBORclass Error < StandardErrorend# Decoded item is not well-formed.class MalformedCBOR < Errorattr_reader :posdef initialize(message, pos:)super(message)@pos = posendendend