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
24 changes: 15 additions & 9 deletions lib/zarith.ex
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,13 @@ defmodule Tezex.Zarith do
"""
@spec consume(zarith_hex()) :: {%{int: String.t()}, pos_integer()}
def consume(binary_input) when is_binary(binary_input) do
if binary_input == "" or rem(byte_size(binary_input), 2) != 0 do
raise ArgumentError, "Zarith input must be a non-empty even-length hex string"
end

{carved_int, rest} = find_int(binary_input)

consumed = String.length(binary_input) - String.length(rest)
consumed = byte_size(binary_input) - byte_size(rest)

binary =
carved_int
Expand Down Expand Up @@ -194,16 +198,18 @@ defmodule Tezex.Zarith do
end
end

defp find_int("", acc) do
{Enum.reverse(acc) |> Enum.join(""), ""}
defp find_int("", _acc) do
raise ArgumentError, "Zarith input truncated: continuation bit set with no following byte"
end

defp read([<<_halt::1, sign::1, tail::integer-size(6)>> | rest]) do
bits =
for bits_part <- read_next(rest, [<<tail::integer-size(6)>>]), into: <<>> do
bits_part
defp read([<<halt::1, sign::1, tail::integer-size(6)>> | rest]) do
parts =
case halt do
0 -> [<<tail::integer-size(6)>>]
1 -> read_next(rest, [<<tail::integer-size(6)>>])
end

bits = for p <- parts, into: <<>>, do: p
bits_count = bit_size(bits)
<<integer::integer-size(bits_count)>> = bits

Expand All @@ -221,8 +227,8 @@ defmodule Tezex.Zarith do
[<<tail::integer-size(7)>> | acc]
end

defp read_next(_, acc) do
acc
defp read_next([], _acc) do
raise ArgumentError, "Zarith input truncated: continuation bit set with no following byte"
end

defp hex_to_dec(hex) do
Expand Down
24 changes: 23 additions & 1 deletion test/zarith_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ defmodule Tezex.ZarithTest do

test "encode/decode" do
n = 1_000_000_000_000
random = fn -> trunc(:rand.uniform(n) - n / 2) end
h = n / 2
random = fn -> trunc(:rand.uniform(n) - h) end

Enum.each(1..5000, fn _ ->
number = random.()
Expand Down Expand Up @@ -72,4 +73,25 @@ defmodule Tezex.ZarithTest do
assert {%{int: "-610913435200"}, byte_size(packed)} == Zarith.consume(packed)
end
end

describe "malformed input" do
test "raises on truncated input with continuation bit set" do
# 0x80: continuation=1, sign=0, tail=0, claims more bytes follow but none do
assert_raise ArgumentError, fn -> Zarith.decode("80") end
# 0xff: continuation=1, sign=1, tail=63, same truncation
assert_raise ArgumentError, fn -> Zarith.decode("ff") end
# Multi-byte truncation: first byte continues, second byte continues, then ends
assert_raise ArgumentError, fn -> Zarith.decode("8080") end
end

test "raises on empty input" do
assert_raise ArgumentError, fn -> Zarith.consume("") end
assert_raise ArgumentError, fn -> Zarith.decode("") end
end

test "raises on odd-length hex input" do
assert_raise ArgumentError, fn -> Zarith.consume("a1d22") end
assert_raise ArgumentError, fn -> Zarith.decode("a1d22") end
end
end
end
Loading