in Sources/NIOHTTP2/StreamStateMachine.swift [528:598]
mutating func sendData(contentLength: Int, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect {
do {
// We can send DATA frames in the following states:
//
// - halfOpenLocalPeerIdle, in which case we are a client sending request data before the server
// has sent its final response headers.
// - fullyOpen, where we could be either a client or a server using a fully bi-directional stream.
// - halfClosedRemoteLocalActive, where the remote peer has completed its data, but we have more to send.
//
// Valid data frames always have a stream effect, because they consume flow control windows.
switch self.state {
case .halfOpenLocalPeerIdle(localWindow: var localWindow, localContentLength: var localContentLength, remoteWindow: let remoteWindow):
try localWindow.consume(flowControlledBytes: flowControlledBytes)
try localContentLength.receivedDataChunk(length: contentLength)
let effect: StreamStateChange
if endStream {
try localContentLength.endOfStream()
self.state = .halfClosedLocalPeerIdle(remoteWindow: remoteWindow)
effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: nil, remoteStreamWindowSize: Int(remoteWindow)))
} else {
self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, localContentLength: localContentLength, remoteWindow: remoteWindow)
effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow)))
}
return .init(result: .succeed, effect: effect)
case .fullyOpen(let localRole, localContentLength: var localContentLength, remoteContentLength: let remoteContentLength, localWindow: var localWindow, remoteWindow: let remoteWindow):
try localWindow.consume(flowControlledBytes: flowControlledBytes)
try localContentLength.receivedDataChunk(length: contentLength)
let effect: StreamStateChange = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow)))
if endStream {
try localContentLength.endOfStream()
self.state = .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: .client, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow)
} else {
self.state = .fullyOpen(localRole: localRole, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow)
}
return .init(result: .succeed, effect: effect)
case .halfClosedRemoteLocalActive(let localRole, let initiatedBy, var localContentLength, var localWindow):
try localWindow.consume(flowControlledBytes: flowControlledBytes)
try localContentLength.receivedDataChunk(length: contentLength)
let effect: StreamStateChange
if endStream {
try localContentLength.endOfStream()
self.state = .closed(reason: nil)
effect = .streamClosed(.init(streamID: self.streamID, reason: nil))
} else {
self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: initiatedBy, localContentLength: localContentLength, localWindow: localWindow)
effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil))
}
return .init(result: .succeed, effect: effect)
// Sending a DATA frame outside any of these states is a stream error of type STREAM_CLOSED (RFC7540 § 6.1)
case .idle, .halfOpenRemoteLocalIdle, .reservedLocal, .reservedRemote, .halfClosedLocalPeerIdle,
.halfClosedLocalPeerActive, .halfClosedRemoteLocalIdle, .closed:
return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.badStreamStateTransition(from: NIOHTTP2StreamState.get(self.state)), type: .streamClosed), effect: nil)
}
} catch let error where error is NIOHTTP2Errors.FlowControlViolation {
return .init(result: .streamError(streamID: self.streamID, underlyingError: error, type: .flowControlError), 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)")
}
}