Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement streaming decode #53

Merged
merged 3 commits into from
Feb 23, 2023
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
15 changes: 10 additions & 5 deletions lib/ex_rlp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,27 @@ defmodule ExRLP do
"""
@spec encode(t) :: binary()
@spec encode(t, keyword()) :: binary()
def encode(item, options \\ [encoding: :binary]) do
def encode(item, options \\ []) do
Encode.encode(item, options)
end

@doc """
Given an RLP-encoded string, returns a decoded RPL structure (which is an
array of RLP structures or binaries).

If stream (`[stream: true]`) is enabled, it will just decode the first rlp sequence

## Examples

iex> ExRLP.decode(<<>>)
** (ExRLP.DecodeError) invalid rlp encoding

iex> ExRLP.decode(<<0xc8, 0x83, ?c, ?a, ?t, 0x83, ?d, ?o, ?g>>)
["cat", "dog"]

iex> ExRLP.decode(<<131, 99, 97, 116, 131, 99, 97, 116>>, stream: true)
{"cat", <<131, 99, 97, 116>>}

iex> ExRLP.decode(<<0x83, ?d, ?o, ?g>>)
"dog"

Expand All @@ -76,9 +84,6 @@ defmodule ExRLP do
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>>)
Enum.join(for _ <- 1..60, do: "A")

iex> ExRLP.decode(<<0xc8, 0x83, ?c, ?a, ?t, 0x83, ?d, ?o, ?g>>)
["cat", "dog"]

iex> ExRLP.decode(<<198, 51, 132, 99, 111, 111, 108>>)
["3", "cool"]

Expand All @@ -105,7 +110,7 @@ defmodule ExRLP do

"""
@spec decode(binary()) :: t
@spec decode(binary(), keyword()) :: t
@spec decode(binary(), keyword()) :: t() | {t(), binary()} | no_return()
def decode(item, options \\ [encoding: :binary]) do
Decode.decode(item, options)
end
Expand Down
9 changes: 6 additions & 3 deletions lib/ex_rlp/decode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ defmodule ExRLP.Decode do
@moduledoc false

alias ExRLP.DecodeItem
@spec decode(binary(), keyword()) :: ExRLP.t()
@spec decode(binary(), keyword()) :: ExRLP.t() | {ExRLP.t(), binary()} | no_return()
def decode(item, options \\ [])

def decode("", _), do: raise(ExRLP.DecodeError)

def decode(item, options) when is_binary(item) do
encoding = Keyword.get(options, :encoding, :binary)
stream = Keyword.get(options, :stream, false)

item
|> unencode(Keyword.get(options, :encoding, :binary))
|> DecodeItem.decode_item()
|> unencode(encoding)
|> DecodeItem.decode_item(stream)
end

@spec unencode(binary(), atom()) :: binary()
Expand Down
96 changes: 61 additions & 35 deletions lib/ex_rlp/decode_item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ defmodule ExRLP.DecodeItem do
Captures bins and decodes them.
"""

@spec decode_item(binary()) :: ExRLP.t()
def decode_item(rlp_binary), do: do_decode_item(rlp_binary, nil)
@spec decode_item(binary(), boolean()) :: ExRLP.t()
def decode_item(rlp_binary, stream), do: do_decode_item(rlp_binary, nil, stream)
##
## HANDLING 0 - 127
##
Expand All @@ -15,11 +15,18 @@ defmodule ExRLP.DecodeItem do
# `list_result` - possibly a result of decoding a list payload:
# - if `nil`: we are not processing a list
# - if a list: we are processing a list, and we have this much currently
@spec do_decode_item(payload :: binary(), list_result :: ExRLP.t()) :: ExRLP.t()
defp do_decode_item(<<n, tail::binary>>, nil) when n < 128,
do: do_decode_item(tail, <<n>>)
@spec do_decode_item(payload :: binary(), list_result :: ExRLP.t(), boolean()) :: ExRLP.t()
defp do_decode_item(payload, result, stream \\ false)

defp do_decode_item(<<n, tail::binary>>, result) when n < 128,
defp do_decode_item(<<n, tail::binary>>, nil, stream) when n < 128 do
if stream do
{<<n>>, tail}
else
do_decode_item(tail, <<n>>)
end
end

defp do_decode_item(<<n, tail::binary>>, result, _stream) when n < 128,
do: do_decode_item(tail, [<<n>> | result])

##
Expand All @@ -29,38 +36,39 @@ defmodule ExRLP.DecodeItem do
##
## HANDLING 128 - 183
##
defp do_decode_item(<<129>>, _) do
defp do_decode_item(<<129>>, _, _) do
raise(ExRLP.DecodeError)
end

defp do_decode_item(<<129, bad::little-size(8), _tail::binary>>, _) when bad < 128 do
defp do_decode_item(<<129, bad::little-size(8), _tail::binary>>, _, _) when bad < 128 do
raise(ExRLP.DecodeError)
end

defp do_decode_item(<<129, bad::little-size(8)>>, _) when bad < 128 do
defp do_decode_item(<<129, bad::little-size(8)>>, _, _) when bad < 128 do
raise(ExRLP.DecodeError)
end

