Module: Arf::Proto

Defined in:
lib/arf/proto/map.rb,
lib/arf/proto/array.rb,
lib/arf/proto/bytes.rb,
lib/arf/proto/float.rb,
lib/arf/proto/types.rb,
lib/arf/proto/union.rb,
lib/arf/proto/scalar.rb,
lib/arf/proto/string.rb,
lib/arf/proto/struct.rb,
lib/arf/proto/boolean.rb,
lib/arf/proto/decoder.rb,
lib/arf/proto/encoder.rb,
lib/arf/proto/registry.rb

Defined Under Namespace

Modules: Registry

Constant Summary collapse

EMPTY_MAP_MASK =
0x01 << 4
ARRAY_EMPTY_MASK =
0x01 << 4
BYTES_EMPTY_MASK =
0x01 << 4
FLOAT64_MASK =
0x01 << 4
FLOAT_EMPTY_MASK =
0x01 << 5
FLOAT32_MIN_VALUE =
-3.4028235e38
FLOAT32_MAX_VALUE =
3.4028235e38
TYPE_VOID =
0b0000
TYPE_SCALAR =
0b0001
TYPE_BOOLEAN =
0b0010
TYPE_FLOAT =
0b0011
TYPE_STRING =
0b0100
TYPE_BYTES =
0b0101
TYPE_ARRAY =
0b0110
TYPE_MAP =
0b0111
TYPE_STRUCT =
0b1000
TYPE_UNION =
0b1001
ALL_PRIMITIVES =
[
  TYPE_VOID,
  TYPE_SCALAR,
  TYPE_BOOLEAN,
  TYPE_FLOAT,
  TYPE_STRING,
  TYPE_BYTES,
  TYPE_ARRAY,
  TYPE_MAP,
  TYPE_STRUCT,
  TYPE_UNION
].freeze
TYPE_NAME =
{
  TYPE_VOID => "Void",
  TYPE_SCALAR => "Scalar",
  TYPE_BOOLEAN => "Boolean",
  TYPE_FLOAT => "Float",
  TYPE_STRING => "String",
  TYPE_BYTES => "Bytes",
  TYPE_ARRAY => "Array",
  TYPE_MAP => "Map",
  TYPE_STRUCT => "Struct",
  TYPE_UNION => "Union"
}.freeze
SIMPLE_PRIMITIVES =
{
  void: TYPE_VOID,
  uint8: TYPE_SCALAR,
  uint16: TYPE_SCALAR,
  uint32: TYPE_SCALAR,
  uint64: TYPE_SCALAR,
  int8: TYPE_SCALAR,
  int16: TYPE_SCALAR,
  int32: TYPE_SCALAR,
  int64: TYPE_SCALAR,
  bool: TYPE_BOOLEAN,
  float32: TYPE_FLOAT,
  float64: TYPE_FLOAT,
  string: TYPE_STRING,
  bytes: TYPE_BYTES
}.freeze
NUMERIC_SIGNED_MASK =
0x01 << 4
NUMERIC_ZERO_MASK =
0x01 << 5
NUMERIC_NEGATIVE_MASK =
0x01 << 6
STRING_EMPTY_MASK =
0x01 << 4
BOOL_FLAG_MASK =
0x01 << 4

Class Method Summary collapse

Class Method Details

.decode(io) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/arf/proto/decoder.rb', line 5

def self.decode(io)
  type, header = read_type(io)
  case type
  when TYPE_VOID
    nil
  when TYPE_SCALAR
    decode_scalar(header, io)
  when TYPE_BOOLEAN
    decode_boolean(header, io)
  when TYPE_FLOAT
    decode_float(header, io)
  when TYPE_STRING
    decode_string(header, io)
  when TYPE_BYTES
    decode_bytes(header, io)
  when TYPE_ARRAY
    decode_array(header, io)
  when TYPE_MAP
    decode_map(header, io)
  when TYPE_STRUCT
    decode_struct(header, io)
  when TYPE_UNION
    decode_union(header, io)
  end
end

