class IcAgent::Candid

Constants

ALL_TYPES
MULTI_TYPES
PREFIX
RESULT_TYPE
SINGLE_TYPES

Public Class Methods

build_type(raw_table, table, entry) click to toggle source
# File lib/ic_agent/candid.rb, line 1417
def self.build_type(raw_table, table, entry)
  ty = entry[0]
  if ty == TypeIds::Vec
    if ty >= raw_table.length
      raise ValueError, 'type index out of range'
    end

    t = get_type(raw_table, table, entry[1])
    if t.nil?
      t = table[t]
    end
    return BaseTypes::vec(t)
  elsif ty == TypeIds::Opt
    if ty >= raw_table.length
      raise ValueError, 'type index out of range'
    end

    t = get_type(raw_table, table, entry[1])
    if t.nil?
      t = table[t]
    end
    return BaseTypes::opt(t)
  elsif ty == TypeIds::Record
    fields = {}
    entry[1].each do |hash, t|
      name = "_#{hash.to_s}_"
      if t >= raw_table.length
        raise ValueError, 'type index out of range'
      end

      temp = get_type(raw_table, table, t)
      fields[name] = temp
    end
    record = BaseTypes::record(fields)
    tup = record.try_as_tuple()
    if tup.is_a?(Array)
      return BaseTypes::tuple(*tup)
    else
      return record
    end
  elsif ty == TypeIds::Variant
    fields = {}
    entry[1].each do |hash, t|
      name = "_#{hash.to_s}_"
      if t >= raw_table.length
        raise ValueError, 'type index out of range'
      end

      temp = get_type(raw_table, table, t)
      fields[name] = temp
    end
    return BaseTypes::variant(fields)
  elsif ty == TypeIds::Func
    return BaseTypes::func([], [], [])
  elsif ty == TypeIds::Service
    return BaseTypes::service({})
  else
    raise ValueError, "Illegal op_code: #{ty}"
  end
end
decode(data, ret_types = nil) click to toggle source

@param [Object] data: decode a bytes value @param [nil] ret_types def decode(retTypes, data):

# File lib/ic_agent/candid.rb, line 1644
def self.decode(data, ret_types = nil)
  pipe = Pipe.new(data)
  if data.length < PREFIX.length
    raise ValueError, 'Message length smaller than prefix number'
  end

  prefix_buffer = safe_read(pipe, PREFIX.length).hex2str

  if prefix_buffer != PREFIX
    raise ValueError, "Wrong prefix:#{prefix_buffer}expected prefix: DIDL"
  end

  raw_table, raw_types = read_type_table(pipe)

  if ret_types
    if ret_types.class != Array
      ret_types = [ret_types]
    end
    if raw_types.length < ret_types.length
      raise ValueError, 'Wrong number of return value'
    end
  end

  table = []
  raw_table.length.times do
    table.append(BaseTypes.rec)
  end

  raw_table.each_with_index do |entry, i|
    t = build_type(raw_table, table, entry)
    table[i].fill(t)
  end

  types = []
  raw_types.each do |t|
    types.append(get_type(raw_table, table, t))
  end

  outputs = []
  types.each_with_index do |t, i|
    outputs.append({
                     'type' => t.name,
                     'value' => t.decode_value(pipe, types[i])
                   })
  end
  outputs
end
encode(params) click to toggle source

@param [Object] params = [{type, value}] @return data = b'DIDL' + len(params) + encoded types + encoded values

# File lib/ic_agent/candid.rb, line 1601
def self.encode(params)
  arg_types = []
  args = []
  params.each do |p|
    arg_types << p[:type]
    args << p[:value]
  end

  if arg_types.length != args.length
    raise ValueError, 'Wrong number of message arguments'
  end

  typetable = TypeTable.new

  arg_types.each do |item|
    item.build_type_table(typetable)
  end

  pre = unicode_to_hex(PREFIX)
  table = unicode_to_hex(typetable.encode())
  length = leb128_string_hex(args.length)

  typs = ''
  arg_types.each do |t|
    typs += unicode_to_hex(t.encode_type(typetable))
  end

  vals = ''
  args.each_with_index do |arg, i|
    t = arg_types[i]
    unless t.covariant(args[i])
      raise TypeError, "Invalid #{t.display} argument: #{args[i]}"
    end

    vals += unicode_to_hex(t.encode_value(args[i]))
  end

  return pre + table + length + typs + vals
