Skip to main content

src/packkit@snappy.erl

-module(packkit@snappy).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/snappy.gleam").
-export([codec/0, raw_decode_with_limits/2, raw_decode/1, decode_with_limits/2, decode/1, raw_encode/1, encode/1]).

-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(
    " Snappy raw-block and framed codec.\n"
    "\n"
    " `snappy.raw_decode` and `snappy.raw_encode` operate on the original\n"
    " Snappy block format documented in `snappy-format-description`.\n"
    " `snappy.decode` and `snappy.encode` operate on the streaming\n"
    " framed form (`sNaPpY` stream identifier and chunked layout).  The\n"
    " raw encoder runs a greedy 4-byte hash-chain match-finder and\n"
    " emits literal + copy-1 / copy-2 / copy-4 sequences in the\n"
    " canonical block layout; the framed encoder dispatches each chunk\n"
    " through the raw encoder and picks the smaller of the compressed\n"
    " (`0x00`) and uncompressed (`0x01`) chunk types.\n"
).

-file("src/packkit/snappy.gleam", 39).
?DOC(" Snappy framed codec smart constructor.\n").
-spec codec() -> packkit@codec:codec().
codec() ->
    packkit@codec:snappy().

-file("src/packkit/snappy.gleam", 258).
-spec read_varint(bitstring(), integer(), integer()) -> {ok,
        {integer(), bitstring()}} |
    {error, packkit@error:codec_error()}.
read_varint(Bytes, Acc, Shift) ->
    case Bytes of
        <<B, Rest/binary>> ->
            Part = erlang:'band'(B, 16#7F),
            Acc@1 = erlang:'bor'(Acc, erlang:'bsl'(Part, Shift)),
            case erlang:'band'(B, 16#80) of
                0 ->
                    {ok, {Acc@1, Rest}};

                _ ->
                    case Shift > 28 of
                        true ->
                            {error,
                                {codec_invalid_data,
                                    <<"snappy: oversize varint"/utf8>>}};

                        false ->
                            read_varint(Rest, Acc@1, Shift + 7)
                    end
            end;

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

-file("src/packkit/snappy.gleam", 286).
-spec write_varint_loop(integer(), bitstring()) -> bitstring().
write_varint_loop(Value, Acc) ->
    case Value < 16#80 of
        true ->
            gleam_stdlib:bit_array_concat([Acc, <<Value>>]);

        false ->
            Low = erlang:'band'(Value, 16#7F),
            High = erlang:'bsr'(Value, 7),
            Byte_value = erlang:'bor'(Low, 16#80),
            write_varint_loop(
                High,
                gleam_stdlib:bit_array_concat([Acc, <<Byte_value>>])
            )
    end.

-file("src/packkit/snappy.gleam", 282).
-spec write_varint(integer()) -> bitstring().
write_varint(Value) ->
    write_varint_loop(Value, <<>>).

-file("src/packkit/snappy.gleam", 369).
-spec read_le(bitstring(), integer()) -> integer().
read_le(Bytes, Width) ->
    case {Width, Bytes} of
        {1, <<B>>} ->
            B;

        {2, <<B1, B2>>} ->
            B1 + (B2 * 256);

        {3, <<B1@1, B2@1, B3>>} ->
            (B1@1 + (B2@1 * 256)) + (B3 * 65536);

        {4, <<B1@2, B2@2, B3@1, B4>>} ->
            ((B1@2 + (B2@2 * 256)) + (B3@1 * 65536)) + (B4 * 16777216);

        {_, _} ->
            0
    end.

-file("src/packkit/snappy.gleam", 350).
-spec read_literal_extra(bitstring(), integer()) -> {ok,
        {integer(), bitstring()}} |
    {error, packkit@error:codec_error()}.
read_literal_extra(Rest, Width) ->
    case erlang:byte_size(Rest) < Width of
        true ->
            {error,
                {codec_invalid_data,
                    <<"snappy: literal length extension truncated"/utf8>>}};

        false ->
            Prefix@1 = case gleam_stdlib:bit_array_slice(Rest, 0, Width) of
                {ok, Prefix} -> Prefix;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/snappy"/utf8>>,
                                function => <<"read_literal_extra"/utf8>>,
                                line => 360,
                                value => _assert_fail,
                                start => 11132,
                                'end' => 11187,
                                pattern_start => 11143,
                                pattern_end => 11153})
            end,
            After@1 = case gleam_stdlib:bit_array_slice(
                Rest,
                Width,
                erlang:byte_size(Rest) - Width
            ) 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/snappy"/utf8>>,
                                function => <<"read_literal_extra"/utf8>>,
                                line => 361,
                                value => _assert_fail@1,
                                start => 11194,
                                'end' => 11288,
                                pattern_start => 11205,
                                pattern_end => 11214})
            end,
            Value = read_le(Prefix@1, Width),
            {ok, {Value + 1, After@1}}
    end.

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

        _ ->
            Acc
    end.

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

        _ ->
            0
    end.

