Skip to main content

src/packkit@lz4.erl

-module(packkit@lz4).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/lz4.gleam").
-export([codec/0, decode_with_limits/2, decode/1, encode/1, encode_with_content_size/1]).
-export_type([legacy_blocks_step/0, blocks_step/0, block_step/0, copy_step/0]).

-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(
    " LZ4 frame format codec.\n"
    "\n"
    " Decodes LZ4 frames (magic `0x184D2204`) as specified in the LZ4\n"
    " Frame Format Description.  The encoder runs a greedy 4-byte hash-\n"
    " chain match-finder over each block and emits the LZ77 sequences\n"
    " in the canonical block layout (token byte + optional length\n"
    " extensions + literals + 16-bit little-endian offset + optional\n"
    " match-length extensions).  Blocks that don't shrink are emitted\n"
    " in the uncompressed form to guarantee the frame never grows\n"
    " beyond `1 + ceil(input_size / block_max) * (4 + block_max)`.\n"
).

-type legacy_blocks_step() :: {legacy_blocks_done, bitstring()} |
    {legacy_blocks_continue, bitstring(), bitstring()}.

-type blocks_step() :: {blocks_done, bitstring()} |
    {blocks_continue, bitstring(), bitstring()}.

-type block_step() :: {block_done, bitstring()} |
    {block_continue, bitstring(), bitstring()}.

-type copy_step() :: {copy_done, bitstring()} |
    {copy_continue, bitstring(), integer()}.

-file("src/packkit/lz4.gleam", 58).
?DOC(" LZ4 frame codec smart constructor.\n").
-spec codec() -> packkit@codec:codec().
codec() ->
    packkit@codec:lz4().

