Skip to main content

src/fcgi@internal@protocol.erl

-module(fcgi@internal@protocol).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fcgi/internal/protocol.gleam").
-export([encode_record_tree_unchecked/3, encode_record_bits_unchecked/3, encode_name_value_pairs/1, encode_record/1, encode_stdout_record_header/2, chunk_stdout/2, parse_name_value_pairs/1, parse_record/1]).
-export_type([outgoing/0, status/0, incoming/0, parse_failure/0, parse_result/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(false).

-type outgoing() :: {end_request, integer(), integer(), status()} |
    {stdout, integer(), bitstring()} |
    {get_values_result, list({binary(), binary()})} |
    {unknown_type, integer()}.

-type status() :: request_complete |
    cant_multiplex_connection |
    overloaded |
    unknown_role.

-type incoming() :: {begin_request, integer(), integer(), boolean()} |
    {abort_request, integer()} |
    {params, integer(), bitstring()} |
    {stdin, integer(), bitstring()} |
    {get_values, list(binary())} |
    {incoming_unknown, integer(), integer()}.

-type parse_failure() :: {unsupported_version, integer()} |
    malformed_record |
    malformed_name_value.

-type parse_result() :: {parsed, incoming(), bitstring()} |
    need_more |
    {parse_error, parse_failure()}.

-file("src/fcgi/internal/protocol.gleam", 105).
?DOC(false).
-spec padding_for(integer()) -> integer().
padding_for(Content_length) ->
    Remainder = case 8 of
        0 -> 0;
        Gleam@denominator -> Content_length rem Gleam@denominator
    end,
    case Remainder of
        0 ->
            0;

        _ ->
            8 - Remainder
    end.

-file("src/fcgi/internal/protocol.gleam", 83).
?DOC(false).
-spec encode_record_tree_unchecked(
    integer(),
    integer(),
    gleam@bytes_tree:bytes_tree()
) -> gleam@bytes_tree:bytes_tree().
encode_record_tree_unchecked(Record_type, Request_id, Body) ->
    Content_length = erlang:iolist_size(Body),
    Padding_length = padding_for(Content_length),
    Header = <<1:8,
        Record_type:8,
        Request_id:16,
        Content_length:16,
        Padding_length:8,
        0:8>>,
    Padding = <<0:(erlang:max(0, (Padding_length * 8)))>>,
    _pipe = gleam@bytes_tree:from_bit_array(Header),
    _pipe@1 = gleam_stdlib:iodata_append(_pipe, Body),
    gleam@bytes_tree:append(_pipe@1, Padding).

-file("src/fcgi/internal/protocol.gleam", 74).
?DOC(false).
-spec encode_record_bits_unchecked(integer(), integer(), bitstring()) -> gleam@bytes_tree:bytes_tree().
encode_record_bits_unchecked(Record_type, Request_id, Body) ->
    _pipe = gleam@bytes_tree:from_bit_array(Body),
    encode_record_tree_unchecked(Record_type, Request_id, _pipe).

-file("src/fcgi/internal/protocol.gleam", 138).
?DOC(false).
-spec encode_length(integer()) -> bitstring().
encode_length(N) ->
    case N < 128 of
        true ->
            <<N:8>>;

        false ->
            <<1:1, N:31>>
    end.

-file("src/fcgi/internal/protocol.gleam", 122).
?DOC(false).
-spec encode_name_value_pairs(list({binary(), binary()})) -> gleam@bytes_tree:bytes_tree().
encode_name_value_pairs(Pairs) ->
    gleam@list:fold(
        Pairs,
        gleam@bytes_tree:new(),
        fun(Acc, Pair) ->
            {Name, Value} = Pair,
            Name_bytes = gleam_stdlib:identity(Name),
            Value_bytes = gleam_stdlib:identity(Value),
            Name_length = erlang:byte_size(Name_bytes),
            Value_length = erlang:byte_size(Value_bytes),
            _pipe = Acc,
            _pipe@1 = gleam@bytes_tree:append(_pipe, encode_length(Name_length)),
            _pipe@2 = gleam@bytes_tree:append(
                _pipe@1,
                encode_length(Value_length)
            ),
            _pipe@3 = gleam@bytes_tree:append(_pipe@2, Name_bytes),
            gleam@bytes_tree:append(_pipe@3, Value_bytes)
        end
    ).

-file("src/fcgi/internal/protocol.gleam", 113).
?DOC(false).
-spec status_to_int(status()) -> integer().
status_to_int(Status) ->
    case Status of
        request_complete ->
            0;

        cant_multiplex_connection ->
            1;

        overloaded ->
            2;

        unknown_role ->
            3
    end.

-file("src/fcgi/internal/protocol.gleam", 50).
?DOC(false).
-spec encode_record(outgoing()) -> gleam@bytes_tree:bytes_tree().
encode_record(Record) ->
    case Record of
        {end_request, Id, App_status, Protocol_status} ->
            Body = <<App_status:32, (status_to_int(Protocol_status)):8, 0:24>>,
            encode_record_bits_unchecked(3, Id, Body);

        {stdout, Id@1, Data} ->
            encode_record_bits_unchecked(6, Id@1, Data);

        {get_values_result, Pairs} ->
            encode_record_tree_unchecked(10, 0, encode_name_value_pairs(Pairs));

        {unknown_type, Type_byte} ->
            Body@1 = <<Type_byte:8, 0:56>>,
            encode_record_bits_unchecked(11, 0, Body@1)
    end.

-file("src/fcgi/internal/protocol.gleam", 149).
?DOC(false).
-spec encode_stdout_record_header(integer(), integer()) -> {bitstring(),
    integer()}.
encode_stdout_record_header(Request_id, Content_length) ->
    Padding_length = padding_for(Content_length),
    Header = <<1:8,
        6:8,
        Request_id:16,
        Content_length:16,
        Padding_length:8,
        0:8>>,
    {Header, Padding_length}.

-file("src/fcgi/internal/protocol.gleam", 170).
?DOC(false).
-spec chunk_stdout_loop(integer(), bitstring(), list(outgoing())) -> list(outgoing()).
chunk_stdout_loop(Request_id, Body, Acc) ->
    Total = erlang:byte_size(Body),
    gleam@bool:guard(
        Total =:= 0,
        lists:reverse(Acc),
        fun() ->
            gleam@bool:guard(
                Total =< 65535,
                lists:reverse([{stdout, Request_id, Body} | Acc]),
                fun() ->
                    Head@1 = case gleam_stdlib:bit_array_slice(Body, 0, 65535) of
                        {ok, Head} -> Head;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"fcgi/internal/protocol"/utf8>>,
                                        function => <<"chunk_stdout_loop"/utf8>>,
                                        line => 183,
                                        value => _assert_fail,
                                        start => 4563,
                                        'end' => 4634,
                                        pattern_start => 4574,
                                        pattern_end => 4582})
                    end,
                    Tail@1 = case gleam_stdlib:bit_array_slice(
                        Body,
                        65535,
                        Total - 65535
                    ) of
                        {ok, Tail} -> Tail;
                        _assert_fail@1 ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"fcgi/internal/protocol"/utf8>>,
                                        function => <<"chunk_stdout_loop"/utf8>>,
                                        line => 184,
                                        value => _assert_fail@1,
                                        start => 4637,
                                        'end' => 4767,
                                        pattern_start => 4648,
                                        pattern_end => 4656})
                    end,
                    chunk_stdout_loop(
                        Request_id,
                        Tail@1,
                        [{stdout, Request_id, Head@1} | Acc]
                    )
                end
            )
        end
    ).

