Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 8 additions & 17 deletions .dialyzer.ignore-warnings
Original file line number Diff line number Diff line change
@@ -1,19 +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.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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
68 changes: 30 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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)
[[[], []], []]
```

Expand All @@ -69,49 +69,41 @@ 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
defimpl ExRLP.Encode, for: ExRLP.LogEntry do
alias ExRLP.{Encode, LogEntry}

value
|> Decode.decode
|> Enum.with_index
|> Enum.reduce(%{}, fn({value, index}, acc) ->
key = keys |> Enum.at(index)

acc |> Map.put(key, value)
end)

...
@spec encode(LogEntry.t(), keyword()) :: binary()
def encode(log, options \\ []) do
log
|> LogEntry.to_list()
|> Encode.encode(options)
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
Expand Down
17 changes: 8 additions & 9 deletions lib/ex_rlp.ex
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 """
Expand All @@ -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>>)
Expand Down Expand Up @@ -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 \\ [encoding: :binary]) do
item |> Decode.decode(options)
end
end
37 changes: 3 additions & 34 deletions lib/ex_rlp/decoder.ex → lib/ex_rlp/decode.ex
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -113,34 +113,3 @@ defmodule ExRLP.Decode do
decoded_binary
end
end

defprotocol ExRLP.Decoder do
def decode(value, type \\ :binary, 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
value |> Decode.decode(Keyword.get(options, :encoding, :binary))
end
end
89 changes: 89 additions & 0 deletions lib/ex_rlp/encode.ex
Original file line number Diff line number Diff line change
@@ -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(<<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) 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
Loading