-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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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( [<>, 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([<>, 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), <>. -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 <> -> 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 => <>, 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 => <>, 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; <> -> Cp = case B < 16#80 of true -> <>; 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 => <>, 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 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 => <>, 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 => <>, 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 <> -> {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 <> -> 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 => <>, 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 => <>, 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 <> -> 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 <> -> 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()).