-file("src/fcgi/internal/protocol.gleam", 166).
?DOC(false).
-spec chunk_stdout(integer(), bitstring()) -> list(outgoing()).
chunk_stdout(Request_id, Body) ->
    chunk_stdout_loop(Request_id, Body, []).

-file("src/fcgi/internal/protocol.gleam", 343).
?DOC(false).
-spec parse_length(bitstring()) -> {ok, {integer(), bitstring()}} |
    {error, parse_failure()}.
parse_length(Bytes) ->
    case Bytes of
        <<0:1, N:7, Rest/bitstring>> ->
            {ok, {N, Rest}};

        <<1:1, N@1:31, Rest@1/bitstring>> ->
            {ok, {N@1, Rest@1}};

        _ ->
            {error, malformed_name_value}
    end.

-file("src/fcgi/internal/protocol.gleam", 316).
?DOC(false).
-spec parse_one_pair(bitstring()) -> {ok, {{binary(), binary()}, bitstring()}} |
    {error, parse_failure()}.
parse_one_pair(Bytes) ->
    gleam@result:'try'(
        parse_length(Bytes),
        fun(_use0) ->
            {Name_length, After_name_length} = _use0,
            gleam@result:'try'(
                parse_length(After_name_length),
                fun(_use0@1) ->
                    {Value_length, After_value_length} = _use0@1,
                    case After_value_length of
                        <<Name_bytes:Name_length/binary,
                            Value_bytes:Value_length/binary,
                            Rest/bitstring>> ->
                            gleam@result:'try'(
                                begin
                                    _pipe = gleam@bit_array:to_string(
                                        Name_bytes
                                    ),
                                    gleam@result:replace_error(
                                        _pipe,
                                        malformed_name_value
                                    )
                                end,
                                fun(Name) ->
                                    gleam@result:map(
                                        begin
                                            _pipe@1 = gleam@bit_array:to_string(
                                                Value_bytes
                                            ),
                                            gleam@result:replace_error(
                                                _pipe@1,
                                                malformed_name_value
                                            )
                                        end,
                                        fun(Value) -> {{Name, Value}, Rest} end
                                    )
                                end
                            );

                        _ ->
                            {error, malformed_name_value}
                    end
                end
            )
        end
    ).

