mutating func receiveHeaders()

in Sources/NIOHTTP2/StreamStateMachine.swift [397:526]


    mutating func receiveHeaders(headers: HPACKHeaders, validateHeaderBlock: Bool, validateContentLength: Bool, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect {
        do {
            // We can receive headers in the following states:
            //
            // - idle, when we are a server, in which case we are receiving request headers
            // - halfOpenLocalPeerIdle, in which case we are receiving either informational or final response headers
            // - halfOpenRemoteLocalIdle, in which case we are receiving trailers
            // - reservedRemote, in which case we are a client receiving either informational or final response headers
            // - fullyOpen, in which case we are receiving trailers
            // - halfClosedLocalPeerIdle, in which case we are receiving either informational or final headers
            //     (see the comment on halfClosedLocalPeerIdle for more)
            // - halfClosedLocalPeerActive, in which case we are receiving trailers
            //
            // In idle or reservedRemote we are opening the stream. In reservedRemote, halfClosedLocalPeerIdle, or halfClosedLocalPeerActive
            // we may be closing the stream. The keen-eyed may notice that reservedLocal may both open *and* close a stream. This is a bit awkward
            // for us, and requires a separate event.
            switch self.state {
            case .idle(.server, localWindow: let localWindow, remoteWindow: let remoteWindow):
                let targetState: State
                let remoteContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled

                if endStream {
                    try remoteContentLength.endOfStream()
                    targetState = .halfClosedRemoteLocalIdle(localWindow: localWindow)
                } else {
                    targetState = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow)
                }

                let targetEffect: StreamStateChange = .streamCreated(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow)))
                return self.processRequestHeaders(headers,
                                                  validateHeaderBlock: validateHeaderBlock,
                                                  targetState: targetState,
                                                  targetEffect: targetEffect)

            case .halfOpenLocalPeerIdle(localWindow: let localWindow, localContentLength: let localContentLength, remoteWindow: let remoteWindow):
                let targetState: State
                let remoteContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled

                if endStream {
                    try remoteContentLength.endOfStream()
                    targetState = .halfClosedRemoteLocalActive(localRole: .client, initiatedBy: .client, localContentLength: localContentLength, localWindow: localWindow)
                } else {
                    targetState = .fullyOpen(localRole: .client, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow)
                }

                return self.processResponseHeaders(headers,
                                                   validateHeaderBlock: validateHeaderBlock,
                                                   targetStateIfFinal: targetState,
                                                   targetEffectIfFinal: nil)

            case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteContentLength: let remoteContentLength, remoteWindow: _):
                try remoteContentLength.endOfStream()
                return self.processTrailers(headers,
                                            validateHeaderBlock: validateHeaderBlock,
                                            isEndStreamSet: endStream,
                                            targetState: .halfClosedRemoteLocalIdle(localWindow: localWindow),
                                            targetEffect: nil)

            case .reservedRemote(let remoteWindow):
                let targetState: State
                let targetEffect: StreamStateChange
                let remoteContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled

                if endStream {
                    try remoteContentLength.endOfStream()
                    targetState = .closed(reason: nil)
                    targetEffect = .streamCreatedAndClosed(.init(streamID: self.streamID))
                } else {
                    targetState = .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .server, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow)
                    targetEffect = .streamCreated(.init(streamID: self.streamID, localStreamWindowSize: nil, remoteStreamWindowSize: Int(remoteWindow)))
                }

                return self.processResponseHeaders(headers,
                                                   validateHeaderBlock: validateHeaderBlock,
                                                   targetStateIfFinal: targetState,
                                                   targetEffectIfFinal: targetEffect)

            case .fullyOpen(let localRole, localContentLength: let localContentLength, remoteContentLength: let remoteContentLength, localWindow: let localWindow, remoteWindow: _):
                try remoteContentLength.endOfStream()
                return self.processTrailers(headers,
                                            validateHeaderBlock: validateHeaderBlock,
                                            isEndStreamSet: endStream,
                                            targetState: .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: .client, localContentLength: localContentLength, localWindow: localWindow),
                                            targetEffect: nil)

            case .halfClosedLocalPeerIdle(let remoteWindow):
                let targetState: State
                let targetEffect: StreamStateChange?
                let remoteContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled

                if endStream {
                    try remoteContentLength.endOfStream()
                    targetState = .closed(reason: nil)
                    targetEffect = .streamClosed(.init(streamID: self.streamID, reason: nil))
                } else {
                    targetState = .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .client, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow)
                    targetEffect = nil
                }

                return self.processResponseHeaders(headers,
                                                   validateHeaderBlock: validateHeaderBlock,
                                                   targetStateIfFinal: targetState,
                                                   targetEffectIfFinal: targetEffect)

            case .halfClosedLocalPeerActive(localRole: _, initiatedBy: _, remoteContentLength: let remoteContentLength, remoteWindow: _):
                try remoteContentLength.endOfStream()
                return self.processTrailers(headers,
                                            validateHeaderBlock: validateHeaderBlock,
                                            isEndStreamSet: endStream,
                                            targetState: .closed(reason: nil),
                                            targetEffect: .streamClosed(.init(streamID: self.streamID, reason: nil)))

            // Receiving a HEADERS frame as an idle client, or on a closed stream, is a connection error
            // of type PROTOCOL_ERROR. In any other state, receiving a HEADERS frame is a stream error of
            // type PROTOCOL_ERROR.
            // (Authors note: I can find nothing in the RFC that actually states what kind of error is
            // triggered for HEADERS frames outside the valid states. So I just guessed here based on what
            // seems reasonable to me: specifically, if we have a stream to fail, fail it, otherwise treat
            // the error as connection scoped.)
            case .idle(.client, _, _), .closed:
                return .init(result: .connectionError(underlyingError: NIOHTTP2Errors.badStreamStateTransition(from: NIOHTTP2StreamState.get(self.state)), type: .protocolError), effect: nil)
            case .reservedLocal, .halfClosedRemoteLocalIdle, .halfClosedRemoteLocalActive:
                return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.badStreamStateTransition(from: NIOHTTP2StreamState.get(self.state)), type: .protocolError), effect: nil)
            }
        } catch let error where error is NIOHTTP2Errors.ContentLengthViolated {
            return .init(result: .streamError(streamID: self.streamID, underlyingError: error, type: .protocolError), effect: nil)
        } catch {
            preconditionFailure("Unexpected error: \(error)")
        }
    }