-file("src/packkit/snappy.gleam", 625).
-spec snappy_hash4(integer(), integer(), integer(), integer()) -> integer().
snappy_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/snappy.gleam", 640).
-spec snappy_bytes4_equal(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer()
) -> boolean().
snappy_bytes4_equal(Table, P1, P2) ->
    (((snappy_byte_at(Table, P1) =:= snappy_byte_at(Table, P2)) andalso (snappy_byte_at(
        Table,
        P1 + 1
    )
    =:= snappy_byte_at(Table, P2 + 1)))
    andalso (snappy_byte_at(Table, P1 + 2) =:= snappy_byte_at(Table, P2 + 2)))
    andalso (snappy_byte_at(Table, P1 + 3) =:= snappy_byte_at(Table, P2 + 3)).

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

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

                false ->
                    Acc
            end
    end.

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

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

-file("src/packkit/snappy.gleam", 714).
?DOC(
    " Emit one or more copy records that together cover a single LZ77\n"
    " match.  The Snappy 1-byte offset form caps match length at 11; the\n"
    " 2- and 4-byte forms cap at 64.  Matches longer than 64 are split\n"
    " into multiple back-to-back copies sharing the same offset.\n"
).
-spec emit_copy_for(integer(), integer(), bitstring()) -> bitstring().
emit_copy_for(Offset, Length, Acc) ->
    case Length of
        0 ->
            Acc;

        _ ->
            case ((Offset < 2048) andalso (Length >= 4)) andalso (Length =< 11) of
                true ->
                    Tag = erlang:'bor'(
                        erlang:'bsl'(Length - 4, 2),
                        erlang:'bor'(
                            erlang:'bsl'(erlang:'bsr'(Offset, 8), 5),
                            1
                        )
                    ),
                    <<Acc/bitstring, Tag, (erlang:'band'(Offset, 16#FF))>>;

                false ->
                    Chunk = case Length > 64 of
                        true ->
                            64;

                        false ->
                            Length
                    end,
                    Piece = case Offset < 65536 of
                        true ->
                            Tag@1 = erlang:'bor'(erlang:'bsl'(Chunk - 1, 2), 2),
                            <<Tag@1, Offset:16/little>>;

                        false ->
                            Tag@2 = erlang:'bor'(erlang:'bsl'(Chunk - 1, 2), 3),
                            <<Tag@2, Offset:32/little>>
                    end,
                    emit_copy_for(
                        Offset,
                        Length - Chunk,
                        <<Acc/bitstring, Piece/bitstring>>
                    )
            end
    end.

-file("src/packkit/snappy.gleam", 772).
-spec literal_extension(integer()) -> {bitstring(), integer()}.
literal_extension(Encoded_len) ->
    case Encoded_len < 16#100 of
        true ->
            {<<Encoded_len>>, 1};

        false ->
            case Encoded_len < 16#10000 of
                true ->
                    {<<Encoded_len:16/little>>, 2};

                false ->
                    case Encoded_len < 16#1000000 of
                        true ->
                            {<<Encoded_len:24/little>>, 3};

                        false ->
                            {<<Encoded_len:32/little>>, 4}
                    end
            end
    end.

-file("src/packkit/snappy.gleam", 751).
-spec emit_literal(bitstring()) -> bitstring().
emit_literal(Bytes) ->
    Length = erlang:byte_size(Bytes),
    case Length of
        0 ->
            <<>>;

        _ ->
            Encoded_len = Length - 1,
            Header = case Encoded_len < 60 of
                true ->
                    <<(erlang:'bsl'(Encoded_len, 2))>>;

                false ->
                    {Extra, Width} = literal_extension(Encoded_len),
                    gleam_stdlib:bit_array_concat(
                        [<<(erlang:'bsl'((60 + Width) - 1, 2))>>, Extra]
                    )
            end,
            gleam_stdlib:bit_array_concat([Header, Bytes])
    end.

-file("src/packkit/snappy.gleam", 787).
-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/snappy.gleam", 223).
-spec decode_uncompressed_chunk(
    bitstring(),
    bitstring(),
    packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
decode_uncompressed_chunk(Body, Output, Limits) ->
    case Body of
        <<_:32/little, Data/binary>> ->
            append_with_limit(Output, Data, Limits);

        _ ->
            {error,
                {codec_invalid_data,
                    <<"snappy: uncompressed chunk too short"/utf8>>}}
    end.

-file("src/packkit/snappy.gleam", 455).
-spec copy_byte_by_byte(
    bitstring(),
    integer(),
    integer(),
    packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
copy_byte_by_byte(Output, Offset, Length, Limits) ->
    case Length of
        0 ->
            {ok, 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/snappy"/utf8>>,
                                function => <<"copy_byte_by_byte"/utf8>>,
                                line => 465,
                                value => _assert_fail,
                                start => 13981,
                                'end' => 14050,
                                pattern_start => 13992,
                                pattern_end => 14006})
            end,
            gleam@result:'try'(
                append_with_limit(Output, Byte_slice@1, Limits),
                fun(New_output) ->
                    copy_byte_by_byte(New_output, Offset, Length - 1, Limits)
                end
            )
    end.

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

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

-file("src/packkit/snappy.gleam", 318).
-spec handle_literal(
    integer(),
    bitstring(),
    bitstring(),
    packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
handle_literal(Tag, Rest, Output, Limits) ->
    Header = erlang:'bsr'(Tag, 2),
    gleam@result:'try'(case Header of
            H when H < 60 ->
                {ok, {H + 1, Rest}};

            60 ->
                read_literal_extra(Rest, 1);

            61 ->
                read_literal_extra(Rest, 2);

            62 ->
                read_literal_extra(Rest, 3);

            _ ->
                read_literal_extra(Rest, 4)
        end, fun(_use0) ->
            {Length, After_extra} = _use0,
            case erlang:byte_size(After_extra) < Length of
                true ->
                    {error,
                        {codec_invalid_data,
                            <<"snappy: literal run truncated"/utf8>>}};

                false ->
                    Chunk@1 = case gleam_stdlib:bit_array_slice(
                        After_extra,
                        0,
                        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/snappy"/utf8>>,
                                        function => <<"handle_literal"/utf8>>,
                                        line => 337,
                                        value => _assert_fail,
                                        start => 10491,
                                        'end' => 10553,
                                        pattern_start => 10502,
                                        pattern_end => 10511})
                    end,
                    After@1 = case gleam_stdlib:bit_array_slice(
                        After_extra,
                        Length,
                        erlang:byte_size(After_extra) - Length
                    ) 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/snappy"/utf8>>,
                                        function => <<"handle_literal"/utf8>>,
                                        line => 338,
                                        value => _assert_fail@1,
                                        start => 10560,
                                        'end' => 10711,
                                        pattern_start => 10571,
                                        pattern_end => 10580})
                    end,
                    gleam@result:'try'(
                        append_with_limit(Output, Chunk@1, Limits),
                        fun(Output@1) ->
                            decode_raw_loop(After@1, Output@1, Limits)
                        end
                    )
            end
        end).

-file("src/packkit/snappy.gleam", 298).
-spec decode_raw_loop(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_raw_loop(Bytes, Output, Limits) ->
    case Bytes of
        <<>> ->
            {ok, Output};

        <<Tag, Rest/binary>> ->
            Kind = erlang:'band'(Tag, 16#03),
            case Kind of
                0 ->
                    handle_literal(Tag, Rest, Output, Limits);

                1 ->
                    handle_copy_1(Tag, Rest, Output, Limits);

                2 ->
                    handle_copy_2(Tag, Rest, Output, Limits);

                _ ->
                    handle_copy_4(Tag, Rest, Output, Limits)
            end;

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

-file("src/packkit/snappy.gleam", 82).
?DOC(" Decode a Snappy raw block using explicit resource limits.\n").
-spec raw_decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
raw_decode_with_limits(Bytes, Limits) ->
    gleam@result:'try'(
        read_varint(Bytes, 0, 0),
        fun(_use0) ->
            {Uncompressed_length, After_varint} = _use0,
            gleam@bool:guard(
                Uncompressed_length > packkit@limit:max_output_bytes(Limits),
                {error,
                    {codec_limit_exceeded,
                        <<"max_output_bytes"/utf8>>,
                        Uncompressed_length}},
                fun() ->
                    gleam@result:'try'(
                        decode_raw_loop(After_varint, <<>>, Limits),
                        fun(Output) ->
                            case erlang:byte_size(Output) =:= Uncompressed_length of
                                true ->
                                    {ok, Output};

                                false ->
                                    {error,
                                        {codec_invalid_data,
                                            <<"snappy: raw output length disagrees with declared length"/utf8>>}}
                            end
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/snappy.gleam", 77).
?DOC(" Decode a single Snappy raw block.\n").
-spec raw_decode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
raw_decode(Bytes) ->
    raw_decode_with_limits(Bytes, packkit@limit:default()).

-file("src/packkit/snappy.gleam", 238).
-spec decode_compressed_chunk(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_compressed_chunk(Body, Output, Limits) ->
    case Body of
        <<_:32/little, Payload/binary>> ->
            gleam@result:'try'(
                raw_decode_with_limits(Payload, Limits),
                fun(Decoded) -> append_with_limit(Output, Decoded, Limits) end
            );

        _ ->
            {error,
                {codec_invalid_data,
                    <<"snappy: compressed chunk too short"/utf8>>}}
    end.

-file("src/packkit/snappy.gleam", 175).
-spec decode_chunks(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_chunks(Bytes, Output, Limits) ->
    case Bytes of
        <<>> ->
            {ok, Output};

        <<Chunk_type, Length:24/little, Rest/binary>> ->
            case erlang:byte_size(Rest) < Length of
                true ->
                    {error,
                        {codec_invalid_data,
                            <<"snappy: chunk payload truncated"/utf8>>}};

                false ->
                    Body@1 = case gleam_stdlib:bit_array_slice(Rest, 0, Length) of
                        {ok, Body} -> Body;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/snappy"/utf8>>,
                                        function => <<"decode_chunks"/utf8>>,
                                        line => 189,
                                        value => _assert_fail,
                                        start => 6189,
                                        'end' => 6243,
                                        pattern_start => 6200,
                                        pattern_end => 6208})
                    end,
                    After@1 = case gleam_stdlib:bit_array_slice(
                        Rest,
                        Length,
                        erlang:byte_size(Rest) - Length
                    ) 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/snappy"/utf8>>,
                                        function => <<"decode_chunks"/utf8>>,
                                        line => 190,
                                        value => _assert_fail@1,
                                        start => 6254,
                                        'end' => 6354,
                                        pattern_start => 6265,
                                        pattern_end => 6274})
                    end,
                    gleam@result:'try'(case Chunk_type of
                            T when T =:= 16#01 ->
                                decode_uncompressed_chunk(
                                    Body@1,
                                    Output,
                                    Limits
                                );

                            T@1 when T@1 =:= 16#00 ->
                                decode_compressed_chunk(Body@1, Output, Limits);

                            T@2 when T@2 =:= 16#FE ->
                                {ok, Output};

                            T@3 when T@3 =:= 16#FF ->
                                case Body@1 =:= <<16#73,
                                    16#4E,
                                    16#61,
                                    16#50,
                                    16#70,
                                    16#59>> of
                                    true ->
                                        {ok, Output};

                                    false ->
                                        {error,
                                            {codec_invalid_data,
                                                <<"snappy: repeated stream identifier mismatched"/utf8>>}}
                                end;

                            T@4 when T@4 >= 16#80 ->
                                {ok, Output};

                            _ ->
                                {error,
                                    {codec_invalid_data,
                                        <<"snappy: unsupported reserved chunk type"/utf8>>}}
                        end, fun(New_output) ->
                            decode_chunks(After@1, New_output, Limits)
                        end)
            end;

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

-file("src/packkit/snappy.gleam", 56).
?DOC(" Decode a framed Snappy stream 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
                <<16#FF,
                    16#06,
                    16#00,
                    16#00,
                    16#73,
                    16#4E,
                    16#61,
                    16#50,
                    16#70,
                    16#59,
                    Rest/binary>> ->
                    decode_chunks(Rest, <<>>, Limits);

                _ ->
                    {error,
                        {codec_invalid_data,
                            <<"snappy: missing stream identifier"/utf8>>}}
            end end
    ).

-file("src/packkit/snappy.gleam", 51).
?DOC(" Decode a framed Snappy stream 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/snappy.gleam", 430).
-spec apply_copy(
    bitstring(),
    bitstring(),
    packkit@limit:limits(),
    integer(),
    integer()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
apply_copy(Rest, Output, Limits, Offset, Length) ->
    Size = erlang:byte_size(Output),
    gleam@bool:guard(
        (Offset =< 0) orelse (Offset > Size),
        {error,
            {codec_invalid_data, <<"snappy: copy offset out of bounds"/utf8>>}},
        fun() -> gleam@result:'try'(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/snappy"/utf8>>,
                                            function => <<"apply_copy"/utf8>>,
                                            line => 447,
                                            value => _assert_fail,
                                            start => 13507,
                                            'end' => 13576,
                                            pattern_start => 13518,
                                            pattern_end => 13527})
                        end,
                        append_with_limit(Output, Chunk@1, Limits);

                    false ->
                        copy_byte_by_byte(Output, Offset, Length, Limits)
                end, fun(New_output) ->
                    decode_raw_loop(Rest, New_output, Limits)
                end) end
    ).

-file("src/packkit/snappy.gleam", 379).
-spec handle_copy_1(integer(), bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
handle_copy_1(Tag, Rest, Output, Limits) ->
    case Rest of
        <<Offset_low, After_offset/binary>> ->
            Length = erlang:'band'(erlang:'bsr'(Tag, 2), 16#07) + 4,
            Offset_high = erlang:'band'(erlang:'bsr'(Tag, 5), 16#07),
            Offset = erlang:'bor'(erlang:'bsl'(Offset_high, 8), Offset_low),
            apply_copy(After_offset, Output, Limits, Offset, Length);

        _ ->
            {error,
                {codec_invalid_data, <<"snappy: copy-1 offset truncated"/utf8>>}}
    end.

-file("src/packkit/snappy.gleam", 398).
-spec handle_copy_2(integer(), bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
handle_copy_2(Tag, Rest, Output, Limits) ->
    case Rest of
        <<Offset:16/little, After/binary>> ->
            Length = erlang:'bsr'(Tag, 2) + 1,
            apply_copy(After, Output, Limits, Offset, Length);

        _ ->
            {error,
                {codec_invalid_data, <<"snappy: copy-2 offset truncated"/utf8>>}}
    end.

-file("src/packkit/snappy.gleam", 414).
-spec handle_copy_4(integer(), bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
handle_copy_4(Tag, Rest, Output, Limits) ->
    case Rest of
        <<Offset:32/little, After/binary>> ->
            Length = erlang:'bsr'(Tag, 2) + 1,
            apply_copy(After, Output, Limits, Offset, Length);

        _ ->
            {error,
                {codec_invalid_data, <<"snappy: copy-4 offset truncated"/utf8>>}}
    end.

-file("src/packkit/snappy.gleam", 517).
-spec snappy_step(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer()),
    list(bitstring())
) -> bitstring().
snappy_step(Table, Size, Pos, Last_lit_start, Hashes, Acc) ->
    Key = snappy_hash4(
        snappy_byte_at(Table, Pos),
        snappy_byte_at(Table, Pos + 1),
        snappy_byte_at(Table, Pos + 2),
        snappy_byte_at(Table, Pos + 3)
    ),
    case gleam_stdlib:map_get(Hashes, Key) of
        {ok, Prev} ->
            Offset = Pos - Prev,
            Valid = ((Offset >= 1) andalso (Offset =< 65535)) andalso snappy_bytes4_equal(
                Table,
                Prev,
                Pos
            ),
            case Valid of
                false ->
                    snappy_compress_loop(
                        Table,
                        Size,
                        Pos + 1,
                        Last_lit_start,
                        gleam@dict:insert(Hashes, Key, Pos),
                        Acc
                    );

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

                        false ->
                            Literals = case Pos > Last_lit_start of
                                true ->
                                    emit_literal(
                                        snappy_slice(
                                            Table,
                                            Last_lit_start,
                                            Pos - Last_lit_start,
                                            <<>>
                                        )
                                    );

                                false ->
                                    <<>>
                            end,
                            Copy = emit_copy_for(Offset, Match_len, <<>>),
                            Next_pos = Pos + Match_len,
                            New_hashes = snappy_insert_hashes(
                                Table,
                                gleam@dict:insert(Hashes, Key, Pos),
                                Pos + 1,
                                Next_pos - 1,
                                Size
                            ),
                            snappy_compress_loop(
                                Table,
                                Size,
                                Next_pos,
                                Next_pos,
                                New_hashes,
                                [Copy, Literals | Acc]
                            )
                    end
            end;

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

-file("src/packkit/snappy.gleam", 496).
-spec snappy_compress_loop(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer()),
    list(bitstring())
) -> bitstring().
snappy_compress_loop(Table, Size, Pos, Last_lit_start, Hashes, Acc) ->
    case (Pos + 4) > Size of
        true ->
            Lit_len = Size - Last_lit_start,
            Tail = case Lit_len of
                0 ->
                    <<>>;

                _ ->
                    emit_literal(
                        snappy_slice(Table, Last_lit_start, Lit_len, <<>>)
                    )
            end,
            gleam_stdlib:bit_array_concat(lists:reverse([Tail | Acc]));

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

-file("src/packkit/snappy.gleam", 486).
-spec compress_raw_body(bitstring(), integer()) -> bitstring().
compress_raw_body(Bytes, Size) ->
    case Size < 4 of
        true ->
            emit_literal(Bytes);

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

-file("src/packkit/snappy.gleam", 114).
?DOC(
    " Encode `bytes` as a Snappy raw block.  Runs a greedy LZ77 match-\n"
    " finder (4-byte hash table, 16-bit hash) and emits literal +\n"
    " copy-1 / copy-2 / copy-4 sequences in the canonical block layout.\n"
    " Inputs of fewer than 4 bytes — where no copy can fit the 4-byte\n"
    " minimum match — degenerate to a single literal run.\n"
).
-spec raw_encode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
raw_encode(Bytes) ->
    Length = erlang:byte_size(Bytes),
    Length_varint = write_varint(Length),
    Body = compress_raw_body(Bytes, Length),
    {ok, gleam_stdlib:bit_array_concat([Length_varint, Body])}.

-file("src/packkit/snappy.gleam", 169).
-spec encode_raw_block(bitstring(), integer()) -> bitstring().
encode_raw_block(Bytes, Size) ->
    Varint = write_varint(Size),
    Body = compress_raw_body(Bytes, Size),
    gleam_stdlib:bit_array_concat([Varint, Body]).

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

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

                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/snappy"/utf8>>,
                                function => <<"encode_uncompressed_chunks"/utf8>>,
                                line => 133,
                                value => _assert_fail,
                                start => 4316,
                                'end' => 4380,
                                pattern_start => 4327,
                                pattern_end => 4336})
            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/snappy"/utf8>>,
                                function => <<"encode_uncompressed_chunks"/utf8>>,
                                line => 134,
                                value => _assert_fail@1,
                                start => 4387,
                                'end' => 4476,
                                pattern_start => 4398,
                                pattern_end => 4407})
            end,
            Crc = packkit@checksum:snappy_mask(packkit@checksum:crc32c(Chunk@1)),
            Raw_body = encode_raw_block(Chunk@1, Chunk_size),
            Raw_body_size = erlang:byte_size(Raw_body),
            Framed = case Raw_body_size < Chunk_size of
                true ->
                    Payload_size = Raw_body_size + 4,
                    Header = <<16#00, Payload_size:24/little, Crc:32/little>>,
                    gleam_stdlib:bit_array_concat([Header, Raw_body]);

                false ->
                    Payload_size@1 = Chunk_size + 4,
                    Header@1 = <<16#01,
                        Payload_size@1:24/little,
                        Crc:32/little>>,
                    gleam_stdlib:bit_array_concat([Header@1, Chunk@1])
            end,
            encode_uncompressed_chunks(After@1, [Framed | Acc])
    end.

-file("src/packkit/snappy.gleam", 45).
?DOC(
    " Encode `bytes` as a framed Snappy stream that stores every chunk\n"
    " in the uncompressed form.\n"
).
-spec encode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode(Bytes) ->
    Chunks = encode_uncompressed_chunks(Bytes, []),
    {ok,
        gleam_stdlib:bit_array_concat(
            [<<16#FF,
                    16#06,
                    16#00,
                    16#00,
                    16#73,
                    16#4E,
                    16#61,
                    16#50,
                    16#70,
                    16#59>>,
                Chunks]
        )}.