-module(packkit@gzip).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/gzip.gleam").
-export([codec/0, default_header/0, with_name_checked/2, with_name/2, with_comment_checked/2, with_comment/2, with_modified_at_checked/2, with_modified_at/2, name/1, comment/1, modified_at_unix/1, extra/1, with_extra_checked/2, with_extra/2, new_decoder/0, new_decoder_with_limits/1, push/2, decode_with_limits/2, decode/1, decode_payload/1, decode_payload_with_limits/2, finish/1, encode_with_header/2, encode/1]).
-export_type([header/0, subfield/0, decoder/0, header_error/0, decoded/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(
" RFC 1952 gzip codec.\n"
"\n"
" gzip wraps a DEFLATE stream in a member header that may carry an\n"
" original filename, free-text comment, and modification timestamp.\n"
" A trailing CRC-32 and ISIZE pair lets readers verify the decoded\n"
" payload.\n"
).
-opaque header() :: {header,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(integer()),
list(subfield())}.
-type subfield() :: {subfield, integer(), integer(), bitstring()}.
-opaque decoder() :: {decoder,
list(bitstring()),
integer(),
packkit@limit:limits()}.
-type header_error() :: header_name_contains_nul |
header_comment_contains_nul |
{header_modified_at_out_of_range, integer()} |
{header_extra_subfield_too_long, integer()} |
{header_extra_total_too_long, integer()} |
{header_extra_subfield_id_out_of_range, integer(), integer()}.
-type decoded() :: {decoded, header(), bitstring()}.
-file("src/packkit/gzip.gleam", 86).
?DOC(" Gzip codec smart constructor.\n").
-spec codec() -> packkit@codec:codec().
codec() ->
packkit@codec:gzip().
-file("src/packkit/gzip.gleam", 91).
?DOC(" Default gzip header with no optional fields populated.\n").
-spec default_header() -> header().
default_header() ->
{header, none, none, none, []}.
-file("src/packkit/gzip.gleam", 150).
?DOC(
" Attach an optional filename after validating that it does not\n"
" contain the NUL byte gzip uses as the FNAME terminator. Use this\n"
" when the value comes from untrusted input that must round-trip.\n"
).
-spec with_name_checked(header(), binary()) -> {ok, header()} |
{error, header_error()}.
with_name_checked(Header, Name) ->
gleam@bool:guard(
gleam_stdlib:contains_string(Name, <<"\x{0000}"/utf8>>),
{error, header_name_contains_nul},
fun() ->
{ok,
{header,
{some, Name},
erlang:element(3, Header),
erlang:element(4, Header),
erlang:element(5, Header)}}
end
).
-file("src/packkit/gzip.gleam", 106).
?DOC(
" Attach an optional filename. Panics if `name` contains the NUL\n"
" byte gzip uses as the FNAME terminator — see [with_name_checked]\n"
" when the value comes from untrusted input.\n"
"\n"
" The unchecked variant guarantees that the value stored in the\n"
" header is exactly what the caller passed (lawful round-trip via\n"
" `name(with_name(h, x)) == Some(x)`). Earlier revisions silently\n"
" stripped NULs to \"be helpful\"; that broke the round-trip law and\n"
" is now a panic, matching the other unchecked setters in this\n"
" module ([with_modified_at] / [with_extra]) and across the package\n"
" ([packkit/entry.with_mode] etc.).\n"
).
-spec with_name(header(), binary()) -> header().
with_name(Header, Name) ->
case with_name_checked(Header, Name) of
{ok, H} ->
H;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/gzip.with_name: name must not contain NUL (0x00)"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"with_name"/utf8>>,
line => 110})
end.
-file("src/packkit/gzip.gleam", 163).
?DOC(
" Attach an optional comment after validating that it does not\n"
" contain the NUL byte gzip uses as the FCOMMENT terminator.\n"
).
-spec with_comment_checked(header(), binary()) -> {ok, header()} |
{error, header_error()}.
with_comment_checked(Header, Comment) ->
gleam@bool:guard(
gleam_stdlib:contains_string(Comment, <<"\x{0000}"/utf8>>),
{error, header_comment_contains_nul},
fun() ->
{ok,
{header,
erlang:element(2, Header),
{some, Comment},
erlang:element(4, Header),
erlang:element(5, Header)}}
end
).
-file("src/packkit/gzip.gleam", 120).
?DOC(
" Attach an optional comment. Panics if `comment` contains the NUL\n"
" byte gzip uses as the FCOMMENT terminator — see\n"
" [with_comment_checked] when the value comes from untrusted input.\n"
"\n"
" Like [with_name], the stored value is exactly what the caller\n"
" passed; earlier revisions silently stripped NULs.\n"
).
-spec with_comment(header(), binary()) -> header().
with_comment(Header, Comment) ->
case with_comment_checked(Header, Comment) of
{ok, H} ->
H;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/gzip.with_comment: comment must not contain NUL (0x00)"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"with_comment"/utf8>>,
line => 124})
end.
-file("src/packkit/gzip.gleam", 191).
?DOC(
" Attach an optional Unix mtime after validating it fits gzip's\n"
" 32-bit MTIME field.\n"
).
-spec with_modified_at_checked(header(), integer()) -> {ok, header()} |
{error, header_error()}.
with_modified_at_checked(Header, Unix_seconds) ->
gleam@bool:guard(
(Unix_seconds < 0) orelse (Unix_seconds > 16#FFFFFFFF),
{error, {header_modified_at_out_of_range, Unix_seconds}},
fun() ->
{ok,
{header,
erlang:element(2, Header),
erlang:element(3, Header),
{some, Unix_seconds},
erlang:element(5, Header)}}
end
).
-file("src/packkit/gzip.gleam", 178).
?DOC(
" Attach an optional Unix mtime. Out-of-range values panic at\n"
" construction time so a `Header` value cannot quietly carry a\n"
" timestamp gzip's 32-bit MTIME field cannot represent. Use\n"
" [with_modified_at_checked] when the input is untrusted.\n"
).
-spec with_modified_at(header(), integer()) -> header().
with_modified_at(Header, Unix_seconds) ->
case with_modified_at_checked(Header, Unix_seconds) of
{ok, H} ->
H;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/gzip.with_modified_at: unix_seconds must be in the inclusive range 0..0xFFFFFFFF"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"with_modified_at"/utf8>>,
line => 185})
end.
-file("src/packkit/gzip.gleam", 203).
?DOC(" Read the optional filename field.\n").
-spec name(header()) -> gleam@option:option(binary()).
name(Header) ->
erlang:element(2, Header).
-file("src/packkit/gzip.gleam", 208).
?DOC(" Read the optional comment field.\n").
-spec comment(header()) -> gleam@option:option(binary()).
comment(Header) ->
erlang:element(3, Header).
-file("src/packkit/gzip.gleam", 213).
?DOC(" Read the optional mtime field.\n").
-spec modified_at_unix(header()) -> gleam@option:option(integer()).
modified_at_unix(Header) ->
erlang:element(4, Header).
-file("src/packkit/gzip.gleam", 219).
?DOC(
" Read the FEXTRA subfields. Empty when the gzip header carries\n"
" no FEXTRA region.\n"
).
-spec extra(header()) -> list(subfield()).
extra(Header) ->
erlang:element(5, Header).
-file("src/packkit/gzip.gleam", 256).
-spec validate_extra_subfield_ids(list(subfield())) -> {ok, nil} |
{error, header_error()}.
validate_extra_subfield_ids(Subfields) ->
case Subfields of
[] ->
{ok, nil};
[{subfield, Id_1, Id_2, _} | Rest] ->
case (((Id_1 < 0) orelse (Id_1 > 16#FF)) orelse (Id_2 < 0)) orelse (Id_2
> 16#FF) of
true ->
{error, {header_extra_subfield_id_out_of_range, Id_1, Id_2}};
false ->
validate_extra_subfield_ids(Rest)
end
end.
-file("src/packkit/gzip.gleam", 269).
-spec measure_extra_subfields(list(subfield()), integer()) -> {ok, integer()} |
{error, header_error()}.
measure_extra_subfields(Subfields, Acc) ->
case Subfields of
[] ->
{ok, Acc};
[{subfield, _, _, Data} | Rest] ->
Len = erlang:byte_size(Data),
case Len > 16#FFFF of
true ->
{error, {header_extra_subfield_too_long, Len}};
false ->
measure_extra_subfields(Rest, (Acc + 4) + Len)
end
end.
-file("src/packkit/gzip.gleam", 243).
?DOC(
" Attach a list of FEXTRA subfields after validating that every\n"
" subfield ID byte fits the 8-bit slot, every subfield body fits\n"
" gzip's 16-bit LEN, and the catenated total fits the 16-bit\n"
" XLEN. Returns a typed `HeaderError` on any of those overflows.\n"
).
-spec with_extra_checked(header(), list(subfield())) -> {ok, header()} |
{error, header_error()}.
with_extra_checked(Header, Subfields) ->
gleam@result:'try'(
validate_extra_subfield_ids(Subfields),
fun(_) ->
gleam@result:'try'(
measure_extra_subfields(Subfields, 0),
fun(Total) ->
gleam@bool:guard(
Total > 16#FFFF,
{error, {header_extra_total_too_long, Total}},
fun() ->
{ok,
{header,
erlang:element(2, Header),
erlang:element(3, Header),
erlang:element(4, Header),
Subfields}}
end
)
end
)
end
).
-file("src/packkit/gzip.gleam", 226).
?DOC(
" Attach a list of FEXTRA subfields. Out-of-range IDs or\n"
" overlong bodies panic; use [with_extra_checked] when the caller\n"
" has not pre-validated the values.\n"
).
-spec with_extra(header(), list(subfield())) -> header().
with_extra(Header, Subfields) ->
case with_extra_checked(Header, Subfields) of
{ok, H} ->
H;
{error, {header_extra_subfield_id_out_of_range, _, _}} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/gzip.with_extra: subfield id bytes must each be in 0..255"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"with_extra"/utf8>>,
line => 230});
{error, {header_extra_subfield_too_long, _}} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/gzip.with_extra: each subfield body must be at most 65535 bytes"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"with_extra"/utf8>>,
line => 232});
{error, {header_extra_total_too_long, _}} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/gzip.with_extra: total FEXTRA region must be at most 65535 bytes"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"with_extra"/utf8>>,
line => 234});
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/gzip.with_extra: unexpected validation error"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"with_extra"/utf8>>,
line => 235})
end.
-file("src/packkit/gzip.gleam", 374).
-spec encode_extra_subfields(list(subfield()), bitstring()) -> bitstring().
encode_extra_subfields(Subfields, Acc) ->
case Subfields of
[] ->
Acc;
[{subfield, Id_1, Id_2, Data} | Rest] ->
Len = erlang:byte_size(Data),
Chunk = gleam_stdlib:bit_array_concat(
[<<Id_1, Id_2, Len:16/little>>, Data]
),
encode_extra_subfields(
Rest,
gleam_stdlib:bit_array_concat([Acc, Chunk])
)
end.
-file("src/packkit/gzip.gleam", 363).
-spec encode_extra_block(list(subfield())) -> bitstring().
encode_extra_block(Subfields) ->
case Subfields of
[] ->
<<>>;
_ ->
Body = encode_extra_subfields(Subfields, <<>>),
Xlen = erlang:byte_size(Body),
gleam_stdlib:bit_array_concat([<<Xlen:16/little>>, Body])
end.
-file("src/packkit/gzip.gleam", 385).
-spec trailer_bytes(bitstring()) -> bitstring().
trailer_bytes(Plain) ->
Crc = packkit@checksum:crc32(Plain),
Isize = erlang:'band'(erlang:byte_size(Plain), 16#FFFFFFFF),
<<Crc:32/little, Isize:32/little>>.
-file("src/packkit/gzip.gleam", 604).
-spec apply_optional_name(header(), gleam@option:option(binary())) -> header().
apply_optional_name(Header, Value) ->
case Value of
{some, V} ->
with_name(Header, V);
none ->
Header
end.
-file("src/packkit/gzip.gleam", 611).
-spec apply_optional_comment(header(), gleam@option:option(binary())) -> header().
apply_optional_comment(Header, Value) ->
case Value of
{some, V} ->
with_comment(Header, V);
none ->
Header
end.
-file("src/packkit/gzip.gleam", 618).
-spec apply_extra(header(), list(subfield())) -> header().
apply_extra(Header, Subfields) ->
case Subfields of
[] ->
Header;
_ ->
{header,
erlang:element(2, Header),
erlang:element(3, Header),
erlang:element(4, Header),
Subfields}
end.
-file("src/packkit/gzip.gleam", 655).
-spec parse_extra_subfields(bitstring(), list(subfield())) -> {ok,
list(subfield())} |
{error, packkit@error:codec_error()}.
parse_extra_subfields(Bytes, Acc) ->
case erlang:byte_size(Bytes) of
0 ->
{ok, Acc};
_ ->
case Bytes of
<<Id_1, Id_2, Len:16/little, Rest/binary>> ->
case erlang:byte_size(Rest) < Len of
true ->
{error,
{codec_invalid_data,
<<"gzip extra subfield body truncated"/utf8>>}};
false ->
Data@1 = case gleam_stdlib:bit_array_slice(
Rest,
0,
Len
) of
{ok, Data} -> Data;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"parse_extra_subfields"/utf8>>,
line => 670,
value => _assert_fail,
start => 21538,
'end' => 21589,
pattern_start => 21549,
pattern_end => 21557})
end,
Remaining@1 = case gleam_stdlib:bit_array_slice(
Rest,
Len,
erlang:byte_size(Rest) - Len
) of
{ok, Remaining} -> Remaining;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"parse_extra_subfields"/utf8>>,
line => 671,
value => _assert_fail@1,
start => 21604,
'end' => 21706,
pattern_start => 21615,
pattern_end => 21628})
end,
parse_extra_subfields(
Remaining@1,
[{subfield, Id_1, Id_2, Data@1} | Acc]
)
end;
_ ->
{error,
{codec_invalid_data,
<<"gzip extra subfield header truncated"/utf8>>}}
end
end.
-file("src/packkit/gzip.gleam", 717).
-spec latin1_decode(bitstring(), binary()) -> binary().
latin1_decode(Bytes, Acc) ->
case Bytes of
<<>> ->
Acc;
<<B, Rest/binary>> ->
Cp = case B < 16#80 of
true ->
<<B>>;
false ->
<<(erlang:'bor'(16#C0, erlang:'bsr'(B, 6))),
(erlang:'bor'(16#80, erlang:'band'(B, 16#3F)))>>
end,
Ch@1 = case gleam@bit_array:to_string(Cp) of
{ok, Ch} -> Ch;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"latin1_decode"/utf8>>,
line => 736,
value => _assert_fail,
start => 24024,
'end' => 24067,
pattern_start => 24035,
pattern_end => 24041})
end,
latin1_decode(Rest, <<Acc/binary, Ch@1/binary>>);
_ ->
Acc
end.
-file("src/packkit/gzip.gleam", 743).
-spec split_at_nul(bitstring(), integer()) -> {ok, {bitstring(), bitstring()}} |
{error, packkit@error:codec_error()}.
split_at_nul(Bytes, Offset) ->
case gleam_stdlib:bit_array_slice(Bytes, Offset, 1) of
{ok, <<0>>} ->
Prefix@1 = case gleam_stdlib:bit_array_slice(Bytes, 0, Offset) 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/gzip"/utf8>>,
function => <<"split_at_nul"/utf8>>,
line => 749,
value => _assert_fail,
start => 24305,
'end' => 24362,
pattern_start => 24316,
pattern_end => 24326})
end,
Total = erlang:byte_size(Bytes),
Rest@1 = case gleam_stdlib:bit_array_slice(
Bytes,
Offset + 1,
(Total - Offset) - 1
) of
{ok, Rest} -> Rest;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"split_at_nul"/utf8>>,
line => 751,
value => _assert_fail@1,
start => 24414,
'end' => 24498,
pattern_start => 24425,
pattern_end => 24433})
end,
{ok, {Prefix@1, Rest@1}};
{ok, _} ->
split_at_nul(Bytes, Offset + 1);
{error, _} ->
{error,
{codec_invalid_data, <<"gzip header string missing NUL"/utf8>>}}
end.
-file("src/packkit/gzip.gleam", 696).
-spec maybe_read_string(bitstring(), integer(), integer()) -> {ok,
{bitstring(), gleam@option:option(binary())}} |
{error, packkit@error:codec_error()}.
maybe_read_string(Bytes, Flg, Mask) ->
case erlang:'band'(Flg, Mask) of
0 ->
{ok, {Bytes, none}};
_ ->
case split_at_nul(Bytes, 0) of
{ok, {String_bits, After}} ->
Decoded = case gleam@bit_array:to_string(String_bits) of
{ok, Value} ->
Value;
{error, _} ->
latin1_decode(String_bits, <<""/utf8>>)
end,
{ok, {After, {some, Decoded}}};
{error, Err} ->
{error, Err}
end
end.
-file("src/packkit/gzip.gleam", 797).
-spec read_header_crc16(bitstring()) -> {ok, {integer(), bitstring()}} |
{error, packkit@error:codec_error()}.
read_header_crc16(Bytes) ->
case Bytes of
<<Expected:16/little, Rest/binary>> ->
{ok, {Expected, Rest}};
_ ->
{error, {codec_invalid_data, <<"gzip header CRC missing"/utf8>>}}
end.
-file("src/packkit/gzip.gleam", 806).
-spec slice_header_for_crc(bitstring(), integer()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
slice_header_for_crc(Original, Header_len) ->
case gleam_stdlib:bit_array_slice(Original, 0, Header_len) of
{ok, Header_bytes} ->
{ok, Header_bytes};
{error, _} ->
{error,
{codec_invalid_data, <<"gzip header CRC bounds error"/utf8>>}}
end.
-file("src/packkit/gzip.gleam", 782).
-spec verify_header_crc(bitstring(), bitstring()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
verify_header_crc(Bytes, Original) ->
Header_len = erlang:byte_size(Original) - erlang:byte_size(Bytes),
gleam@result:'try'(
read_header_crc16(Bytes),
fun(_use0) ->
{Expected, Rest} = _use0,
gleam@result:'try'(
slice_header_for_crc(Original, Header_len),
fun(Header_bytes) ->
Actual = erlang:'band'(
packkit@checksum:crc32(Header_bytes),
16#FFFF
),
case Actual =:= Expected of
true ->
{ok, Rest};
false ->
{error,
{codec_invalid_data,
<<"gzip header CRC16 mismatch"/utf8>>}}
end
end
)
end
).
-file("src/packkit/gzip.gleam", 818).
?DOC(" Create a new incremental decoder state using the default limits.\n").
-spec new_decoder() -> decoder().
new_decoder() ->
{decoder, [], 0, packkit@limit:default()}.
-file("src/packkit/gzip.gleam", 823).
?DOC(" Create a new incremental decoder state with explicit limits.\n").
-spec new_decoder_with_limits(packkit@limit:limits()) -> decoder().
new_decoder_with_limits(Limits) ->
{decoder, [], 0, Limits}.
-file("src/packkit/gzip.gleam", 836).
?DOC(
" Append a chunk of input bytes to the decoder, enforcing\n"
" `max_input_bytes` incrementally. Returns the updated decoder; no\n"
" output is produced until [finish] runs (the underlying DEFLATE\n"
" decoder is eager).\n"
"\n"
" The shape mirrors [packkit/stream] so callers don't have to remember\n"
" which streaming module returns which tuple — previously this push\n"
" returned `(Decoder, List(BitArray))` and the equivalent\n"
" `stream.push` returned a bare `Decoder`.\n"
).
-spec push(decoder(), bitstring()) -> {ok, decoder()} |
{error, packkit@error:codec_error()}.
push(Decoder, Chunk) ->
Chunk_size = erlang:byte_size(Chunk),
New_total = erlang:element(3, Decoder) + Chunk_size,
case New_total > packkit@limit:max_input_bytes(erlang:element(4, Decoder)) of
true ->
{error,
{codec_limit_exceeded, <<"max_input_bytes"/utf8>>, New_total}};
false ->
{ok,
{decoder,
[Chunk | erlang:element(2, Decoder)],
New_total,
erlang:element(4, Decoder)}}
end.
-file("src/packkit/gzip.gleam", 771).
-spec maybe_verify_header_crc(bitstring(), integer(), bitstring()) -> {ok,
bitstring()} |
{error, packkit@error:codec_error()}.
maybe_verify_header_crc(Bytes, Flg, Original) ->
case erlang:'band'(Flg, 16#02) of
0 ->
{ok, Bytes};
_ ->
verify_header_crc(Bytes, Original)
end.
-file("src/packkit/gzip.gleam", 625).
-spec maybe_read_extra(bitstring(), integer()) -> {ok,
{bitstring(), list(subfield())}} |
{error, packkit@error:codec_error()}.
maybe_read_extra(Bytes, Flg) ->
case erlang:'band'(Flg, 16#04) of
0 ->
{ok, {Bytes, []}};
_ ->
case Bytes of
<<Xlen:16/little, Rest/binary>> ->
case erlang:byte_size(Rest) < Xlen of
true ->
{error,
{codec_invalid_data,
<<"gzip extra field truncated"/utf8>>}};
false ->
Extra_bytes@1 = case gleam_stdlib:bit_array_slice(
Rest,
0,
Xlen
) of
{ok, Extra_bytes} -> Extra_bytes;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/gzip"/utf8>>,
function => <<"maybe_read_extra"/utf8>>,
line => 640,
value => _assert_fail,
start => 20576,
'end' => 20635,
pattern_start => 20587,
pattern_end => 20602})
end,
After@1 = case gleam_stdlib:bit_array_slice(
Rest,
Xlen,
erlang:byte_size(Rest) - Xlen
) 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/gzip"/utf8>>,
function => <<"maybe_read_extra"/utf8>>,
line => 641,
value => _assert_fail@1,
start => 20650,
'end' => 20750,
pattern_start => 20661,
pattern_end => 20670})
end,
gleam@result:'try'(
parse_extra_subfields(Extra_bytes@1, []),
fun(Subfields) ->
{ok, {After@1, lists:reverse(Subfields)}}
end
)
end;
_ ->
{error,
{codec_invalid_data,
<<"gzip extra header truncated"/utf8>>}}
end
end.
-file("src/packkit/gzip.gleam", 549).
-spec decode_header_and_payload(
bitstring(),
integer(),
header(),
packkit@limit:limits(),
bitstring()
) -> {ok, {decoded(), bitstring()}} | {error, packkit@error:codec_error()}.
decode_header_and_payload(Bytes, Flg, Acc, Limits, Original) ->
gleam@result:'try'(
maybe_read_extra(Bytes, Flg),
fun(_use0) ->
{Bytes@1, Extra_value} = _use0,
gleam@result:'try'(
maybe_read_string(Bytes@1, Flg, 16#08),
fun(_use0@1) ->
{Bytes@2, Name_value} = _use0@1,
gleam@result:'try'(
maybe_read_string(Bytes@2, Flg, 16#10),
fun(_use0@2) ->
{Bytes@3, Comment_value} = _use0@2,
gleam@result:'try'(
maybe_verify_header_crc(Bytes@3, Flg, Original),
fun(Bytes@4) ->
Header = begin
_pipe = Acc,
_pipe@1 = apply_optional_name(
_pipe,
Name_value
),
_pipe@2 = apply_optional_comment(
_pipe@1,
Comment_value
),
apply_extra(_pipe@2, Extra_value)
end,
_ = erlang:'band'(Flg, 16#01),
gleam@result:'try'(
packkit@deflate:decode_with_remainder(
Bytes@4,
Limits
),
fun(_use0@3) ->
{Plain, After_deflate} = _use0@3,
case After_deflate of
<<Expected_crc:32/little,
Expected_isize:32/little,
Rest/binary>> ->
gleam@bool:guard(
packkit@checksum:crc32(
Plain
)
/= Expected_crc,
{error,
{codec_invalid_data,
<<"gzip CRC-32 mismatch"/utf8>>}},
fun() ->
Isize = erlang:'band'(
erlang:byte_size(
Plain
),
16#FFFFFFFF
),
gleam@bool:guard(
Isize /= Expected_isize,
{error,
{codec_invalid_data,
<<"gzip ISIZE mismatch"/utf8>>}},
fun() ->
{ok,
{{decoded,
Header,
Plain},
Rest}}
end
)
end
);
_ ->
{error,
{codec_invalid_data,
<<"gzip trailer truncated"/utf8>>}}
end
end
)
end
)
end
)
end
)
end
).
-file("src/packkit/gzip.gleam", 513).
-spec decode_one_member(bitstring(), packkit@limit:limits()) -> {ok,
{decoded(), bitstring()}} |
{error, packkit@error:codec_error()}.
decode_one_member(Bytes, Limits) ->
case Bytes of
<<M1, M2, Cm, Flg, Mtime:32/little, _, _, Rest/binary>> ->
gleam@bool:guard(
(M1 /= 16#1F) orelse (M2 /= 16#8B),
{error, {codec_invalid_data, <<"gzip magic mismatch"/utf8>>}},
fun() ->
gleam@bool:guard(
Cm /= 8,
{error,
{codec_invalid_data,
<<"gzip compression method is not deflate"/utf8>>}},
fun() ->
gleam@bool:guard(
erlang:'band'(Flg, 16#E0) /= 0,
{error,
{codec_invalid_data,
<<"gzip reserved FLG bits set"/utf8>>}},
fun() ->
Initial = case Mtime of
0 ->
default_header();
N ->
_record = default_header(),
{header,
erlang:element(2, _record),
erlang:element(3, _record),
{some, N},
erlang:element(5, _record)}
end,
decode_header_and_payload(
Rest,
Flg,
Initial,
Limits,
Bytes
)
end
)
end
)
end
);
_ ->
{error, {codec_invalid_data, <<"gzip header truncated"/utf8>>}}
end.
-file("src/packkit/gzip.gleam", 484).
-spec decode_remaining_members(
bitstring(),
bitstring(),
integer(),
packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
decode_remaining_members(Bytes, Acc, Accumulated_size, Limits) ->
case erlang:byte_size(Bytes) of
0 ->
{ok, Acc};
_ ->
gleam@result:'try'(
decode_one_member(Bytes, Limits),
fun(_use0) ->
{Decoded, Rest} = _use0,
Next_size = Accumulated_size + erlang:byte_size(
erlang:element(3, Decoded)
),
case Next_size > packkit@limit:max_output_bytes(Limits) of
true ->
{error,
{codec_limit_exceeded,
<<"max_output_bytes"/utf8>>,
Next_size}};
false ->
decode_remaining_members(
Rest,
gleam_stdlib:bit_array_concat(
[Acc, erlang:element(3, Decoded)]
),
Next_size,
Limits
)
end
end
)
end.
-file("src/packkit/gzip.gleam", 459).
-spec decode_first_member_and_continue(bitstring(), packkit@limit:limits()) -> {ok,
decoded()} |
{error, packkit@error:codec_error()}.
decode_first_member_and_continue(Bytes, Limits) ->
gleam@result:'try'(
decode_one_member(Bytes, Limits),
fun(_use0) ->
{Decoded, Rest} = _use0,
case erlang:byte_size(Rest) of
0 ->
{ok, Decoded};
_ ->
Initial_size = erlang:byte_size(erlang:element(3, Decoded)),
gleam@result:'try'(
decode_remaining_members(
Rest,
<<>>,
Initial_size,
Limits
),
fun(Additional) ->
{ok,
{decoded,
erlang:element(2, Decoded),
gleam_stdlib:bit_array_concat(
[erlang:element(3, Decoded), Additional]
)}}
end
)
end
end
).
-file("src/packkit/gzip.gleam", 444).
?DOC(
" Decode a gzip byte stream using explicit limits.\n"
"\n"
" Handles multi-member streams (RFC 1952 §2.2 — concatenated gzip\n"
" files such as those produced by `cat a.gz b.gz`). The returned\n"
" `Decoded` carries the header from the FIRST member and the\n"
" concatenated payload of every member that decoded successfully;\n"
" no other gzip API exposes per-member headers yet.\n"
).
-spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok, decoded()} |
{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() -> decode_first_member_and_continue(Bytes, Limits) end
).
-file("src/packkit/gzip.gleam", 407).
?DOC(
" Decode a gzip byte stream using default limits and return the\n"
" rich [Decoded] record (header + payload).\n"
"\n"
" `decode` is asymmetric with [encode]: `encode` takes payload bytes\n"
" and emits a stream, while `decode` returns both the payload and the\n"
" header. The asymmetry is intentional — gzip is the only codec in\n"
" the package that carries meaningful per-stream metadata (filename,\n"
" comment, mtime), and surfacing it on the decode side is what makes\n"
" `decode |> .header` useful. When you only care about the payload\n"
" and want the shape every other codec uses (`BitArray ->\n"
" Result(BitArray, _)`), use [decode_payload].\n"
).
-spec decode(bitstring()) -> {ok, decoded()} |
{error, packkit@error:codec_error()}.
decode(Bytes) ->
decode_with_limits(Bytes, packkit@limit:default()).
-file("src/packkit/gzip.gleam", 417).
?DOC(
" Decode a gzip byte stream and return only the payload bytes.\n"
" Parallels every other codec's `decode/1`, which returns\n"
" `Result(BitArray, _)` — use this when you don't need the gzip\n"
" header (mtime / filename / comment).\n"
"\n"
" Law: `decode_payload(b) == decode(b) |> result.map(fn(d) { d.payload })`.\n"
).
-spec decode_payload(bitstring()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
decode_payload(Bytes) ->
case decode(Bytes) of
{ok, Decoded} ->
{ok, erlang:element(3, Decoded)};
{error, E} ->
{error, E}
end.
-file("src/packkit/gzip.gleam", 427).
?DOC(" Like [decode_payload] but accepts an explicit `Limits` value.\n").
-spec decode_payload_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
bitstring()} |
{error, packkit@error:codec_error()}.
decode_payload_with_limits(Bytes, Limits) ->
case decode_with_limits(Bytes, Limits) of
{ok, Decoded} ->
{ok, erlang:element(3, Decoded)};
{error, E} ->
{error, E}
end.
-file("src/packkit/gzip.gleam", 863).
?DOC(
" Finalize the decoder and return the full decoded payload.\n"
"\n"
" Returns a bare `BitArray` (not `List(BitArray)`) so the gzip\n"
" streaming surface matches `packkit/stream` exactly.\n"
).
-spec finish(decoder()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
finish(Decoder) ->
Bytes = gleam_stdlib:bit_array_concat(
lists:reverse(erlang:element(2, Decoder))
),
case decode_with_limits(Bytes, erlang:element(4, Decoder)) of
{ok, Decoded} ->
{ok, erlang:element(3, Decoded)};
{error, E} ->
{error, E}
end.
-file("src/packkit/gzip.gleam", 302).
?DOC(
" Encode `bytes` as a gzip stream using `header`. The DEFLATE body\n"
" uses the dynamic-Huffman encoder, which on typical text and\n"
" structured-data payloads shrinks ~10–30 % more than the fixed-\n"
" Huffman variant; for pathologically skewed inputs the encoder\n"
" transparently falls back to fixed Huffman inside\n"
" `deflate.encode_dynamic` so the stream is always a valid\n"
" RFC 1951 BTYPE=01 or BTYPE=10 block.\n"
).
-spec encode_with_header(bitstring(), header()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
encode_with_header(Bytes, Header) ->
gleam@result:'try'(
packkit@deflate:encode_dynamic(Bytes),
fun(Deflated) ->
Mtime = case erlang:element(4, Header) of
{some, Value} ->
Value;
none ->
0
end,
Flag_extra = case erlang:element(5, Header) of
[] ->
0;
_ ->
16#04
end,
Flag_name = case erlang:element(2, Header) of
{some, _} ->
16#08;
none ->
0
end,
Flag_comment = case erlang:element(3, Header) of
{some, _} ->
16#10;
none ->
0
end,
Flg = erlang:'bor'(
erlang:'bor'(Flag_extra, Flag_name),
Flag_comment
),
Header_bytes = <<16#1F, 16#8B, 8, Flg, Mtime:32/little, 0, 16#FF>>,
Extra_block = encode_extra_block(erlang:element(5, Header)),
Name_block = case erlang:element(2, Header) of
{some, Value@1} ->
gleam_stdlib:bit_array_concat(
[gleam_stdlib:identity(Value@1), <<0>>]
);
none ->
<<>>
end,
Comment_block = case erlang:element(3, Header) of
{some, Value@2} ->
gleam_stdlib:bit_array_concat(
[gleam_stdlib:identity(Value@2), <<0>>]
);
none ->
<<>>
end,
Trailer = trailer_bytes(Bytes),
{ok,
gleam_stdlib:bit_array_concat(
[Header_bytes,
Extra_block,
Name_block,
Comment_block,
Deflated,
Trailer]
)}
end
).
-file("src/packkit/gzip.gleam", 291).
?DOC(
" Encode `bytes` as a gzip stream using the default header (no\n"
" FNAME / FCOMMENT / FEXTRA / mtime). Use this when you just want\n"
" \"compress these bytes\" — the symmetric counterpart of\n"
" `decode_payload`, mirroring every other codec's `encode/1` shape.\n"
" Use [encode_with_header] when you need to attach a filename,\n"
" comment, or mtime to the stream.\n"
).
-spec encode(bitstring()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
encode(Bytes) ->
encode_with_header(Bytes, default_header()).