%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2003-2013. All Rights Reserved.
%%
%% 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.
%%
%% %CopyrightEnd%
%%
-module(zlib).
-export([open/0,close/1,deflateInit/1,deflateInit/2,deflateInit/6,
deflateSetDictionary/2,deflateReset/1,deflateParams/3,
deflate/2,deflate/3,deflateEnd/1,
inflateInit/1,inflateInit/2,inflateSetDictionary/2,
inflateSync/1,inflateReset/1,inflate/2,inflateEnd/1,
inflateChunk/1, inflateChunk/2,
setBufSize/2,getBufSize/1,
crc32/1,crc32/2,crc32/3,adler32/2,adler32/3,getQSize/1,
crc32_combine/4,adler32_combine/4,
compress/1,uncompress/1,zip/1,unzip/1,
gzip/1,gunzip/1]).
-export_type([zstream/0, zlevel/0, zwindowbits/0, zmemlevel/0, zstrategy/0]).
%% flush argument encoding
-define(Z_NO_FLUSH, 0).
-define(Z_SYNC_FLUSH, 2).
-define(Z_FULL_FLUSH, 3).
-define(Z_FINISH, 4).
%% compression level
-define(Z_NO_COMPRESSION, 0).
-define(Z_BEST_SPEED, 1).
-define(Z_BEST_COMPRESSION, 9).
-define(Z_DEFAULT_COMPRESSION, (-1)).
%% compresssion strategy
-define(Z_FILTERED, 1).
-define(Z_HUFFMAN_ONLY, 2).
-define(Z_RLE, 3).
-define(Z_DEFAULT_STRATEGY, 0).
%% deflate compression method
-define(Z_DEFLATED, 8).
-define(Z_NULL, 0).
-define(MAX_WBITS, 15).
%% gzip defs (rfc 1952)
-define(ID1, 16#1f).
-define(ID2, 16#8b).
-define(FTEXT, 16#01).
-define(FHCRC, 16#02).
-define(FEXTRA, 16#04).
-define(FNAME, 16#08).
-define(FCOMMENT, 16#10).
-define(RESERVED, 16#E0).
-define(OS_MDDOS, 0).
-define(OS_AMIGA, 1).
-define(OS_OPENVMS, 2).
-define(OS_UNIX, 3).
-define(OS_VMCMS, 4).
-define(OS_ATARI, 5).
-define(OS_OS2, 6).
-define(OS_MAC, 7).
-define(OS_ZSYS, 8).
-define(OS_CPM, 9).
-define(OS_TOP20, 10).
-define(OS_NTFS, 11).
-define(OS_QDOS, 12).
-define(OS_ACORN, 13).
-define(OS_UNKNOWN,255).
-define(DEFLATE_INIT, 1).
-define(DEFLATE_INIT2, 2).
-define(DEFLATE_SETDICT, 3).
-define(DEFLATE_RESET, 4).
-define(DEFLATE_END, 5).
-define(DEFLATE_PARAMS, 6).
-define(DEFLATE, 7).
-define(INFLATE_INIT, 8).
-define(INFLATE_INIT2, 9).
-define(INFLATE_SETDICT, 10).
-define(INFLATE_SYNC, 11).
-define(INFLATE_RESET, 12).
-define(INFLATE_END, 13).
-define(INFLATE, 14).
-define(INFLATE_CHUNK, 25).
-define(CRC32_0, 15).
-define(CRC32_1, 16).
-define(CRC32_2, 17).
-define(SET_BUFSZ, 18).
-define(GET_BUFSZ, 19).
-define(GET_QSIZE, 20).
-define(ADLER32_1, 21).
-define(ADLER32_2, 22).
-define(CRC32_COMBINE, 23).
-define(ADLER32_COMBINE, 24).
%%------------------------------------------------------------------------
%% Main data types of the file
-type zstream() :: port().
%% Auxiliary data types of the file
-type zlevel() :: 'none' | 'default' | 'best_compression' | 'best_speed'
| 0..9.
-type zmethod() :: 'deflated'.
-type zwindowbits() :: -15..-8 | 8..47.
-type zmemlevel() :: 1..9.
-type zstrategy() :: 'default' | 'filtered' | 'huffman_only' | 'rle'.
%%------------------------------------------------------------------------
%% open a z_stream
-spec open() -> zstream().
open() ->
open_port({spawn, "zlib_drv"}, [binary]).
%% close and release z_stream
-spec close(Z) -> 'ok' when
Z :: zstream().
close(Z) ->
try
true = port_close(Z),
receive %In case the caller is the owner and traps exits
{'EXIT',Z,_} -> ok
after 0 -> ok
end
catch _:_ -> erlang:error(badarg)
end.
-spec deflateInit(Z) -> 'ok' when
Z :: zstream().
deflateInit(Z) ->
call(Z, ?DEFLATE_INIT, <<?Z_DEFAULT_COMPRESSION:32>>).
-spec deflateInit(Z, Level) -> 'ok' when
Z :: zstream(),
Level :: zlevel().
deflateInit(Z, Level) ->
call(Z, ?DEFLATE_INIT, <<(arg_level(Level)):32>>).
-spec deflateInit(Z, Level, Method,
WindowBits, MemLevel, Strategy) -> 'ok' when
Z :: zstream(),
Level :: zlevel(),
Method :: zmethod(),
WindowBits :: zwindowbits(),
MemLevel :: zmemlevel(),
Strategy :: zstrategy().
deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) ->
call(Z, ?DEFLATE_INIT2, <<(arg_level(Level)):32,
(arg_method(Method)):32,
(arg_bitsz(WindowBits)):32,
(arg_mem(MemLevel)):32,
(arg_strategy(Strategy)):32>>).
-spec deflateSetDictionary(Z, Dictionary) -> Adler32 when
Z :: zstream(),
Dictionary :: iodata(),
Adler32 :: integer().
deflateSetDictionary(Z, Dictionary) ->
call(Z, ?DEFLATE_SETDICT, Dictionary).
-spec deflateReset(Z) -> 'ok' when
Z :: zstream().
deflateReset(Z) ->
call(Z, ?DEFLATE_RESET, []).
-spec deflateParams(Z, Level, Strategy) -> ok when
Z :: zstream(),
Level :: zlevel(),
Strategy :: zstrategy().
deflateParams(Z, Level, Strategy) ->
call(Z, ?DEFLATE_PARAMS, <<(arg_level(Level)):32,
(arg_strategy(Strategy)):32>>).
-spec deflate(Z, Data) -> Compressed when
Z :: zstream(),
Data :: iodata(),
Compressed :: iolist().
deflate(Z, Data) ->
deflate(Z, Data, none).
-spec deflate(Z, Data, Flush) -> Compressed when
Z :: zstream(),
Data :: iodata(),
Flush :: none | sync | full | finish,
Compressed :: iolist().
deflate(Z, Data, Flush) ->
try port_command(Z, Data) of
true ->
_ = call(Z, ?DEFLATE, <<(arg_flush(Flush)):32>>),
collect(Z)
catch
error:_Err ->
flush(Z),
erlang:error(badarg)
end.
-spec deflateEnd(Z) -> 'ok' when
Z :: zstream().
deflateEnd(Z) ->
call(Z, ?DEFLATE_END, []).
-spec inflateInit(Z) -> 'ok' when
Z :: zstream().
inflateInit(Z) ->
call(Z, ?INFLATE_INIT, []).
-spec inflateInit(Z, WindowBits) -> 'ok' when
Z :: zstream(),
WindowBits :: zwindowbits().
inflateInit(Z, WindowBits) ->
call(Z, ?INFLATE_INIT2, <<(arg_bitsz(WindowBits)):32>>).
-spec inflateSetDictionary(Z, Dictionary) -> 'ok' when
Z :: zstream(),
Dictionary :: iodata().
inflateSetDictionary(Z, Dictionary) ->
call(Z, ?INFLATE_SETDICT, Dictionary).
-spec inflateSync(zstream()) -> 'ok'.
inflateSync(Z) ->
call(Z, ?INFLATE_SYNC, []).
-spec inflateReset(Z) -> 'ok' when
Z :: zstream().
inflateReset(Z) ->
call(Z, ?INFLATE_RESET, []).
-spec inflate(Z, Data) -> Decompressed when
Z :: zstream(),
Data :: iodata(),
Decompressed :: iolist().
inflate(Z, Data) ->
try port_command(Z, Data) of
true ->
_ = call(Z, ?INFLATE, <<?Z_NO_FLUSH:32>>),
collect(Z)
catch
error:_Err ->
flush(Z),
erlang:error(badarg)
end.
-spec inflateChunk(Z, Data) -> Decompressed | {more, Decompressed} when
Z :: zstream(),
Data :: iodata(),
Decompressed :: iolist().
inflateChunk(Z, Data) ->
try port_command(Z, Data) of
true ->
inflateChunk(Z)
catch
error:_Err ->
flush(Z),
erlang:error(badarg)
end.
-spec inflateChunk(Z) -> Decompressed | {more, Decompressed} when
Z :: zstream(),
Decompressed :: iolist().
inflateChunk(Z) ->
Status = call(Z, ?INFLATE_CHUNK, []),
Data = receive
{Z, {data, Bin}} ->
Bin
after 0 ->
[]
end,
case Status of
Good when (Good == ok) orelse (Good == stream_end) ->
Data;
inflate_has_more ->
{more, Data}
end.
-spec inflateEnd(Z) -> 'ok' when
Z :: zstream().
inflateEnd(Z) ->
call(Z, ?INFLATE_END, []).
-spec setBufSize(Z, Size) -> 'ok' when
Z :: zstream(),
Size :: non_neg_integer().
setBufSize(Z, Size) ->
call(Z, ?SET_BUFSZ, <<Size:32>>).
-spec getBufSize(Z) -> Size when
Z :: zstream(),
Size :: non_neg_integer().
getBufSize(Z) ->
call(Z, ?GET_BUFSZ, []).
-spec crc32(Z) -> CRC when
Z :: zstream(),
CRC :: integer().
crc32(Z) ->
call(Z, ?CRC32_0, []).
-spec crc32(Z, Data) -> CRC when
Z :: zstream(),
Data :: iodata(),
CRC :: integer().
crc32(Z, Data) ->
call(Z, ?CRC32_1, Data).
-spec crc32(Z, PrevCRC, Data) -> CRC when
Z :: zstream(),
PrevCRC :: integer(),
Data :: iodata(),
CRC :: integer().
crc32(Z, CRC, Data) ->
call(Z, ?CRC32_2, [<<CRC:32>>, Data]).
-spec adler32(Z, Data) -> CheckSum when
Z :: zstream(),
Data :: iodata(),
CheckSum :: integer().
adler32(Z, Data) ->
call(Z, ?ADLER32_1, Data).
-spec adler32(Z, PrevAdler, Data) -> CheckSum when
Z :: zstream(),
PrevAdler :: integer(),
Data :: iodata(),
CheckSum :: integer().
adler32(Z, Adler, Data) when is_integer(Adler) ->
call(Z, ?ADLER32_2, [<<Adler:32>>, Data]);
adler32(_Z, _Adler, _Data) ->
erlang:error(badarg).
-spec crc32_combine(Z, CRC1, CRC2, Size2) -> CRC when
Z :: zstream(),
CRC :: integer(),
CRC1 :: integer(),
CRC2 :: integer(),
Size2 :: integer().
crc32_combine(Z, CRC1, CRC2, Len2)
when is_integer(CRC1), is_integer(CRC2), is_integer(Len2) ->
call(Z, ?CRC32_COMBINE, <<CRC1:32, CRC2:32, Len2:32>>);
crc32_combine(_Z, _CRC1, _CRC2, _Len2) ->
erlang:error(badarg).
-spec adler32_combine(Z, Adler1, Adler2, Size2) -> Adler when
Z :: zstream(),
Adler :: integer(),
Adler1 :: integer(),
Adler2 :: integer(),
Size2 :: integer().
adler32_combine(Z, Adler1, Adler2, Len2)
when is_integer(Adler1), is_integer(Adler2), is_integer(Len2) ->
call(Z, ?ADLER32_COMBINE, <<Adler1:32, Adler2:32, Len2:32>>);
adler32_combine(_Z, _Adler1, _Adler2, _Len2) ->
erlang:error(badarg).
-spec getQSize(zstream()) -> non_neg_integer().
getQSize(Z) ->
call(Z, ?GET_QSIZE, []).
%% compress/uncompress zlib with header
-spec compress(Data) -> Compressed when
Data :: iodata(),
Compressed :: binary().
compress(Data) ->
Z = open(),
Bs = try
deflateInit(Z, default),
B = deflate(Z, Data, finish),
deflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec uncompress(Data) -> Decompressed when
Data :: iodata(),
Decompressed :: binary().
uncompress(Data) ->
try iolist_size(Data) of
Size ->
if
Size >= 8 ->
Z = open(),
Bs = try
inflateInit(Z),
B = inflate(Z, Data),
inflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs);
true ->
erlang:error(data_error)
end
catch
_:_ ->
erlang:error(badarg)
end.
%% unzip/zip zlib without header (zip members)
-spec zip(Data) -> Compressed when
Data :: iodata(),
Compressed :: binary().
zip(Data) ->
Z = open(),
Bs = try
deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default),
B = deflate(Z, Data, finish),
deflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec unzip(Data) -> Decompressed when
Data :: iodata(),
Decompressed :: binary().
unzip(Data) ->
Z = open(),
Bs = try
inflateInit(Z, -?MAX_WBITS),
B = inflate(Z, Data),
inflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec gzip(Data) -> Compressed when
Data :: iodata(),
Compressed :: binary().
gzip(Data) ->
Z = open(),
Bs = try
deflateInit(Z, default, deflated, 16+?MAX_WBITS, 8, default),
B = deflate(Z, Data, finish),
deflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec gunzip(Data) -> Decompressed when
Data :: iodata(),
Decompressed :: binary().
gunzip(Data) ->
Z = open(),
Bs = try
inflateInit(Z, 16+?MAX_WBITS),
B = inflate(Z, Data),
inflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec collect(zstream()) -> iolist().
collect(Z) ->
collect(Z, []).
-spec collect(zstream(), iolist()) -> iolist().
collect(Z, Acc) ->
receive
{Z, {data, Bin}} ->
collect(Z, [Bin|Acc])
after 0 ->
reverse(Acc)
end.
-spec flush(zstream()) -> 'ok'.
flush(Z) ->
receive
{Z, {data,_}} ->
flush(Z)
after 0 ->
ok
end.
arg_flush(none) -> ?Z_NO_FLUSH;
%% ?Z_PARTIAL_FLUSH is deprecated in zlib -- deliberately not included.
arg_flush(sync) -> ?Z_SYNC_FLUSH;
arg_flush(full) -> ?Z_FULL_FLUSH;
arg_flush(finish) -> ?Z_FINISH;
arg_flush(_) -> erlang:error(badarg).
arg_level(none) -> ?Z_NO_COMPRESSION;
arg_level(best_speed) -> ?Z_BEST_SPEED;
arg_level(best_compression) -> ?Z_BEST_COMPRESSION;
arg_level(default) -> ?Z_DEFAULT_COMPRESSION;
arg_level(Level) when is_integer(Level), Level >= 0, Level =< 9 -> Level;
arg_level(_) -> erlang:error(badarg).
arg_strategy(filtered) -> ?Z_FILTERED;
arg_strategy(huffman_only) -> ?Z_HUFFMAN_ONLY;
arg_strategy(rle) -> ?Z_RLE;
arg_strategy(default) -> ?Z_DEFAULT_STRATEGY;
arg_strategy(_) -> erlang:error(badarg).
arg_method(deflated) -> ?Z_DEFLATED;
arg_method(_) -> erlang:error(badarg).
-spec arg_bitsz(zwindowbits()) -> zwindowbits().
arg_bitsz(Bits) when is_integer(Bits) andalso
((8 =< Bits andalso Bits < 48) orelse
(-15 =< Bits andalso Bits =< -8)) ->
Bits;
arg_bitsz(_) -> erlang:error(badarg).
-spec arg_mem(zmemlevel()) -> zmemlevel().
arg_mem(Level) when is_integer(Level), 1 =< Level, Level =< 9 -> Level;
arg_mem(_) -> erlang:error(badarg).
call(Z, Cmd, Arg) ->
try port_control(Z, Cmd, Arg) of
[0|Res] -> list_to_atom(Res);
[1|Res] ->
flush(Z),
erlang:error(list_to_atom(Res));
[2,A,B,C,D] ->
(A bsl 24)+(B bsl 16)+(C bsl 8)+D;
[3,A,B,C,D] ->
erlang:error({need_dictionary,(A bsl 24)+(B bsl 16)+(C bsl 8)+D});
[4, _, _, _, _] ->
inflate_has_more
catch
error:badarg -> %% Rethrow loses port_control from stacktrace.
erlang:error(badarg)
end.
reverse(X) ->
reverse(X, []).
reverse([H|T], Y) ->
reverse(T, [H|Y]);
reverse([], X) ->
X.