Skip to content

Commit

Permalink
Add compress_frame/1 and decompress_frame/1 (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
whatyouhide authored Aug 7, 2024
1 parent 4abeafa commit 0a494da
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 2 deletions.
20 changes: 20 additions & 0 deletions lib/nimble_lz4.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,24 @@ defmodule NimbleLZ4 do
def decompress(_binary, _uncompressed_size) do
:erlang.nif_error(:nif_not_loaded)
end

@doc """
Compresses the given binary using the [LZ4 frame
format](https://github.com/lz4/lz4/blob/dev/doc/lz4_Frame_format.md) into a frame.
"""
@doc since: "1.1.0"
@spec compress_frame(binary()) :: binary()
def compress_frame(_binary) do
:erlang.nif_error(:nif_not_loaded)
end

@doc """
Decompresses the given frame binary using the [LZ4 frame
format](https://github.com/lz4/lz4/blob/dev/doc/lz4_Frame_format.md).
"""
@doc since: "1.1.0"
@spec decompress_frame(binary()) :: {:ok, binary()} | {:error, term()}
def decompress_frame(_binary) do
:erlang.nif_error(:nif_not_loaded)
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ defmodule NimbleLz4.MixProject do
# Dev and test dependencies
{:benchee, "~> 1.1", only: :dev},
{:ex_doc, "~> 0.28", only: :dev},
{:stream_data, "~> 1.1", only: :test}
{:stream_data, "~> 1.1", only: [:dev, :test]}
]
end
end
41 changes: 40 additions & 1 deletion native/nimblelz4/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ fn compress<'a>(env: Env<'a>, iolist_to_compress: Term<'a>) -> Result<Term<'a>,
Ok(erl_bin.release(env).encode(env))
}

#[rustler::nif(schedule = "DirtyCpu")]
fn compress_frame<'a>(env: Env<'a>, iolist_to_compress: Term<'a>) -> Result<Term<'a>, Error> {
let binary_to_compress: Binary = Binary::from_iolist(iolist_to_compress).unwrap();

let mut compressor = lz4_flex::frame::FrameEncoder::new(Vec::new());
std::io::Write::write(&mut compressor, binary_to_compress.as_slice()).unwrap();
let compressed = compressor.finish().unwrap();

let mut erl_bin: OwnedBinary = OwnedBinary::new(compressed.len()).unwrap();

erl_bin
.as_mut_slice()
.copy_from_slice(compressed.as_slice());

Ok(erl_bin.release(env).encode(env))
}

#[rustler::nif(schedule = "DirtyCpu")]
fn decompress<'a>(
env: Env<'a>,
Expand All @@ -38,8 +55,30 @@ fn decompress<'a>(
}
}

#[rustler::nif(schedule = "DirtyCpu")]
fn decompress_frame<'a>(env: Env<'a>, binary_to_decompress: Binary) -> Result<Term<'a>, Error> {
let mut decompressed_buf: Vec<u8> = Vec::new();
let mut decompressor = lz4_flex::frame::FrameDecoder::new(binary_to_decompress.as_slice());

match std::io::Read::read_to_end(&mut decompressor, &mut decompressed_buf) {
Ok(_) => {
let mut erl_bin: OwnedBinary = OwnedBinary::new(decompressed_buf.len()).unwrap();
erl_bin
.as_mut_slice()
.copy_from_slice(decompressed_buf.as_slice());

Ok((atom::ok(), erl_bin.release(env)).encode(env))
}
Err(e) => Ok((atom::error(), e.to_string()).encode(env)),
}
}

fn load(_: Env, _: Term) -> bool {
true
}

rustler::init!("Elixir.NimbleLZ4", [compress, decompress], load = load);
rustler::init!(
"Elixir.NimbleLZ4",
[compress, compress_frame, decompress, decompress_frame],
load = load
);
40 changes: 40 additions & 0 deletions test/nimble_lz4_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ defmodule NimbleLZ4Test do
end
end

property "compress_frame + uncompress_frame are circular" do
check all binary <- binary() do
assert {:ok, ^binary} =
binary
|> NimbleLZ4.compress_frame()
|> NimbleLZ4.decompress_frame()
end
end

property "compress_frame + uncompress_frame are circular with iodata as input" do
check all iodata <- iodata() do
assert iodata
|> NimbleLZ4.compress_frame()
|> NimbleLZ4.decompress_frame() == {:ok, IO.iodata_to_binary(iodata)}
end
end

describe "compress/1" do
test "with binaries" do
assert NimbleLZ4.compress("foo") == "0foo"
Expand All @@ -31,6 +48,19 @@ defmodule NimbleLZ4Test do
end
end

describe "compress_frame/1" do
test "with binaries" do
assert NimbleLZ4.compress_frame("foo") == "\x04\"M\x18`@\x82\x03\0\0\x80foo\0\0\0\0"
end

test "with iodata" do
assert NimbleLZ4.compress_frame([]) == ""

assert NimbleLZ4.compress_frame([?f, [[[?o]]], "o"]) ==
"\x04\"M\x18`@\x82\x03\0\0\x80foo\0\0\0\0"
end
end

describe "decompress/2" do
test "with bad arguments" do
assert_raise ArgumentError, fn -> NimbleLZ4.decompress(:banana, :apple) end
Expand All @@ -41,4 +71,14 @@ defmodule NimbleLZ4Test do
assert message == "the expected decompressed size differs, actual 3, expected 6"
end
end

describe "decompress_frame/1" do
test "with bad arguments" do
assert_raise ArgumentError, fn -> NimbleLZ4.decompress_frame(:banana) end
end

test "decompresses correctly" do
assert {:ok, "foo"} = NimbleLZ4.decompress_frame("\x04\"M\x18`@\x82\x03\0\0\x80foo\0\0\0\0")
end
end
end

0 comments on commit 0a494da

Please sign in to comment.