-file("src/packkit/lz4.gleam", 356).
-spec build_byte_table(
    bitstring(),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
build_byte_table(Bytes, Index, Acc) ->
    case Bytes of
        <<B, Rest/binary>> ->
            build_byte_table(Rest, Index + 1, gleam@dict:insert(Acc, Index, B));

        _ ->
            Acc
    end.

-file("src/packkit/lz4.gleam", 368).
-spec byte_at(gleam@dict:dict(integer(), integer()), integer()) -> integer().
byte_at(Table, Index) ->
    case gleam_stdlib:map_get(Table, Index) of
        {ok, B} ->
            B;

        _ ->
            0
    end.

-file("src/packkit/lz4.gleam", 375).
-spec hash4(integer(), integer(), integer(), integer()) -> integer().
hash4(B0, B1, B2, B3) ->
    Combined = erlang:'bor'(
        B0,
        erlang:'bor'(
            erlang:'bsl'(B1, 8),
            erlang:'bor'(erlang:'bsl'(B2, 16), erlang:'bsl'(B3, 24))
        )
    ),
    erlang:'band'(Combined * 2654435761, 16#FFFF).

-file("src/packkit/lz4.gleam", 393).
-spec bytes4_equal(gleam@dict:dict(integer(), integer()), integer(), integer()) -> boolean().
bytes4_equal(Table, P1, P2) ->
    (((byte_at(Table, P1) =:= byte_at(Table, P2)) andalso (byte_at(
        Table,
        P1 + 1
    )
    =:= byte_at(Table, P2 + 1)))
    andalso (byte_at(Table, P1 + 2) =:= byte_at(Table, P2 + 2)))
    andalso (byte_at(Table, P1 + 3) =:= byte_at(Table, P2 + 3)).

-file("src/packkit/lz4.gleam", 400).
-spec lz4_match_length(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    integer()
) -> integer().
lz4_match_length(Table, Base, Cursor, Limit_pos, Acc) ->
    case (Cursor + Acc) >= Limit_pos of
        true ->
            Acc;

        false ->
            case byte_at(Table, Base + Acc) =:= byte_at(Table, Cursor + Acc) of
                true ->
                    lz4_match_length(Table, Base, Cursor, Limit_pos, Acc + 1);

                false ->
                    Acc
            end
    end.

-file("src/packkit/lz4.gleam", 498).
-spec encode_length_extension(integer()) -> bitstring().
encode_length_extension(N) ->
    case N of
        N@1 when N@1 < 255 ->
            <<N@1>>;

        _ ->
            gleam_stdlib:bit_array_concat(
                [<<16#FF>>, encode_length_extension(N - 255)]
            )
    end.

-file("src/packkit/lz4.gleam", 505).
-spec collect_bytes(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    bitstring()
) -> bitstring().
collect_bytes(Table, Start, Count, Acc) ->
    case Count of
        0 ->
            Acc;

        _ ->
            collect_bytes(
                Table,
                Start + 1,
                Count - 1,
                <<Acc/bitstring, (byte_at(Table, Start))>>
            )
    end.

-file("src/packkit/lz4.gleam", 480).
-spec emit_literal_only_chunk(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer()
) -> bitstring().
emit_literal_only_chunk(Table, Start, Lit_len) ->
    Lit_high = case Lit_len >= 15 of
        true ->
            15;

        false ->
            Lit_len
    end,
    Token = erlang:'bsl'(Lit_high, 4),
    Lit_ext = case Lit_len >= 15 of
        true ->
            encode_length_extension(Lit_len - 15);

        false ->
            <<>>
    end,
    Literals = collect_bytes(Table, Start, Lit_len, <<>>),
    gleam_stdlib:bit_array_concat([<<Token>>, Lit_ext, Literals]).

-file("src/packkit/lz4.gleam", 475).
-spec emit_literal_only_block(bitstring(), integer(), integer()) -> bitstring().
emit_literal_only_block(Bytes, Start, Size) ->
    Table = build_byte_table(Bytes, 0, maps:new()),
    emit_literal_only_chunk(Table, Start, Size).

-file("src/packkit/lz4.gleam", 703).
-spec finalize(bitstring(), bitstring(), boolean()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
finalize(After_end, Output, Content_checksum) ->
    case Content_checksum of
        false ->
            {ok, Output};

        true ->
            case erlang:byte_size(After_end) >= 4 of
                true ->
                    {ok, Output};

                false ->
                    {error,
                        {codec_invalid_data,
                            <<"lz4: content checksum missing"/utf8>>}}
            end
    end.

-file("src/packkit/lz4.gleam", 834).
-spec read_extension(bitstring(), integer()) -> {ok, {integer(), bitstring()}} |
    {error, packkit@error:codec_error()}.
read_extension(Bytes, Acc) ->
    case Bytes of
        <<B, Rest/binary>> ->
            case B of
                16#FF ->
                    read_extension(Rest, Acc + 16#FF);

                _ ->
                    {ok, {Acc + B, Rest}}
            end;

        _ ->
            {error,
                {codec_invalid_data, <<"lz4: length extension truncated"/utf8>>}}
    end.

-file("src/packkit/lz4.gleam", 911).
-spec append_with_limit(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
append_with_limit(Output, Chunk, Limits) ->
    Projected = erlang:byte_size(Output) + erlang:byte_size(Chunk),
    case Projected > packkit@limit:max_output_bytes(Limits) of
        true ->
            {error,
                {codec_limit_exceeded, <<"max_output_bytes"/utf8>>, Projected}};

        false ->
            {ok, gleam_stdlib:bit_array_concat([Output, Chunk])}
    end.

-file("src/packkit/lz4.gleam", 894).
-spec copy_match_byte_step(
    bitstring(),
    integer(),
    integer(),
    packkit@limit:limits()
) -> {ok, copy_step()} | {error, packkit@error:codec_error()}.
copy_match_byte_step(Output, Offset, Length, Limits) ->
    case Length of
        0 ->
            {ok, {copy_done, Output}};

        _ ->
            Size = erlang:byte_size(Output),
            Byte_slice@1 = case gleam_stdlib:bit_array_slice(
                Output,
                Size - Offset,
                1
            ) of
                {ok, Byte_slice} -> Byte_slice;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/lz4"/utf8>>,
                                function => <<"copy_match_byte_step"/utf8>>,
                                line => 904,
                                value => _assert_fail,
                                start => 26798,
                                'end' => 26867,
                                pattern_start => 26809,
                                pattern_end => 26823})
            end,
            gleam@result:'try'(
                append_with_limit(Output, Byte_slice@1, Limits),
                fun(New_output) ->
                    {ok, {copy_continue, New_output, Length - 1}}
                end
            )
    end.

-file("src/packkit/lz4.gleam", 875).
-spec copy_match_byte_by_byte(
    bitstring(),
    integer(),
    integer(),
    packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
copy_match_byte_by_byte(Output, Offset, Length, Limits) ->
    case copy_match_byte_step(Output, Offset, Length, Limits) of
        {error, Err} ->
            {error, Err};

        {ok, {copy_done, Out}} ->
            {ok, Out};

        {ok, {copy_continue, New_output, New_length}} ->
            copy_match_byte_by_byte(New_output, Offset, New_length, Limits)
    end.

-file("src/packkit/lz4.gleam", 849).
-spec copy_match(bitstring(), integer(), integer(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
copy_match(Output, Offset, Length, Limits) ->
    case Length of
        0 ->
            {ok, Output};

        _ ->
            Size = erlang:byte_size(Output),
            case Offset >= Length of
                true ->
                    Chunk@1 = case gleam_stdlib:bit_array_slice(
                        Output,
                        Size - Offset,
                        Length
                    ) of
                        {ok, Chunk} -> Chunk;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/lz4"/utf8>>,
                                        function => <<"copy_match"/utf8>>,
                                        line => 861,
                                        value => _assert_fail,
                                        start => 25518,
                                        'end' => 25587,
                                        pattern_start => 25529,
                                        pattern_end => 25538})
                    end,
                    append_with_limit(Output, Chunk@1, Limits);

                false ->
                    copy_match_byte_by_byte(Output, Offset, Length, Limits)
            end
    end.

-file("src/packkit/lz4.gleam", 756).
-spec decode_block_step(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        block_step()} |
    {error, packkit@error:codec_error()}.
decode_block_step(Block, Output, Limits) ->
    case Block of
        <<Token, Rest/binary>> ->
            Lit_len_base = erlang:'bsr'(Token, 4),
            Match_len_base = erlang:'band'(Token, 16#0F),
            gleam@result:'try'(case Lit_len_base of
                    15 ->
                        read_extension(Rest, 15);

                    _ ->
                        {ok, {Lit_len_base, Rest}}
                end, fun(_use0) ->
                    {Lit_len, Rest@1} = _use0,
                    case erlang:byte_size(Rest@1) < Lit_len of
                        true ->
                            {error,
                                {codec_invalid_data,
                                    <<"lz4: literal length exceeds block payload"/utf8>>}};

                        false ->
                            Literals@1 = case gleam_stdlib:bit_array_slice(
                                Rest@1,
                                0,
                                Lit_len
                            ) of
                                {ok, Literals} -> Literals;
                                _assert_fail ->
                                    erlang:error(#{gleam_error => let_assert,
                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                file => <<?FILEPATH/utf8>>,
                                                module => <<"packkit/lz4"/utf8>>,
                                                function => <<"decode_block_step"/utf8>>,
                                                line => 777,
                                                value => _assert_fail,
                                                start => 22993,
                                                'end' => 23052,
                                                pattern_start => 23004,
                                                pattern_end => 23016})
                            end,
                            After_literals@1 = case gleam_stdlib:bit_array_slice(
                                Rest@1,
                                Lit_len,
                                erlang:byte_size(Rest@1) - Lit_len
                            ) of
                                {ok, After_literals} -> After_literals;
                                _assert_fail@1 ->
                                    erlang:error(#{gleam_error => let_assert,
                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                file => <<?FILEPATH/utf8>>,
                                                module => <<"packkit/lz4"/utf8>>,
                                                function => <<"decode_block_step"/utf8>>,
                                                line => 778,
                                                value => _assert_fail@1,
                                                start => 23063,
                                                'end' => 23174,
                                                pattern_start => 23074,
                                                pattern_end => 23092})
                            end,
                            gleam@result:'try'(
                                append_with_limit(Output, Literals@1, Limits),
                                fun(Output@1) ->
                                    case erlang:byte_size(After_literals@1) of
                                        0 ->
                                            {ok, {block_done, Output@1}};

                                        _ ->
                                            case After_literals@1 of
                                                <<Offset:16/little,
                                                    After_offset/binary>> ->
                                                    gleam@bool:guard(
                                                        Offset =:= 0,
                                                        {error,
                                                            {codec_invalid_data,
                                                                <<"lz4: zero match offset"/utf8>>}},
                                                        fun() ->
                                                            gleam@result:'try'(
                                                                case Match_len_base of
                                                                    15 ->
                                                                        read_extension(
                                                                            After_offset,
                                                                            15
                                                                        );

                                                                    _ ->
                                                                        {ok,
                                                                            {Match_len_base,
                                                                                After_offset}}
                                                                end,
                                                                fun(_use0@1) ->
                                                                    {Match_len_extra,
                                                                        After_offset@1} = _use0@1,
                                                                    Match_len = Match_len_extra
                                                                    + 4,
                                                                    gleam@bool:guard(
                                                                        Offset > erlang:byte_size(
                                                                            Output@1
                                                                        ),
                                                                        {error,
                                                                            {codec_invalid_data,
                                                                                <<"lz4: match offset exceeds output"/utf8>>}},
                                                                        fun() ->
                                                                            gleam@result:'try'(
                                                                                copy_match(
                                                                                    Output@1,
                                                                                    Offset,
                                                                                    Match_len,
                                                                                    Limits
                                                                                ),
                                                                                fun(
                                                                                    Output@2
                                                                                ) ->
                                                                                    {ok,
                                                                                        {block_continue,
                                                                                            After_offset@1,
                                                                                            Output@2}}
                                                                                end
                                                                            )
                                                                        end
                                                                    )
                                                                end
                                                            )
                                                        end
                                                    );

                                                _ ->
                                                    {error,
                                                        {codec_invalid_data,
                                                            <<"lz4: match offset truncated"/utf8>>}}
                                            end
                                    end
                                end
                            )
                    end
                end);

        <<>> ->
            {ok, {block_done, Output}};

        _ ->
            {error, {codec_invalid_data, <<"lz4: malformed block"/utf8>>}}
    end.

-file("src/packkit/lz4.gleam", 738).
-spec decode_block_loop(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_block_loop(Block, Output, Limits) ->
    case decode_block_step(Block, Output, Limits) of
        {error, Err} ->
            {error, Err};

        {ok, {block_done, Out}} ->
            {ok, Out};

        {ok, {block_continue, Next_block, Next_output}} ->
            decode_block_loop(Next_block, Next_output, Limits)
    end.

-file("src/packkit/lz4.gleam", 719).
-spec decode_block(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_block(Block, Output, Limits) ->
    case erlang:byte_size(Block) of
        0 ->
            {ok, Output};

        _ ->
            decode_block_loop(Block, Output, Limits)
    end.

-file("src/packkit/lz4.gleam", 927).
-spec maybe_skip(bitstring(), boolean(), integer(), binary()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
maybe_skip(Bytes, Active, Count, Message) ->
    case Active of
        false ->
            {ok, Bytes};

        true ->
            case erlang:byte_size(Bytes) < Count of
                true ->
                    {error, {codec_invalid_data, Message}};

                false ->
                    After@1 = case gleam_stdlib:bit_array_slice(
                        Bytes,
                        Count,
                        erlang:byte_size(Bytes) - Count
                    ) of
                        {ok, After} -> After;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/lz4"/utf8>>,
                                        function => <<"maybe_skip"/utf8>>,
                                        line => 939,
                                        value => _assert_fail,
                                        start => 27760,
                                        'end' => 27860,
                                        pattern_start => 27771,
                                        pattern_end => 27780})
                    end,
                    {ok, After@1}
            end
    end.

-file("src/packkit/lz4.gleam", 947).
-spec block_max_bytes(integer()) -> integer().
block_max_bytes(Index) ->
    case Index of
        4 ->
            64000;

        5 ->
            256000;

        6 ->
            1000000;

        7 ->
            4000000;

        _ ->
            4000000
    end.

-file("src/packkit/lz4.gleam", 542).
-spec decode_legacy_blocks_step(
    bitstring(),
    bitstring(),
    packkit@limit:limits()
) -> {ok, legacy_blocks_step()} | {error, packkit@error:codec_error()}.
decode_legacy_blocks_step(Bytes, Output, Limits) ->
    case Bytes of
        <<>> ->
            {ok, {legacy_blocks_done, Output}};

        <<Block_size:32/little, _/binary>> when Block_size =:= 0 ->
            {ok, {legacy_blocks_done, Output}};

        <<Block_size@1:32/little, _/binary>> when Block_size@1 > 8388608 ->
            {ok, {legacy_blocks_done, Output}};

        <<Block_size@2:32/little, Rest/binary>> ->
            case erlang:byte_size(Rest) < Block_size@2 of
                true ->
                    {error,
                        {codec_invalid_data,
                            <<"lz4 legacy: block payload truncated"/utf8>>}};

                false ->
                    Block@1 = case gleam_stdlib:bit_array_slice(
                        Rest,
                        0,
                        Block_size@2
                    ) of
                        {ok, Block} -> Block;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/lz4"/utf8>>,
                                        function => <<"decode_legacy_blocks_step"/utf8>>,
                                        line => 569,
                                        value => _assert_fail,
                                        start => 16860,
                                        'end' => 16919,
                                        pattern_start => 16871,
                                        pattern_end => 16880})
                    end,
                    After_block@1 = case gleam_stdlib:bit_array_slice(
                        Rest,
                        Block_size@2,
                        erlang:byte_size(Rest) - Block_size@2
                    ) of
                        {ok, After_block} -> After_block;
                        _assert_fail@1 ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/lz4"/utf8>>,
                                        function => <<"decode_legacy_blocks_step"/utf8>>,
                                        line => 570,
                                        value => _assert_fail@1,
                                        start => 16930,
                                        'end' => 17101,
                                        pattern_start => 16941,
                                        pattern_end => 16956})
                    end,
                    gleam@result:'try'(
                        decode_block(Block@1, Output, Limits),
                        fun(New_output) ->
                            {ok,
                                {legacy_blocks_continue,
                                    After_block@1,
                                    New_output}}
                        end
                    )
            end;

        _ ->
            {error,
                {codec_invalid_data,
                    <<"lz4 legacy: block header truncated"/utf8>>}}
    end.

-file("src/packkit/lz4.gleam", 524).
-spec decode_legacy_blocks(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_legacy_blocks(Bytes, Output, Limits) ->
    case decode_legacy_blocks_step(Bytes, Output, Limits) of
        {error, Err} ->
            {error, Err};

        {ok, {legacy_blocks_done, Out}} ->
            {ok, Out};

        {ok, {legacy_blocks_continue, Next_bytes, Next_output}} ->
            decode_legacy_blocks(Next_bytes, Next_output, Limits)
    end.

-file("src/packkit/lz4.gleam", 630).
-spec decode_blocks_step(
    bitstring(),
    bitstring(),
    boolean(),
    boolean(),
    integer(),
    packkit@limit:limits()
) -> {ok, blocks_step()} | {error, packkit@error:codec_error()}.
decode_blocks_step(
    Bytes,
    Output,
    Block_checksum,
    Content_checksum,
    Block_max,
    Limits
) ->
    case Bytes of
        <<0:32/little, Rest/binary>> ->
            case finalize(Rest, Output, Content_checksum) of
                {ok, Out} ->
                    {ok, {blocks_done, Out}};

                {error, Err} ->
                    {error, Err}
            end;

        <<Header:32/little, Rest@1/binary>> ->
            Uncompressed = erlang:'band'(Header, 16#80000000) /= 0,
            Block_size = erlang:'band'(
                Header,
                erlang:'bxor'(16#FFFFFFFF, 16#80000000)
            ),
            gleam@bool:guard(
                Block_size > Block_max,
                {error,
                    {codec_invalid_data,
                        <<"lz4: block size exceeds frame max block size"/utf8>>}},
                fun() -> case erlang:byte_size(Rest@1) < Block_size of
                        true ->
                            {error,
                                {codec_invalid_data,
                                    <<"lz4: block payload truncated"/utf8>>}};

                        false ->
                            Block@1 = case gleam_stdlib:bit_array_slice(
                                Rest@1,
                                0,
                                Block_size
                            ) of
                                {ok, Block} -> Block;
                                _assert_fail ->
                                    erlang:error(#{gleam_error => let_assert,
                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                file => <<?FILEPATH/utf8>>,
                                                module => <<"packkit/lz4"/utf8>>,
                                                function => <<"decode_blocks_step"/utf8>>,
                                                line => 662,
                                                value => _assert_fail,
                                                start => 19471,
                                                'end' => 19530,
                                                pattern_start => 19482,
                                                pattern_end => 19491})
                            end,
                            After_block@1 = case gleam_stdlib:bit_array_slice(
                                Rest@1,
                                Block_size,
                                erlang:byte_size(Rest@1) - Block_size
                            ) of
                                {ok, After_block} -> After_block;
                                _assert_fail@1 ->
                                    erlang:error(#{gleam_error => let_assert,
                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                file => <<?FILEPATH/utf8>>,
                                                module => <<"packkit/lz4"/utf8>>,
                                                function => <<"decode_blocks_step"/utf8>>,
                                                line => 663,
                                                value => _assert_fail@1,
                                                start => 19541,
                                                'end' => 19712,
                                                pattern_start => 19552,
                                                pattern_end => 19567})
                            end,
                            gleam@result:'try'(case Block_checksum of
                                    true ->
                                        case erlang:byte_size(After_block@1) < 4 of
                                            true ->
                                                {error,
                                                    {codec_invalid_data,
                                                        <<"lz4: block checksum missing"/utf8>>}};

                                            false ->
                                                After@1 = case gleam_stdlib:bit_array_slice(
                                                    After_block@1,
                                                    4,
                                                    erlang:byte_size(
                                                        After_block@1
                                                    )
                                                    - 4
                                                ) of
                                                    {ok, After} -> After;
                                                    _assert_fail@2 ->
                                                        erlang:error(
                                                                #{gleam_error => let_assert,
                                                                    message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                    file => <<?FILEPATH/utf8>>,
                                                                    module => <<"packkit/lz4"/utf8>>,
                                                                    function => <<"decode_blocks_step"/utf8>>,
                                                                    line => 678,
                                                                    value => _assert_fail@2,
                                                                    start => 20052,
                                                                    'end' => 20253,
                                                                    pattern_start => 20063,
                                                                    pattern_end => 20072}
                                                            )
                                                end,
                                                {ok, After@1}
                                        end;

                                    false ->
                                        {ok, After_block@1}
                                end, fun(After_block@2) ->
                                    gleam@result:'try'(case Uncompressed of
                                            true ->
                                                append_with_limit(
                                                    Output,
                                                    Block@1,
                                                    Limits
                                                );

                                            false ->
                                                decode_block(
                                                    Block@1,
                                                    Output,
                                                    Limits
                                                )
                                        end, fun(New_output) ->
                                            {ok,
                                                {blocks_continue,
                                                    After_block@2,
                                                    New_output}}
                                        end)
                                end)
                    end end
            );

        _ ->
            {error,
                {codec_invalid_data, <<"lz4: block header truncated"/utf8>>}}
    end.

-file("src/packkit/lz4.gleam", 593).
-spec decode_blocks(
    bitstring(),
    bitstring(),
    boolean(),
    boolean(),
    integer(),
    packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
decode_blocks(
    Bytes,
    Output,
    Block_checksum,
    Content_checksum,
    Block_max,
    Limits
) ->
    case decode_blocks_step(
        Bytes,
        Output,
        Block_checksum,
        Content_checksum,
        Block_max,
        Limits
    ) of
        {error, Err} ->
            {error, Err};

        {ok, {blocks_done, Out}} ->
            {ok, Out};

        {ok, {blocks_continue, Next_bytes, Next_output}} ->
            decode_blocks(
                Next_bytes,
                Next_output,
                Block_checksum,
                Content_checksum,
                Block_max,
                Limits
            )
    end.

-file("src/packkit/lz4.gleam", 112).
?DOC(" Decode an LZ4 frame using explicit resource limits.\n").
-spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_with_limits(Bytes, Limits) ->
    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
                <<M:32/little, _/binary>> when M =:= 16#184C2102 ->
                    After_magic@1 = case gleam_stdlib:bit_array_slice(
                        Bytes,
                        4,
                        erlang:byte_size(Bytes) - 4
                    ) of
                        {ok, After_magic} -> After_magic;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/lz4"/utf8>>,
                                        function => <<"decode_with_limits"/utf8>>,
                                        line => 126,
                                        value => _assert_fail,
                                        start => 4235,
                                        'end' => 4329,
                                        pattern_start => 4246,
                                        pattern_end => 4261})
                    end,
                    decode_legacy_blocks(After_magic@1, <<>>, Limits);

                <<M@1:32/little, Flg, Bd, Rest/binary>> ->
                    gleam@bool:guard(
                        M@1 /= 16#184D2204,
                        {error,
                            {codec_invalid_data,
                                <<"lz4: bad frame magic"/utf8>>}},
                        fun() ->
                            gleam@bool:guard(
                                erlang:'band'(Flg, 16#C0) /= 16#40,
                                {error,
                                    {codec_invalid_data,
                                        <<"lz4: unsupported frame version"/utf8>>}},
                                fun() ->
                                    Block_checksum = erlang:'band'(Flg, 16#10)
                                    /= 0,
                                    Content_size_present = erlang:'band'(
                                        Flg,
                                        16#08
                                    )
                                    /= 0,
                                    Content_checksum = erlang:'band'(Flg, 16#04)
                                    /= 0,
                                    Dict_id_present = erlang:'band'(Flg, 16#01)
                                    /= 0,
                                    Bd_block_max_idx = erlang:'band'(
                                        erlang:'bsr'(Bd, 4),
                                        7
                                    ),
                                    Block_max = block_max_bytes(
                                        Bd_block_max_idx
                                    ),
                                    gleam@result:'try'(
                                        maybe_skip(
                                            Rest,
                                            Content_size_present,
                                            8,
                                            <<"lz4: content size truncated"/utf8>>
                                        ),
                                        fun(Rest@1) ->
                                            gleam@result:'try'(
                                                maybe_skip(
                                                    Rest@1,
                                                    Dict_id_present,
                                                    4,
                                                    <<"lz4: dictionary id truncated"/utf8>>
                                                ),
                                                fun(Rest@2) ->
                                                    gleam@result:'try'(
                                                        maybe_skip(
                                                            Rest@2,
                                                            true,
                                                            1,
                                                            <<"lz4: header checksum missing"/utf8>>
                                                        ),
                                                        fun(Rest@3) ->
                                                            decode_blocks(
                                                                Rest@3,
                                                                <<>>,
                                                                Block_checksum,
                                                                Content_checksum,
                                                                Block_max,
                                                                Limits
                                                            )
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    );

                _ ->
                    {error,
                        {codec_invalid_data,
                            <<"lz4: frame header truncated"/utf8>>}}
            end end
    ).

-file("src/packkit/lz4.gleam", 107).
?DOC(" Decode an LZ4 frame using the default resource limits.\n").
-spec decode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
decode(Bytes) ->
    decode_with_limits(Bytes, packkit@limit:default()).

-file("src/packkit/lz4.gleam", 417).
-spec lz4_insert_hashes(
    gleam@dict:dict(integer(), integer()),
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer()
) -> gleam@dict:dict(integer(), integer()).
lz4_insert_hashes(Table, Hashes, From, To, Size) ->
    case (From > To) orelse ((From + 4) > Size) of
        true ->
            Hashes;

        false ->
            Key = hash4(
                byte_at(Table, From),
                byte_at(Table, From + 1),
                byte_at(Table, From + 2),
                byte_at(Table, From + 3)
            ),
            lz4_insert_hashes(
                Table,
                gleam@dict:insert(Hashes, Key, From),
                From + 1,
                To,
                Size
            )
    end.

-file("src/packkit/lz4.gleam", 445).
-spec emit_sequence(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    integer()
) -> bitstring().
emit_sequence(Table, Lit_start, Lit_len, Distance, Match_len) ->
    Ml_stored = Match_len - 4,
    Lit_high = case Lit_len >= 15 of
        true ->
            15;

        false ->
            Lit_len
    end,
    Ml_high = case Ml_stored >= 15 of
        true ->
            15;

        false ->
            Ml_stored
    end,
    Token = erlang:'bor'(erlang:'bsl'(Lit_high, 4), Ml_high),
    Lit_ext = case Lit_len >= 15 of
        true ->
            encode_length_extension(Lit_len - 15);

        false ->
            <<>>
    end,
    Literals = collect_bytes(Table, Lit_start, Lit_len, <<>>),
    Offset = <<Distance:16/little>>,
    Ml_ext = case Ml_stored >= 15 of
        true ->
            encode_length_extension(Ml_stored - 15);

        false ->
            <<>>
    end,
    gleam_stdlib:bit_array_concat(
        [<<Token>>, Lit_ext, Literals, Offset, Ml_ext]
    ).

-file("src/packkit/lz4.gleam", 268).
-spec step_compress(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer()),
    list(bitstring())
) -> bitstring().
step_compress(Table, Size, Pos, Last_lit_start, Hashes, Acc) ->
    Key = hash4(
        byte_at(Table, Pos),
        byte_at(Table, Pos + 1),
        byte_at(Table, Pos + 2),
        byte_at(Table, Pos + 3)
    ),
    case gleam_stdlib:map_get(Hashes, Key) of
        {ok, Prev} ->
            Distance = Pos - Prev,
            Valid = ((Distance >= 1) andalso (Distance =< 65535)) andalso bytes4_equal(
                Table,
                Prev,
                Pos
            ),
            case Valid of
                false ->
                    compress_loop(
                        Table,
                        Size,
                        Pos + 1,
                        Last_lit_start,
                        gleam@dict:insert(Hashes, Key, Pos),
                        Acc
                    );

                true ->
                    Search_end = Size - 5,
                    Match_len = lz4_match_length(
                        Table,
                        Prev,
                        Pos,
                        Search_end,
                        0
                    ),
                    case Match_len < 4 of
                        true ->
                            compress_loop(
                                Table,
                                Size,
                                Pos + 1,
                                Last_lit_start,
                                gleam@dict:insert(Hashes, Key, Pos),
                                Acc
                            );

                        false ->
                            Lit_len = Pos - Last_lit_start,
                            Seq = emit_sequence(
                                Table,
                                Last_lit_start,
                                Lit_len,
                                Distance,
                                Match_len
                            ),
                            Next_pos = Pos + Match_len,
                            New_hashes = lz4_insert_hashes(
                                Table,
                                gleam@dict:insert(Hashes, Key, Pos),
                                Pos + 1,
                                Next_pos - 1,
                                Size
                            ),
                            compress_loop(
                                Table,
                                Size,
                                Next_pos,
                                Next_pos,
                                New_hashes,
                                [Seq | Acc]
                            )
                    end
            end;

        _ ->
            compress_loop(
                Table,
                Size,
                Pos + 1,
                Last_lit_start,
                gleam@dict:insert(Hashes, Key, Pos),
                Acc
            )
    end.

-file("src/packkit/lz4.gleam", 248).
-spec compress_loop(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer()),
    list(bitstring())
) -> bitstring().
compress_loop(Table, Size, Pos, Last_lit_start, Hashes, Acc) ->
    Last_search = Size - 12,
    case Pos > Last_search of
        true ->
            Lit_len = Size - Last_lit_start,
            Tail = emit_literal_only_chunk(Table, Last_lit_start, Lit_len),
            gleam_stdlib:bit_array_concat(lists:reverse([Tail | Acc]));

        false ->
            step_compress(Table, Size, Pos, Last_lit_start, Hashes, Acc)
    end.

-file("src/packkit/lz4.gleam", 238).
-spec compress_block(bitstring(), integer()) -> bitstring().
compress_block(Bytes, Size) ->
    case Size =< 12 of
        true ->
            emit_literal_only_block(Bytes, 0, Size);

        false ->
            Table = build_byte_table(Bytes, 0, maps:new()),
            compress_loop(Table, Size, 0, 0, maps:new(), [])
    end.

-file("src/packkit/lz4.gleam", 183).
-spec encode_blocks(bitstring(), list(bitstring())) -> bitstring().
encode_blocks(Remaining, Acc) ->
    Total = erlang:byte_size(Remaining),
    case Total of
        0 ->
            gleam_stdlib:bit_array_concat(lists:reverse(Acc));

        _ ->
            Chunk_size = case Total > 4194304 of
                true ->
                    4194304;

                false ->
                    Total
            end,
            Chunk@1 = case gleam_stdlib:bit_array_slice(
                Remaining,
                0,
                Chunk_size
            ) of
                {ok, Chunk} -> Chunk;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/lz4"/utf8>>,
                                function => <<"encode_blocks"/utf8>>,
                                line => 192,
                                value => _assert_fail,
                                start => 6195,
                                'end' => 6259,
                                pattern_start => 6206,
                                pattern_end => 6215})
            end,
            After@1 = case gleam_stdlib:bit_array_slice(
                Remaining,
                Chunk_size,
                Total - Chunk_size
            ) of
                {ok, After} -> After;
                _assert_fail@1 ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/lz4"/utf8>>,
                                function => <<"encode_blocks"/utf8>>,
                                line => 193,
                                value => _assert_fail@1,
                                start => 6266,
                                'end' => 6355,
                                pattern_start => 6277,
                                pattern_end => 6286})
            end,
            Compressed = compress_block(Chunk@1, Chunk_size),
            Compressed_size = erlang:byte_size(Compressed),
            Framed = case Compressed_size < Chunk_size of
                true ->
                    <<Compressed_size:32/little, Compressed/bitstring>>;

                false ->
                    <<(erlang:'bor'(16#80000000, Chunk_size)):32/little,
                        Chunk@1/bitstring>>
            end,
            encode_blocks(After@1, [Framed | Acc])
    end.

-file("src/packkit/lz4.gleam", 82).
-spec encode_internal(bitstring(), boolean()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode_internal(Bytes, Content_size_present) ->
    Flg = case Content_size_present of
        true ->
            16#60 + 16#08;

        false ->
            16#60
    end,
    Bd = 16#70,
    Descriptor = case Content_size_present of
        true ->
            <<Flg, Bd, (erlang:byte_size(Bytes)):64/little>>;

        false ->
            <<Flg, Bd>>
    end,
    Hc = erlang:'band'(
        erlang:'bsr'(packkit@internal@xxh32:digest(Descriptor, 0), 8),
        16#FF
    ),
    Header = gleam_stdlib:bit_array_concat(
        [<<16#184D2204:32/little>>, Descriptor, <<Hc>>]
    ),
    Blocks = encode_blocks(Bytes, []),
    End_mark = <<0:32/little>>,
    {ok, gleam_stdlib:bit_array_concat([Header, Blocks, End_mark])}.

-file("src/packkit/lz4.gleam", 66).
?DOC(
    " Encode `bytes` as an LZ4 frame.  The frame descriptor uses\n"
    " independent blocks, the v1 frame version, and a 4 MiB block\n"
    " maximum; no content size, block checksum, content checksum, or\n"
    " dictionary id is written.\n"
).
-spec encode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode(Bytes) ->
    encode_internal(Bytes, false).

-file("src/packkit/lz4.gleam", 76).
?DOC(
    " Encode `bytes` as an LZ4 frame and store the uncompressed\n"
    " content size in the frame descriptor.  Strict LZ4 decoders use\n"
    " the value to pre-allocate the output buffer and reject any\n"
    " frame whose payload disagrees with the declared length; our own\n"
    " decoder simply skips the field today, so encoding it does not\n"
    " change `encode -> decode` round trips.\n"
).
-spec encode_with_content_size(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode_with_content_size(Bytes) ->
    encode_internal(Bytes, true).