Skip to main content

src/packkit@zlib.erl

-module(packkit@zlib).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/zlib.gleam").
-export([codec/0, encode/1, encode_with_dictionary/2, decode_with_limits/2, decode/1, decode_with_dictionary_and_limits/3, decode_with_dictionary/2]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " RFC 1950 zlib codec.\n"
    "\n"
    " zlib wraps a DEFLATE stream in a two-byte header and a trailing\n"
    " Adler-32 checksum.  The encoder delegates to the DEFLATE\n"
    " fixed-Huffman writer; the decoder enforces the CMF/FLG check bits\n"
    " and the trailing Adler-32, and can resolve the optional `FDICT`\n"
    " preset-dictionary path when the caller supplies the dictionary.\n"
).

-file("src/packkit/zlib.gleam", 28).
?DOC(" Zlib codec smart constructor.\n").
-spec codec() -> packkit@codec:codec().
codec() ->
    packkit@codec:zlib().

-file("src/packkit/zlib.gleam", 188).
-spec adler_trailer(bitstring()) -> bitstring().
adler_trailer(Payload) ->
    Value = packkit@checksum:adler32(Payload),
    <<Value:32/big>>.

-file("src/packkit/zlib.gleam", 206).
-spec verify_dict_id(bitstring(), bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
verify_dict_id(Rest, Dictionary) ->
    case Rest of
        <<Stored_dict_id:32/big, After_dict_id/binary>> ->
            Expected = packkit@checksum:adler32(Dictionary),
            case Stored_dict_id =:= Expected of
                true ->
                    {ok, After_dict_id};

                false ->
                    {error, {codec_dictionary_mismatch, <<"zlib"/utf8>>}}
            end;

        _ ->
            {error,
                {codec_invalid_data,
                    <<"zlib: FDICT set but DICT_ID truncated"/utf8>>}}
    end.

-file("src/packkit/zlib.gleam", 193).
-spec consume_fdict_if_present(bitstring(), boolean(), boolean(), bitstring()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
consume_fdict_if_present(Rest, Has_fdict_bit, Has_dict, Dictionary) ->
    case {Has_fdict_bit, Has_dict} of
        {false, _} ->
            {ok, Rest};

        {true, false} ->
            {error, {codec_dictionary_required, <<"zlib"/utf8>>}};

        {true, true} ->
            verify_dict_id(Rest, Dictionary)
    end.

-file("src/packkit/zlib.gleam", 36).
?DOC(
    " Encode `data` as a zlib byte stream.  The DEFLATE body uses the\n"
    " dynamic-Huffman encoder (BTYPE=10) for better compression on\n"
    " typical inputs; pathologically-skewed payloads fall back to\n"
    " fixed Huffman (BTYPE=01) inside `deflate.encode_dynamic`.\n"
).
-spec encode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode(Bytes) ->
    gleam@result:'try'(
        packkit@deflate:encode_dynamic(Bytes),
        fun(Deflated) ->
            Header = <<16#78, 16#01>>,
            Trailer = adler_trailer(Bytes),
            {ok, gleam_stdlib:bit_array_concat([Header, Deflated, Trailer])}
        end
    ).

-file("src/packkit/zlib.gleam", 50).
?DOC(
    " Encode `bytes` as a zlib stream carrying the preset-dictionary\n"
    " adler ID for `dictionary`.  The body is emitted by the\n"
    " dynamic-Huffman DEFLATE encoder over `bytes` alone; the receiver\n"
    " must already share `dictionary` to verify the four-byte DICT_ID.\n"
    " Callers that have the dictionary on both sides can use this to\n"
    " round-trip a stream they will later decode with\n"
    " `decode_with_dictionary`.\n"
).
-spec encode_with_dictionary(bitstring(), bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode_with_dictionary(Bytes, Dictionary) ->
    gleam@result:'try'(
        packkit@deflate:encode_dynamic(Bytes),
        fun(Deflated) ->
            Dict_id = packkit@checksum:adler32(Dictionary),
            Header = <<16#78, 16#BB, Dict_id:32/big>>,
            Trailer = adler_trailer(Bytes),
            {ok, gleam_stdlib:bit_array_concat([Header, Deflated, Trailer])}
        end
    ).

-file("src/packkit/zlib.gleam", 107).
-spec decode_inner(bitstring(), packkit@limit:limits(), bitstring(), boolean()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_inner(Bytes, Limits, Dictionary, Has_dict) ->
    gleam@bool:guard(
        erlang:byte_size(Bytes) > packkit@limit:max_input_bytes(Limits),
        {error,
            {codec_limit_exceeded,
                <<"max_input_bytes"/utf8>>,
                erlang:byte_size(Bytes)}},
        fun() -> case Bytes of
                <<Cmf, Flg, Rest/binary>> ->
                    gleam@bool:guard(
                        erlang:'band'(Cmf, 16#0F) /= 8,
                        {error,
                            {codec_invalid_data,
                                <<"zlib: compression method is not deflate"/utf8>>}},
                        fun() ->
                            gleam@bool:guard(
                                (((Cmf * 256) + Flg) rem 31) /= 0,
                                {error,
                                    {codec_invalid_data,
                                        <<"zlib: header check bits mismatch"/utf8>>}},
                                fun() ->
                                    Cinfo = erlang:'bsr'(Cmf, 4),
                                    Window_bits = Cinfo + 8,
                                    gleam@bool:guard(
                                        Window_bits > packkit@limit:max_window_bits(
                                            Limits
                                        ),
                                        {error,
                                            {codec_limit_exceeded,
                                                <<"max_window_bits"/utf8>>,
                                                Window_bits}},
                                        fun() ->
                                            Has_fdict_bit = erlang:'band'(
                                                Flg,
                                                16#20
                                            )
                                            /= 0,
                                            gleam@result:'try'(
                                                consume_fdict_if_present(
                                                    Rest,
                                                    Has_fdict_bit,
                                                    Has_dict,
                                                    Dictionary
                                                ),
                                                fun(Rest@1) ->
                                                    Payload_size = erlang:byte_size(
                                                        Rest@1
                                                    ),
                                                    gleam@bool:guard(
                                                        Payload_size < 4,
                                                        {error,
                                                            {codec_invalid_data,
                                                                <<"zlib: stream missing Adler-32 trailer"/utf8>>}},
                                                        fun() ->
                                                            Deflate_size = Payload_size
                                                            - 4,
                                                            Deflate_bits@1 = case gleam_stdlib:bit_array_slice(
                                                                Rest@1,
                                                                0,
                                                                Deflate_size
                                                            ) of
                                                                {ok,
                                                                    Deflate_bits} -> Deflate_bits;
                                                                _assert_fail ->
                                                                    erlang:error(
                                                                            #{gleam_error => let_assert,
                                                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                                file => <<?FILEPATH/utf8>>,
                                                                                module => <<"packkit/zlib"/utf8>>,
                                                                                function => <<"decode_inner"/utf8>>,
                                                                                line => 166,
                                                                                value => _assert_fail,
                                                                                start => 5438,
                                                                                'end' => 5506,
                                                                                pattern_start => 5449,
                                                                                pattern_end => 5465}
                                                                        )
                                                            end,
                                                            Checksum_bits@1 = case gleam_stdlib:bit_array_slice(
                                                                Rest@1,
                                                                Deflate_size,
                                                                4
                                                            ) of
                                                                {ok,
                                                                    Checksum_bits} -> Checksum_bits;
                                                                _assert_fail@1 ->
                                                                    erlang:error(
                                                                            #{gleam_error => let_assert,
                                                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                                file => <<?FILEPATH/utf8>>,
                                                                                module => <<"packkit/zlib"/utf8>>,
                                                                                function => <<"decode_inner"/utf8>>,
                                                                                line => 167,
                                                                                value => _assert_fail@1,
                                                                                start => 5513,
                                                                                'end' => 5582,
                                                                                pattern_start => 5524,
                                                                                pattern_end => 5541}
                                                                        )
                                                            end,
                                                            gleam@result:'try'(
                                                                packkit@deflate:decode_with_limits(
                                                                    Deflate_bits@1,
                                                                    Limits
                                                                ),
                                                                fun(Plain) ->
                                                                    Expected = packkit@checksum:adler32(
                                                                        Plain
                                                                    ),
                                                                    Stored@1 = case Checksum_bits@1 of
                                                                        <<Stored:32/big>> -> Stored;
                                                                        _assert_fail@2 ->
                                                                            erlang:error(
                                                                                    #{gleam_error => let_assert,
                                                                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                                        file => <<?FILEPATH/utf8>>,
                                                                                        module => <<"packkit/zlib"/utf8>>,
                                                                                        function => <<"decode_inner"/utf8>>,
                                                                                        line => 175,
                                                                                        value => _assert_fail@2,
                                                                                        start => 5756,
                                                                                        'end' => 5806,
                                                                                        pattern_start => 5767,
                                                                                        pattern_end => 5790}
                                                                                )
                                                                    end,
                                                                    gleam@bool:guard(
                                                                        Stored@1
                                                                        /= Expected,
                                                                        {error,
                                                                            {codec_invalid_data,
                                                                                <<"zlib: Adler-32 mismatch"/utf8>>}},
                                                                        fun() ->
                                                                            {ok,
                                                                                Plain}
                                                                        end
                                                                    )
                                                                end
                                                            )
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    );

                _ ->
                    {error,
                        {codec_invalid_data,
                            <<"zlib: input too short for header"/utf8>>}}
            end end
    ).

-file("src/packkit/zlib.gleam", 69).
?DOC(
    " Decode a zlib byte stream using explicit limits.  Returns\n"
    " `CodecDictionaryRequired` when the stream sets the `FDICT` bit;\n"
    " use [decode_with_dictionary] in that case.\n"
).
-spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_with_limits(Bytes, Limits) ->
    decode_inner(Bytes, Limits, <<>>, false).

-file("src/packkit/zlib.gleam", 62).
?DOC(" Decode a zlib byte stream using default limits.\n").
-spec decode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
decode(Bytes) ->
    decode_with_limits(Bytes, packkit@limit:default()).

-file("src/packkit/zlib.gleam", 94).
?DOC(
    " Like [decode_with_dictionary] but accepts an explicit `Limits`\n"
    " value.\n"
).
-spec decode_with_dictionary_and_limits(
    bitstring(),
    bitstring(),
    packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
decode_with_dictionary_and_limits(Bytes, Dictionary, Limits) ->
    decode_inner(Bytes, Limits, Dictionary, true).

-file("src/packkit/zlib.gleam", 81).
?DOC(
    " Decode a zlib byte stream that uses the FDICT preset-dictionary\n"
    " path.  Verifies the four-byte DICT_ID against the supplied bytes\n"
    " and returns `CodecDictionaryMismatch` when they do not agree.  If\n"
    " `bytes` does not advertise FDICT this falls through to the regular\n"
    " decoder so callers can pass the dictionary defensively.\n"
).
-spec decode_with_dictionary(bitstring(), bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
decode_with_dictionary(Bytes, Dictionary) ->
    decode_with_dictionary_and_limits(
        Bytes,
        Dictionary,
        packkit@limit:default()
    ).