src/quicer_server_conn_callback.erl

%%--------------------------------------------------------------------
%% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(quicer_server_conn_callback).

-behavior(quicer_connection).

-include_lib("snabbkaffe/include/snabbkaffe.hrl").
-include("quicer_types.hrl").

%% Callback init
-export([init/1]).

%% Connection Callbacks
-export([
    new_conn/3,
    connected/3,
    transport_shutdown/3,
    shutdown/3,
    closed/3,
    local_address_changed/3,
    peer_address_changed/3,
    streams_available/3,
    peer_needs_streams/3,
    resumed/3,
    nst_received/3,
    new_stream/3
]).

-export([handle_info/2]).

init(#{stream_opts := SOpts} = S) when is_list(SOpts) ->
    init(S#{stream_opts := maps:from_list(SOpts)});
init(ConnOpts) when is_map(ConnOpts) ->
    {ok, ConnOpts}.

closed(_Conn, #{} = _Flags, S) ->
    {stop, normal, S}.

new_conn(Conn, #{version := _Vsn}, #{stream_opts := SOpts} = S) ->
    %% @TODO configurable behavior of spawning stream acceptor
    case quicer_remote_stream:start_link(maps:get(stream_callback, SOpts), Conn, SOpts) of
        {ok, Pid} ->
            ok = quicer:async_handshake(Conn),
            {ok, S#{
                conn => Conn,
                %% @TODO track the streams?
                streams => [{Pid, accepting}]
            }};
        {error, _} = Error ->
            Error
    end.

resumed(Conn, Data, #{resumed_callback := ResumeFun} = S) when
    is_function(ResumeFun)
->
    ResumeFun(Conn, Data, S);
resumed(_Conn, _Data, S) ->
    {ok, S}.

nst_received(_Conn, _Data, S) ->
    {stop, no_nst_for_server, S}.

%% handles stream when there is no stream acceptors.
new_stream(
    Stream,
    #{is_orphan := true} = StreamProps,
    #{conn := Conn, streams := Streams, stream_opts := SOpts} = CBState
) ->
    %% Spawn new stream
    case
        quicer_remote_stream:start_link(
            maps:get(stream_callback, SOpts),
            Stream,
            Conn,
            SOpts,
            StreamProps
        )
    of
        {ok, StreamOwner} ->
            case quicer:handoff_stream(Stream, StreamOwner) of
                ok ->
                    {ok, CBState#{streams := [{StreamOwner, Stream} | Streams]}};
                {error, _} = E ->
                    {stop, {shutdown, {handoff, E}, CBState}}
            end;
        Other ->
            Other
    end.

shutdown(Conn, _ErrorCode, S) ->
    quicer:async_close_connection(Conn),
    {ok, S}.

transport_shutdown(_C, _DownInfo, S) ->
    {ok, S}.

peer_address_changed(_C, _NewAddr, S) ->
    {ok, S}.

local_address_changed(_C, _NewAddr, S) ->
    {ok, S}.

streams_available(_C, {BidirCnt, UnidirCnt}, S) ->
    {ok, S#{
        peer_unidi_stream_count => UnidirCnt,
        peer_bidi_stream_count => BidirCnt
    }}.

%% @doc May integrate with App flow control
peer_needs_streams(_C, _UnidiOrBidi, S) ->
    {ok, S}.

connected(
    Conn,
    _Flags,
    #{
        slow_start := false,
        stream_opts := SOpts,
        stream_callback := Callback
    } = S
) ->
    %% @TODO configurable behavior of spawing stream acceptor
    _ = quicer_stream:start_link(Callback, Conn, SOpts),
    {ok, S#{conn => Conn}};
connected(_Connection, _Flags, S) ->
    {ok, S}.

handle_info({'EXIT', _Pid, _Reason}, State) ->
    {ok, State}.

%% Internals

-ifdef(EUNIT).

-endif.