-file("src/fcgi/internal/protocol.gleam", 303).
?DOC(false).
-spec parse_name_value_pairs_loop(bitstring(), list({binary(), binary()})) -> {ok,
        list({binary(), binary()})} |
    {error, parse_failure()}.
parse_name_value_pairs_loop(Bytes, Acc) ->
    gleam@bool:guard(
        erlang:byte_size(Bytes) =:= 0,
        {ok, lists:reverse(Acc)},
        fun() ->
            gleam@result:'try'(
                parse_one_pair(Bytes),
                fun(_use0) ->
                    {Pair, Rest} = _use0,
                    parse_name_value_pairs_loop(Rest, [Pair | Acc])
                end
            )
        end
    ).

-file("src/fcgi/internal/protocol.gleam", 297).
?DOC(false).
-spec parse_name_value_pairs(bitstring()) -> {ok, list({binary(), binary()})} |
    {error, parse_failure()}.
parse_name_value_pairs(Bytes) ->
    parse_name_value_pairs_loop(Bytes, []).

-file("src/fcgi/internal/protocol.gleam", 287).
?DOC(false).
-spec parse_get_values(bitstring(), bitstring()) -> parse_result().
parse_get_values(Body, Rest) ->
    case parse_name_value_pairs(Body) of
        {error, Reason} ->
            {parse_error, Reason};

        {ok, Pairs} ->
            Names = gleam@list:map(Pairs, fun gleam@pair:first/1),
            {parsed, {get_values, Names}, Rest}
    end.

-file("src/fcgi/internal/protocol.gleam", 276).
?DOC(false).
-spec parse_begin_request(integer(), bitstring(), bitstring()) -> parse_result().
parse_begin_request(Id, Body, Rest) ->
    case Body of
        <<Role:16, _:7, Keep_bit:1, _:40>> ->
            {parsed, {begin_request, Id, Role, Keep_bit =:= 1}, Rest};

        _ ->
            {parse_error, malformed_record}
    end.

-file("src/fcgi/internal/protocol.gleam", 260).
?DOC(false).
-spec parse_body(integer(), integer(), bitstring(), bitstring()) -> parse_result().
parse_body(Record_type, Id, Body, Rest) ->
    case Record_type of
        1 ->
            parse_begin_request(Id, Body, Rest);

        2 ->
            {parsed, {abort_request, Id}, Rest};

        4 ->
            {parsed, {params, Id, Body}, Rest};

        5 ->
            {parsed, {stdin, Id, Body}, Rest};

        9 ->
            parse_get_values(Body, Rest);

        _ ->
            {parsed, {incoming_unknown, Id, Record_type}, Rest}
    end.

-file("src/fcgi/internal/protocol.gleam", 238).
?DOC(false).
-spec parse_after_header(
    integer(),
    integer(),
    integer(),
    integer(),
    integer(),
    bitstring()
) -> parse_result().
parse_after_header(
    Version,
    Record_type,
    Id,
    Content_length,
    Padding_length,
    Rest
) ->
    gleam@bool:guard(
        Version /= 1,
        {parse_error, {unsupported_version, Version}},
        fun() -> case Rest of
                <<Body:Content_length/binary,
                    _:Padding_length/binary,
                    Remaining/bitstring>> ->
                    parse_body(Record_type, Id, Body, Remaining);

                _ ->
                    need_more
            end end
    ).

-file("src/fcgi/internal/protocol.gleam", 215).
?DOC(false).
-spec parse_record(bitstring()) -> parse_result().
parse_record(Buffer) ->
    case Buffer of
        <<Version:8,
            Record_type:8,
            Id:16,
            Content_length:16,
            Padding_length:8,
            _:8,
            Rest/bitstring>> ->
            parse_after_header(
                Version,
                Record_type,
                Id,
                Content_length,
                Padding_length,
                Rest
            );

        _ ->
            need_more
    end.