.decode_array(header, io) ⇒ Object



20
21
22
23
24
25
26
27
28
29
# File 'lib/arf/proto/array.rb', line 20

def self.decode_array(header, io)
  return [] if header.anybits?(ARRAY_EMPTY_MASK)

  len = decode_uint64(io)
  arr = []
  len.times do
    arr << decode(io)
  end
  arr
end

.decode_boolean(header, _io) ⇒ Object



13
# File 'lib/arf/proto/boolean.rb', line 13

def self.decode_boolean(header, _io) = header.allbits?(BOOL_FLAG_MASK)

.decode_bytes(header, io) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/arf/proto/bytes.rb', line 22

def self.decode_bytes(header, io)
  return "" if header.anybits?(BYTES_EMPTY_MASK)

  size = decode_uint64(io)
  data = StringIO.new
  until size.zero?
    read = io.readpartial(size)
    data.write(read)
    size -= read.length
  end
  data.string
end

.decode_float(header, io) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/arf/proto/float.rb', line 36

def self.decode_float(header, io)
  return 0.0 if header.anybits?(FLOAT_EMPTY_MASK)

  bits = header.nobits?(FLOAT64_MASK) ? 32 : 64
  data = io.read(bits / 8)
  if bits == 32
    data.unpack("L>").pack("L>").unpack1("g")
  else
    data.unpack("Q>").pack("Q>").unpack1("G")
  end
end

.decode_map(header, io) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/arf/proto/map.rb', line 34

def self.decode_map(header, io)
  return {} if header.anybits?(EMPTY_MAP_MASK)

  decode_uint64(io) # Discard full length

  pairs_len = decode_uint64(io)
  keys = []
  values = []

  pairs_len.times { keys << decode(io) }
  pairs_len.times { values << decode(io) }

  keys.zip(values).to_h
end

.decode_scalar(header, io) ⇒ Object



24
25
26
27
28
29
30
# File 'lib/arf/proto/scalar.rb', line 24

def self.decode_scalar(header, io)
  return 0 if header.anybits?(NUMERIC_ZERO_MASK)

  v = decode_uint64(io)
  v *= -1 if header.anybits?(NUMERIC_NEGATIVE_MASK)
  v
end

.decode_string(header, io) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/arf/proto/string.rb', line 23

def self.decode_string(header, io)
  return "" if header.anybits?(STRING_EMPTY_MASK)

  size = decode_uint64(io)
  data = StringIO.new
  until size.zero?
    read = io.readpartial(size)
    data.write(read)
    size -= read.length
  end
  data.string.encode("UTF-8")
end

.decode_struct(_header, io) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/arf/proto/struct.rb', line 58

def self.decode_struct(_header, io)
  id_type, id_header = read_type(io)
  if id_type != TYPE_STRING
    # :nocov:
    raise DecodeFailedError, "cannot decode struct: expected String, found #{TYPE_NAME[id_type]}"
    # :nocov:
  end

  struct_id = decode_string(id_header, io)
  bytes_len = decode_uint64(io)
  reader = IO::LimitReader.new(io, bytes_len)
  fields = {}
  loop do
    id = decode_uint64(reader)
    fields[id] = decode(reader)
  rescue EOFError
    break
  end

  meta_str = Registry.find(struct_id)
  raise UnknownMeessageError, "Unknown message ID #{struct_id}" unless meta_str

  inst = meta_str[:type].new
  inst.decode_fields(fields)
end

.decode_uint64(io) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/arf/proto/scalar.rb', line 42

def self.decode_uint64(io)
  x = 0
  s = 0
  b = 0
  loop do
    b = io.read(1).getbyte(0)
    return (x | (b << s)) if b < 0x80

    x |= ((b & 0x7f) << s)
    s += 7
  end
end

.decode_union(_header, io) ⇒ Object



17
18
19
20
21
22
23
# File 'lib/arf/proto/union.rb', line 17

def self.decode_union(_header, io)
  {
    union: true,
    id: decode_uint64(io),
    value: decode(io)
  }
end

