|
| 1 | +defmodule Protobuf.Wire do |
| 2 | + @moduledoc """ |
| 3 | + Utilities to convert data from wire format to protobuf and back. |
| 4 | + """ |
| 5 | + |
| 6 | + alias Protobuf.Wire.{Varint, Zigzag} |
| 7 | + |
| 8 | + require Logger |
| 9 | + |
| 10 | + @type proto_type :: |
| 11 | + :int32 |
| 12 | + | :int64 |
| 13 | + | :fixed32 |
| 14 | + | :fixed64 |
| 15 | + | :uint32 |
| 16 | + | :uint64 |
| 17 | + | :sfixed32 |
| 18 | + | :sfixed64 |
| 19 | + | :sint32 |
| 20 | + | :sint64 |
| 21 | + | :float |
| 22 | + | :double |
| 23 | + | :bool |
| 24 | + | :string |
| 25 | + | :bytes |
| 26 | + | {:enum, any} |
| 27 | + |
| 28 | + @type proto_float :: :infinity | :negative_infinity | :nan | float |
| 29 | + |
| 30 | + @type proto_value :: binary | integer | bool | proto_float | atom |
| 31 | + |
| 32 | + @sint32_range -0x80000000..0x7FFFFFFF |
| 33 | + @sint64_range -0x8000000000000000..0x7FFFFFFFFFFFFFFF |
| 34 | + @uint32_range 0..0xFFFFFFFF |
| 35 | + @uint64_range 0..0xFFFFFFFFFFFFFFFF |
| 36 | + |
| 37 | + @spec from_proto(proto_type, proto_value) :: iodata |
| 38 | + # Returns improper list, but still valid iodata. |
| 39 | + def from_proto(type, binary) when type in [:string, :bytes] do |
| 40 | + len = binary |> IO.iodata_length() |> Varint.encode() |
| 41 | + len ++ binary |
| 42 | + end |
| 43 | + |
| 44 | + def from_proto(:int32, n) when n in @sint32_range, do: Varint.encode(n) |
| 45 | + def from_proto(:int64, n) when n in @sint64_range, do: Varint.encode(n) |
| 46 | + def from_proto(:uint32, n) when n in @uint32_range, do: Varint.encode(n) |
| 47 | + def from_proto(:uint64, n) when n in @uint64_range, do: Varint.encode(n) |
| 48 | + |
| 49 | + def from_proto(:bool, true), do: Varint.encode(1) |
| 50 | + def from_proto(:bool, false), do: Varint.encode(0) |
| 51 | + |
| 52 | + def from_proto({:enum, enum}, key) when is_atom(key), do: Varint.encode(enum.value(key)) |
| 53 | + def from_proto({:enum, _}, n) when is_integer(n), do: Varint.encode(n) |
| 54 | + |
| 55 | + def from_proto(:float, :infinity), do: [0, 0, 128, 127] |
| 56 | + def from_proto(:float, :negative_infinity), do: [0, 0, 128, 255] |
| 57 | + def from_proto(:float, :nan), do: [0, 0, 192, 127] |
| 58 | + def from_proto(:float, n), do: <<n::32-float-little>> |
| 59 | + |
| 60 | + def from_proto(:double, :infinity), do: [0, 0, 0, 0, 0, 0, 240, 127] |
| 61 | + def from_proto(:double, :negative_infinity), do: [0, 0, 0, 0, 0, 0, 240, 255] |
| 62 | + def from_proto(:double, :nan), do: [1, 0, 0, 0, 0, 0, 248, 127] |
| 63 | + def from_proto(:double, n), do: <<n::64-float-little>> |
| 64 | + |
| 65 | + def from_proto(:sint32, n) when n in @sint32_range, do: Varint.encode(Zigzag.encode(n)) |
| 66 | + def from_proto(:sint64, n) when n in @sint64_range, do: Varint.encode(Zigzag.encode(n)) |
| 67 | + def from_proto(:fixed32, n) when n in @uint32_range, do: <<n::32-little>> |
| 68 | + def from_proto(:fixed64, n) when n in @uint64_range, do: <<n::64-little>> |
| 69 | + def from_proto(:sfixed32, n) when n in @sint32_range, do: <<n::32-signed-little>> |
| 70 | + def from_proto(:sfixed64, n) when n in @sint64_range, do: <<n::64-signed-little>> |
| 71 | + |
| 72 | + def from_proto(type, n) do |
| 73 | + raise Protobuf.TypeEncodeError, message: "#{inspect(n)} is invalid for type #{type}" |
| 74 | + end |
| 75 | + |
| 76 | + @spec to_proto(proto_type, binary | integer) :: proto_value |
| 77 | + def to_proto(type, val) when type in [:string, :bytes], do: val |
| 78 | + |
| 79 | + def to_proto(:int32, val) do |
| 80 | + <<n::signed-integer-32>> = <<val::32>> |
| 81 | + n |
| 82 | + end |
| 83 | + |
| 84 | + def to_proto(:int64, val) do |
| 85 | + <<n::signed-integer-64>> = <<val::64>> |
| 86 | + n |
| 87 | + end |
| 88 | + |
| 89 | + def to_proto(:uint32, val) do |
| 90 | + <<n::unsigned-integer-32>> = <<val::32>> |
| 91 | + n |
| 92 | + end |
| 93 | + |
| 94 | + def to_proto(:uint64, val) do |
| 95 | + <<n::unsigned-integer-64>> = <<val::64>> |
| 96 | + n |
| 97 | + end |
| 98 | + |
| 99 | + def to_proto(:bool, val), do: val != 0 |
| 100 | + |
| 101 | + def to_proto({:enum, enum}, val) do |
| 102 | + enum.key(val) |
| 103 | + rescue |
| 104 | + FunctionClauseError -> |
| 105 | + Logger.warn("unknown enum value #{val} when decoding for #{inspect(enum)}") |
| 106 | + val |
| 107 | + end |
| 108 | + |
| 109 | + def to_proto(:float, <<n::little-float-32>>), do: n |
| 110 | + # little endianness, should be 0b0_11111111_000000000... |
| 111 | + def to_proto(:float, <<0, 0, 0b1000_0000::8, 0b01111111::8>>), do: :infinity |
| 112 | + # little endianness, should be 0b1_11111111_000000000... |
| 113 | + def to_proto(:float, <<0, 0, 0b1000_0000::8, 0b11111111::8>>), do: :negative_infinity |
| 114 | + # should be 0b*_11111111_not_zero... |
| 115 | + def to_proto(:float, <<a::16, 1::1, b::7, _::1, 0b1111111::7>>) when a != 0 or b != 0, |
| 116 | + do: :nan |
| 117 | + |
| 118 | + def to_proto(:double, <<n::little-float-64>>), do: n |
| 119 | + # little endianness, should be 0b0_11111111111_000000000... |
| 120 | + def to_proto(:double, <<0::48, 0b1111::4, 0::4, 0b01111111::8>>), do: :infinity |
| 121 | + # little endianness, should be 0b1_11111111111_000000000... |
| 122 | + def to_proto(:double, <<0::48, 0b1111::4, 0::4, 0b11111111::8>>), do: :negative_infinity |
| 123 | + |
| 124 | + def to_proto(:double, <<a::48, 0b1111::4, b::4, _::1, 0b1111111::7>>) when a != 0 or b != 0, |
| 125 | + do: :nan |
| 126 | + |
| 127 | + def to_proto(type, val) when type in [:sint32, :sint64], do: Zigzag.decode(val) |
| 128 | + def to_proto(:fixed32, <<n::little-32>>), do: n |
| 129 | + def to_proto(:fixed64, <<n::little-64>>), do: n |
| 130 | + def to_proto(:sfixed32, <<n::little-signed-32>>), do: n |
| 131 | + def to_proto(:sfixed64, <<n::little-signed-64>>), do: n |
| 132 | + |
| 133 | + def to_proto(type, val) do |
| 134 | + raise Protobuf.DecodeError, message: "can't decode #{inspect(val)} into type #{type}" |
| 135 | + end |
| 136 | +end |
0 commit comments