lib/ex_rlp/encode.ex

defprotocol ExRLP.Encode do
  def encode(value, options \\ [])
end

defimpl ExRLP.Encode, for: BitString do
  alias ExRLP.Utils

  @spec encode(ExRLP.t(), keyword()) :: binary()
  def encode(value, options \\ []) do
    value
    |> encode_item
    |> Utils.maybe_encode_hex(Keyword.get(options, :encoding, :binary))
  end

  @spec encode_item(binary()) :: binary()
  defp encode_item(<<byte>> = item) when byte_size(item) == 1 and byte < 128 do
    item
  end

  defp encode_item(item) when is_binary(item) and byte_size(item) < 56 do
    prefix = 128 + byte_size(item)

    <<prefix>> <> item
  end

  defp encode_item(item) do
    be_size = Utils.big_endian_size(item)
    byte_size = byte_size(be_size)

    <<183 + byte_size>> <> be_size <> item
  end
end

defimpl ExRLP.Encode, for: Integer do
  alias ExRLP.{Utils, Encode}

  @spec encode(ExRLP.t(), keyword()) :: binary()
  def encode(value, options \\ []) do
    value
    |> to_binary()
    |> Encode.encode()
    |> Utils.maybe_encode_hex(Keyword.get(options, :encoding, :binary))
  end

  @spec to_binary(integer()) :: binary()
  defp to_binary(0), do: ""

  defp to_binary(object) do
    :binary.encode_unsigned(object)
  end
end

defimpl ExRLP.Encode, for: List do
  alias ExRLP.{Utils, Encode}

  @spec encode([ExRLP.t()], keyword()) :: binary()
  def encode(values, options \\ []) do
    values
    |> encode_items("")
    |> Utils.maybe_encode_hex(Keyword.get(options, :encoding, :binary))
  end

  @spec encode_items([ExRLP.t()], binary()) :: binary()
  defp encode_items([], acc) do
    prefix_list(acc)
  end

  defp encode_items([item | tail], acc) do
    encoded_item = Encode.encode(item)

    encode_items(tail, acc <> encoded_item)
  end

  @spec prefix_list(binary()) :: binary()
  defp prefix_list(encoded_concat) when byte_size(encoded_concat) < 56 do
    size = byte_size(encoded_concat)

    <<192 + size>> <> encoded_concat
  end

  defp prefix_list(encoded_concat) do
    be_size = Utils.big_endian_size(encoded_concat)
    byte_size = byte_size(be_size)

    <<247 + byte_size>> <> be_size <> encoded_concat
  end
end