end
get_type(raw_table, table, t) click to toggle source
# File lib/ic_agent/candid.rb, line 1544
def self.get_type(raw_table, table, t)
  if t < -24
    raise ValueError, 'not supported type'
  end

  if t < 0
    case t
    when -1
      return BaseTypes.null
    when -2
      return BaseTypes.bool
    when -3
      return BaseTypes.nat
    when -4
      return BaseTypes.int
    when -5
      return BaseTypes.nat8
    when -6
      return BaseTypes.nat16
    when -7
      return BaseTypes.nat32
    when -8
      return BaseTypes.nat64
    when -9
      return BaseTypes.int8
    when -10
      return BaseTypes.int16
    when -11
      return BaseTypes.int32
    when -12
      return BaseTypes.int64
    when -13
      return BaseTypes.float32
    when -14
      return BaseTypes.float64
    when -15
      return BaseTypes.text
    when -16
      return BaseTypes.reserved
    when -17
      return BaseTypes.empty
    when -24
      return BaseTypes.principal
    else
      raise ValueError, "Illegal op_code: #{t}"
    end
  end

  if t >= raw_table.length
    raise ValueError, 'type index out of range'
  end

  return table[t]
end
leb128_string_hex(p_str) click to toggle source
# File lib/ic_agent/candid.rb, line 1409
def self.leb128_string_hex(p_str)
  LEB128.encode_signed(p_str).string.to_hex
end
leb128i_decode(pipe) click to toggle source
# File lib/ic_agent/candid.rb, line 1379
def self.leb128i_decode(pipe)
  length = pipe.buffer.length
  count = 0
  (0...length).each do |i|
    count = i
    if pipe.buffer[i] < '80'   # 0x80
      if pipe.buffer[i] < '40' # 0x40
        return leb128u_decode(pipe)
      end

      break
    end
  end
  res = StringIO.new
  res.putc(safe_read(pipe, count + 1).hex)
  LEB128.decode_signed(res)
end
leb128u_decode(pipe) click to toggle source

through Pipe to decode bytes

# File lib/ic_agent/candid.rb, line 1368
def self.leb128u_decode(pipe)
  res = StringIO.new
  loop do
    byte = safe_read_byte(pipe)
    res.putc(byte.hex)
    break if byte < '80' || pipe.length.zero?
  end

  LEB128.decode_signed(res)
end
read_type_table(pipe) click to toggle source
# File lib/ic_agent/candid.rb, line 1478
def self.read_type_table(pipe)
  type_table = []

  type_table_len = leb128u_decode(pipe).to_i
  type_table_len.times do
    ty = leb128i_decode(pipe)

    if [TypeIds::Opt, TypeIds::Vec].include?(ty)
      t = leb128i_decode(pipe)
      type_table << [ty, t]
    elsif [TypeIds::Record, TypeIds::Variant].include?(ty)
      fields = []
      obj_length = leb128u_decode(pipe)
      prev_hash = -1

      obj_length.times do
        hash = leb128u_decode(pipe)

        if hash >= 2**32
          raise ValueError, 'field id out of 32-bit range'
        end

        if prev_hash.is_a?(Integer) && prev_hash >= hash
          raise ValueError, 'field id collision or not sorted'
        end

        prev_hash = hash
        t = leb128i_decode(pipe)
        fields << [hash, t]
      end

      type_table << [ty, fields]
    elsif ty == TypeIds::Func
      2.times do
        fun_len = leb128u_decode(pipe)
        fun_len.times { leb128i_decode(pipe) }
      end

      ann_len = leb128u_decode(pipe)
      safe_read(pipe, ann_len)
      type_table << [ty, nil]
    elsif ty == TypeIds::Service
      serv_len = leb128u_decode(pipe)

      serv_len.times do
        l = leb128u_decode(pipe)
        safe_read(pipe, l)
        leb128i_decode(pipe)
      end

      type_table << [ty, nil]
    else
      raise ValueError, "Illegal op_code: #{ty}"
    end
  end

  raw_list = []
  types_len = leb128u_decode(pipe).to_i

  types_len.times do
    raw_list << leb128i_decode(pipe)
  end

  [type_table, raw_list]
end
safe_read(pipe, num) click to toggle source
# File lib/ic_agent/candid.rb, line 1397
def self.safe_read(pipe, num)
  raise ArgumentError, 'unexpected end of buffer' if pipe.length < num

  pipe.read(num)
end
safe_read_byte(pipe) click to toggle source
# File lib/ic_agent/candid.rb, line 1403
def self.safe_read_byte(pipe)
  raise ArgumentError, 'unexpected end of buffer' if pipe.length < 1

  pipe.readbyte
end
unicode_to_hex(u_code) click to toggle source
# File lib/ic_agent/candid.rb, line 1413
def self.unicode_to_hex(u_code)
  u_code.to_hex
end