-module(nquic_stream_manager).
-moduledoc """
Stream ID allocation and validation per RFC 9000 Section 2.
Manages stream creation with limit checking. Stream IDs encode the
initiator (client/server) and type (bidi/uni) in the low 2 bits, and
increment by 4 for each new stream of the same type.
""".
-include("nquic_conn.hrl").
-export([
first_peer_stream_id/2,
get_or_create/3,
get_or_create/4,
type/1
]).
-spec check_stream_limit(nquic:stream_id(), bidi | uni, client | server, client | server, map()) ->
ok | {error, stream_limit_error}.
check_stream_limit(StreamID, Type, Init, MyRole, Limits) ->
case Init =:= MyRole of
true ->
ok;
false ->
StreamNum = StreamID div 4,
MaxCount =
case Type of
bidi -> maps:get(max_bidi, Limits, infinity);
uni -> maps:get(max_uni, Limits, infinity)
end,
case MaxCount of
infinity -> ok;
N when StreamNum < N -> ok;
_ -> {error, stream_limit_error}
end
end.
-doc "Return the first stream ID initiated by the peer for the given type.".
-spec first_peer_stream_id(client | server, bidi | uni) -> nquic:stream_id().
first_peer_stream_id(server, bidi) -> 0;
first_peer_stream_id(server, uni) -> 2;
first_peer_stream_id(client, bidi) -> 1;
first_peer_stream_id(client, uni) -> 3.
-doc "Retrieve an existing stream or create a new one with no limit checking.".
-spec get_or_create(nquic:stream_id(), map(), client | server) ->
{ok, #stream_state{}, map()} | {error, term()}.
get_or_create(StreamID, Streams, Role) ->
get_or_create(StreamID, Streams, Role, #{max_bidi => infinity, max_uni => infinity}).
-doc "Retrieve an existing stream or create a new one, validating against concurrency limits.".
-spec get_or_create(nquic:stream_id(), map(), client | server, map()) ->
{ok, #stream_state{}, map()} | {error, term()}.
get_or_create(StreamID, Streams, Role, Limits) ->
case maps:find(StreamID, Streams) of
{ok, State} ->
{ok, State, Streams};
error ->
Type = type(StreamID),
Init = nquic_frame_handler:stream_initiator(StreamID),
case check_stream_limit(StreamID, Type, Init, Role, Limits) of
{error, _} = Err ->
Err;
ok ->
if
StreamID > 16#3FFFFFFFFFFFFFFF ->
{error, invalid_stream_id};
true ->
State = nquic_stream_statem:new(StreamID, Type),
{ok, State, Streams#{StreamID => State}}
end
end
end.
-doc "Return the stream type (bidi or uni) based on the low 2 bits of the stream ID.".
-spec type(nquic:stream_id()) -> bidi | uni.
type(StreamID) ->
case StreamID rem 4 of
0 -> bidi;
1 -> bidi;
2 -> uni;
3 -> uni
end.