-module(nquic_frame_handler). -moduledoc """ QUIC frame handling helpers. This module provides validation and processing helpers for QUIC frames, including stream validation per RFC 9000 and TLS message validation per RFC 9001. """. -include("nquic_conn.hrl"). -include("nquic_frame.hrl"). -export([stream_initiator/1, stream_is_unidirectional/1]). -export([i_can_send/2, is_locally_initiated/2, peer_can_send/2]). -export([validate_stream_for_reset/2, validate_stream_for_stop_sending/3]). -export([validate_stream_for_max_stream_data/3, validate_stream_for_recv/3]). -export([check_handshake_crypto/1, check_post_handshake_crypto/1]). -export([ensure_stream_limits/2]). -export([decode_ack_ranges/3]). %%%----------------------------------------------------------------------------- %% STREAM VALIDATION (RFC 9000 SECTION 2 1) %%%----------------------------------------------------------------------------- -doc """ Returns true if we can send data on this stream. Bidirectional streams allow both sides to send. Unidirectional streams only allow the initiator to send. """. -spec i_can_send(nquic:stream_id(), client | server) -> boolean(). i_can_send(StreamID, MyRole) -> case stream_is_unidirectional(StreamID) of false -> true; true -> Initiator = stream_initiator(StreamID), Initiator =:= MyRole end. -doc "Returns true if this stream was locally initiated.". -spec is_locally_initiated(nquic:stream_id(), client | server) -> boolean(). is_locally_initiated(StreamID, MyRole) -> stream_initiator(StreamID) =:= MyRole. -doc """ Returns true if the peer can send data on this stream. Bidirectional streams allow both sides to send. Unidirectional streams only allow the initiator to send. """. -spec peer_can_send(nquic:stream_id(), client | server) -> boolean(). peer_can_send(StreamID, MyRole) -> case stream_is_unidirectional(StreamID) of false -> true; true -> Initiator = stream_initiator(StreamID), Initiator =/= MyRole end. -doc "Returns the initiator (client or server) of a stream based on its ID.". -spec stream_initiator(nquic:stream_id()) -> client | server. stream_initiator(StreamID) -> case StreamID band 1 of 0 -> client; 1 -> server end. -doc "Returns true if the stream is unidirectional.". -spec stream_is_unidirectional(nquic:stream_id()) -> boolean(). stream_is_unidirectional(StreamID) -> (StreamID band 2) =:= 2. -doc """ Validate MAX_STREAM_DATA frame per RFC 9000 Section 19.10. Can only receive for streams where we can send. Stream must exist or be implicitly creatable. """. -spec validate_stream_for_max_stream_data(nquic:stream_id(), client | server, map()) -> ok | {error, stream_state_error}. validate_stream_for_max_stream_data(StreamID, MyRole, Streams) -> case i_can_send(StreamID, MyRole) of false -> {error, stream_state_error}; true -> case maps:is_key(StreamID, Streams) of true -> ok; false -> case is_locally_initiated(StreamID, MyRole) of true -> {error, stream_state_error}; false -> ok end end end. -doc """ Validate STREAM frame per RFC 9000 Section 19.8. Can only receive on streams where peer can send. For locally-initiated streams, we must have created it first. """. -spec validate_stream_for_recv(nquic:stream_id(), client | server, map()) -> ok | {error, stream_state_error}. validate_stream_for_recv(StreamID, MyRole, Streams) -> case peer_can_send(StreamID, MyRole) of false -> {error, stream_state_error}; true -> case is_locally_initiated(StreamID, MyRole) of true -> case maps:is_key(StreamID, Streams) of true -> ok; false -> {error, stream_state_error} end; false -> ok end end. -doc """ Validate RESET_STREAM frame per RFC 9000 Section 19.4. Can only receive RESET_STREAM on streams where peer can send. """. -spec validate_stream_for_reset(nquic:stream_id(), client | server) -> ok | {error, stream_state_error}. validate_stream_for_reset(StreamID, MyRole) -> case peer_can_send(StreamID, MyRole) of true -> ok; false -> {error, stream_state_error} end. -doc """ Validate STOP_SENDING frame per RFC 9000 Section 19.5. Can only receive STOP_SENDING for streams where we can send. Stream must exist or be implicitly creatable. """. -spec validate_stream_for_stop_sending(nquic:stream_id(), client | server, map()) -> ok | {error, stream_state_error}. validate_stream_for_stop_sending(StreamID, MyRole, Streams) -> case i_can_send(StreamID, MyRole) of false -> {error, stream_state_error}; true -> case maps:is_key(StreamID, Streams) of true -> ok; false -> case is_locally_initiated(StreamID, MyRole) of true -> {error, stream_state_error}; false -> ok end end end. %%%----------------------------------------------------------------------------- %% TLS CRYPTO VALIDATION (RFC 9001) %%%----------------------------------------------------------------------------- -doc """ Check for invalid TLS message types in Handshake-level CRYPTO frames. RFC 9001 Section 6: KeyUpdate (24) MUST NOT be sent in CRYPTO frames. RFC 9001 Section 8.3: EndOfEarlyData (5) is not used in QUIC. """. -spec check_handshake_crypto(binary()) -> ok | {error, {tls_alert, unexpected_message}}. check_handshake_crypto(<<>>) -> ok; check_handshake_crypto(<>) -> case Type of 5 -> {error, {tls_alert, unexpected_message}}; 24 -> {error, {tls_alert, unexpected_message}}; _ -> check_handshake_crypto(Rest) end; check_handshake_crypto(_) -> ok. -doc """ Check for invalid TLS message types in post-handshake CRYPTO frames. RFC 9001 Section 6: KeyUpdate (24) MUST NOT be sent in CRYPTO frames. RFC 9001 Section 8.3: EndOfEarlyData (5) is not used in QUIC. NewSessionTicket (4) is allowed. """. -spec check_post_handshake_crypto(binary()) -> ok | {error, {tls_alert, unexpected_message}}. check_post_handshake_crypto(<<>>) -> ok; check_post_handshake_crypto(<>) -> case Type of 5 -> {error, {tls_alert, unexpected_message}}; 24 -> {error, {tls_alert, unexpected_message}}; _ -> check_post_handshake_crypto(Rest) end; check_post_handshake_crypto(_) -> ok. %%%----------------------------------------------------------------------------- %% STREAM LIMIT HELPERS %%%----------------------------------------------------------------------------- -doc "Ensure stream flow control limits are initialized.". -spec ensure_stream_limits(#stream_state{}, #conn_state{}) -> #stream_state{}. ensure_stream_limits(StreamState, ConnState) -> if StreamState#stream_state.send_max_data =:= 0 andalso StreamState#stream_state.recv_window =:= 0 -> StreamType = nquic_stream_manager:type(StreamState#stream_state.stream_id), nquic_flow:init_stream_limits(StreamState, ConnState, StreamType); true -> StreamState end. %%%----------------------------------------------------------------------------- %% ACK PROCESSING %%%----------------------------------------------------------------------------- -doc "Decode ACK ranges into {Low, High} inclusive intervals. O(R) where R is the number of ranges.". -spec decode_ack_ranges(non_neg_integer(), non_neg_integer(), [#ack_range{}]) -> [{non_neg_integer(), non_neg_integer()}]. decode_ack_ranges(Largest, FirstRange, Ranges) -> FirstLow = Largest - FirstRange, decode_ack_ranges_loop(FirstLow, Ranges, [{FirstLow, Largest}]). -spec decode_ack_ranges_loop( non_neg_integer(), [#ack_range{}], [{non_neg_integer(), non_neg_integer()}] ) -> [{non_neg_integer(), non_neg_integer()}]. decode_ack_ranges_loop(_, [], Acc) -> Acc; decode_ack_ranges_loop(PrevSmallest, [#ack_range{gap = Gap, length = Len} | Rest], Acc) -> High = PrevSmallest - Gap - 2, Low = High - Len, decode_ack_ranges_loop(Low, Rest, [{Low, High} | Acc]).