diff --git a/Sources/NIOHTTP2/ConnectionStateMachine/ConnectionStateMachine.swift b/Sources/NIOHTTP2/ConnectionStateMachine/ConnectionStateMachine.swift index 06612027..d8675a6c 100644 --- a/Sources/NIOHTTP2/ConnectionStateMachine/ConnectionStateMachine.swift +++ b/Sources/NIOHTTP2/ConnectionStateMachine/ConnectionStateMachine.swift @@ -66,12 +66,14 @@ struct HTTP2ConnectionStateMachine { private struct IdleConnectionState: ConnectionStateWithRole, ConnectionStateWithConfiguration { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState } /// The state required for a connection that has sent a connection preface. private struct PrefaceSentState: ConnectionStateWithRole, ConnectionStateWithConfiguration, MaySendFrames, HasLocalSettings, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var localSettings: HTTP2SettingsState var streamState: ConnectionStreamState var inboundFlowControlWindow: HTTP2FlowControlWindow @@ -88,6 +90,7 @@ struct HTTP2ConnectionStateMachine { init(fromIdle idleState: IdleConnectionState, localSettings settings: HTTP2SettingsState) { self.role = idleState.role self.headerBlockValidation = idleState.headerBlockValidation + self.contentLengthValidation = idleState.contentLengthValidation self.localSettings = settings self.streamState = ConnectionStreamState() @@ -100,6 +103,7 @@ struct HTTP2ConnectionStateMachine { private struct PrefaceReceivedState: ConnectionStateWithRole, ConnectionStateWithConfiguration, MayReceiveFrames, HasRemoteSettings, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var remoteSettings: HTTP2SettingsState var streamState: ConnectionStreamState var inboundFlowControlWindow: HTTP2FlowControlWindow @@ -116,6 +120,7 @@ struct HTTP2ConnectionStateMachine { init(fromIdle idleState: IdleConnectionState, remoteSettings settings: HTTP2SettingsState) { self.role = idleState.role self.headerBlockValidation = idleState.headerBlockValidation + self.contentLengthValidation = idleState.contentLengthValidation self.remoteSettings = settings self.streamState = ConnectionStreamState() @@ -128,6 +133,7 @@ struct HTTP2ConnectionStateMachine { private struct ActiveConnectionState: ConnectionStateWithRole, ConnectionStateWithConfiguration, MaySendFrames, MayReceiveFrames, HasLocalSettings, HasRemoteSettings, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var localSettings: HTTP2SettingsState var remoteSettings: HTTP2SettingsState var streamState: ConnectionStreamState @@ -145,6 +151,7 @@ struct HTTP2ConnectionStateMachine { init(fromPrefaceReceived state: PrefaceReceivedState, localSettings settings: HTTP2SettingsState) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.remoteSettings = state.remoteSettings self.streamState = state.streamState self.localSettings = settings @@ -156,6 +163,7 @@ struct HTTP2ConnectionStateMachine { init(fromPrefaceSent state: PrefaceSentState, remoteSettings settings: HTTP2SettingsState) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.localSettings = state.localSettings self.streamState = state.streamState self.remoteSettings = settings @@ -170,6 +178,7 @@ struct HTTP2ConnectionStateMachine { private struct QuiescingPrefaceReceivedState: ConnectionStateWithRole, ConnectionStateWithConfiguration, RemotelyQuiescingState, MayReceiveFrames, HasRemoteSettings, QuiescingState, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var remoteSettings: HTTP2SettingsState var streamState: ConnectionStreamState var inboundFlowControlWindow: HTTP2FlowControlWindow @@ -192,6 +201,7 @@ struct HTTP2ConnectionStateMachine { init(fromPrefaceReceived state: PrefaceReceivedState, lastStreamID: HTTP2StreamID) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.remoteSettings = state.remoteSettings self.streamState = state.streamState self.inboundFlowControlWindow = state.inboundFlowControlWindow @@ -206,6 +216,7 @@ struct HTTP2ConnectionStateMachine { private struct QuiescingPrefaceSentState: ConnectionStateWithRole, ConnectionStateWithConfiguration, LocallyQuiescingState, MaySendFrames, HasLocalSettings, QuiescingState, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var localSettings: HTTP2SettingsState var streamState: ConnectionStreamState var inboundFlowControlWindow: HTTP2FlowControlWindow @@ -228,6 +239,7 @@ struct HTTP2ConnectionStateMachine { init(fromPrefaceSent state: PrefaceSentState, lastStreamID: HTTP2StreamID) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.localSettings = state.localSettings self.streamState = state.streamState self.inboundFlowControlWindow = state.inboundFlowControlWindow @@ -241,6 +253,7 @@ struct HTTP2ConnectionStateMachine { private struct RemotelyQuiescedState: ConnectionStateWithRole, ConnectionStateWithConfiguration, RemotelyQuiescingState, MayReceiveFrames, MaySendFrames, HasLocalSettings, HasRemoteSettings, QuiescingState, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var localSettings: HTTP2SettingsState var remoteSettings: HTTP2SettingsState var streamState: ConnectionStreamState @@ -264,6 +277,7 @@ struct HTTP2ConnectionStateMachine { init(fromActive state: ActiveConnectionState, lastLocalStreamID streamID: HTTP2StreamID) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.localSettings = state.localSettings self.remoteSettings = state.remoteSettings self.streamState = state.streamState @@ -275,6 +289,7 @@ struct HTTP2ConnectionStateMachine { init(fromQuiescingPrefaceReceived state: QuiescingPrefaceReceivedState, localSettings settings: HTTP2SettingsState) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.remoteSettings = state.remoteSettings self.localSettings = settings self.streamState = state.streamState @@ -288,6 +303,7 @@ struct HTTP2ConnectionStateMachine { private struct LocallyQuiescedState: ConnectionStateWithRole, ConnectionStateWithConfiguration, LocallyQuiescingState, MaySendFrames, MayReceiveFrames, HasLocalSettings, HasRemoteSettings, QuiescingState, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var localSettings: HTTP2SettingsState var remoteSettings: HTTP2SettingsState var streamState: ConnectionStreamState @@ -311,6 +327,7 @@ struct HTTP2ConnectionStateMachine { init(fromActive state: ActiveConnectionState, lastRemoteStreamID streamID: HTTP2StreamID) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.localSettings = state.localSettings self.remoteSettings = state.remoteSettings self.streamState = state.streamState @@ -322,6 +339,7 @@ struct HTTP2ConnectionStateMachine { init(fromQuiescingPrefaceSent state: QuiescingPrefaceSentState, remoteSettings settings: HTTP2SettingsState) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.localSettings = state.localSettings self.remoteSettings = settings self.streamState = state.streamState @@ -335,6 +353,7 @@ struct HTTP2ConnectionStateMachine { private struct BothQuiescingState: ConnectionStateWithRole, ConnectionStateWithConfiguration, LocallyQuiescingState, RemotelyQuiescingState, MaySendFrames, MayReceiveFrames, HasLocalSettings, HasRemoteSettings, QuiescingState, HasFlowControlWindows { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var localSettings: HTTP2SettingsState var remoteSettings: HTTP2SettingsState var streamState: ConnectionStreamState @@ -358,6 +377,7 @@ struct HTTP2ConnectionStateMachine { init(fromRemotelyQuiesced state: RemotelyQuiescedState, lastRemoteStreamID streamID: HTTP2StreamID) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.localSettings = state.localSettings self.remoteSettings = state.remoteSettings self.streamState = state.streamState @@ -371,6 +391,7 @@ struct HTTP2ConnectionStateMachine { init(fromLocallyQuiesced state: LocallyQuiescedState, lastLocalStreamID streamID: HTTP2StreamID) { self.role = state.role self.headerBlockValidation = state.headerBlockValidation + self.contentLengthValidation = state.contentLengthValidation self.localSettings = state.localSettings self.remoteSettings = state.remoteSettings self.streamState = state.streamState @@ -386,6 +407,7 @@ struct HTTP2ConnectionStateMachine { private struct FullyQuiescedState: ConnectionStateWithRole, ConnectionStateWithConfiguration, LocallyQuiescingState, RemotelyQuiescingState, SendAndReceiveGoawayState { let role: ConnectionRole var headerBlockValidation: ValidationState + var contentLengthValidation: ValidationState var streamState: ConnectionStreamState var lastLocalStreamID: HTTP2StreamID var lastRemoteStreamID: HTTP2StreamID @@ -393,6 +415,7 @@ struct HTTP2ConnectionStateMachine { init(previousState: PreviousState) { self.role = previousState.role self.headerBlockValidation = previousState.headerBlockValidation + self.contentLengthValidation = previousState.contentLengthValidation self.streamState = previousState.streamState self.lastLocalStreamID = previousState.lastLocalStreamID self.lastRemoteStreamID = previousState.lastRemoteStreamID @@ -401,6 +424,7 @@ struct HTTP2ConnectionStateMachine { init(previousState: PreviousState) { self.role = previousState.role self.headerBlockValidation = previousState.headerBlockValidation + self.contentLengthValidation = previousState.contentLengthValidation self.streamState = previousState.streamState self.lastLocalStreamID = .maxID self.lastRemoteStreamID = previousState.lastRemoteStreamID @@ -409,6 +433,7 @@ struct HTTP2ConnectionStateMachine { init(previousState: PreviousState) { self.role = previousState.role self.headerBlockValidation = previousState.headerBlockValidation + self.contentLengthValidation = previousState.contentLengthValidation self.streamState = previousState.streamState self.lastLocalStreamID = previousState.lastLocalStreamID self.lastRemoteStreamID = .maxID @@ -478,8 +503,8 @@ struct HTTP2ConnectionStateMachine { private var state: State - init(role: ConnectionRole, headerBlockValidation: ValidationState = .enabled) { - self.state = .idle(.init(role: role, headerBlockValidation: headerBlockValidation)) + init(role: ConnectionRole, headerBlockValidation: ValidationState = .enabled, contentLengthValidation: ValidationState = .enabled) { + self.state = .idle(.init(role: role, headerBlockValidation: headerBlockValidation, contentLengthValidation: contentLengthValidation)) } /// Whether this connection is closed. @@ -668,38 +693,38 @@ extension HTTP2ConnectionStateMachine { } /// Called when a DATA frame has been received. - mutating func receiveData(streamID: HTTP2StreamID, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { + mutating func receiveData(streamID: HTTP2StreamID, contentLength: Int, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { switch self.state { case .prefaceReceived(var state): - let result = state.receiveData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.receiveData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .prefaceReceived(state) return result case .active(var state): - let result = state.receiveData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.receiveData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .active(state) return result case .locallyQuiesced(var state): - let result = state.receiveData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.receiveData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .locallyQuiesced(state) self.closeIfNeeded(state) return result case .remotelyQuiesced(var state): - let result = state.receiveData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.receiveData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .remotelyQuiesced(state) self.closeIfNeeded(state) return result case .bothQuiescing(var state): - let result = state.receiveData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.receiveData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .bothQuiescing(state) self.closeIfNeeded(state) return result case .quiescingPrefaceReceived(var state): - let result = state.receiveData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.receiveData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .quiescingPrefaceReceived(state) return result @@ -713,38 +738,38 @@ extension HTTP2ConnectionStateMachine { } /// Called when a user is trying to send a DATA frame. - mutating func sendData(streamID: HTTP2StreamID, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { + mutating func sendData(streamID: HTTP2StreamID, contentLength: Int, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { switch self.state { case .prefaceSent(var state): - let result = state.sendData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.sendData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .prefaceSent(state) return result case .active(var state): - let result = state.sendData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.sendData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .active(state) return result case .locallyQuiesced(var state): - let result = state.sendData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.sendData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .locallyQuiesced(state) self.closeIfNeeded(state) return result case .remotelyQuiesced(var state): - let result = state.sendData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.sendData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .remotelyQuiesced(state) self.closeIfNeeded(state) return result case .bothQuiescing(var state): - let result = state.sendData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.sendData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .bothQuiescing(state) self.closeIfNeeded(state) return result case .quiescingPrefaceSent(var state): - let result = state.sendData(streamID: streamID, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + let result = state.sendData(streamID: streamID, contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) self.state = .quiescingPrefaceSent(state) return result @@ -1388,4 +1413,6 @@ extension ConnectionStateWithRole { /// A simple protocol that provides helpers that apply to all connection states that have configuration. private protocol ConnectionStateWithConfiguration { var headerBlockValidation: HTTP2ConnectionStateMachine.ValidationState { get } + + var contentLengthValidation: HTTP2ConnectionStateMachine.ValidationState { get} } diff --git a/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingDataState.swift b/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingDataState.swift index e1a3a42b..fb7e1899 100644 --- a/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingDataState.swift +++ b/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingDataState.swift @@ -24,7 +24,7 @@ protocol ReceivingDataState: HasFlowControlWindows { extension ReceivingDataState { /// Called to receive a DATA frame. - mutating func receiveData(streamID: HTTP2StreamID, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { + mutating func receiveData(streamID: HTTP2StreamID, contentLength: Int, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { do { try self.inboundFlowControlWindow.consume(flowControlledBytes: flowControlledBytes) } catch let error where error is NIOHTTP2Errors.FlowControlViolation { @@ -34,7 +34,7 @@ extension ReceivingDataState { } let result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: true) { - $0.receiveData(flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + $0.receiveData(contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) } // We need to be a bit careful here. The backing code may have triggered either an ignoreFrame or streamError. While both of these diff --git a/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingHeadersState.swift b/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingHeadersState.swift index 137d2065..bd9b6f68 100644 --- a/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingHeadersState.swift +++ b/Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingHeadersState.swift @@ -22,6 +22,8 @@ protocol ReceivingHeadersState: HasFlowControlWindows { var headerBlockValidation: HTTP2ConnectionStateMachine.ValidationState { get } + var contentLengthValidation: HTTP2ConnectionStateMachine.ValidationState { get } + var streamState: ConnectionStreamState { get set } var localInitialWindowSize: UInt32 { get } @@ -34,11 +36,12 @@ extension ReceivingHeadersState { mutating func receiveHeaders(streamID: HTTP2StreamID, headers: HPACKHeaders, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { let result: StateMachineResultWithStreamEffect let validateHeaderBlock = self.headerBlockValidation == .enabled + let validateContentLength = self.contentLengthValidation == .enabled if self.role == .server && streamID.mayBeInitiatedBy(.client) { do { result = try self.streamState.modifyStreamStateCreateIfNeeded(streamID: streamID, localRole: .server, localInitialWindowSize: self.localInitialWindowSize, remoteInitialWindowSize: self.remoteInitialWindowSize) { - $0.receiveHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, isEndStreamSet: endStream) + $0.receiveHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, validateContentLength: validateContentLength, isEndStreamSet: endStream) } } catch { return StateMachineResultWithEffect(result: .connectionError(underlyingError: error, type: .protocolError), effect: nil) @@ -46,7 +49,7 @@ extension ReceivingHeadersState { } else { // HEADERS cannot create streams for servers, so this must be for a stream we already know about. result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: true) { - $0.receiveHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, isEndStreamSet: endStream) + $0.receiveHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, validateContentLength: validateContentLength, isEndStreamSet: endStream) } } @@ -63,6 +66,7 @@ extension ReceivingHeadersState where Self: LocallyQuiescingState { /// new streams. mutating func receiveHeaders(streamID: HTTP2StreamID, headers: HPACKHeaders, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { let validateHeaderBlock = self.headerBlockValidation == .enabled + let validateContentLength = self.contentLengthValidation == .enabled if streamID.mayBeInitiatedBy(.client) && streamID > self.lastRemoteStreamID { return StateMachineResultWithEffect(result: .ignoreFrame, effect: nil) @@ -70,7 +74,7 @@ extension ReceivingHeadersState where Self: LocallyQuiescingState { // At this stage we've quiesced, so the remote peer is not allowed to create new streams. let result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: true) { - $0.receiveHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, isEndStreamSet: endStream) + $0.receiveHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, validateContentLength: validateContentLength, isEndStreamSet: endStream) } return StateMachineResultWithEffect(result, connectionState: self) } diff --git a/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingDataState.swift b/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingDataState.swift index 851a93d5..0e1899ba 100644 --- a/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingDataState.swift +++ b/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingDataState.swift @@ -24,7 +24,7 @@ protocol SendingDataState: HasFlowControlWindows { extension SendingDataState { /// Called to send a DATA frame. - mutating func sendData(streamID: HTTP2StreamID, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { + mutating func sendData(streamID: HTTP2StreamID, contentLength: Int, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { do { try self.outboundFlowControlWindow.consume(flowControlledBytes: flowControlledBytes) } catch let error where error is NIOHTTP2Errors.FlowControlViolation { @@ -34,7 +34,7 @@ extension SendingDataState { } let result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: false) { - $0.sendData(flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) + $0.sendData(contentLength: contentLength, flowControlledBytes: flowControlledBytes, isEndStreamSet: endStream) } return StateMachineResultWithEffect(result, connectionState: self) } diff --git a/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingHeadersState.swift b/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingHeadersState.swift index 5063af09..89ee8f0f 100644 --- a/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingHeadersState.swift +++ b/Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingHeadersState.swift @@ -23,6 +23,8 @@ protocol SendingHeadersState: HasFlowControlWindows { var headerBlockValidation: HTTP2ConnectionStateMachine.ValidationState { get } + var contentLengthValidation: HTTP2ConnectionStateMachine.ValidationState { get } + var streamState: ConnectionStreamState { get set } var localInitialWindowSize: UInt32 { get } @@ -35,6 +37,7 @@ extension SendingHeadersState { mutating func sendHeaders(streamID: HTTP2StreamID, headers: HPACKHeaders, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { let result: StateMachineResultWithStreamEffect let validateHeaderBlock = self.headerBlockValidation == .enabled + let validateContentLength = self.contentLengthValidation == .enabled if self.role == .client && streamID.mayBeInitiatedBy(.client) { do { @@ -42,7 +45,7 @@ extension SendingHeadersState { localRole: .client, localInitialWindowSize: self.localInitialWindowSize, remoteInitialWindowSize: self.remoteInitialWindowSize) { - $0.sendHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, isEndStreamSet: endStream) + $0.sendHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, validateContentLength: validateContentLength, isEndStreamSet: endStream) } } catch { return StateMachineResultWithEffect(result: .connectionError(underlyingError: error, type: .protocolError), effect: nil) @@ -50,7 +53,7 @@ extension SendingHeadersState { } else { // HEADERS cannot create streams for servers, so this must be for a stream we already know about. result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: false) { - $0.sendHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, isEndStreamSet: endStream) + $0.sendHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, validateContentLength: validateContentLength, isEndStreamSet: endStream) } } @@ -64,9 +67,10 @@ extension SendingHeadersState where Self: RemotelyQuiescingState { /// be modifying an existing one. mutating func sendHeaders(streamID: HTTP2StreamID, headers: HPACKHeaders, isEndStreamSet endStream: Bool) -> StateMachineResultWithEffect { let validateHeaderBlock = self.headerBlockValidation == .enabled + let validateContentLength = self.contentLengthValidation == .enabled let result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: false) { - $0.sendHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, isEndStreamSet: endStream) + $0.sendHeaders(headers: headers, validateHeaderBlock: validateHeaderBlock, validateContentLength: validateContentLength, isEndStreamSet: endStream) } return StateMachineResultWithEffect(result, connectionState: self) } diff --git a/Sources/NIOHTTP2/ContentLengthVerifier.swift b/Sources/NIOHTTP2/ContentLengthVerifier.swift new file mode 100644 index 00000000..ebbc0058 --- /dev/null +++ b/Sources/NIOHTTP2/ContentLengthVerifier.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import NIOHPACK + +/// An object that verifies that a content-length field on a HTTP request or +/// response is respected. +struct ContentLengthVerifier { + private var expectedContentLength: Int? +} + +extension ContentLengthVerifier { + /// A chunk of data has been received from the network. + mutating func receivedDataChunk(length: Int) throws { + assert(length >= 0, "received data chunks must be positive") + + // If there was no content-length, don't keep track. + guard let expectedContentLength = self.expectedContentLength else { + return + } + + let newContentLength = expectedContentLength - length + if newContentLength < 0 { + throw NIOHTTP2Errors.ContentLengthViolated() + } + self.expectedContentLength = newContentLength + } + + /// Called when end of stream has been received. Validates that the complete body was received. + func endOfStream() throws { + switch self.expectedContentLength { + case .none, .some(0): + break + default: + throw NIOHTTP2Errors.ContentLengthViolated() + } + } +} + +extension ContentLengthVerifier { + internal init(_ headers: HPACKHeaders) { + self.expectedContentLength = headers[canonicalForm: "content-length"].first.flatMap { Int($0, radix: 10) } + } + + /// The verifier for use when content length verification is disabled. + internal static var disabled: ContentLengthVerifier { + return ContentLengthVerifier(expectedContentLength: nil) + } +} + +extension ContentLengthVerifier: CustomStringConvertible { + var description: String { + return "ContentLengthVerifier(length: \(String(describing: self.expectedContentLength)))" + } +} diff --git a/Sources/NIOHTTP2/HTTP2ChannelHandler.swift b/Sources/NIOHTTP2/HTTP2ChannelHandler.swift index 10f81aca..c5e7b337 100644 --- a/Sources/NIOHTTP2/HTTP2ChannelHandler.swift +++ b/Sources/NIOHTTP2/HTTP2ChannelHandler.swift @@ -95,8 +95,8 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler { case disabled } - public init(mode: ParserMode, initialSettings: HTTP2Settings = nioDefaultSettings, headerBlockValidation: ValidationState = .enabled) { - self.stateMachine = HTTP2ConnectionStateMachine(role: .init(mode), headerBlockValidation: .init(headerBlockValidation)) + public init(mode: ParserMode, initialSettings: HTTP2Settings = nioDefaultSettings, headerBlockValidation: ValidationState = .enabled, contentLengthValidation: ValidationState = .enabled) { + self.stateMachine = HTTP2ConnectionStateMachine(role: .init(mode), headerBlockValidation: .init(headerBlockValidation), contentLengthValidation: .init(contentLengthValidation)) self.mode = mode self.initialSettings = initialSettings self.outboundBuffer = CompoundOutboundBuffer(mode: mode, initialMaxOutboundStreams: 100) @@ -220,7 +220,7 @@ extension NIOHTTP2Handler { // TODO(cory): Implement fatalError("Currently some frames are unhandled.") case .data(let dataBody): - result = self.stateMachine.receiveData(streamID: frame.streamID, flowControlledBytes: flowControlledLength, isEndStreamSet: dataBody.endStream) + result = self.stateMachine.receiveData(streamID: frame.streamID, contentLength: dataBody.data.readableBytes, flowControlledBytes: flowControlledLength, isEndStreamSet: dataBody.endStream) case .goAway(let lastStreamID, _, _): result = self.stateMachine.receiveGoaway(lastStreamID: lastStreamID) case .headers(let headerBody): @@ -368,7 +368,7 @@ extension NIOHTTP2Handler { fatalError("Currently some frames are unhandled.") case .data(let data): // TODO(cory): Correctly account for padding data. - result = self.stateMachine.sendData(streamID: frame.streamID, flowControlledBytes: data.data.readableBytes, isEndStreamSet: data.endStream) + result = self.stateMachine.sendData(streamID: frame.streamID, contentLength: data.data.readableBytes, flowControlledBytes: data.data.readableBytes, isEndStreamSet: data.endStream) case .goAway(let lastStreamID, _, _): result = self.stateMachine.sendGoaway(lastStreamID: lastStreamID) case .headers(let headerContent): diff --git a/Sources/NIOHTTP2/HTTP2Error.swift b/Sources/NIOHTTP2/HTTP2Error.swift index 8f2ab795..c08e08c2 100644 --- a/Sources/NIOHTTP2/HTTP2Error.swift +++ b/Sources/NIOHTTP2/HTTP2Error.swift @@ -264,6 +264,11 @@ public enum NIOHTTP2Errors { self.value = value } } + + /// A request or response has violated the expected content length, either exceeding or falling beneath it. + public struct ContentLengthViolated: NIOHTTP2Error { + public init() { } + } } diff --git a/Sources/NIOHTTP2/StreamStateMachine.swift b/Sources/NIOHTTP2/StreamStateMachine.swift index eff8d806..7c4f16ca 100644 --- a/Sources/NIOHTTP2/StreamStateMachine.swift +++ b/Sources/NIOHTTP2/StreamStateMachine.swift @@ -122,19 +122,19 @@ struct HTTP2StreamStateMachine { /// from the remote peer in this state, however. If we are in this state, we must be a client: servers /// initiating streams put them into reservedLocal, and then sending HEADERS transfers them directly to /// halfClosedRemoteLocalActive. - case halfOpenLocalPeerIdle(localWindow: HTTP2FlowControlWindow, remoteWindow: HTTP2FlowControlWindow) + case halfOpenLocalPeerIdle(localWindow: HTTP2FlowControlWindow, localContentLength: ContentLengthVerifier, remoteWindow: HTTP2FlowControlWindow) /// This state does not exist on the diagram above. It encodes the notion that this stream has /// been opened by the remote user sending a HEADERS frame, but we have not yet sent our HEADERS frame /// in response. If we are in this state, we must be a server: clients receiving streams that were opened /// by servers put them into reservedRemote, and then receiving the response HEADERS transitions them directly /// to halfClosedLocalPeerActive. - case halfOpenRemoteLocalIdle(localWindow: HTTP2FlowControlWindow, remoteWindow: HTTP2FlowControlWindow) + case halfOpenRemoteLocalIdle(localWindow: HTTP2FlowControlWindow, remoteContentLength: ContentLengthVerifier, remoteWindow: HTTP2FlowControlWindow) /// This state is when both peers have sent a HEADERS frame, but neither has sent a frame with END_STREAM /// set. Both peers may exchange data fully. In this state we keep track of whether we are a client or a /// server, as only servers may push new streams. - case fullyOpen(localRole: StreamRole, localWindow: HTTP2FlowControlWindow, remoteWindow: HTTP2FlowControlWindow) + case fullyOpen(localRole: StreamRole, localContentLength: ContentLengthVerifier, remoteContentLength: ContentLengthVerifier, localWindow: HTTP2FlowControlWindow, remoteWindow: HTTP2FlowControlWindow) /// In the halfClosedLocalPeerIdle state, the local user has sent END_STREAM, but the remote peer has not /// yet sent its HEADERS frame. This mostly happens on GET requests, when END_HEADERS and END_STREAM are @@ -159,7 +159,7 @@ struct HTTP2StreamStateMachine { /// by us: if we're a server, it was initiated by the peer. This is because server-initiated streams never /// enter fullyOpen, as the client is never actually open on those streams. If we came here from /// reservedRemote, this stream must be peer initiated, as this is the client side of a pushed stream. - case halfClosedLocalPeerActive(localRole: StreamRole, initiatedBy: StreamRole, remoteWindow: HTTP2FlowControlWindow) + case halfClosedLocalPeerActive(localRole: StreamRole, initiatedBy: StreamRole, remoteContentLength: ContentLengthVerifier, remoteWindow: HTTP2FlowControlWindow) /// In the halfClosedRemoteLocalIdle state, the remote peer has sent END_STREAM, but the local user has not /// yet sent its HEADERS frame. This mostly happens on GET requests, when END_HEADERS and END_STREAM are @@ -184,7 +184,7 @@ struct HTTP2StreamStateMachine { /// by us: if we're a server, it was initiated by the peer. This is because server-initiated streams never /// enter fullyOpen, as the client is never actually open on those streams. If we came here from /// reservedLocal, this stream must be initiated by us, as this is the server side of a pushed stream. - case halfClosedRemoteLocalActive(localRole: StreamRole, initiatedBy: StreamRole, localWindow: HTTP2FlowControlWindow) + case halfClosedRemoteLocalActive(localRole: StreamRole, initiatedBy: StreamRole, localContentLength: ContentLengthVerifier, localWindow: HTTP2FlowControlWindow) /// Both peers have sent their END_STREAM flags, and the stream is closed. In this stage no further data /// may be exchanged. @@ -265,206 +265,268 @@ extension HTTP2StreamStateMachine { /// it meets the requirements of RFC 7540 for containing a well-formed header block, and additionally /// checks whether the value of the end stream bit is acceptable. If all checks pass, transitions the /// state to the appropriate next entry. - mutating func sendHeaders(headers: HPACKHeaders, validateHeaderBlock: Bool, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect { - // We can send headers in the following states: - // - // - idle, when we are a client, in which case we are sending our request headers - // - halfOpenRemoteLocalIdle, in which case we are a server sending either informational or final headers - // - halfOpenLocalPeerIdle, in which case we are a client sending trailers - // - reservedLocal, in which case we are a server sending either informational or final headers - // - fullyOpen, in which case we are sending trailers - // - halfClosedRemoteLocalIdle, in which case we area server sending either informational or final headers - // (see the comment on halfClosedRemoteLocalIdle for more) - // - halfClosedRemoteLocalActive, in which case we are sending trailers - // - // In idle or reservedLocal we are opening the stream. In reservedLocal, halfClosedRemoteLocalIdle, or halfClosedremoteLocalActive - // 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(.client, localWindow: let localWindow, remoteWindow: let remoteWindow): - let targetState: State = endStream ? .halfClosedLocalPeerIdle(remoteWindow: remoteWindow) : .halfOpenLocalPeerIdle(localWindow: localWindow, 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 .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteWindow: let remoteWindow): - let targetState: State = endStream ? .halfClosedLocalPeerActive(localRole: .server, initiatedBy: .client, remoteWindow: remoteWindow) : .fullyOpen(localRole: .server, localWindow: localWindow, remoteWindow: remoteWindow) - return self.processResponseHeaders(headers, - validateHeaderBlock: validateHeaderBlock, - targetStateIfFinal: targetState, - targetEffectIfFinal: nil) - - case .halfOpenLocalPeerIdle(localWindow: _, remoteWindow: let remoteWindow): - return self.processTrailers(headers, - validateHeaderBlock: validateHeaderBlock, - isEndStreamSet: endStream, - targetState: .halfClosedLocalPeerIdle(remoteWindow: remoteWindow), - targetEffect: nil) - - case .reservedLocal(let localWindow): - let targetState: State - let targetEffect: StreamStateChange - - if endStream { - targetState = .closed(reason: nil) - targetEffect = .streamCreatedAndClosed(.init(streamID: self.streamID)) - } else { - targetState = .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .server, localWindow: localWindow) - targetEffect = .streamCreated(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) - } + mutating func sendHeaders(headers: HPACKHeaders, validateHeaderBlock: Bool, validateContentLength: Bool, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect { + do { + // We can send headers in the following states: + // + // - idle, when we are a client, in which case we are sending our request headers + // - halfOpenRemoteLocalIdle, in which case we are a server sending either informational or final headers + // - halfOpenLocalPeerIdle, in which case we are a client sending trailers + // - reservedLocal, in which case we are a server sending either informational or final headers + // - fullyOpen, in which case we are sending trailers + // - halfClosedRemoteLocalIdle, in which case we area server sending either informational or final headers + // (see the comment on halfClosedRemoteLocalIdle for more) + // - halfClosedRemoteLocalActive, in which case we are sending trailers + // + // In idle or reservedLocal we are opening the stream. In reservedLocal, halfClosedRemoteLocalIdle, or halfClosedremoteLocalActive + // 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(.client, localWindow: let localWindow, remoteWindow: let remoteWindow): + let targetState: State + let localContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled + + if endStream { + try localContentLength.endOfStream() + targetState = .halfClosedLocalPeerIdle(remoteWindow: remoteWindow) + } else { + targetState = .halfOpenLocalPeerIdle(localWindow: localWindow, localContentLength: localContentLength, 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 .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteContentLength: let remoteContentLength, remoteWindow: let remoteWindow): + let targetState: State + let localContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled - return self.processResponseHeaders(headers, - validateHeaderBlock: validateHeaderBlock, - targetStateIfFinal: targetState, - targetEffectIfFinal: targetEffect) - - case .fullyOpen(let localRole, localWindow: _, remoteWindow: let remoteWindow): - return self.processTrailers(headers, - validateHeaderBlock: validateHeaderBlock, - isEndStreamSet: endStream, - targetState: .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: .client, remoteWindow: remoteWindow), - targetEffect: nil) - - case .halfClosedRemoteLocalIdle(let localWindow): - let targetState: State - let targetEffect: StreamStateChange? - - if endStream { - targetState = .closed(reason: nil) - targetEffect = .streamClosed(.init(streamID: self.streamID, reason: nil)) - } else { - targetState = .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .client, localWindow: localWindow) - targetEffect = nil + if endStream { + try localContentLength.endOfStream() + targetState = .halfClosedLocalPeerActive(localRole: .server, initiatedBy: .client, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) + } else { + targetState = .fullyOpen(localRole: .server, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow) + } + + return self.processResponseHeaders(headers, + validateHeaderBlock: validateHeaderBlock, + targetStateIfFinal: targetState, + targetEffectIfFinal: nil) + + case .halfOpenLocalPeerIdle(localWindow: _, localContentLength: let localContentLength, remoteWindow: let remoteWindow): + try localContentLength.endOfStream() + return self.processTrailers(headers, + validateHeaderBlock: validateHeaderBlock, + isEndStreamSet: endStream, + targetState: .halfClosedLocalPeerIdle(remoteWindow: remoteWindow), + targetEffect: nil) + + case .reservedLocal(let localWindow): + let targetState: State + let targetEffect: StreamStateChange + let localContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled + + if endStream { + try localContentLength.endOfStream() + targetState = .closed(reason: nil) + targetEffect = .streamCreatedAndClosed(.init(streamID: self.streamID)) + } else { + targetState = .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .server, localContentLength: localContentLength, localWindow: localWindow) + targetEffect = .streamCreated(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) + } + + return self.processResponseHeaders(headers, + validateHeaderBlock: validateHeaderBlock, + targetStateIfFinal: targetState, + targetEffectIfFinal: targetEffect) + + case .fullyOpen(let localRole, localContentLength: let localContentLength, remoteContentLength: let remoteContentLength, localWindow: _, remoteWindow: let remoteWindow): + try localContentLength.endOfStream() + return self.processTrailers(headers, + validateHeaderBlock: validateHeaderBlock, + isEndStreamSet: endStream, + targetState: .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: .client, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow), + targetEffect: nil) + + case .halfClosedRemoteLocalIdle(let localWindow): + let targetState: State + let targetEffect: StreamStateChange? + let localContentLength = validateContentLength ? ContentLengthVerifier(headers) : .disabled + + if endStream { + try localContentLength.endOfStream() + targetState = .closed(reason: nil) + targetEffect = .streamClosed(.init(streamID: self.streamID, reason: nil)) + } else { + targetState = .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .client, localContentLength: localContentLength, localWindow: localWindow) + targetEffect = nil + } + return self.processResponseHeaders(headers, + validateHeaderBlock: validateHeaderBlock, + targetStateIfFinal: targetState, + targetEffectIfFinal: targetEffect) + + case .halfClosedRemoteLocalActive(localRole: _, initiatedBy: _, localContentLength: let localContentLength, localWindow: _): + try localContentLength.endOfStream() + return self.processTrailers(headers, + validateHeaderBlock: validateHeaderBlock, + isEndStreamSet: endStream, + targetState: .closed(reason: nil), + targetEffect: .streamClosed(.init(streamID: self.streamID, reason: nil))) + + // Sending a HEADERS frame as an idle server, or on a closed stream, is a connection error + // of type PROTOCOL_ERROR. In any other state, sending 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(.server, _, _), .closed: + return .init(result: .connectionError(underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), type: .protocolError), effect: nil) + case .reservedRemote, .halfClosedLocalPeerIdle, .halfClosedLocalPeerActive: + return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), type: .protocolError), effect: nil) } - return self.processResponseHeaders(headers, - validateHeaderBlock: validateHeaderBlock, - targetStateIfFinal: targetState, - targetEffectIfFinal: targetEffect) - - case .halfClosedRemoteLocalActive: - return self.processTrailers(headers, - validateHeaderBlock: validateHeaderBlock, - isEndStreamSet: endStream, - targetState: .closed(reason: nil), - targetEffect: .streamClosed(.init(streamID: self.streamID, reason: nil))) - - // Sending a HEADERS frame as an idle server, or on a closed stream, is a connection error - // of type PROTOCOL_ERROR. In any other state, sending 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(.server, _, _), .closed: - return .init(result: .connectionError(underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), type: .protocolError), effect: nil) - case .reservedRemote, .halfClosedLocalPeerIdle, .halfClosedLocalPeerActive: - return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), 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)") } } - mutating func receiveHeaders(headers: HPACKHeaders, validateHeaderBlock: Bool, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect { - // 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 = endStream ? .halfClosedRemoteLocalIdle(localWindow: localWindow) : .halfOpenRemoteLocalIdle(localWindow: localWindow, 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, remoteWindow: let remoteWindow): - let targetState: State = endStream ? .halfClosedRemoteLocalActive(localRole: .client,initiatedBy: .client, localWindow: localWindow) : .fullyOpen(localRole: .client, localWindow: localWindow, remoteWindow: remoteWindow) - return self.processResponseHeaders(headers, - validateHeaderBlock: validateHeaderBlock, - targetStateIfFinal: targetState, - targetEffectIfFinal: nil) - - case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteWindow: _): - return self.processTrailers(headers, - validateHeaderBlock: validateHeaderBlock, - isEndStreamSet: endStream, - targetState: .halfClosedRemoteLocalIdle(localWindow: localWindow), - targetEffect: nil) - - case .reservedRemote(let remoteWindow): - let targetState: State - let targetEffect: StreamStateChange - - if endStream { - targetState = .closed(reason: nil) - targetEffect = .streamCreatedAndClosed(.init(streamID: self.streamID)) - } else { - targetState = .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .server, remoteWindow: remoteWindow) - targetEffect = .streamCreated(.init(streamID: self.streamID, localStreamWindowSize: nil, remoteStreamWindowSize: Int(remoteWindow))) - } + 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 - return self.processResponseHeaders(headers, - validateHeaderBlock: validateHeaderBlock, - targetStateIfFinal: targetState, - targetEffectIfFinal: targetEffect) - - case .fullyOpen(let localRole, localWindow: let localWindow, remoteWindow: _): - return self.processTrailers(headers, - validateHeaderBlock: validateHeaderBlock, - isEndStreamSet: endStream, - targetState: .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: .client, localWindow: localWindow), - targetEffect: nil) - - case .halfClosedLocalPeerIdle(let remoteWindow): - let targetState: State - let targetEffect: StreamStateChange? - - if endStream { - targetState = .closed(reason: nil) - targetEffect = .streamClosed(.init(streamID: self.streamID, reason: nil)) - } else { - targetState = .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .client, remoteWindow: remoteWindow) - targetEffect = nil - } + if endStream { + try remoteContentLength.endOfStream() + targetState = .halfClosedRemoteLocalIdle(localWindow: localWindow) + } else { + targetState = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) + } - return self.processResponseHeaders(headers, - validateHeaderBlock: validateHeaderBlock, - targetStateIfFinal: targetState, - targetEffectIfFinal: targetEffect) - - case .halfClosedLocalPeerActive: - 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(), type: .protocolError), effect: nil) - case .reservedLocal, .halfClosedRemoteLocalIdle, .halfClosedRemoteLocalActive: - return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), type: .protocolError), effect: nil) + 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(), type: .protocolError), effect: nil) + case .reservedLocal, .halfClosedRemoteLocalIdle, .halfClosedRemoteLocalActive: + return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), 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)") } } - mutating func sendData(flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect { + mutating func sendData(contentLength: Int, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect { do { // We can send DATA frames in the following states: // @@ -475,36 +537,48 @@ extension HTTP2StreamStateMachine { // // Valid data frames always have a stream effect, because they consume flow control windows. switch self.state { - case .halfOpenLocalPeerIdle(localWindow: var localWindow, remoteWindow: let remoteWindow): + 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, remoteWindow: remoteWindow) + 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, localWindow: var localWindow, remoteWindow: let remoteWindow): + 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))) - self.state = endStream ? .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: .client, remoteWindow: remoteWindow) : .fullyOpen(localRole: localRole, localWindow: localWindow, remoteWindow: 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 localWindow): + 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, localWindow: localWindow) + self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: initiatedBy, localContentLength: localContentLength, localWindow: localWindow) effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) } @@ -517,12 +591,14 @@ extension HTTP2StreamStateMachine { } } 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)") } } - mutating func receiveData(flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect { + mutating func receiveData(contentLength: Int, flowControlledBytes: Int, isEndStreamSet endStream: Bool) -> StateMachineResultWithStreamEffect { do { // We can receive DATA frames in the following states: // @@ -531,43 +607,49 @@ extension HTTP2StreamStateMachine { // - fullyOpen, where we could be either a client or a server using a fully bi-directional stream. // - halfClosedLocalPeerActive, whe have completed our data, but the remote peer has more to send. switch self.state { - case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteWindow: var remoteWindow): + case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteContentLength: var remoteContentLength, remoteWindow: var remoteWindow): try remoteWindow.consume(flowControlledBytes: flowControlledBytes) + try remoteContentLength.receivedDataChunk(length: contentLength) let effect: StreamStateChange if endStream { + try remoteContentLength.endOfStream() self.state = .halfClosedRemoteLocalIdle(localWindow: localWindow) effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) } else { - self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) } return .init(result: .succeed, effect: effect) - case .fullyOpen(let localRole, localWindow: let localWindow, remoteWindow: var remoteWindow): + case .fullyOpen(let localRole, localContentLength: let localContentLength, remoteContentLength: var remoteContentLength, localWindow: let localWindow, remoteWindow: var remoteWindow): try remoteWindow.consume(flowControlledBytes: flowControlledBytes) + try remoteContentLength.receivedDataChunk(length: contentLength) let effect: StreamStateChange if endStream { - self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: .client, localWindow: localWindow) + try remoteContentLength.endOfStream() + self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: .client, localContentLength: localContentLength, localWindow: localWindow) effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) } else { - self.state = .fullyOpen(localRole: localRole, localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .fullyOpen(localRole: localRole, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow) effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) } return .init(result: .succeed, effect: effect) - case .halfClosedLocalPeerActive(let localRole, let initiatedBy, var remoteWindow): + case .halfClosedLocalPeerActive(let localRole, let initiatedBy, var remoteContentLength, var remoteWindow): try remoteWindow.consume(flowControlledBytes: flowControlledBytes) + try remoteContentLength.receivedDataChunk(length: contentLength) let effect: StreamStateChange if endStream { + try remoteContentLength.endOfStream() self.state = .closed(reason: nil) effect = .streamClosed(.init(streamID: self.streamID, reason: nil)) } else { - self.state = .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: initiatedBy, remoteWindow: remoteWindow) + self.state = .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: initiatedBy, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) effect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: nil, remoteStreamWindowSize: Int(remoteWindow))) } @@ -580,6 +662,8 @@ extension HTTP2StreamStateMachine { } } 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)") } @@ -595,17 +679,17 @@ extension HTTP2StreamStateMachine { // // PUSH_PROMISE frames never have stream effects: they cannot create or close streams, or affect flow control state. switch self.state { - case .fullyOpen(localRole: .server, localWindow: _, remoteWindow: _), - .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .client, localWindow: _): + case .fullyOpen(localRole: .server, localContentLength: _, remoteContentLength: _, localWindow: _, remoteWindow: _), + .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .client, localContentLength: _, localWindow: _): return self.processRequestHeaders(headers, validateHeaderBlock: validateHeaderBlock, targetState: self.state, targetEffect: nil) // Sending a PUSH_PROMISE frame outside any of these states is a stream error of type PROTOCOL_ERROR. // Authors note: I cannot find a citation for this in RFC 7540, but this seems a sensible choice. case .idle, .reservedLocal, .reservedRemote, .halfClosedLocalPeerIdle, .halfClosedLocalPeerActive, .halfClosedRemoteLocalIdle, .halfOpenLocalPeerIdle, .halfOpenRemoteLocalIdle, .closed, - .fullyOpen(localRole: .client, localWindow: _, remoteWindow: _), - .halfClosedRemoteLocalActive(localRole: .client, initiatedBy: _, localWindow: _), - .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .server, localWindow: _): + .fullyOpen(localRole: .client, localContentLength: _, remoteContentLength: _, localWindow: _, remoteWindow: _), + .halfClosedRemoteLocalActive(localRole: .client, initiatedBy: _, localContentLength: _, localWindow: _), + .halfClosedRemoteLocalActive(localRole: .server, initiatedBy: .server, localContentLength: _, localWindow: _): return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), type: .protocolError), effect: nil) } } @@ -618,17 +702,17 @@ extension HTTP2StreamStateMachine { // // RFC 7540 § 6.6 forbids receiving PUSH_PROMISE frames on remotely-initiated streams. switch self.state { - case .fullyOpen(localRole: .client, localWindow: _, remoteWindow: _), - .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .client, remoteWindow: _): + case .fullyOpen(localRole: .client, localContentLength: _, remoteContentLength: _, localWindow: _, remoteWindow: _), + .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .client, remoteContentLength: _, remoteWindow: _): return self.processRequestHeaders(headers, validateHeaderBlock: validateHeaderBlock, targetState: self.state, targetEffect: nil) // Receiving a PUSH_PROMISE frame outside any of these states is a stream error of type PROTOCOL_ERROR. // Authors note: I cannot find a citation for this in RFC 7540, but this seems a sensible choice. case .idle, .reservedLocal, .reservedRemote, .halfClosedLocalPeerIdle, .halfClosedRemoteLocalIdle, .halfClosedRemoteLocalActive, .halfOpenLocalPeerIdle, .halfOpenRemoteLocalIdle, .closed, - .fullyOpen(localRole: .server, localWindow: _, remoteWindow: _), - .halfClosedLocalPeerActive(localRole: .server, initiatedBy: _, remoteWindow: _), - .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .server, remoteWindow: _): + .fullyOpen(localRole: .server, localContentLength: _, remoteContentLength: _, localWindow: _, remoteWindow: _), + .halfClosedLocalPeerActive(localRole: .server, initiatedBy: _, remoteContentLength: _, remoteWindow: _), + .halfClosedLocalPeerActive(localRole: .client, initiatedBy: .server, remoteContentLength: _, remoteWindow: _): return .init(result: .streamError(streamID: self.streamID, underlyingError: NIOHTTP2Errors.BadStreamStateTransition(), type: .protocolError), effect: nil) } } @@ -652,19 +736,19 @@ extension HTTP2StreamStateMachine { self.state = .reservedRemote(remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: nil, remoteStreamWindowSize: Int(remoteWindow))) - case .halfOpenLocalPeerIdle(localWindow: let localWindow, remoteWindow: var remoteWindow): + case .halfOpenLocalPeerIdle(localWindow: let localWindow, localContentLength: let localContentLength, remoteWindow: var remoteWindow): try remoteWindow.windowUpdate(by: windowIncrement) - self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, localContentLength: localContentLength, remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) - case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteWindow: var remoteWindow): + case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteContentLength: let remoteContentLength, remoteWindow: var remoteWindow): try remoteWindow.windowUpdate(by: windowIncrement) - self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) - case .fullyOpen(localRole: let localRole, localWindow: let localWindow, remoteWindow: var remoteWindow): + case .fullyOpen(localRole: let localRole, localContentLength: let localContentLength, remoteContentLength: let remoteContentLength, localWindow: let localWindow, remoteWindow: var remoteWindow): try remoteWindow.windowUpdate(by: windowIncrement) - self.state = .fullyOpen(localRole: localRole, localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .fullyOpen(localRole: localRole, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) case .halfClosedLocalPeerIdle(remoteWindow: var remoteWindow): @@ -672,9 +756,9 @@ extension HTTP2StreamStateMachine { self.state = .halfClosedLocalPeerIdle(remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: nil, remoteStreamWindowSize: Int(remoteWindow))) - case .halfClosedLocalPeerActive(localRole: let localRole, initiatedBy: let initiatedBy, remoteWindow: var remoteWindow): + case .halfClosedLocalPeerActive(localRole: let localRole, initiatedBy: let initiatedBy, remoteContentLength: let remoteContentLength, remoteWindow: var remoteWindow): try remoteWindow.windowUpdate(by: windowIncrement) - self.state = .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: initiatedBy, remoteWindow: remoteWindow) + self.state = .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: initiatedBy, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: nil, remoteStreamWindowSize: Int(remoteWindow))) case .idle, .reservedLocal, .halfClosedRemoteLocalIdle, .halfClosedRemoteLocalActive, .closed: @@ -712,19 +796,19 @@ extension HTTP2StreamStateMachine { self.state = .reservedLocal(localWindow: localWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) - case .halfOpenLocalPeerIdle(localWindow: var localWindow, remoteWindow: let remoteWindow): + case .halfOpenLocalPeerIdle(localWindow: var localWindow, localContentLength: let localContentLength, remoteWindow: let remoteWindow): try localWindow.windowUpdate(by: windowIncrement) - self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, localContentLength: localContentLength, remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) - case .halfOpenRemoteLocalIdle(localWindow: var localWindow, remoteWindow: let remoteWindow): + case .halfOpenRemoteLocalIdle(localWindow: var localWindow, remoteContentLength: let remoteContentLength, remoteWindow: let remoteWindow): try localWindow.windowUpdate(by: windowIncrement) - self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) - case .fullyOpen(localRole: let localRole, localWindow: var localWindow, remoteWindow: let remoteWindow): + case .fullyOpen(localRole: let localRole, localContentLength: let localContentLength, remoteContentLength: let remoteContentLength, localWindow: var localWindow, remoteWindow: let remoteWindow): try localWindow.windowUpdate(by: windowIncrement) - self.state = .fullyOpen(localRole: localRole, localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .fullyOpen(localRole: localRole, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: Int(remoteWindow))) case .halfClosedRemoteLocalIdle(localWindow: var localWindow): @@ -732,9 +816,9 @@ extension HTTP2StreamStateMachine { self.state = .halfClosedRemoteLocalIdle(localWindow: localWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) - case .halfClosedRemoteLocalActive(localRole: let localRole, initiatedBy: let initiatedBy, localWindow: var localWindow): + case .halfClosedRemoteLocalActive(localRole: let localRole, initiatedBy: let initiatedBy, localContentLength: let localContentLength, localWindow: var localWindow): try localWindow.windowUpdate(by: windowIncrement) - self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: initiatedBy, localWindow: localWindow) + self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: initiatedBy, localContentLength: localContentLength, localWindow: localWindow) windowEffect = .windowSizeChange(.init(streamID: self.streamID, localStreamWindowSize: Int(localWindow), remoteStreamWindowSize: nil)) case .halfClosedLocalPeerIdle, .halfClosedLocalPeerActive: @@ -785,25 +869,25 @@ extension HTTP2StreamStateMachine { try remoteWindow.initialSizeChanged(by: change) self.state = .reservedRemote(remoteWindow: remoteWindow) - case .halfOpenLocalPeerIdle(localWindow: let localWindow, remoteWindow: var remoteWindow): + case .halfOpenLocalPeerIdle(localWindow: let localWindow, localContentLength: let localContentLength, remoteWindow: var remoteWindow): try remoteWindow.initialSizeChanged(by: change) - self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, localContentLength: localContentLength, remoteWindow: remoteWindow) - case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteWindow: var remoteWindow): + case .halfOpenRemoteLocalIdle(localWindow: let localWindow, remoteContentLength: let remoteContentLength, remoteWindow: var remoteWindow): try remoteWindow.initialSizeChanged(by: change) - self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) - case .fullyOpen(localRole: let localRole, localWindow: let localWindow, remoteWindow: var remoteWindow): + case .fullyOpen(localRole: let localRole, localContentLength: let localContentLength, remoteContentLength: let remoteContentLength, localWindow: let localWindow, remoteWindow: var remoteWindow): try remoteWindow.initialSizeChanged(by: change) - self.state = .fullyOpen(localRole: localRole, localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .fullyOpen(localRole: localRole, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow) case .halfClosedLocalPeerIdle(remoteWindow: var remoteWindow): try remoteWindow.initialSizeChanged(by: change) self.state = .halfClosedLocalPeerIdle(remoteWindow: remoteWindow) - case .halfClosedLocalPeerActive(localRole: let localRole, initiatedBy: let initiatedBy, remoteWindow: var remoteWindow): + case .halfClosedLocalPeerActive(localRole: let localRole, initiatedBy: let initiatedBy, remoteContentLength: let remoteContentLength, remoteWindow: var remoteWindow): try remoteWindow.initialSizeChanged(by: change) - self.state = .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: initiatedBy, remoteWindow: remoteWindow) + self.state = .halfClosedLocalPeerActive(localRole: localRole, initiatedBy: initiatedBy, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) case .reservedLocal, .halfClosedRemoteLocalIdle, .halfClosedRemoteLocalActive: // In these states the remote side of this stream is closed and will never be open, so its flow control window is not relevant. @@ -829,25 +913,25 @@ extension HTTP2StreamStateMachine { try localWindow.initialSizeChanged(by: change) self.state = .reservedLocal(localWindow: localWindow) - case .halfOpenLocalPeerIdle(localWindow: var localWindow, remoteWindow: let remoteWindow): + case .halfOpenLocalPeerIdle(localWindow: var localWindow, localContentLength: let localContentLength, remoteWindow: let remoteWindow): try localWindow.initialSizeChanged(by: change) - self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenLocalPeerIdle(localWindow: localWindow, localContentLength: localContentLength, remoteWindow: remoteWindow) - case .halfOpenRemoteLocalIdle(localWindow: var localWindow, remoteWindow: let remoteWindow): + case .halfOpenRemoteLocalIdle(localWindow: var localWindow, remoteContentLength: let remoteContentLength, remoteWindow: let remoteWindow): try localWindow.initialSizeChanged(by: change) - self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .halfOpenRemoteLocalIdle(localWindow: localWindow, remoteContentLength: remoteContentLength, remoteWindow: remoteWindow) - case .fullyOpen(localRole: let localRole, localWindow: var localWindow, remoteWindow: let remoteWindow): + case .fullyOpen(localRole: let localRole, localContentLength: let localContentLength, remoteContentLength: let remoteContentLength, localWindow: var localWindow, remoteWindow: let remoteWindow): try localWindow.initialSizeChanged(by: change) - self.state = .fullyOpen(localRole: localRole, localWindow: localWindow, remoteWindow: remoteWindow) + self.state = .fullyOpen(localRole: localRole, localContentLength: localContentLength, remoteContentLength: remoteContentLength, localWindow: localWindow, remoteWindow: remoteWindow) case .halfClosedRemoteLocalIdle(localWindow: var localWindow): try localWindow.initialSizeChanged(by: change) self.state = .halfClosedRemoteLocalIdle(localWindow: localWindow) - case .halfClosedRemoteLocalActive(localRole: let localRole, initiatedBy: let initiatedBy, localWindow: var localWindow): + case .halfClosedRemoteLocalActive(localRole: let localRole, initiatedBy: let initiatedBy, localContentLength: let localContentLength, localWindow: var localWindow): try localWindow.initialSizeChanged(by: change) - self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: initiatedBy, localWindow: localWindow) + self.state = .halfClosedRemoteLocalActive(localRole: localRole, initiatedBy: initiatedBy, localContentLength: localContentLength, localWindow: localWindow) case .reservedRemote, .halfClosedLocalPeerIdle, .halfClosedLocalPeerActive: // In these states the local side of this stream is closed and will never be open, so its flow control window is not relevant. diff --git a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift index d5776070..8ebf8d80 100644 --- a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift +++ b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift @@ -124,6 +124,20 @@ extension ConnectionStateMachineTests { ("testAllowHeadersWithTEHeaderSetToTrailers", testAllowHeadersWithTEHeaderSetToTrailers), ("testSettingActualMaxFrameSize", testSettingActualMaxFrameSize), ("testSettingActualInitialWindowSize", testSettingActualInitialWindowSize), + ("testPolicingExceedingContentLengthForRequests", testPolicingExceedingContentLengthForRequests), + ("testPolicingExceedingContentLengthForResponses", testPolicingExceedingContentLengthForResponses), + ("testPolicingMissingContentLengthForRequests", testPolicingMissingContentLengthForRequests), + ("testPolicingMissingContentLengthForResponses", testPolicingMissingContentLengthForResponses), + ("testPolicingInvalidContentLengthForRequestsWithEndStream", testPolicingInvalidContentLengthForRequestsWithEndStream), + ("testPolicingInvalidContentLengthForResponsesWithEndStream", testPolicingInvalidContentLengthForResponsesWithEndStream), + ("testValidContentLengthForRequestsWithEndStream", testValidContentLengthForRequestsWithEndStream), + ("testValidContentLengthForResponsesWithEndStream", testValidContentLengthForResponsesWithEndStream), + ("testNoPolicingExceedingContentLengthForRequestsWhenValidationDisabled", testNoPolicingExceedingContentLengthForRequestsWhenValidationDisabled), + ("testNoPolicingExceedingContentLengthForResponsesWhenValidationDisabled", testNoPolicingExceedingContentLengthForResponsesWhenValidationDisabled), + ("testNoPolicingMissingContentLengthForRequestsWhenValidationDisabled", testNoPolicingMissingContentLengthForRequestsWhenValidationDisabled), + ("testNoPolicingMissingContentLengthForResponsesWhenValidationDisabled", testNoPolicingMissingContentLengthForResponsesWhenValidationDisabled), + ("testNoPolicingInvalidContentLengthForRequestsWithEndStreamWhenValidationDisabled", testNoPolicingInvalidContentLengthForRequestsWithEndStreamWhenValidationDisabled), + ("testNoPolicingInvalidContentLengthForResponsesWithEndStreamWhenValidationDisabled", testNoPolicingInvalidContentLengthForResponsesWithEndStreamWhenValidationDisabled), ] } } diff --git a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift index 0d464a4d..7128392f 100644 --- a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift +++ b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift @@ -109,7 +109,7 @@ class ConnectionStateMachineTests: XCTestCase { var clientDecoder: HTTP2FrameDecoder! static let requestHeaders = { - return HPACKHeaders([(":method", "GET"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "0")]) + return HPACKHeaders([(":method", "GET"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("user-agent", "test")]) }() static let responseHeaders = { @@ -264,20 +264,20 @@ class ConnectionStateMachineTests: XCTestCase { self.exchangePreamble() assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertSucceeds(self.server.sendData(streamID: streamOne, flowControlledBytes: 300, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 300, flowControlledBytes: 300, isEndStreamSet: false)) assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertSucceeds(self.client.receiveData(streamID: streamOne, flowControlledBytes: 300, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 300, flowControlledBytes: 300, isEndStreamSet: false)) // Now both sides send another DATA frame and then trailers. Oooooh, trailers. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertSucceeds(self.server.sendData(streamID: streamOne, flowControlledBytes: 300, isEndStreamSet: false)) - assertSucceeds(self.client.receiveData(streamID: streamOne, flowControlledBytes: 300, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 300, flowControlledBytes: 300, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 300, flowControlledBytes: 300, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.trailers, isEndStreamSet: true)) assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.trailers, isEndStreamSet: true)) @@ -365,8 +365,8 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.client.receiveRstStream(streamID: streamOne, reason: .noError)) // Client attempts to send on this stream fail. Servers ignore the frame. - assertConnectionError(type: .streamClosed, self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) - assertIgnored(self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) + assertConnectionError(type: .streamClosed, self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertIgnored(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) assertSucceeds(self.client.sendGoaway(lastStreamID: .rootStream)) assertSucceeds(self.server.receiveGoaway(lastStreamID: .rootStream)) @@ -410,13 +410,13 @@ class ConnectionStateMachineTests: XCTestCase { // Client attempts to send on a closed stream fails, but the server ignores such frames. var temporaryServer = self.server! var temporaryClient = self.client! - assertConnectionError(type: .streamClosed, temporaryServer.sendData(streamID: streamFive, flowControlledBytes: 15, isEndStreamSet: true)) - assertConnectionError(type: .streamClosed, temporaryClient.receiveData(streamID: streamFive, flowControlledBytes: 15, isEndStreamSet: true)) + assertConnectionError(type: .streamClosed, temporaryServer.sendData(streamID: streamFive, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertConnectionError(type: .streamClosed, temporaryClient.receiveData(streamID: streamFive, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) temporaryServer = self.server! temporaryClient = self.client! - assertConnectionError(type: .streamClosed, temporaryClient.sendData(streamID: streamFive, flowControlledBytes: 15, isEndStreamSet: true)) - assertIgnored(temporaryServer.receiveData(streamID: streamFive, flowControlledBytes: 15, isEndStreamSet: true)) + assertConnectionError(type: .streamClosed, temporaryClient.sendData(streamID: streamFive, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertIgnored(temporaryServer.receiveData(streamID: streamFive, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) } func testWindowUpdateOnClosedStreamAfterServerGoaway() { @@ -533,13 +533,13 @@ class ConnectionStateMachineTests: XCTestCase { // Client attempts to send on a closed stream fails, and the server rejects such frames. var temporaryServer = self.server! var temporaryClient = self.client! - assertConnectionError(type: .streamClosed, temporaryServer.sendData(streamID: streamFour, flowControlledBytes: 15, isEndStreamSet: true)) - assertIgnored(temporaryClient.receiveData(streamID: streamFour, flowControlledBytes: 15, isEndStreamSet: true)) + assertConnectionError(type: .streamClosed, temporaryServer.sendData(streamID: streamFour, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertIgnored(temporaryClient.receiveData(streamID: streamFour, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) temporaryServer = self.server! temporaryClient = self.client! - assertConnectionError(type: .streamClosed, temporaryClient.sendData(streamID: streamFour, flowControlledBytes: 15, isEndStreamSet: true)) - assertConnectionError(type: .streamClosed, temporaryServer.receiveData(streamID: streamFour, flowControlledBytes: 15, isEndStreamSet: true)) + assertConnectionError(type: .streamClosed, temporaryClient.sendData(streamID: streamFour, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertConnectionError(type: .streamClosed, temporaryServer.receiveData(streamID: streamFour, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) } func testWindowUpdateOnClosedStreamAfterClientGoaway() { @@ -605,7 +605,7 @@ class ConnectionStateMachineTests: XCTestCase { // We only need one of the state machines here. assertConnectionError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertConnectionError(type: .protocolError, self.client.sendPing()) assertConnectionError(type: .protocolError, self.client.sendPriority()) assertConnectionError(type: .protocolError, self.client.sendPushPromise(originalStreamID: streamOne, childStreamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders)) @@ -620,7 +620,7 @@ class ConnectionStateMachineTests: XCTestCase { // We only need one of the state machines here. assertConnectionError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertConnectionError(type: .protocolError, self.client.sendPing()) assertConnectionError(type: .protocolError, self.client.sendPriority()) assertConnectionError(type: .protocolError, self.client.sendPushPromise(originalStreamID: streamOne, childStreamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders)) @@ -638,7 +638,7 @@ class ConnectionStateMachineTests: XCTestCase { // We only need one of the state machines here. assertConnectionError(type: .protocolError, self.server.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, self.server.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.server.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertConnectionError(type: .protocolError, self.server.sendPing()) assertConnectionError(type: .protocolError, self.server.sendPriority()) assertConnectionError(type: .protocolError, self.server.sendPushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders)) @@ -653,7 +653,7 @@ class ConnectionStateMachineTests: XCTestCase { // We only need one of the state machines here. assertConnectionError(type: .protocolError, self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, self.client.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.client.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertConnectionError(type: .protocolError, self.client.receivePing(ackFlagSet: false)) assertConnectionError(type: .protocolError, self.client.receivePriority()) assertConnectionError(type: .protocolError, self.client.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders)) @@ -670,7 +670,7 @@ class ConnectionStateMachineTests: XCTestCase { // We only need one of the state machines here. assertConnectionError(type: .protocolError, self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, self.client.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.client.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertConnectionError(type: .protocolError, self.client.receivePing(ackFlagSet: false)) assertConnectionError(type: .protocolError, self.client.receivePriority()) assertConnectionError(type: .protocolError, self.client.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders)) @@ -688,7 +688,7 @@ class ConnectionStateMachineTests: XCTestCase { // We only need one of the state machines here. assertConnectionError(type: .protocolError, self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertConnectionError(type: .protocolError, self.server.receivePing(ackFlagSet: false)) assertConnectionError(type: .protocolError, self.server.receivePriority()) assertConnectionError(type: .protocolError, self.server.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders)) @@ -773,8 +773,8 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) // We can now do all of the streamy things. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.client.sendWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertSucceeds(self.server.receiveWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertSucceeds(self.client.sendRstStream(streamID: streamOne, reason: .noError)) @@ -818,8 +818,8 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) // We can now do all of the streamy things. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.client.sendWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertSucceeds(self.server.receiveWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertSucceeds(self.client.sendRstStream(streamID: streamOne, reason: .noError)) @@ -857,10 +857,10 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.client.receiveGoaway(lastStreamID: streamOne)) // All the streamy operations work on stream one except push promise. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertSucceeds(self.server.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertSucceeds(self.client.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.client.sendWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertSucceeds(self.server.sendWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertSucceeds(self.server.receiveWindowUpdate(streamID: streamOne, windowIncrement: 1024)) @@ -951,8 +951,8 @@ class ConnectionStateMachineTests: XCTestCase { // Stream specific things don't work. assertConnectionError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) assertConnectionError(type: .protocolError, self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) - assertConnectionError(type: .protocolError, self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertConnectionError(type: .protocolError, self.client.sendWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertConnectionError(type: .protocolError, self.server.receiveWindowUpdate(streamID: streamOne, windowIncrement: 1024)) assertConnectionError(type: .protocolError, self.client.sendRstStream(streamID: streamOne, reason: .noError)) @@ -1088,8 +1088,8 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) // The default value of the flow control window is 65535. So let's see if we can hit it. First, let's send 65535 bytes from client to server. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 65535, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 65535, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 65535, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 65535, isEndStreamSet: false)) // Now the server opens the *stream window only*. assertSucceeds(self.server.sendWindowUpdate(streamID: streamOne, windowIncrement: 1000)) @@ -1098,22 +1098,22 @@ class ConnectionStateMachineTests: XCTestCase { // The client cannot send more than one byte on this stream. var temporaryServer = self.server! var temporaryClient = self.client! - assertConnectionError(type: .flowControlError, temporaryClient.sendData(streamID: streamOne, flowControlledBytes: 1, isEndStreamSet: false)) - assertConnectionError(type: .flowControlError, temporaryServer.receiveData(streamID: streamOne, flowControlledBytes: 1, isEndStreamSet: false)) + assertConnectionError(type: .flowControlError, temporaryClient.sendData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) + assertConnectionError(type: .flowControlError, temporaryServer.receiveData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) // Now the server opens the connection flow control window. assertSucceeds(self.server.sendWindowUpdate(streamID: .rootStream, windowIncrement: 65535)) assertSucceeds(self.client.receiveWindowUpdate(streamID: .rootStream, windowIncrement: 65535)) // The client may now send 1000 bytes. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 1000, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 1000, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 1000, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 1000, isEndStreamSet: false)) // But any attempt to send more fails, again. temporaryServer = self.server! temporaryClient = self.client! - assertStreamError(type: .flowControlError, temporaryClient.sendData(streamID: streamOne, flowControlledBytes: 1, isEndStreamSet: false)) - assertStreamError(type: .flowControlError, temporaryServer.receiveData(streamID: streamOne, flowControlledBytes: 1, isEndStreamSet: false)) + assertStreamError(type: .flowControlError, temporaryClient.sendData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) + assertStreamError(type: .flowControlError, temporaryServer.receiveData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) // The server can increase the flow control window by sending a SETTINGS frame with the appropriate new setting. // This adds 1000 to the window size. @@ -1123,13 +1123,13 @@ class ConnectionStateMachineTests: XCTestCase { // In this state, the client may send new data, but if the server doesn't receive the ACK first it holds the client to the new value. temporaryServer = self.server! temporaryClient = self.client! - assertSucceeds(temporaryClient.sendData(streamID: streamOne, flowControlledBytes: 1000, isEndStreamSet: false)) - assertStreamError(type: .flowControlError, temporaryServer.receiveData(streamID: streamOne, flowControlledBytes: 1000, isEndStreamSet: false)) + assertSucceeds(temporaryClient.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 1000, isEndStreamSet: false)) + assertStreamError(type: .flowControlError, temporaryServer.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 1000, isEndStreamSet: false)) // Once the server receives the ACK, it's fine. assertSucceeds(self.server.receiveSettings(.ack, frameEncoder: &self.serverEncoder, frameDecoder: &self.serverDecoder)) - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 1000, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 1000, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 1000, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 1000, isEndStreamSet: false)) // Open the flow control window of the stream. assertSucceeds(self.server.sendWindowUpdate(streamID: streamOne, windowIncrement: 65535)) @@ -1137,10 +1137,10 @@ class ConnectionStateMachineTests: XCTestCase { // At this stage the stream has a window size of 65535, but the connection should not have gained the extra 2000 bytes from the change in // SETTINGS_INITIAL_WINDOW_SIZE, so it should have a size of 63535. Verify this by exceeding it. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 63535, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 63535, isEndStreamSet: false)) - assertConnectionError(type: .flowControlError, self.client.sendData(streamID: streamOne, flowControlledBytes: 1, isEndStreamSet: false)) - assertConnectionError(type: .flowControlError, self.server.receiveData(streamID: streamOne, flowControlledBytes: 1, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 63535, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 63535, isEndStreamSet: false)) + assertConnectionError(type: .flowControlError, self.client.sendData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) + assertConnectionError(type: .flowControlError, self.server.receiveData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) } func testTrailersWithoutData() { @@ -1172,8 +1172,8 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: true)) assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: true)) - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: true)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: true)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: true)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: true)) } func testPushedResponsesMayHaveBodies() { @@ -1194,8 +1194,8 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.server.sendHeaders(streamID: streamTwo, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) assertSucceeds(self.client.receiveHeaders(streamID: streamTwo, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertSucceeds(self.server.sendData(streamID: streamTwo, flowControlledBytes: 1024, isEndStreamSet: true)) - assertSucceeds(self.client.receiveData(streamID: streamTwo, flowControlledBytes: 1024, isEndStreamSet: true)) + assertSucceeds(self.server.sendData(streamID: streamTwo, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: true)) + assertSucceeds(self.client.receiveData(streamID: streamTwo, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: true)) } func testDataFramesWithoutEndStream() { @@ -1206,20 +1206,20 @@ class ConnectionStateMachineTests: XCTestCase { // We can send some DATA frames while only the client has opened. assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: false)) // We can send some more while the server has opened. assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: false)) // And we can send some after the server is done. - assertSucceeds(self.server.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) - assertSucceeds(self.client.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: false)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: false)) } func testSendingCompleteRequestBeforeResponse() { @@ -1230,14 +1230,14 @@ class ConnectionStateMachineTests: XCTestCase { // We can send a complete request before the remote peer sends us anything. assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false)) - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: true)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 1024, isEndStreamSet: true)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: true)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 1024, flowControlledBytes: 1024, isEndStreamSet: true)) // The remote peer can then respond. assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) - assertSucceeds(self.server.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) - assertSucceeds(self.client.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) } func testWindowUpdateValidity() { @@ -1270,8 +1270,8 @@ class ConnectionStateMachineTests: XCTestCase { var tempServer = self.server! // If we close the client side of the stream, it's ok for the client to send. The server may not send, but the client will tolerate it. - assertSucceeds(tempClient.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) - assertSucceeds(tempServer.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(tempClient.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(tempServer.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) assertSucceeds(tempClient.sendWindowUpdate(streamID: streamOne, windowIncrement: 10)) assertSucceeds(tempServer.receiveWindowUpdate(streamID: streamOne, windowIncrement: 10)) @@ -1288,8 +1288,8 @@ class ConnectionStateMachineTests: XCTestCase { assertCanWindowUpdate(client: self.client, server: self.server) // If we close now it's ok for the client to send. The server may not send, but the client will tolerate it. - assertSucceeds(self.client.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) - assertSucceeds(self.server.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) tempClient = self.client! tempServer = self.server! assertSucceeds(tempClient.sendWindowUpdate(streamID: streamOne, windowIncrement: 10)) @@ -1345,8 +1345,8 @@ class ConnectionStateMachineTests: XCTestCase { var tempServer = self.server! // We can't do it before we've opened a stream. - assertConnectionError(type: .protocolError, tempClient.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertConnectionError(type: .protocolError, tempServer.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, tempClient.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .protocolError, tempServer.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) @@ -1354,10 +1354,10 @@ class ConnectionStateMachineTests: XCTestCase { // We can't send it after we have sent end stream, or before we've sent our headers. tempClient = self.client! tempServer = self.server! - assertStreamError(type: .streamClosed, tempClient.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertStreamError(type: .streamClosed, tempServer.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertStreamError(type: .streamClosed, tempServer.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertStreamError(type: .streamClosed, tempClient.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempClient.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempServer.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempServer.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempClient.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) @@ -1365,8 +1365,8 @@ class ConnectionStateMachineTests: XCTestCase { // The client still can't do it, regardless of what the server just did. tempClient = self.client! tempServer = self.server! - assertStreamError(type: .streamClosed, tempClient.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertStreamError(type: .streamClosed, tempServer.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempClient.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempServer.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) assertSucceeds(self.server.sendPushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders)) assertSucceeds(self.client.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders)) @@ -1376,19 +1376,19 @@ class ConnectionStateMachineTests: XCTestCase { // The client can't send on pushed streams either. tempClient = self.client! tempServer = self.server! - assertStreamError(type: .streamClosed, tempClient.sendData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) - assertStreamError(type: .streamClosed, tempServer.receiveData(streamID: streamOne, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempClient.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertStreamError(type: .streamClosed, tempServer.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) - assertSucceeds(self.server.sendData(streamID: streamTwo, flowControlledBytes: 15, isEndStreamSet: true)) - assertSucceeds(self.client.receiveData(streamID: streamTwo, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.server.sendData(streamID: streamTwo, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) + assertSucceeds(self.client.receiveData(streamID: streamTwo, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: true)) // Neither peer can send on closed streams. tempClient = self.client! tempServer = self.server! - assertConnectionError(type: .streamClosed, tempClient.sendData(streamID: streamTwo, flowControlledBytes: 15, isEndStreamSet: false)) - assertConnectionError(type: .streamClosed, tempServer.receiveData(streamID: streamTwo, flowControlledBytes: 15, isEndStreamSet: false)) - assertConnectionError(type: .streamClosed, tempServer.sendData(streamID: streamTwo, flowControlledBytes: 15, isEndStreamSet: false)) - assertConnectionError(type: .streamClosed, tempClient.receiveData(streamID: streamTwo, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .streamClosed, tempClient.sendData(streamID: streamTwo, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .streamClosed, tempServer.receiveData(streamID: streamTwo, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .streamClosed, tempServer.sendData(streamID: streamTwo, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertConnectionError(type: .streamClosed, tempClient.receiveData(streamID: streamTwo, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) } func testChangingInitialWindowSizeLotsOfStreams() { @@ -1451,20 +1451,20 @@ class ConnectionStateMachineTests: XCTestCase { for streamID in [streamOne, streamTwo, streamThree, streamFour, streamFive, streamSeven] { var tempClient = self.client! var tempServer = self.server! - assertSucceeds(tempServer.sendData(streamID: streamID, flowControlledBytes: 66535, isEndStreamSet: false)) - assertSucceeds(tempClient.receiveData(streamID: streamID, flowControlledBytes: 66535, isEndStreamSet: false)) - assertStreamError(type: .flowControlError, tempServer.sendData(streamID: streamID, flowControlledBytes: 1, isEndStreamSet: false)) - assertStreamError(type: .flowControlError, tempClient.receiveData(streamID: streamID, flowControlledBytes: 1, isEndStreamSet: false)) + assertSucceeds(tempServer.sendData(streamID: streamID, contentLength: 15, flowControlledBytes: 66535, isEndStreamSet: false)) + assertSucceeds(tempClient.receiveData(streamID: streamID, contentLength: 15, flowControlledBytes: 66535, isEndStreamSet: false)) + assertStreamError(type: .flowControlError, tempServer.sendData(streamID: streamID, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) + assertStreamError(type: .flowControlError, tempClient.receiveData(streamID: streamID, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) } // The client can only send on streams three and five, but it will. for streamID in [streamThree, streamFive] { var tempClient = self.client! var tempServer = self.server! - assertSucceeds(tempClient.sendData(streamID: streamID, flowControlledBytes: 66535, isEndStreamSet: false)) - assertSucceeds(tempServer.receiveData(streamID: streamID, flowControlledBytes: 66535, isEndStreamSet: false)) - assertStreamError(type: .flowControlError, tempClient.sendData(streamID: streamID, flowControlledBytes: 1, isEndStreamSet: false)) - assertStreamError(type: .flowControlError, tempServer.receiveData(streamID: streamID, flowControlledBytes: 1, isEndStreamSet: false)) + assertSucceeds(tempClient.sendData(streamID: streamID, contentLength: 15, flowControlledBytes: 66535, isEndStreamSet: false)) + assertSucceeds(tempServer.receiveData(streamID: streamID, contentLength: 15, flowControlledBytes: 66535, isEndStreamSet: false)) + assertStreamError(type: .flowControlError, tempClient.sendData(streamID: streamID, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) + assertStreamError(type: .flowControlError, tempServer.receiveData(streamID: streamID, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: false)) } } @@ -2518,6 +2518,334 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.server.receiveSettings(.settings(trickySettings), frameEncoder: &self.serverEncoder, frameDecoder: &self.serverDecoder)) assertSucceeds(self.client.receiveSettings(.ack, frameEncoder: &self.clientEncoder, frameDecoder: &self.clientDecoder)) } + + func testPolicingExceedingContentLengthForRequests() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let requestHeaders = HPACKHeaders([(":method", "POST"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + + // Send in 25 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + var tempClient = self.client! + var tempServer = self.server! + + // Sending any more data fails. + assertStreamError(type: .protocolError, tempClient.sendData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + assertStreamError(type: .protocolError, tempServer.receiveData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + + // But if we send a zero length frame that works fine. + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testPolicingExceedingContentLengthForResponses() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + + // The server responds + assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + + // Send in 25 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + var tempClient = self.client! + var tempServer = self.server! + + // Sending any more data fails. + assertStreamError(type: .protocolError, tempServer.sendData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + assertStreamError(type: .protocolError, tempClient.receiveData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + + // But if we send a zero length frame that works fine. + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testPolicingMissingContentLengthForRequests() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let requestHeaders = HPACKHeaders([(":method", "POST"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + + // Send in 20 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + // Closing the stream fails. + assertStreamError(type: .protocolError, self.client.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testPolicingMissingContentLengthForResponses() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + + // The server responds + assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + + // Send in 20 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + // Closing the stream fails. + assertStreamError(type: .protocolError, self.server.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testPolicingInvalidContentLengthForRequestsWithEndStream() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let requestHeaders = HPACKHeaders([(":method", "POST"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "25")]) + + // Set up the connection + assertStreamError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.receiveHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: true)) + } + + func testPolicingInvalidContentLengthForResponsesWithEndStream() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + + // The server responds + assertStreamError(type: .protocolError, self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true)) + } + + func testValidContentLengthForRequestsWithEndStream() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let requestHeaders = HPACKHeaders([(":method", "POST"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "0")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: true)) + } + + func testValidContentLengthForResponsesWithEndStream() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "0")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + + // The server responds + assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true)) + } + + func testNoPolicingExceedingContentLengthForRequestsWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + + // Override the setup with validation disabled. + self.server = .init(role: .server, contentLengthValidation: .disabled) + self.client = .init(role: .client, contentLengthValidation: .disabled) + + self.exchangePreamble() + + let requestHeaders = HPACKHeaders([(":method", "POST"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + + // Send in 25 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + var tempClient = self.client! + var tempServer = self.server! + + // Sending any more data succeeds. + assertSucceeds(tempClient.sendData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(tempServer.receiveData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + + // But if we send a zero length frame that works fine. + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testNoPolicingExceedingContentLengthForResponsesWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + + // Override the setup with validation disabled. + self.server = .init(role: .server, contentLengthValidation: .disabled) + self.client = .init(role: .client, contentLengthValidation: .disabled) + + self.exchangePreamble() + + let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + + // The server responds + assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + + // Send in 25 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 15, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + var tempClient = self.client! + var tempServer = self.server! + + // Sending any more data succeeds. + assertSucceeds(tempServer.sendData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(tempClient.receiveData(streamID: streamOne, contentLength: 1, flowControlledBytes: 1, isEndStreamSet: true)) + + // But if we send a zero length frame that works fine. + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testNoPolicingMissingContentLengthForRequestsWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + + // Override the setup with validation disabled. + self.server = .init(role: .server, contentLengthValidation: .disabled) + self.client = .init(role: .client, contentLengthValidation: .disabled) + + self.exchangePreamble() + + let requestHeaders = HPACKHeaders([(":method", "POST"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: false)) + + // Send in 20 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + // Closing the stream succeeds. + assertSucceeds(self.client.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(self.server.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testNoPolicingMissingContentLengthForResponsesWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + + // Override the setup with validation disabled. + self.server = .init(role: .server, contentLengthValidation: .disabled) + self.client = .init(role: .client, contentLengthValidation: .disabled) + + self.exchangePreamble() + + let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + + // The server responds + assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false)) + + // Send in 20 bytes over two frames, messing with the flow controlled length to just be a bit tricky. + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 10, flowControlledBytes: 15, isEndStreamSet: false)) + + // Closing the stream succeeds. + assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + assertSucceeds(self.client.receiveData(streamID: streamOne, contentLength: 0, flowControlledBytes: 1, isEndStreamSet: true)) + } + + func testNoPolicingInvalidContentLengthForRequestsWithEndStreamWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + + // Override the setup with validation disabled. + self.server = .init(role: .server, contentLengthValidation: .disabled) + self.client = .init(role: .client, contentLengthValidation: .disabled) + + self.exchangePreamble() + + let requestHeaders = HPACKHeaders([(":method", "POST"), (":authority", "localhost"), (":scheme", "https"), (":path", "/"), ("content-length", "25")]) + + // Set up the succeeds + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: requestHeaders, isEndStreamSet: true)) + } + + func testNoPolicingInvalidContentLengthForResponsesWithEndStreamWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + + // Override the setup with validation disabled. + self.server = .init(role: .server, contentLengthValidation: .disabled) + self.client = .init(role: .client, contentLengthValidation: .disabled) + + self.exchangePreamble() + + let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "25")]) + + // Set up the connection + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + + // The server succeeds + assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true)) + } } diff --git a/scripts/test_h2spec.sh b/scripts/test_h2spec.sh index 7847e8fa..88e85f25 100755 --- a/scripts/test_h2spec.sh +++ b/scripts/test_h2spec.sh @@ -37,7 +37,7 @@ function stop_server() { # Simple thing to do. Start the server in the background. swift build -"$(swift build --show-bin-path)/NIOHTTP2Server" 127.0.0.1 8888 & +"$(swift build --show-bin-path)/NIOHTTP2Server" 127.0.0.1 8888 > /dev/null 2>&1 & disown SERVER_PID=$! echo "$SERVER_PID"