From 360278535e3e80cbb28148961e1c6f7459c7cce1 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 30 Apr 2018 20:33:56 +0300 Subject: [PATCH 1/4] remove protocol for Map --- .dialyzer.ignore-warnings | 1 + CHANGELOG.md | 2 + README.md | 69 +++++++++++++++------------------- lib/ex_rlp.ex | 9 ++--- lib/ex_rlp/decoder.ex | 24 ++---------- lib/ex_rlp/encoder.ex | 13 ------- mix.exs | 8 +++- test/ex_rlp/decoder_test.exs | 37 +++++++----------- test/ex_rlp/encoder_test.exs | 9 ----- test/ex_rlp/protocols_test.exs | 25 ++++++++++++ test/support/log_entry.ex | 34 +++++++++++++++++ 11 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 test/ex_rlp/protocols_test.exs create mode 100644 test/support/log_entry.ex diff --git a/.dialyzer.ignore-warnings b/.dialyzer.ignore-warnings index 0eefb9f..f5b42a1 100644 --- a/.dialyzer.ignore-warnings +++ b/.dialyzer.ignore-warnings @@ -17,3 +17,4 @@ :0: Unknown function 'Elixir.ExRLP.Encoder.Port':'__impl__'/1 :0: Unknown function 'Elixir.ExRLP.Encoder.Reference':'__impl__'/1 :0: Unknown function 'Elixir.ExRLP.Encoder.Tuple':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encoder.Map':'__impl__'/1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de7e0c..050a8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# 0.3.0 +* Remove protocols for Map because it overrides custom struct protocols. # 0.2.1 * Improve typespecs to allow for integers as a valid value to encode in RLP. # 0.2.0 diff --git a/README.md b/README.md index 7a9168b..1e1b9d7 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Add `:ex_rlp` as a dependency to your project's `mix.exs`: ```elixir defp deps do [ - {:ex_rlp, "~> 0.2.1"} + {:ex_rlp, "~> 0.3.0"} ] end ``` @@ -53,13 +53,13 @@ Use ExRLP.decode/1 method to decode a rlp encoded data. All items except lists a ## Examples - iex(1)> "83646f67" |> ExRLP.decode(:binary, encoding: :hex) + iex(1)> "83646f67" |> ExRLP.decode(encoding: :hex) "dog" - iex(2)> "8203e8" |> ExRLP.decode(:binary, encoding: :hex) |> :binary.decode_unsigned + iex(2)> "8203e8" |> ExRLP.decode(encoding: :hex) |> :binary.decode_unsigned 1000 - iex(3)> "c4c2c0c0c0" |> ExRLP.decode(:binary, encoding: :hex) + iex(3)> "c4c2c0c0c0" |> ExRLP.decode(encoding: :hex) [[[], []], []] ``` @@ -69,49 +69,42 @@ More examples can be found in test files. You can define protocols for encoding/decoding custom data types. -Custom protocols for Map have already been implemented in ExRLP: - ```elixir +defmodule ExRLP.LogEntry do + defstruct address: nil, topics: [], data: nil + + @type t :: %__MODULE__{ + address: EVM.address(), + topics: [integer()], + data: binary() + } + + @spec new(binary, [integer()], binary()) :: t() + def new(address, topics, data) do + %__MODULE__{ + address: address, + topics: topics, + data: data + } + end -defimpl ExRLP.Encoder, for: Map do - alias ExRLP.Encode - - def encode(map, _) do - map - |> Map.values - |> Encode.encode + def to_list(log) do + [log.address, log.topics, log.data] end end -defimpl ExRLP.Decoder, for: BitString do - alias ExRLP.Decode - - def decode(value, :map, options) do - keys = - options - |> Keyword.fetch!(:keys) - |> Enum.sort - - value - |> Decode.decode - |> Enum.with_index - |> Enum.reduce(%{}, fn({value, index}, acc) -> - key = keys |> Enum.at(index) - - acc |> Map.put(key, value) - end) +defimpl ExRLP.Encoder, for: ExRLP.LogEntry do + alias ExRLP.Encode + alias ExRLP.LogEntry - ... + @spec encode(LogEntry.t(), keyword()) :: binary() + def encode(log, options \\ []) do + log + |> LogEntry.to_list() + |> Encode.encode(Keyword.get(options, :encoding, :binary)) end end -``` -So now it's possible to encode/decode maps: -```elixir -iex(1)> %{name: "Vitalik", surname: "Buterin"} |> ExRLP.encode(encoding: :hex) -"d087566974616c696b874275746572696e" -iex(2)> "d087566974616c696b874275746572696e" |> ExRLP.decode(:map, keys: [:surname, :name], encoding: :hex) -%{name: "Vitalik", surname: "Buterin"} ``` ## Contributing diff --git a/lib/ex_rlp.ex b/lib/ex_rlp.ex index aaecd4f..bc9a47c 100644 --- a/lib/ex_rlp.ex +++ b/lib/ex_rlp.ex @@ -66,7 +66,7 @@ defmodule ExRLP do iex> ExRLP.decode(<<0x83, ?d, ?o, ?g>>) "dog" - iex> ExRLP.decode("83646f67", :binary, encoding: :hex) + iex> ExRLP.decode("83646f67", encoding: :hex) "dog" iex> ExRLP.decode(<<184, 60, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65>>) @@ -100,9 +100,8 @@ defmodule ExRLP do 15_000_000_000_000_000_000_000_000_000_000_000 """ @spec decode(binary()) :: t - @spec decode(binary(), atom()) :: t - @spec decode(binary(), atom(), keyword()) :: t - def decode(item, type \\ :binary, options \\ []) do - item |> Decoder.decode(type, options) + @spec decode(binary(), keyword()) :: t + def decode(item, options \\ []) do + item |> Decoder.decode(options) end end diff --git a/lib/ex_rlp/decoder.ex b/lib/ex_rlp/decoder.ex index 069de70..a5040d8 100644 --- a/lib/ex_rlp/decoder.ex +++ b/lib/ex_rlp/decoder.ex @@ -115,32 +115,14 @@ defmodule ExRLP.Decode do end defprotocol ExRLP.Decoder do - def decode(value, type \\ :binary, options \\ nil) + def decode(value, options \\ nil) end defimpl ExRLP.Decoder, for: BitString do alias ExRLP.Decode - @spec decode(binary(), atom(), keyword()) :: ExRLP.t() - def decode(value, type \\ :binary, options \\ []) - - def decode(value, :map, options) do - keys = - options - |> Keyword.get(:keys, []) - |> Enum.sort() - - value - |> Decode.decode(Keyword.get(options, :encoding, :binary)) - |> Enum.with_index() - |> Enum.reduce(%{}, fn {value, index}, acc -> - key = keys |> Enum.at(index) - - acc |> Map.put(key, value) - end) - end - - def decode(value, :binary, options) do + @spec decode(binary(), keyword()) :: ExRLP.t() + def decode(value, options) do value |> Decode.decode(Keyword.get(options, :encoding, :binary)) end end diff --git a/lib/ex_rlp/encoder.ex b/lib/ex_rlp/encoder.ex index a61da3c..0b9f1f4 100644 --- a/lib/ex_rlp/encoder.ex +++ b/lib/ex_rlp/encoder.ex @@ -114,16 +114,3 @@ defimpl ExRLP.Encoder, for: List do value |> Encode.encode(Keyword.get(options, :encoding, :binary)) end end - -defimpl ExRLP.Encoder, for: Map do - alias ExRLP.Encode - - @dialyzer {:nowarn_function, encode: 2} - - @spec encode(%{key: ExRLP.t()}, keyword()) :: binary() - def encode(map, options) do - map - |> Map.values() - |> Encode.encode(Keyword.get(options, :encoding, :binary)) - end -end diff --git a/mix.exs b/mix.exs index 4e20662..1637fbc 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ExRLP.Mixfile do def project do [ app: :ex_rlp, - version: "0.2.1", + version: "0.3.0", elixir: "~> 1.6", description: "Ethereum's Recursive Length Prefix (RLP) encoding", package: [ @@ -15,7 +15,8 @@ defmodule ExRLP.Mixfile do build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, deps: deps(), - dialyzer: [ignore_warnings: ".dialyzer.ignore-warnings"] + dialyzer: [ignore_warnings: ".dialyzer.ignore-warnings"], + elixirc_paths: elixirc_paths(Mix.env()) ] end @@ -30,4 +31,7 @@ defmodule ExRLP.Mixfile do {:dialyxir, "~> 0.5", only: [:dev], runtime: false} ] end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] end diff --git a/test/ex_rlp/decoder_test.exs b/test/ex_rlp/decoder_test.exs index ba6c897..b03e194 100644 --- a/test/ex_rlp/decoder_test.exs +++ b/test/ex_rlp/decoder_test.exs @@ -6,7 +6,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "80" expected_result = "" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -15,7 +15,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "00" expected_result = "\u0000" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -24,7 +24,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "01" expected_result = "\u0001" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -33,7 +33,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "7f" expected_result = "\u007F" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -42,7 +42,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "83646f67" expected_result = "dog" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -54,7 +54,7 @@ defmodule ExRLP.DecoderTest do expected_result = "Lorem ipsum dolor sit amet, consectetur adipisicing eli" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -66,7 +66,7 @@ defmodule ExRLP.DecoderTest do expected_result = "Lorem ipsum dolor sit amet, consectetur adipisicing elit" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -115,7 +115,7 @@ defmodule ExRLP.DecoderTest do "lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, " <> "metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat" - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -124,7 +124,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "cc83646f6783676f6483636174" expected_result = ["dog", "god", "cat"] - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -148,7 +148,7 @@ defmodule ExRLP.DecoderTest do "qwer" ] - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -210,7 +210,7 @@ defmodule ExRLP.DecoderTest do ["asdf", "qwer", "zxcv"] ] - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -219,7 +219,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "c4c2c0c0c0" expected_result = [[[], []], []] - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -228,7 +228,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "c7c0c1c0c3c0c1c0" expected_result = [[], [[]], [[], [[]]]] - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end @@ -245,16 +245,7 @@ defmodule ExRLP.DecoderTest do ["key4", "val4"] ] - result = rlp_binary |> Decoder.decode(:binary, encoding: :hex) - - assert result == expected_result - end - - test "decoders Map" do - rlp_binary = "da8b526f636b276e27526f6c6c85417972617487426164796b6f76" - expected_result = %{name: "Ayrat", surname: "Badykov", music: "Rock'n'Roll"} - - result = rlp_binary |> Decoder.decode(:map, keys: [:name, :surname, :music], encoding: :hex) + result = rlp_binary |> Decoder.decode(encoding: :hex) assert result == expected_result end diff --git a/test/ex_rlp/encoder_test.exs b/test/ex_rlp/encoder_test.exs index d150d92..3f9b6d4 100644 --- a/test/ex_rlp/encoder_test.exs +++ b/test/ex_rlp/encoder_test.exs @@ -378,13 +378,4 @@ defmodule ExRLP.EncoderTest do assert result == expected_result end - - test "encodes Map" do - map = %{name: "Ayrat", surname: "Badykov", music: "Rock'n'Roll"} - expected_result = "da8b526f636b276e27526f6c6c85417972617487426164796b6f76" - - result = map |> Encoder.encode(encoding: :hex) - - assert result == expected_result - end end diff --git a/test/ex_rlp/protocols_test.exs b/test/ex_rlp/protocols_test.exs new file mode 100644 index 0000000..fbc8388 --- /dev/null +++ b/test/ex_rlp/protocols_test.exs @@ -0,0 +1,25 @@ +defmodule ExRLP.ProtocolsTest do + use ExUnit.Case + alias ExRLP.LogEntry + + describe "encode/2" do + test "encodes custom struct" do + log_entry = + LogEntry.new( + <<15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 109, 45, 108, 123, 94, + 198>>, + [0, 0, 0], + <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255>> + ) + + expected_result = + <<248, 58, 148, 15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 109, 45, + 108, 123, 94, 198, 195, 128, 128, 128, 160, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255>> + + assert ExRLP.encode(log_entry) == expected_result + end + end +end diff --git a/test/support/log_entry.ex b/test/support/log_entry.ex new file mode 100644 index 0000000..87b67d9 --- /dev/null +++ b/test/support/log_entry.ex @@ -0,0 +1,34 @@ +defmodule ExRLP.LogEntry do + defstruct address: nil, topics: [], data: nil + + @type t :: %__MODULE__{ + address: EVM.address(), + topics: [integer()], + data: binary() + } + + @spec new(binary, [integer()], binary()) :: t() + def new(address, topics, data) do + %__MODULE__{ + address: address, + topics: topics, + data: data + } + end + + def to_list(log) do + [log.address, log.topics, log.data] + end +end + +defimpl ExRLP.Encoder, for: ExRLP.LogEntry do + alias ExRLP.Encode + alias ExRLP.LogEntry + + @spec encode(LogEntry.t(), keyword()) :: binary() + def encode(log, options \\ []) do + log + |> LogEntry.to_list() + |> Encode.encode(Keyword.get(options, :encoding, :binary)) + end +end From 972543ff2af73783f1f3a941bab20992c285a7ed Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 30 Apr 2018 20:38:31 +0300 Subject: [PATCH 2/4] fix credo warning --- test/support/log_entry.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/support/log_entry.ex b/test/support/log_entry.ex index 87b67d9..db0e0bb 100644 --- a/test/support/log_entry.ex +++ b/test/support/log_entry.ex @@ -1,4 +1,6 @@ defmodule ExRLP.LogEntry do + @moduledoc false + defstruct address: nil, topics: [], data: nil @type t :: %__MODULE__{ From 53ade86f8c2a509a874a807e4e974f2e6dba65a4 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 30 Apr 2018 23:36:14 +0300 Subject: [PATCH 3/4] rewrite encoding using only protocols --- .dialyzer.ignore-warnings | 26 ++-- lib/ex_rlp.ex | 12 +- lib/ex_rlp/{decoder.ex => decode.ex} | 19 +-- lib/ex_rlp/encode.ex | 89 ++++++++++++++ lib/ex_rlp/encoder.ex | 116 ------------------ lib/ex_rlp/utils.ex | 19 +++ .../{decoder_test.exs => decode_test.exs} | 32 ++--- .../{encoder_test.exs => encode_test.exs} | 58 ++++----- test/ex_rlp/protocols_test.exs | 29 +++-- test/support/log_entry.ex | 7 +- 10 files changed, 192 insertions(+), 215 deletions(-) rename lib/ex_rlp/{decoder.ex => decode.ex} (89%) create mode 100644 lib/ex_rlp/encode.ex delete mode 100644 lib/ex_rlp/encoder.ex create mode 100644 lib/ex_rlp/utils.ex rename test/ex_rlp/{decoder_test.exs => decode_test.exs} (90%) rename test/ex_rlp/{encoder_test.exs => encode_test.exs} (88%) diff --git a/.dialyzer.ignore-warnings b/.dialyzer.ignore-warnings index f5b42a1..bdf3301 100644 --- a/.dialyzer.ignore-warnings +++ b/.dialyzer.ignore-warnings @@ -1,20 +1,10 @@ # protocol warnings -:0: Unknown function 'Elixir.ExRLP.Decoder.Atom':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.Float':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.Function':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.Integer':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.List':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.Map':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.PID':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.Port':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.Reference':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Decoder.Tuple':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.Atom':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.Float':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.Function':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.PID':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.Port':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.Reference':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.Tuple':'__impl__'/1 -:0: Unknown function 'Elixir.ExRLP.Encoder.Map':'__impl__'/1 \ No newline at end of file +:0: Unknown function 'Elixir.ExRLP.Encode.Atom':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encode.Float':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encode.Function':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encode.Map':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encode.PID':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encode.Port':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encode.Reference':'__impl__'/1 +:0: Unknown function 'Elixir.ExRLP.Encode.Tuple':'__impl__'/1 diff --git a/lib/ex_rlp.ex b/lib/ex_rlp.ex index bc9a47c..5e78a97 100644 --- a/lib/ex_rlp.ex +++ b/lib/ex_rlp.ex @@ -1,9 +1,9 @@ defmodule ExRLP do - alias ExRLP.{Encoder, Decoder} + alias ExRLP.{Encode, Decode} @moduledoc File.read!("#{__DIR__}/../README.md") - @type t :: nil | binary() | integer() | [t] + @type t :: any() @doc """ Given an RLP structure, returns the encoding as a string. @@ -51,8 +51,8 @@ defmodule ExRLP do """ @spec encode(t) :: binary() @spec encode(t, keyword()) :: binary() - def encode(item, options \\ []) do - item |> Encoder.encode(options) + def encode(item, options \\ [encoding: :binary]) do + item |> Encode.encode(options) end @doc """ @@ -101,7 +101,7 @@ defmodule ExRLP do """ @spec decode(binary()) :: t @spec decode(binary(), keyword()) :: t - def decode(item, options \\ []) do - item |> Decoder.decode(options) + def decode(item, options \\ [encoding: :binary]) do + item |> Decode.decode(options) end end diff --git a/lib/ex_rlp/decoder.ex b/lib/ex_rlp/decode.ex similarity index 89% rename from lib/ex_rlp/decoder.ex rename to lib/ex_rlp/decode.ex index a5040d8..dc225b0 100644 --- a/lib/ex_rlp/decoder.ex +++ b/lib/ex_rlp/decode.ex @@ -1,10 +1,10 @@ defmodule ExRLP.Decode do @moduledoc false - @spec decode(binary(), :binary | :hex) :: ExRLP.t() - def decode(item, encoding) when is_binary(item) do + @spec decode(binary(), keyword()) :: ExRLP.t() + def decode(item, options \\ []) when is_binary(item) do item - |> maybe_decode_hex(encoding) + |> maybe_decode_hex(Keyword.get(options, :encoding, :binary)) |> decode_item end @@ -113,16 +113,3 @@ defmodule ExRLP.Decode do decoded_binary end end - -defprotocol ExRLP.Decoder do - def decode(value, options \\ nil) -end - -defimpl ExRLP.Decoder, for: BitString do - alias ExRLP.Decode - - @spec decode(binary(), keyword()) :: ExRLP.t() - def decode(value, options) do - value |> Decode.decode(Keyword.get(options, :encoding, :binary)) - end -end diff --git a/lib/ex_rlp/encode.ex b/lib/ex_rlp/encode.ex new file mode 100644 index 0000000..e320a30 --- /dev/null +++ b/lib/ex_rlp/encode.ex @@ -0,0 +1,89 @@ +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(<> = 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) + + <> <> item + end + + defp encode_item(item) when is_binary(item) do + be_size = item |> Utils.big_endian_size() + byte_size = be_size |> byte_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 \\ []) when value >= 0 do + value + |> to_binary() + |> Encode.encode() + |> Utils.maybe_encode_hex(Keyword.get(options, :encoding, :binary)) + end + + @spec to_binary(integer()) :: binary() + defp to_binary(object) when is_integer(object) and object == 0 do + "" + end + + defp to_binary(object) when is_integer(object) and object > 0 do + object |> :binary.encode_unsigned() + 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 + acc |> prefix_list + end + + defp encode_items([item | tail], acc) do + encoded_item = item |> Encode.encode() + + tail |> encode_items(acc <> encoded_item) + end + + @spec prefix_list(binary()) :: binary() + defp prefix_list(encoded_concat) when byte_size(encoded_concat) < 56 do + size = encoded_concat |> byte_size + + <<192 + size>> <> encoded_concat + end + + defp prefix_list(encoded_concat) do + be_size = encoded_concat |> Utils.big_endian_size() + byte_size = be_size |> byte_size + + <<247 + byte_size>> <> be_size <> encoded_concat + end +end diff --git a/lib/ex_rlp/encoder.ex b/lib/ex_rlp/encoder.ex deleted file mode 100644 index 0b9f1f4..0000000 --- a/lib/ex_rlp/encoder.ex +++ /dev/null @@ -1,116 +0,0 @@ -defmodule ExRLP.Encode do - @moduledoc false - - @spec encode(ExRLP.t(), :binary | :hex) :: binary() - def encode(item, encoding) do - item - |> encode_item - |> maybe_encode_hex(encoding) - end - - @spec maybe_encode_hex(binary(), atom()) :: binary() - def maybe_encode_hex(value, :binary), do: value - def maybe_encode_hex(value, :hex), do: encode_hex(value) - - @spec encode_item(binary()) :: binary() - defp encode_item(<> = 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) - - <> <> item - end - - defp encode_item(item) when is_binary(item) do - be_size = item |> big_endian_size - byte_size = be_size |> byte_size - - <<183 + byte_size>> <> be_size <> item - end - - defp encode_item(items) when is_list(items) do - encoded_concat = - items - |> Enum.reduce("", fn item, acc -> - encoded_item = item |> encode_item - - acc <> encoded_item - end) - - encoded_concat |> prefix_list - end - - defp encode_item(item) do - item - |> encode_binary - |> encode_item - end - - @spec prefix_list(binary()) :: binary() - defp prefix_list(encoded_concat) when byte_size(encoded_concat) < 56 do - size = encoded_concat |> byte_size - - <<192 + size>> <> encoded_concat - end - - defp prefix_list(encoded_concat) do - be_size = encoded_concat |> big_endian_size - byte_size = be_size |> byte_size - - <<247 + byte_size>> <> be_size <> encoded_concat - end - - @spec big_endian_size(binary()) :: bitstring() - defp big_endian_size(binary) do - binary - |> byte_size - |> :binary.encode_unsigned() - end - - @spec encode_hex(binary()) :: binary() - defp encode_hex(binary) do - binary |> Base.encode16(case: :lower) - end - - @spec encode_binary(integer()) :: binary() - defp encode_binary(object) when is_integer(object) and object == 0 do - "" - end - - defp encode_binary(object) when is_integer(object) and object > 0 do - object |> :binary.encode_unsigned() - end -end - -defprotocol ExRLP.Encoder do - def encode(value, options \\ nil) -end - -defimpl ExRLP.Encoder, for: BitString do - alias ExRLP.Encode - - @spec encode(ExRLP.t(), keyword()) :: binary() - def encode(value, options) do - value |> Encode.encode(Keyword.get(options, :encoding, :binary)) - end -end - -defimpl ExRLP.Encoder, for: Integer do - alias ExRLP.Encode - - @spec encode(ExRLP.t(), keyword()) :: binary() - def encode(value, options) when value >= 0 do - value |> Encode.encode(Keyword.get(options, :encoding, :binary)) - end -end - -defimpl ExRLP.Encoder, for: List do - alias ExRLP.Encode - - @spec encode(ExRLP.t(), keyword()) :: binary() - def encode(value, options) do - value |> Encode.encode(Keyword.get(options, :encoding, :binary)) - end -end diff --git a/lib/ex_rlp/utils.ex b/lib/ex_rlp/utils.ex new file mode 100644 index 0000000..3335d2e --- /dev/null +++ b/lib/ex_rlp/utils.ex @@ -0,0 +1,19 @@ +defmodule ExRLP.Utils do + @moduledoc false + + @spec maybe_encode_hex(binary(), atom()) :: binary() + def maybe_encode_hex(value, :hex), do: encode_hex(value) + def maybe_encode_hex(value, _encoding), do: value + + @spec encode_hex(binary()) :: binary() + def encode_hex(binary) do + binary |> Base.encode16(case: :lower) + end + + @spec big_endian_size(binary()) :: bitstring() + def big_endian_size(binary) do + binary + |> byte_size + |> :binary.encode_unsigned() + end +end diff --git a/test/ex_rlp/decoder_test.exs b/test/ex_rlp/decode_test.exs similarity index 90% rename from test/ex_rlp/decoder_test.exs rename to test/ex_rlp/decode_test.exs index b03e194..41ded2c 100644 --- a/test/ex_rlp/decoder_test.exs +++ b/test/ex_rlp/decode_test.exs @@ -1,12 +1,12 @@ -defmodule ExRLP.DecoderTest do +defmodule ExRLP.DecodeTest do use ExUnit.Case - alias ExRLP.Decoder + alias ExRLP.Decode test "decodes empty string" do rlp_binary = "80" expected_result = "" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -15,7 +15,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "00" expected_result = "\u0000" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -24,7 +24,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "01" expected_result = "\u0001" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -33,7 +33,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "7f" expected_result = "\u007F" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -42,7 +42,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "83646f67" expected_result = "dog" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -54,7 +54,7 @@ defmodule ExRLP.DecoderTest do expected_result = "Lorem ipsum dolor sit amet, consectetur adipisicing eli" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -66,7 +66,7 @@ defmodule ExRLP.DecoderTest do expected_result = "Lorem ipsum dolor sit amet, consectetur adipisicing elit" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -115,7 +115,7 @@ defmodule ExRLP.DecoderTest do "lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, " <> "metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat" - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -124,7 +124,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "cc83646f6783676f6483636174" expected_result = ["dog", "god", "cat"] - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -148,7 +148,7 @@ defmodule ExRLP.DecoderTest do "qwer" ] - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -210,7 +210,7 @@ defmodule ExRLP.DecoderTest do ["asdf", "qwer", "zxcv"] ] - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -219,7 +219,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "c4c2c0c0c0" expected_result = [[[], []], []] - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -228,7 +228,7 @@ defmodule ExRLP.DecoderTest do rlp_binary = "c7c0c1c0c3c0c1c0" expected_result = [[], [[]], [[], [[]]]] - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end @@ -245,7 +245,7 @@ defmodule ExRLP.DecoderTest do ["key4", "val4"] ] - result = rlp_binary |> Decoder.decode(encoding: :hex) + result = rlp_binary |> Decode.decode(encoding: :hex) assert result == expected_result end diff --git a/test/ex_rlp/encoder_test.exs b/test/ex_rlp/encode_test.exs similarity index 88% rename from test/ex_rlp/encoder_test.exs rename to test/ex_rlp/encode_test.exs index 3f9b6d4..f4b5e3c 100644 --- a/test/ex_rlp/encoder_test.exs +++ b/test/ex_rlp/encode_test.exs @@ -1,12 +1,12 @@ -defmodule ExRLP.EncoderTest do +defmodule ExRLP.EncodeTest do use ExUnit.Case - alias ExRLP.Encoder + alias ExRLP.Encode test "encodes empty string" do string = "" expected_result = "80" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -15,7 +15,7 @@ defmodule ExRLP.EncoderTest do string = "\u0000" expected_result = "00" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -24,7 +24,7 @@ defmodule ExRLP.EncoderTest do string = "\u0001" expected_result = "01" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -33,7 +33,7 @@ defmodule ExRLP.EncoderTest do string = "\u007F" expected_result = "7f" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -42,7 +42,7 @@ defmodule ExRLP.EncoderTest do string = "dog" expected_result = "83646f67" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -54,7 +54,7 @@ defmodule ExRLP.EncoderTest do "b74c6f72656d20697073756d20646f6c6f722073697" <> "420616d65742c20636f6e7365637465747572206164697069736963696e6720656c69" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -66,7 +66,7 @@ defmodule ExRLP.EncoderTest do "b8384c6f72656d20697073756d20646f6c6f722073697" <> "420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -115,7 +115,7 @@ defmodule ExRLP.EncoderTest do "e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20" <> "756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -124,7 +124,7 @@ defmodule ExRLP.EncoderTest do string = 0 expected_result = "80" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -133,7 +133,7 @@ defmodule ExRLP.EncoderTest do string = 1 expected_result = "01" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -142,7 +142,7 @@ defmodule ExRLP.EncoderTest do string = 16 expected_result = "10" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -151,7 +151,7 @@ defmodule ExRLP.EncoderTest do string = 79 expected_result = "4f" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -160,7 +160,7 @@ defmodule ExRLP.EncoderTest do string = 127 expected_result = "7f" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -169,7 +169,7 @@ defmodule ExRLP.EncoderTest do string = 128 expected_result = "8180" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -178,7 +178,7 @@ defmodule ExRLP.EncoderTest do string = 1000 expected_result = "8203e8" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -187,7 +187,7 @@ defmodule ExRLP.EncoderTest do string = 100_000 expected_result = "830186a0" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -196,7 +196,7 @@ defmodule ExRLP.EncoderTest do string = 83_729_609_699_884_896_815_286_331_701_780_722 expected_result = "8f102030405060708090a0b0c0d0e0f2" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -207,7 +207,7 @@ defmodule ExRLP.EncoderTest do expected_result = "9c0100020003000400050006000700080009000a000b000c000d000e01" - result = string |> Encoder.encode(encoding: :hex) + result = string |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -216,7 +216,7 @@ defmodule ExRLP.EncoderTest do list = ["dog", "god", "cat"] expected_result = "cc83646f6783676f6483636174" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -225,7 +225,7 @@ defmodule ExRLP.EncoderTest do list = ["zw", [4], 1] expected_result = "c6827a77c10401" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -249,7 +249,7 @@ defmodule ExRLP.EncoderTest do "f784617364668471776572847a7863768461736466847" <> "1776572847a78637684617364668471776572847a78637684617364668471776572" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -266,7 +266,7 @@ defmodule ExRLP.EncoderTest do "f840cf84617364668471776572847a786376cf84617364668" <> "471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -328,7 +328,7 @@ defmodule ExRLP.EncoderTest do "84617364668471776572847a786376cf84617364668471776572847a" <> "786376cf84617364668471776572847a786376" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -337,7 +337,7 @@ defmodule ExRLP.EncoderTest do list = [[[], []], []] expected_result = "c4c2c0c0c0" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -346,7 +346,7 @@ defmodule ExRLP.EncoderTest do list = [[], [[]], [[], [[]]]] expected_result = "c7c0c1c0c3c0c1c0" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -363,7 +363,7 @@ defmodule ExRLP.EncoderTest do "ecca846b6579318476616c31ca846b65" <> "79328476616c32ca846b6579338476616c33ca846b6579348476616c34" - result = list |> Encoder.encode(encoding: :hex) + result = list |> Encode.encode(encoding: :hex) assert result == expected_result end @@ -374,7 +374,7 @@ defmodule ExRLP.EncoderTest do expected_result = "a1010000000000000000000000000000000000000000000000000000000000000000" - result = big_integer |> Encoder.encode(encoding: :hex) + result = big_integer |> Encode.encode(encoding: :hex) assert result == expected_result end diff --git a/test/ex_rlp/protocols_test.exs b/test/ex_rlp/protocols_test.exs index fbc8388..106c766 100644 --- a/test/ex_rlp/protocols_test.exs +++ b/test/ex_rlp/protocols_test.exs @@ -2,24 +2,33 @@ defmodule ExRLP.ProtocolsTest do use ExUnit.Case alias ExRLP.LogEntry + @log_entry LogEntry.new( + <<15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 109, 45, 108, 123, + 94, 198>>, + [0, 0, 0], + <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255>> + ) + describe "encode/2" do test "encodes custom struct" do - log_entry = - LogEntry.new( - <<15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 109, 45, 108, 123, 94, - 198>>, - [0, 0, 0], - <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255>> - ) - expected_result = <<248, 58, 148, 15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 109, 45, 108, 123, 94, 198, 195, 128, 128, 128, 160, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255>> - assert ExRLP.encode(log_entry) == expected_result + assert ExRLP.encode(@log_entry) == expected_result + end + + test "encodes custom struct in list" do + expected_result = + <<248, 60, 248, 58, 148, 15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, + 109, 45, 108, 123, 94, 198, 195, 128, 128, 128, 160, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255>> + + assert ExRLP.encode([@log_entry]) == expected_result end end end diff --git a/test/support/log_entry.ex b/test/support/log_entry.ex index db0e0bb..669a841 100644 --- a/test/support/log_entry.ex +++ b/test/support/log_entry.ex @@ -23,14 +23,13 @@ defmodule ExRLP.LogEntry do end end -defimpl ExRLP.Encoder, for: ExRLP.LogEntry do - alias ExRLP.Encode - alias ExRLP.LogEntry +defimpl ExRLP.Encode, for: ExRLP.LogEntry do + alias ExRLP.{Encode, LogEntry} @spec encode(LogEntry.t(), keyword()) :: binary() def encode(log, options \\ []) do log |> LogEntry.to_list() - |> Encode.encode(Keyword.get(options, :encoding, :binary)) + |> Encode.encode(options) end end From d0b1e15d955aeda95be9a29a8f7c2cd4576c14d5 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 1 May 2018 05:17:31 +0300 Subject: [PATCH 4/4] update protocol README --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1e1b9d7..8df948e 100644 --- a/README.md +++ b/README.md @@ -93,15 +93,14 @@ defmodule ExRLP.LogEntry do end end -defimpl ExRLP.Encoder, for: ExRLP.LogEntry do - alias ExRLP.Encode - alias ExRLP.LogEntry +defimpl ExRLP.Encode, for: ExRLP.LogEntry do + alias ExRLP.{Encode, LogEntry} @spec encode(LogEntry.t(), keyword()) :: binary() def encode(log, options \\ []) do log |> LogEntry.to_list() - |> Encode.encode(Keyword.get(options, :encoding, :binary)) + |> Encode.encode(options) end end