nquic_token_cache (nquic v1.0.0)

View Source

Per-instance NEW_TOKEN cache for QUIC client reconnection (RFC 9000 §8.1.3).

Mirrors nquic_session_cache: each cache is a gen_server that owns one named ETS table and runs a periodic eviction sweep. The cache is opt-in; the caller starts it under its own supervisor and references it by name on nquic:connect/3.

Usage

[nquic_token_cache:child_spec(my_quic_tokens, #{sweep_ms => 60_000})].
{ok, Conn} = nquic:connect(Host, Port, #{token_cache => my_quic_tokens}).

The client records any NEW_TOKEN frame received from the server into the cache. A subsequent connect/4 to the same {Host, Port} reads back the token and attaches it to the outgoing Initial; the server validates it against its static key, treats the source address as already-validated, and skips the Retry round-trip.

The cache stores raw token bytes; the server's HMAC binds the token to a specific address + lifetime, so a stale or address-mismatched token simply fails server-side validation and triggers a normal Retry. Keep the client TTL shorter than the server's Lifetime (default 24 h) to avoid presenting tokens the server will reject.

Custom backends

The token_cache connect option also accepts {module, Mod} where Mod exports:

store(Host, Port, Token) -> ok.
lookup(Host, Port)       -> {ok, binary()} | {error, not_found}.
delete(Host, Port)       -> ok.

Summary

Functions

Standard child spec with the default sweep interval.

Standard child spec with custom options.

Drop every entry in cache Name. Raises badarg if not started.

Delete a token from cache Name. Raises badarg if not started.

Look up a NEW_TOKEN for {Host, Port} in cache Name. Returns {ok, Token} for a live token, {error, not_found} for a miss or an expired entry. Raises badarg if Name is not a live cache.

If Opts does not already carry a client_token, resolve the configured token_cache selector and inject any cached NEW_TOKEN into Opts. Returns Opts unchanged on miss or when no cache is configured. Accepts the same selector shape as the connect/3,4 option: false (no cache), an atom naming a started cache, or {module, Mod} for a custom backend exporting lookup/2.

Return the number of entries in cache Name.

Start a cache registered under Name with default options.

Start a cache registered under Name. Accepted options

Stop the cache and tear down its ETS table.

Store a NEW_TOKEN under {Host, Port} for the cache Name. The optional TTL (seconds) bounds how long the cache will hand the token back; default 24 h. Keep this less-than-or-equal to the server's new_token_lifetime so the cache never serves a token the server would reject.

Types

cache_name()

-type cache_name() :: atom().

host()

-type host() :: inet:hostname() | inet:ip_address().

Functions

child_spec(Name)

-spec child_spec(cache_name()) -> supervisor:child_spec().

Standard child spec with the default sweep interval.

child_spec(Name, Opts)

-spec child_spec(cache_name(), map()) -> supervisor:child_spec().

Standard child spec with custom options.

clear(Name)

-spec clear(cache_name()) -> ok.

Drop every entry in cache Name. Raises badarg if not started.

delete(Name, Host, Port)

-spec delete(cache_name(), host(), inet:port_number()) -> ok.

Delete a token from cache Name. Raises badarg if not started.

handle_call(Request, From, State)

-spec handle_call(term(),
                  gen_server:from(),
                  #state{name :: cache_name(),
                         sweep_ms :: pos_integer(),
                         sweep_ref :: reference() | undefined}) ->
                     {reply,
                      {error, unknown_request},
                      #state{name :: cache_name(),
                             sweep_ms :: pos_integer(),
                             sweep_ref :: reference() | undefined}}.

handle_cast(Msg, State)

-spec handle_cast(term(),
                  #state{name :: cache_name(),
                         sweep_ms :: pos_integer(),
                         sweep_ref :: reference() | undefined}) ->
                     {noreply,
                      #state{name :: cache_name(),
                             sweep_ms :: pos_integer(),
                             sweep_ref :: reference() | undefined}}.

handle_info/2

-spec handle_info(sweep | term(),
                  #state{name :: cache_name(),
                         sweep_ms :: pos_integer(),
                         sweep_ref :: reference() | undefined}) ->
                     {noreply,
                      #state{name :: cache_name(),
                             sweep_ms :: pos_integer(),
                             sweep_ref :: reference() | undefined}}.

init/1

-spec init({cache_name(), map()}) ->
              {ok,
               #state{name :: cache_name(),
                      sweep_ms :: pos_integer(),
                      sweep_ref :: reference() | undefined}}.

lookup(Name, Host, Port)

-spec lookup(cache_name(), host(), inet:port_number()) -> {ok, binary()} | {error, not_found}.

Look up a NEW_TOKEN for {Host, Port} in cache Name. Returns {ok, Token} for a live token, {error, not_found} for a miss or an expired entry. Raises badarg if Name is not a live cache.

maybe_load_token(Host, Port, Opts)

-spec maybe_load_token(host(), inet:port_number(), map()) -> map().

If Opts does not already carry a client_token, resolve the configured token_cache selector and inject any cached NEW_TOKEN into Opts. Returns Opts unchanged on miss or when no cache is configured. Accepts the same selector shape as the connect/3,4 option: false (no cache), an atom naming a started cache, or {module, Mod} for a custom backend exporting lookup/2.

size(Name)

-spec size(cache_name()) -> non_neg_integer().

Return the number of entries in cache Name.

start_link(Name)

-spec start_link(cache_name()) -> {ok, pid()} | ignore | {error, term()}.

Start a cache registered under Name with default options.

start_link(Name, Opts)

-spec start_link(cache_name(), map()) -> {ok, pid()} | ignore | {error, term()}.

Start a cache registered under Name. Accepted options:

  • sweep_ms - eviction interval in milliseconds (default 60_000). Returns {error, {already_started, Pid}} if a cache with this name is already registered.

stop(Name)

-spec stop(cache_name()) -> ok.

Stop the cache and tear down its ETS table.

store(Name, Host, Port, Token)

-spec store(cache_name(), host(), inet:port_number(), binary()) -> ok.

Store a NEW_TOKEN under {Host, Port} for the cache Name. The optional TTL (seconds) bounds how long the cache will hand the token back; default 24 h. Keep this less-than-or-equal to the server's new_token_lifetime so the cache never serves a token the server would reject.

terminate/2

-spec terminate(term(),
                #state{name :: cache_name(),
                       sweep_ms :: pos_integer(),
                       sweep_ref :: reference() | undefined}) ->
                   ok.