.encode(value) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/arf/proto/encoder.rb', line 5

def self.encode(value)
  case value
  when NilClass
    [TYPE_VOID].pack("C*")
  when String, Symbol
    encode_string(value.to_s)
  when TrueClass, FalseClass
    encode_boolean(value)
  when Float
    if value.between?(FLOAT32_MIN_VALUE, FLOAT32_MAX_VALUE)
      encode_float32(value)
    else
      encode_float64(value)
    end
  when Integer
    encode_scalar(value, signed: value.negative?)
  when Array
    encode_array(value)
  when Hash
    encode_map(value)
  else
    unless value.class.ancestors.include? Arf::RPC::Struct
      raise InvalidEncodingTypeError, "Unable to encode value of type #{value.class.name}"
    end

    encode_struct(value)

  end
end

.encode_array(v) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/arf/proto/array.rb', line 7

def self.encode_array(v)
  b = IO::Buffer.new
  t = TYPE_ARRAY
  if v.empty?
    t |= ARRAY_EMPTY_MASK
    return b.write(t).string if v.empty?
  end

  b.write(t).write_raw(encode_uint64(v.length))
  v.each { b.write_raw(encode(_1)) }
  b.string
end

.encode_as(value, type) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/arf/proto/encoder.rb', line 35

def self.encode_as(value, type)
  return [TYPE_VOID].pack("C*") if value.nil?

  case type
  when :uint8, :uint16, :uint32, :uint64
    encode_scalar(value, signed: false)
  when :int8, :int16, :int32, :int64
    encode_scalar(value, signed: true)
  when :float32
    encode_float32(value)
  when :float64
    encode_float64(value)
  when :bool
    encode_boolean(value)
  when :string
    encode_string(value)
  when :bytes
    encode_bytes(value)
  else
    if type.is_a?(Arf::Types::MapType)
      encode_map(value)
    elsif type.is_a?(Arf::Types::ArrayType)
      encode_array(value)
    elsif type.is_a?(String) && value.class.ancestors.include?(Arf::RPC::Struct)
      encode_struct(value)
    elsif type.is_a?(Class) && type.ancestors.include?(Arf::RPC::Struct)
      encode_struct(value)
    elsif type.is_a?(Class) && type.ancestors.include?(Arf::RPC::Enum)
      encode_scalar(type.to_i(value), signed: false)
    else
      raise InvalidEncodingTypeError,
            "Unable to encode value of type #{value.class.name} (reported type is #{type.inspect})"
    end
  end
end

.encode_boolean(b) ⇒ Object



7
8
9
10
11
# File 'lib/arf/proto/boolean.rb', line 7

def self.encode_boolean(b)
  v = TYPE_BOOLEAN
  v |= BOOL_FLAG_MASK if b
  [v].pack("C*")
end

.encode_bytes(b) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/arf/proto/bytes.rb', line 7

def self.encode_bytes(b)
  v = TYPE_BYTES

  if b.empty?
    v |= BYTES_EMPTY_MASK
    return [v].pack("C*")
  end

  IO::Buffer.new
    .write(v)
    .write_raw(encode_uint64(b.length))
    .write_raw(b)
    .string
end

.encode_float32(value) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/arf/proto/float.rb', line 10

def self.encode_float32(value)
  t = TYPE_FLOAT
  if value.zero?
    t |= FLOAT_EMPTY_MASK
    return [t].pack("C*")
  end

  [
    [t].pack("C*"),
    [value].pack("g").unpack("N").pack("L>")
  ].join
end

.encode_float64(value) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/arf/proto/float.rb', line 23

def self.encode_float64(value)
  t = TYPE_FLOAT | FLOAT64_MASK
  if value.zero?
    t |= FLOAT_EMPTY_MASK
    return [t].pack("C*")
  end

  [
    [t].pack("C*"),
    [value].pack("G").unpack("Q>").pack("Q>")
  ].join
end

.encode_map(v) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/arf/proto/map.rb', line 7

