-module(nquic_lib_timer). -moduledoc false. %% Timer scheduling and migration finalisation for `nquic_lib'. %% %% Internal glue behind the `nquic_lib' facade. Owns the %% `erlang:send_after' / `erlang:cancel_timer' bookkeeping for QUIC %% protocol timers, the egress-ECN socket transition, and the %% post-self-migration dispatch-table cleanup. Runs in the owner %% process. `self()' is the connection owner. -include("nquic_conn.hrl"). -export([ absorb_migration_events/2, apply_timer_actions/2, maybe_apply_ecn_transition/2 ]). -spec absorb_migration_events([nquic_protocol:event()], nquic:ctx()) -> {[nquic_protocol:event()], nquic:ctx()}. absorb_migration_events([], Ctx) -> {[], Ctx}; absorb_migration_events(Events, Ctx) -> absorb_migration_events(Events, Ctx, []). -spec absorb_migration_events( [nquic_protocol:event()], nquic:ctx(), [nquic_protocol:event()] ) -> {[nquic_protocol:event()], nquic:ctx()}. absorb_migration_events([], Ctx, Acc) -> {lists:reverse(Acc), Ctx}; absorb_migration_events([local_migration_validated | Rest], Ctx, Acc) -> absorb_migration_events(Rest, finalize_self_migration(Ctx), Acc); absorb_migration_events([Event | Rest], Ctx, Acc) -> absorb_migration_events(Rest, Ctx, [Event | Acc]). -spec apply_timer_actions(nquic:ctx(), [nquic_protocol:timeout_action()]) -> nquic:ctx(). apply_timer_actions(Ctx, []) -> Ctx; apply_timer_actions(Ctx, [{set_timer, Type, Ms} | Rest]) -> Timers = nquic_ctx:timers(Ctx), _ = cancel_timer_if_set(Type, Timers), Ref = erlang:send_after(Ms, self(), {quic_timeout, Type}), apply_timer_actions(nquic_ctx:set_timers(Ctx, Timers#{Type => Ref}), Rest); apply_timer_actions(Ctx, [{cancel_timer, Type} | Rest]) -> Timers = nquic_ctx:timers(Ctx), _ = cancel_timer_if_set(Type, Timers), apply_timer_actions(nquic_ctx:set_timers(Ctx, maps:remove(Type, Timers)), Rest). -spec cancel_timer_if_set(nquic_protocol:timer_type(), #{ nquic_protocol:timer_type() => reference() }) -> ok. cancel_timer_if_set(Type, Timers) -> case maps:find(Type, Timers) of {ok, Ref} -> _ = erlang:cancel_timer(Ref, [{async, true}, {info, false}]), ok; error -> ok end. -spec finalize_self_migration(nquic:ctx()) -> nquic:ctx(). finalize_self_migration(Ctx) -> State0 = nquic_ctx:state(Ctx), {Table, State1} = nquic_protocol:clear_dispatch_table(State0), case Table of undefined -> nquic_ctx:set_state(Ctx, State1); _ -> ok = lists:foreach( fun(CID) -> nquic_listener:dispatch_unregister(Table, CID) end, nquic_protocol:local_cids(State1) ), case nquic_protocol:odcid(State1) of undefined -> ok; <<>> -> ok; ODCID -> _ = nquic_listener:dispatch_unregister(Table, ODCID), ok end, Ctx1 = nquic_ctx:set_state(Ctx, State1), nquic_ctx:set_dispatch(Ctx1, undefined) end. -spec maybe_apply_ecn_transition(nquic_socket:t(), nquic_protocol:state()) -> nquic_protocol:state(). maybe_apply_ecn_transition(Socket, #conn_state{loss_state = LS} = State) -> case nquic_loss:is_ecn_socket_dirty(LS) of false -> State; true -> Mark = case nquic_loss:is_ecn_enabled(LS) of true -> ect0; false -> not_ect end, ok = nquic_socket:set_egress_ecn(Socket, Mark), State#conn_state{loss_state = nquic_loss:clear_ecn_socket_dirty(LS)} end.