defp do_decode_item(<<n, tail::binary>>, nil) when n < 184 do
do_decode_when_valid_length(n - 128, tail, nil)
end

defp do_decode_item(<<n, tail::binary>>, result) when n < 184 do
do_decode_when_valid_length(n - 128, tail, result)
defp do_decode_item(<<n, tail::binary>>, result, stream) when n < 184 do
do_decode_when_valid_length(n - 128, tail, result, stream)
end

##
## FINISHED HANDLING 128-183
##

# decode_long_binary - CAN'T OPTIMISE FOR NOW
defp do_decode_item(<<be_size_prefix, tail::binary>>, nil) when be_size_prefix < 192 do
defp do_decode_item(<<be_size_prefix, tail::binary>>, nil, stream) when be_size_prefix < 192 do
{new_item, new_tail} = decode_long_binary(be_size_prefix - 183, tail)

do_decode_item(new_tail, new_item)
if stream do
{new_item, new_tail}
else
do_decode_item(new_tail, new_item)
end
end

defp do_decode_item(<<be_size_prefix, tail::binary>>, result) when be_size_prefix < 192 do
defp do_decode_item(<<be_size_prefix, tail::binary>>, result, _stream)
when be_size_prefix < 192 do
{new_item, new_tail} = decode_long_binary(be_size_prefix - 183, tail)

do_decode_item(new_tail, [new_item | result])
Expand All @@ -69,11 +77,15 @@ defmodule ExRLP.DecodeItem do
##
## HANDLING 192
##
defp do_decode_item(<<192, tail::binary>>, nil) do
do_decode_item(tail, [])
defp do_decode_item(<<192, tail::binary>>, nil, stream) do
if stream do
{[], tail}
else
do_decode_item(tail, [])
end
end

defp do_decode_item(<<192, tail::binary>>, result) do
defp do_decode_item(<<192, tail::binary>>, result, _stream) do
do_decode_item(tail, [[] | result])
end

Expand All @@ -84,19 +96,24 @@ defmodule ExRLP.DecodeItem do
##
## HANDLING 193-247
##
defp do_decode_item(<<n, tail::binary>>, nil) when n < 248 do
defp do_decode_item(<<n, tail::binary>>, nil, stream) when n < 248 do
size = n - 192

if byte_size(tail) >= size do
<<item::binary-size(size), new_tail::binary>> = tail
new_item = Enum.reverse(do_decode_item(item, []))
do_decode_item(new_tail, new_item)

if stream do
{new_item, new_tail}
else
do_decode_item(new_tail, new_item)
end
else
raise(ExRLP.DecodeError)
end
end

defp do_decode_item(<<n, tail::binary>>, result) when n < 248 do
defp do_decode_item(<<n, tail::binary>>, result, _stream) when n < 248 do
size = n - 192

if byte_size(tail) >= size do
Expand All @@ -111,40 +128,49 @@ defmodule ExRLP.DecodeItem do
##
## FINISHED HANDLING 193-247
##

# decode_long_binary - CAN'T OPTIMISE FOR NOW
defp do_decode_item(<<be_size_prefix, tail::binary>>, nil) when be_size_prefix > 247 do
defp do_decode_item(<<be_size_prefix, tail::binary>>, nil, stream) when be_size_prefix > 247 do
{list_binary, new_tail} = decode_long_binary(be_size_prefix - 247, tail)
reversed_list = do_decode_item(list_binary, [])
new_result = Enum.reverse(reversed_list)

do_decode_item(new_tail, new_result)
if stream do
{new_result, new_tail}
else
do_decode_item(new_tail, new_result)
end
end

defp do_decode_item(<<be_size_prefix, tail::binary>>, result) when be_size_prefix > 247 do
defp do_decode_item(<<be_size_prefix, tail::binary>>, result, _stream)
when be_size_prefix > 247 do
{list_binary, new_tail} = decode_long_binary(be_size_prefix - 247, tail)
list = do_decode_item(list_binary, [])

do_decode_item(new_tail, [list | result])
end

defp do_decode_item(<<>>, result) when is_list(result) do
defp do_decode_item(<<>>, result, _stream) when is_list(result) do
Enum.reverse(result)
end

defp do_decode_item(<<>>, result), do: result
defp do_decode_item(<<>>, result, _stream), do: result

defp do_decode_when_valid_length(size, tail, result) when byte_size(tail) >= size do
defp do_decode_when_valid_length(size, tail, nil, stream) when byte_size(tail) >= size do
<<item::binary-size(size), new_tail::binary>> = tail

if result do
do_decode_item(new_tail, [item | result])
if stream do
{item, new_tail}
else
do_decode_item(new_tail, item)
end
end

defp do_decode_when_valid_length(_size, _tail, _result), do: raise(ExRLP.DecodeError)
defp do_decode_when_valid_length(size, tail, result, _stream) when byte_size(tail) >= size do
<<item::binary-size(size), new_tail::binary>> = tail

do_decode_item(new_tail, [item | result])
end

defp do_decode_when_valid_length(_size, _tail, _result, _stream), do: raise(ExRLP.DecodeError)

# decodes a long string or long list, based on the already decoded size of length provided (`be_size`)
#
Expand Down
Loading