def self.encode_map(v)
  t = TYPE_MAP
  if v.empty?
    t |= EMPTY_MAP_MASK
    return [t].pack("C*")
  end

  keys = []
  values = []
  v.each_pair do |key, value|
    keys << encode(key)
    values << encode(value)
  end

  encoded_len = encode_uint64(v.length)
  keys = keys.join
  values = values.join

  IO::Buffer.new
    .write(t)
    .write_raw(encode_uint64(keys.length + values.length + encoded_len.length))
    .write_raw(encoded_len)
    .write_raw(keys)
    .write_raw(values)
    .string
end

.encode_scalar(v, signed: false) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/arf/proto/scalar.rb', line 9

def self.encode_scalar(v, signed: false)
  type = TYPE_SCALAR
  type |= NUMERIC_SIGNED_MASK if signed
  type |= NUMERIC_ZERO_MASK if v.zero?
  if v.negative?
    type |= NUMERIC_NEGATIVE_MASK
    v *= -1
  end

  [
    [type].pack("C*"),
    v.zero? ? nil : encode_uint64(v)
  ].compact.join
end

.encode_string(s) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/arf/proto/string.rb', line 7

def self.encode_string(s)
  t = TYPE_STRING
  if s.nil? || s.empty?
    t |= STRING_EMPTY_MASK
    return [t].pack("C*")
  end

  s = s.to_s.encode("UTF-8")

  [
    [t].pack("C*"),
    encode_uint64(s.bytesize),
    s
  ].join
end

.encode_struct(v) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/arf/proto/struct.rb', line 37

def self.encode_struct(v)
  return encode_union(v) if v.union?

  struct_id = v.arf_struct_id
  fields = fields_from_struct(v)
  data = []
  fields.each do |f|
    data << encode_uint64(f[:id])
    data << encode_as(v.instance_variable_get("@#{f[:name]}"), f[:type])
  end

  payload = data.join

  [
    [TYPE_STRUCT].pack("C*"),
    encode_string(struct_id),
    encode_uint64(payload.length),
    payload
  ].join
end

.encode_uint64(v) ⇒ Object



32
33
34
35
36
37
38
39
40
# File 'lib/arf/proto/scalar.rb', line 32

def self.encode_uint64(v)
  bytes = []
  while v >= 0x80
    bytes << ((v & 0xFF) | 0x80)
    v >>= 7
  end
  bytes << v
  bytes.pack("C*")
end

.encode_union(v) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
# File 'lib/arf/proto/union.rb', line 5

def self.encode_union(v)
  selected = v.__arf_union_set_id
  f = fields_from_struct(v).find { _1[:id] == selected }
  payload = encode_as(v.instance_variable_get("@#{f[:name]}"), f[:type])

  [
    [TYPE_UNION].pack("C*"),
    encode_uint64(selected),
    payload
  ].join
end

.fields_from_struct(v) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/arf/proto/struct.rb', line 5

def self.fields_from_struct(v)
  base = v.is_a?(Class) ? v : v.class
  fields = []
  base.fields.each do |f|
    if f[:id].is_a? Integer
      fields << if f[:type].is_a?(Symbol) ||
                   f[:type].is_a?(Arf::Types::ArrayType) ||
                   f[:type].is_a?(Arf::Types::MapType)
                  f
                else
                  {
                    id: f[:id],
                    name: f[:name],
                    type: base.find_type(f[:type])
                  }
                end
    else
      raise UnsupportedNestedUnionError, "Nested unions are not supported" if base.union?

      union_type = base.find_type(f[:type])
      fields << {
        id: union_type.fields.first[:id],
        type: union_type,
        name: f[:name]
      }
    end
  end

  fields.sort_by! { _1[:id] }
  fields
end

.read_type(io) ⇒ Object

Raises:



59
60
61
62
63
64
65
# File 'lib/arf/proto/types.rb', line 59

def self.read_type(io)
  b = io.read(1).getbyte(0)
  decoded = b & 0xF
  raise UnknownTypeError, format("Unknown type 0x%02x", b) unless ALL_PRIMITIVES.include? decoded

  [decoded, b]
end