%%% Copyright (c) 2018-2021, Klarna Bank AB (publ) %%% %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 %%% %%% Unless required by applicable law or agreed to in writing, software %%% distributed under the License is distributed on an "AS IS" BASIS, %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% See the License for the specific language governing permissions and %%% limitations under the License. %%% -module(kpro_compress). -export([ compress/2 , decompress/2 , codec_to_method/1 , method_to_codec/1 , provide/1 ]). -include("kpro_private.hrl"). %% @doc Set snappy or lz4 compression modules. %% This should override the default usage of `snappyer' and `lz4b_frame'. -spec provide([{snappy | lz4, module()}]) -> ok. provide(Libs) -> lists:foreach(fun({Name, Module}) -> persistent_term:put({?MODULE, Name}, Module) end, Libs). %% @doc Translate codec in kafka batch attributes to compression method. -spec codec_to_method(byte()) -> kpro:compress_option(). codec_to_method(A) when ?KPRO_IS_GZIP_ATTR(A) -> ?gzip; codec_to_method(A) when ?KPRO_IS_SNAPPY_ATTR(A) -> ?snappy; codec_to_method(A) when ?KPRO_IS_LZ4_ATTR(A) -> ?lz4; codec_to_method(_) -> ?no_compression. %% @doc Translate compression method to bits for kafka batch attributes. method_to_codec(?gzip) -> ?KPRO_COMPRESS_GZIP; method_to_codec(?snappy) -> ?KPRO_COMPRESS_SNAPPY; method_to_codec(?lz4) -> ?KPRO_COMPRESS_LZ4; method_to_codec(?no_compression) -> ?KPRO_COMPRESS_NONE. %% @doc Compress encoded batch. -spec compress(kpro:compress_option(), iodata()) -> iodata(). compress(?no_compression, IoData) -> IoData; compress(?gzip, IoData) -> zlib:gzip(IoData); compress(Name, IoData) -> do_compress(Name, IoData). %% @doc Decompress batch. -spec decompress(kpro:compress_option(), binary()) -> binary(). decompress(?no_compression, Bin) -> Bin; decompress(?gzip, Bin) -> zlib:gunzip(Bin); decompress(?snappy, Bin) -> java_snappy_unpack(Bin); decompress(?lz4, Bin) -> do_decompress(?lz4, Bin). %%%_* Internals ================================================================ %% Java snappy implementation has its own non-standard %% magic header, see org/xerial/snappy/SnappyCodec.java java_snappy_unpack(<<130, "SNAPPY", 0, _Version:32, _MinCompatibleV:32, Chunks/binary>>) -> java_snappy_unpack_chunks(Chunks, []); java_snappy_unpack(Bin) -> do_decompress(snappy, Bin). java_snappy_unpack_chunks(<<>>, Acc) -> iolist_to_binary(Acc); java_snappy_unpack_chunks(Chunks, Acc) -> <> = Chunks, case Len =:= 0 of true -> Rest =:= <<>> orelse erlang:error({Len, Rest}), %% assert Acc; false -> <> = Rest, Decompressed = do_decompress(?snappy, Data), java_snappy_unpack_chunks(Tail, [Acc, Decompressed]) end. do_compress(Name, IoData) -> Module = get_module(Name), iodata(Module:compress(IoData)). do_decompress(Name, Bin) -> Module = get_module(Name), iodata(Module:decompress(Bin)). get_module(snappy) -> get_module(snappy, snappyer); get_module(lz4) -> get_module(lz4, lz4b_frame). get_module(Name, Default) -> persistent_term:get({?MODULE, Name}, Default). iodata({ok, IoData}) -> IoData; iodata({error, Reason}) -> error(Reason); iodata(IoData) when is_list(IoData) orelse is_binary(IoData) -> IoData. %%%_* Emacs ==================================================================== %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 2 %%% End: