%% @doc
%% Zstd [1] binding for Erlang.
%%
%% [1]: [http://facebook.github.io/zstd]
-module(ezstd).
-export([
compress/1,
compress/2,
decompress/1,
create_cdict/2,
create_ddict/1,
compress_using_cdict/2,
decompress_using_ddict/2,
get_dict_id_from_frame/1,
get_dict_id_from_ddict/1,
get_dict_id_from_cdict/1,
create_compression_context/1,
select_cdict/2,
set_compression_parameter/3,
compress_streaming/2,
compress_streaming_end/2,
create_decompression_context/1,
set_decompression_parameter/3,
select_ddict/2,
decompress_streaming/2
]).
-type zstd_compression_flag() :: 'zstd_c_compression_level'
| 'zstd_c_window_log'
| 'zstd_c_hash_log'
| 'zstd_c_chain_log'
| 'zstd_c_search_log'
| 'zstd_c_min_match'
| 'zstd_c_target_length'
| 'zstd_c_strategy'.
-type zstd_decompression_flag() :: 'zstd_d_window_log_max'.
-spec create_cdict(binary(), integer()) -> reference() | {error, any()}.
create_cdict(Binary, CompressionLevel) ->
ezstd_nif:create_cdict(Binary, CompressionLevel).
-spec create_ddict(binary()) -> reference() | {error, any()}.
create_ddict(Binary) ->
ezstd_nif:create_ddict(Binary).
-spec compress_using_cdict(binary(), reference()) -> binary() | {error, any()}.
compress_using_cdict(Binary, CCDict) ->
ezstd_nif:compress_using_cdict(Binary, CCDict).
-spec decompress_using_ddict(binary(), reference()) -> binary() | {error, any()}.
decompress_using_ddict(Binary, DDict) ->
ezstd_nif:decompress_using_ddict(Binary, DDict).
-spec get_dict_id_from_frame(binary()) -> integer().
get_dict_id_from_frame(Binary) ->
returns_integers(ezstd_nif:get_dict_id_from_frame(Binary)).
-spec get_dict_id_from_ddict(reference()) -> integer().
get_dict_id_from_ddict(DDict) ->
returns_integers(ezstd_nif:get_dict_id_from_ddict(DDict)).
-spec get_dict_id_from_cdict(reference()) -> integer().
get_dict_id_from_cdict(CDict) ->
returns_integers(ezstd_nif:get_dict_id_from_cdict(CDict)).
%% @doc Compresses the given binary.
-spec compress(binary()) -> binary() | {error, any()}.
compress(Binary) ->
ezstd_nif:compress(Binary, 1).
%% @doc Compresses the given binary with compression level.
-spec compress(binary(), integer()) -> binary() | {error, any()}.
compress(Binary, CompressionLevel) ->
ezstd_nif:compress(Binary, CompressionLevel).
%% @doc Decompresses the given binary.
-spec decompress(binary()) -> binary() | {error, any()}.
decompress(Binary) ->
ezstd_nif:decompress(Binary).
%% @doc Create a streaming compression context, with a buffer of the given size.
-spec create_compression_context(pos_integer()) -> reference() | {error, any()}.
create_compression_context(BufferSize) ->
ezstd_nif:create_compression_context(BufferSize).
%% @doc Set a dictionary for the given streaming compression context. Must be called
%% before beginning compression.
-spec select_cdict(reference(), reference()) -> ok | {error, any()}.
select_cdict(Context, CDict) ->
ezstd_nif:select_cdict(Context, CDict).
%% @doc Set a compression parameter on the given compression context. Valid values for
%% flag are zstd_c_compression_level and zstd_c_window_log.
- spec set_compression_parameter(reference(), zstd_compression_flag(), integer()) -> ok | {error, any()}.
set_compression_parameter(Context, Flag, Value) ->
case flag_to_compression_param_number(Flag) of
{ok, Param} ->
ezstd_nif:set_compression_parameter(Context, Param, Value);
error ->
error
end.
%% @doc Compress some data without closing out the compression frame. This
%% is intended to be used by a streaming decompressor which receives the same
%% data in the same order.
-spec compress_streaming(reference(), binary()) -> iolist() | {error, any()}.
compress_streaming(Context, Binary) ->
compress_streaming_chunk(Context, Binary, flush, 0, [], 1000).
-spec compress_streaming_end(reference(), binary()) -> iolist() | {error, any()}.
compress_streaming_end(Context, Binary) ->
compress_streaming_chunk(Context, Binary, zstdend, 0, [], 1000).
compress_streaming_chunk(_Context, _Binary, _FlushType, _Offset, _Sofar, 0) ->
{error, compressor_stuck};
compress_streaming_chunk(Context, Binary, FlushType, Offset, Sofar, Attempts) ->
case ezstd_nif:compress_streaming_chunk(Context, Binary, FlushType, Offset) of
{ok, Chunk} ->
[Sofar | Chunk];
{continue, Chunk, NextOffset} ->
compress_streaming_chunk(Context, Binary, FlushType, NextOffset, [Sofar | Chunk], Attempts - 1);
Error ->
Error
end.
%% @doc Create a streaming decompression context, with a buffer of the given size.
-spec create_decompression_context(pos_integer()) -> reference() | {error, any()}.
create_decompression_context(BufferSize) ->
ezstd_nif:create_decompression_context(BufferSize).
%% @doc Set a dictionary for the given streaming decompression context. Must be called
%% before beginning decompression.
-spec select_ddict(reference(), reference()) -> ok | {error, any()}.
select_ddict(Context, DDict) ->
ezstd_nif:select_ddict(Context, DDict).
%% @doc Set a decompression parameter on the given compression context. The only valid
%% value for Flag is zstd_d_window_log_max.
- spec set_decompression_parameter(reference(), zstd_decompression_flag(), integer()) -> ok | {error, any()}.
set_decompression_parameter(Context, Flag, Value) ->
case flag_to_decompression_param_number(Flag) of
{ok, Param} ->
ezstd_nif:set_decompression_parameter(Context, Param, Value);
error ->
error
end.
%% @doc Compress some data without closing out the compression frame. This
%% is intended to be used by a streaming decompressor which receives the same
%% data in the same order.
-spec decompress_streaming(reference(), binary()) -> iolist() | {error, any()}.
decompress_streaming(Context, Binary) ->
decompress_streaming_chunk(Context, Binary, 0, [], 1000).
decompress_streaming_chunk(_Context, _Binary, _Offset, _Sofar, 0) ->
{error, decompressor_stuck};
decompress_streaming_chunk(Context, Binary, Offset, Sofar, Attempts) ->
case ezstd_nif:decompress_streaming_chunk(Context, Binary, Offset) of
{ok, Chunk} ->
[Sofar | Chunk];
{continue, Chunk, NextOffset} ->
decompress_streaming_chunk(Context, Binary, NextOffset, [Sofar | Chunk], Attempts - 1);
Error ->
Error
end.
% internals
returns_integers(Value) ->
% the _dict_id functions only return non-integers when
% the preconditions are broken. ie: non-binary or non-reference
% supplied as an argument
% in the case of frame it returns 0 if the frame has no
% dict_id
case Value of
V when is_integer(V) ->
V;
Other ->
error(Other)
end.
flag_to_compression_param_number(zstd_c_compression_level) ->
{ok, 100};
flag_to_compression_param_number(zstd_c_window_log) ->
{ok, 101};
flag_to_compression_param_number(zstd_c_hash_log) ->
{ok, 102};
flag_to_compression_param_number(zstd_c_chain_log) ->
{ok, 103};
flag_to_compression_param_number(zstd_c_search_log) ->
{ok, 104};
flag_to_compression_param_number(zstd_c_min_match) ->
{ok, 105};
flag_to_compression_param_number(zstd_c_target_length) ->
{ok, 106};
flag_to_compression_param_number(zstd_c_strategy) ->
{ok, 107};
flag_to_compression_param_number(_Other) ->
{error, badarg}.
flag_to_decompression_param_number(zstd_d_window_log_max) ->
{ok, 100};
flag_to_decompression_param_number(_Other) ->
{error, badarg}.