@@ -176,7 +176,7 @@ package enum HTTP3FrameValidator: ~Copyable {
176176 /// It looks at both request and response frames.
177177 /// When using this validator for a client, the request frames are the outbound and the response frames are inbound.
178178 /// This is reversed for a server.
179- package struct RequestStreamValidator : ~ Copyable {
179+ private struct RequestStreamValidator : ~ Copyable {
180180 /// Models the state of one side of the connection.
181181 enum State {
182182 /// Nothing has happened yet.
@@ -357,30 +357,151 @@ package enum HTTP3FrameValidator: ~Copyable {
357357 }
358358 }
359359 }
360+
361+ /// Whether the complete request has been received.
362+ private var receivedCompleteRequest : Bool {
363+ switch self . requestState {
364+ case . idle:
365+ // We haven't received the request head part yet, so the request is trivially incomplete.
366+ return false
367+
368+ case . headersProcessed:
369+ // TODO: If a Content-Length header was specified, we need to check whether we have received all the
370+ // specified bytes. If that is not the case, then the request is malformed per RFC 9114 § 4.1.2, and it
371+ // must be treated as a stream error.
372+ return true
373+
374+ case . trailersProcessed:
375+ // If we have processed trailers, we have seen the full request.
376+ return true
377+
378+ case . previousError:
379+ // There was already an error. That will result in the stream closing anyway, so just return `true`.
380+ return true
381+ }
382+ }
383+
384+ /// Whether the complete response has been received.
385+ private var receivedCompleteResponse : Bool {
386+ switch self . responseState {
387+ case . idle:
388+ // We haven't received a final response head part yet, so the response is trivially incomplete.
389+ return false
390+
391+ case . headersProcessed:
392+ // TODO: If a Content-Length header was specified, we need to check whether we have received all the
393+ // specified bytes. If that is not the case, then the request is malformed per RFC 9114 § 4.1.2, and it
394+ // must be treated as a stream error.
395+ return true
396+
397+ case . trailersProcessed:
398+ // If we have processed trailers, we have seen the full response.
399+ return true
400+
401+ case . previousError:
402+ // There was already an error. That will result in the stream closing anyway, so just return `true`.
403+ return true
404+ }
405+ }
406+
407+ /// Records that the inbound request stream has closed.
408+ mutating func processInboundRequestStreamClosed( ) -> InboundClosedAction {
409+ if self . receivedCompleteRequest {
410+ return . doNothing
411+ }
412+
413+ // The input closed before we saw a *complete* request. Per RFC 9114 § 4.1:
414+ // "If a client-initiated stream terminates without enough of the HTTP message to provide a complete
415+ // response, the server SHOULD abort its response stream with the error code H3\_REQUEST\_INCOMPLETE".
416+ //
417+ // We therefore need to reset the stream.
418+ self = . init( requestState: . previousError, responseState: . previousError)
419+
420+ return . resetStream(
421+ HTTP3Error (
422+ code: . peerTerminatedInboundStream,
423+ message: " Inbound request stream closed before a complete request was received " ,
424+ cause: nil ,
425+ errorCode: . H3_REQUEST_INCOMPLETE,
426+ location: . here( )
427+ )
428+ )
429+ }
430+
431+ /// Records that the inbound response stream has closed.
432+ func processInboundResponseStreamClosed( ) -> InboundClosedAction {
433+ if self . receivedCompleteResponse {
434+ return . doNothing
435+ }
436+
437+ // The input closed before we saw a *complete* response. Per RFC 9114 § 4.1.1:
438+ // "... if a stream is cancelled after receiving a partial response, the response SHOULD NOT be used".
439+ //
440+ // We therefore need to inform the downstream so they can decide what to do with the incomplete response.
441+ //
442+ // This is not a stream or connection error, so we don't modify the request or response state.
443+ return . notifyDownstream(
444+ HTTP3Error (
445+ code: . peerTerminatedInboundStream,
446+ message: " Inbound response stream closed before a complete response was received " ,
447+ cause: nil ,
448+ errorCode: . H3_NO_ERROR,
449+ location: . here( )
450+ )
451+ )
452+ }
453+ }
454+
455+ /// The action to take when the inbound side of a stream closes.
456+ package enum InboundClosedAction {
457+ /// We received the full request or response before the inbound closed. As such, there is nothing to do.
458+ case doNothing
459+
460+ /// The inbound closed before we received the full response. The downstream should be notified so they can
461+ /// decide what to do with the partial response.
462+ case notifyDownstream( HTTP3Error )
463+
464+ /// The inbound closed before we received the full request. Per RFC 9114 § 4.1, the server should abort the
465+ /// response stream by sending a RESET_STREAM frame.
466+ case resetStream( HTTP3Error )
360467 }
361468
362469 package struct ServerRequestStreamValidator : ~ Copyable {
363470 private var underlying = RequestStreamValidator ( )
364471
472+ /// Validates an inbound request frame.
365473 package mutating func processInboundFrame( _ frame: HTTP3Frame ) -> ProcessFrameAction {
366474 self . underlying. processRequestFrame ( frame)
367475 }
368476
477+ /// Validates an outbound response frame.
369478 package mutating func processOutboundFrame( _ frame: HTTP3Frame ) -> ProcessFrameAction {
370479 self . underlying. processResponseFrame ( frame)
371480 }
481+
482+ /// Records that the inbound request stream has closed.
483+ package mutating func processInboundClosed( ) -> InboundClosedAction {
484+ self . underlying. processInboundRequestStreamClosed ( )
485+ }
372486 }
373487
374488 package struct ClientRequestStreamValidator : ~ Copyable {
375489 private var underlying = RequestStreamValidator ( )
376490
491+ /// Validates an inbound response frame.
377492 package mutating func processInboundFrame( _ frame: HTTP3Frame ) -> ProcessFrameAction {
378493 self . underlying. processResponseFrame ( frame)
379494 }
380495
496+ /// Validates an outbound request frame.
381497 package mutating func processOutboundFrame( _ frame: HTTP3Frame ) -> ProcessFrameAction {
382498 self . underlying. processRequestFrame ( frame)
383499 }
500+
501+ /// Records that the inbound response stream has closed.
502+ package func processInboundClosed( ) -> InboundClosedAction {
503+ self . underlying. processInboundResponseStreamClosed ( )
504+ }
384505 }
385506
386507 /// Checks that incoming frames are in the right order for a control stream, and that there are no outgoing frames.
@@ -594,6 +715,37 @@ package enum HTTP3FrameValidator: ~Copyable {
594715 return . dropFrame
595716 }
596717 }
718+
719+ /// Records that the inbound side of the stream has closed.
720+ package mutating func processInboundClosed( ) -> InboundClosedAction {
721+ switch self {
722+ case . incomingRequestStream( var validator) :
723+ let result = validator. processInboundClosed ( )
724+ self = . incomingRequestStream( validator)
725+ return result
726+
727+ case . outgoingRequestStream( let validator) :
728+ let result = validator. processInboundClosed ( )
729+ self = . outgoingRequestStream( validator)
730+ return result
731+
732+ case . incomingControlStream( let validator) :
733+ self = . incomingControlStream( validator)
734+ return . doNothing
735+
736+ case . outgoingControlStream( let validator) :
737+ self = . outgoingControlStream( validator)
738+ return . doNothing
739+
740+ case . incomingPushStream( let validator) :
741+ self = . incomingPushStream( validator)
742+ return . doNothing
743+
744+ case . outgoingPushStream( let validator) :
745+ self = . outgoingPushStream( validator)
746+ return . doNothing
747+ }
748+ }
597749}
598750
599751extension HTTP3Frame {
0 commit comments