diff --git a/Sources/NIOHTTP2/HTTP2Frame.swift b/Sources/NIOHTTP2/HTTP2Frame.swift index a7c4dfb3..57313953 100644 --- a/Sources/NIOHTTP2/HTTP2Frame.swift +++ b/Sources/NIOHTTP2/HTTP2Frame.swift @@ -21,108 +21,174 @@ public struct HTTP2Frame { /// The payload of this HTTP/2 frame. public var payload: FramePayload - /// The frame flags as an 8-bit integer. To set/unset well-defined flags, consider using the - /// other properties on this object (e.g. `endStream`). - public var flags: UInt8 - + /// The frame flags. + public var flags: FrameFlags + /// The frame stream ID as a 32-bit integer. public var streamID: HTTP2StreamID - // Whether the END_STREAM flag bit is set. - public var endStream: Bool { - get { - switch self.payload { - case .data, .headers: - return (self.flags & UInt8(NGHTTP2_FLAG_END_STREAM.rawValue) != 0) - default: - return false - } - } - set { - switch self.payload { - case .data, .headers: - self.flags |= UInt8(NGHTTP2_FLAG_END_STREAM.rawValue) - default: - break - } - } - } - - // Whether the PADDED flag bit is set. - public var padded: Bool { - get { - switch self.payload { - case .data, .headers, .pushPromise: - return (self.flags & UInt8(NGHTTP2_FLAG_PADDED.rawValue) != 0) - default: - return false - } - } - set { - switch self.payload { - case .data, .headers, .pushPromise: - self.flags |= UInt8(NGHTTP2_FLAG_PADDED.rawValue) - default: - break - } - } - } - - // Whether the PRIORITY flag bit is set. - public var priority: Bool { - get { - if case .headers = self.payload { - return (self.flags & UInt8(NGHTTP2_FLAG_PRIORITY.rawValue) != 0) - } else { - return false - } - } - set { - if case .headers = self.payload { - self.flags |= UInt8(NGHTTP2_FLAG_PRIORITY.rawValue) - } - } - } - - // Whether the ACK flag bit is set. - public var ack: Bool { - get { - switch self.payload { - case .settings, .ping: - return (self.flags & UInt8(NGHTTP2_FLAG_ACK.rawValue) != 0) - default: - return false - } - } - set { - switch self.payload { - case .settings, .ping: - self.flags |= UInt8(NGHTTP2_FLAG_ACK.rawValue) - default: - break - } - } - } - + /// Frame-type-specific payload data. public enum FramePayload { + /// A DATA frame, containing raw bytes. + /// + /// See [RFC 7540 § 6.1](https://httpwg.org/specs/rfc7540.html#rfc.section.6.1). case data(IOData) + + /// A HEADERS frame, containing all headers or trailers associated with a request + /// or response. + /// + /// Note that swift-nio-http2 automatically coalesces HEADERS and CONTINUATION + /// frames into a single `FramePayload.headers` instance. + /// + /// See [RFC 7540 § 6.2](https://httpwg.org/specs/rfc7540.html#rfc.section.6.2). case headers(HTTPHeaders) + + /// A PRIORITY frame, used to change priority and dependency ordering among + /// streams. + /// + /// See [RFC 7540 § 6.3](https://httpwg.org/specs/rfc7540.html#rfc.section.6.3). case priority + + /// A RST_STREAM (reset stream) frame, sent when a stream has encountered an error + /// condition and needs to be terminated as a result. + /// + /// See [RFC 7540 § 6.4](https://httpwg.org/specs/rfc7540.html#rfc.section.6.4). case rstStream(HTTP2ErrorCode) + + /// A SETTINGS frame, containing various connection--level settings and their + /// desired values. + /// + /// See [RFC 7540 § 6.5](https://httpwg.org/specs/rfc7540.html#rfc.section.6.5). case settings([HTTP2Setting]) + + /// A PUSH_PROMISE frame, used to notify a peer in advance of streams that a sender + /// intends to initiate. It performs much like a request's HEADERS frame, informing + /// a peer that the response for a theoretical request like the one in the promise + /// will arrive on a new stream. + /// + /// As with the HEADERS frame, swift-nio-http2 will coalesce an initial PUSH_PROMISE + /// frame with any CONTINUATION frames that follow, emitting a single + /// `FramePayload.pushPromise` instance for the complete set. + /// + /// See [RFC 7540 § 6.6](https://httpwg.org/specs/rfc7540.html#rfc.section.6.6). + /// + /// For more information on server push in HTTP/2, see + /// [RFC 7540 § 8.2](https://httpwg.org/specs/rfc7540.html#rfc.section.8.2). case pushPromise + + /// A PING frame, used to measure round-trip time between endpoints. + /// + /// See [RFC 7540 § 6.7](https://httpwg.org/specs/rfc7540.html#rfc.section.6.7). case ping(HTTP2PingData) + + /// A GOAWAY frame, used to request that a peer immediately cease communication with + /// the sender. It contains a stream ID indicating the last stream that will be processed + /// by the sender, an error code (if the shutdown was caused by an error), and optionally + /// some additional diagnostic data. + /// + /// See [RFC 7540 § 6.8](https://httpwg.org/specs/rfc7540.html#rfc.section.6.8). case goAway(lastStreamID: HTTP2StreamID, errorCode: HTTP2ErrorCode, opaqueData: ByteBuffer?) + + /// A WINDOW_UPDATE frame. This is used to implement flow control of DATA frames, + /// allowing peers to advertise and update the amount of data they are prepared to + /// process at any given moment. + /// + /// See [RFC 7540 § 6.9](https://httpwg.org/specs/rfc7540.html#rfc.section.6.9). case windowUpdate(windowSizeIncrement: Int) - case alternativeService + + /// An ALTSVC frame. This is sent by an HTTP server to indicate alternative origin + /// locations for accessing the same resource, for instance via another protocol, + /// or over TLS. It consists of an origin and a list of alternate protocols and + /// the locations at which they may be addressed. + /// + /// See [RFC 7838 § 4](https://tools.ietf.org/html/rfc7838#section-4). + case alternativeService(origin: String?, field: ByteBuffer?) + + /// An ORIGIN frame. This allows servers which allow access to multiple origins + /// via the same socket connection to identify which origins may be accessed in + /// this manner. + /// + /// See [RFC 8336 § 2](https://tools.ietf.org/html/rfc8336#section-2). + case origin([String]) + + /// The one-byte identifier used to indicate the type of a frame on the wire. + var code: UInt8 { + switch self { + case .data: return 0x0 + case .headers: return 0x1 + case .priority: return 0x2 + case .rstStream: return 0x3 + case .settings: return 0x4 + case .pushPromise: return 0x5 + case .ping: return 0x6 + case .goAway: return 0x7 + case .windowUpdate: return 0x8 + case .alternativeService: return 0xa + case .origin: return 0xc + } + } + + /// The set of flags that are permitted in the flags field of a particular frame. + var allowedFlags: FrameFlags { + switch self { + case .data: + return [.padded, .endStream] + case .headers: + return [.endStream, .endHeaders, .padded, .priority] + case .pushPromise: + return [.endHeaders, .padded] + case .settings, .ping: + return .ack + case .priority, .rstStream, .goAway, .windowUpdate, + .alternativeService, .origin: + return [] + } + } + } + + /// The flags supported by the frame types understood by this protocol. + public struct FrameFlags: OptionSet { + public typealias RawValue = UInt8 + + public private(set) var rawValue: UInt8 + + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + /// END_STREAM flag. Valid on DATA and HEADERS frames. + public static let endStream = FrameFlags(rawValue: 0x01) + + /// ACK flag. Valid on SETTINGS and PING frames. + public static let ack = FrameFlags(rawValue: 0x01) + + /// END_HEADERS flag. Valid on HEADERS, CONTINUATION, and PUSH_PROMISE frames. + public static let endHeaders = FrameFlags(rawValue: 0x04) + + /// PADDED flag. Valid on DATA, HEADERS, CONTINUATION, and PUSH_PROMISE frames. + /// + /// NB: swift-nio-http2 does not automatically pad outgoing frames. + public static let padded = FrameFlags(rawValue: 0x08) + + /// PRIORITY flag. Valid on HEADERS frames, specifically as the first frame sent + /// on a new stream. + public static let priority = FrameFlags(rawValue: 0x20) + + // useful for test cases + internal static var allFlags: FrameFlags = [.endStream, .endHeaders, .padded, .priority] } } internal extension HTTP2Frame { + internal init(streamID: HTTP2StreamID, flags: HTTP2Frame.FrameFlags, payload: HTTP2Frame.FramePayload) { + self.streamID = streamID + self.flags = flags.intersection(payload.allowedFlags) + self.payload = payload + } internal init(streamID: HTTP2StreamID, flags: UInt8, payload: HTTP2Frame.FramePayload) { self.streamID = streamID - self.flags = flags + self.flags = FrameFlags(rawValue: flags).intersection(payload.allowedFlags) self.payload = payload } } @@ -131,7 +197,7 @@ public extension HTTP2Frame { /// Constructs a frame header for a given stream ID. All flags are unset. public init(streamID: HTTP2StreamID, payload: HTTP2Frame.FramePayload) { self.streamID = streamID - self.flags = 0 + self.flags = [] self.payload = payload } } diff --git a/Sources/NIOHTTP2/HTTP2Settings.swift b/Sources/NIOHTTP2/HTTP2Settings.swift index 8ed3e714..afac85cc 100644 --- a/Sources/NIOHTTP2/HTTP2Settings.swift +++ b/Sources/NIOHTTP2/HTTP2Settings.swift @@ -52,6 +52,9 @@ public struct HTTP2SettingsParameter { /// Corresponds to SETTINGS_MAX_HEADER_LIST_SIZE public static let maxHeaderListSize = HTTP2SettingsParameter(6) + + /// Corresponds to SETTINGS_ENABLE_CONNECT_PROTOCOL from RFC 8441. + public static let enableConnectProtocol = HTTP2SettingsParameter(8) } extension HTTP2SettingsParameter: Equatable { diff --git a/Sources/NIOHTTP2/HTTP2ToHTTP1Codec.swift b/Sources/NIOHTTP2/HTTP2ToHTTP1Codec.swift index 0715feae..e19f40c9 100644 --- a/Sources/NIOHTTP2/HTTP2ToHTTP1Codec.swift +++ b/Sources/NIOHTTP2/HTTP2ToHTTP1Codec.swift @@ -94,13 +94,13 @@ public final class HTTP2ToHTTP1ClientCodec: ChannelInboundHandler, ChannelOutbou } else { let respHead = HTTPResponseHead(http2HeaderBlock: headers) ctx.fireChannelRead(self.wrapInboundOut(.head(respHead))) - if frame.endStream { + if frame.flags.contains(.endStream) { ctx.fireChannelRead(self.wrapInboundOut(.end(nil))) } } case .data(.byteBuffer(let b)): ctx.fireChannelRead(self.wrapInboundOut(.body(b))) - if frame.endStream { + if frame.flags.contains(.endStream) { ctx.fireChannelRead(self.wrapInboundOut(.end(nil))) } case .alternativeService, .rstStream, .priority, .windowUpdate: @@ -131,7 +131,7 @@ public final class HTTP2ToHTTP1ClientCodec: ChannelInboundHandler, ChannelOutbou } var frame = HTTP2Frame(streamID: self.streamID, payload: payload) - frame.endStream = true + frame.flags.insert(.endStream) ctx.write(self.wrapOutboundOut(frame), promise: promise) } } @@ -170,13 +170,13 @@ public final class HTTP2ToHTTP1ServerCodec: ChannelInboundHandler, ChannelOutbou } else { let reqHead = HTTPRequestHead(http2HeaderBlock: headers) ctx.fireChannelRead(self.wrapInboundOut(.head(reqHead))) - if frame.endStream { + if frame.flags.contains(.endStream) { ctx.fireChannelRead(self.wrapInboundOut(.end(nil))) } } case .data(.byteBuffer(let b)): ctx.fireChannelRead(self.wrapInboundOut(.body(b))) - if frame.endStream { + if frame.flags.contains(.endStream) { ctx.fireChannelRead(self.wrapInboundOut(.end(nil))) } case .alternativeService, .rstStream, .priority, .windowUpdate: @@ -207,7 +207,7 @@ public final class HTTP2ToHTTP1ServerCodec: ChannelInboundHandler, ChannelOutbou } var frame = HTTP2Frame(streamID: self.streamID, payload: payload) - frame.endStream = true + frame.flags.insert(.endStream) ctx.write(self.wrapOutboundOut(frame), promise: promise) } } diff --git a/Sources/NIOHTTP2/NGHTTP2Session.swift b/Sources/NIOHTTP2/NGHTTP2Session.swift index 0e7436ee..54469d1b 100644 --- a/Sources/NIOHTTP2/NGHTTP2Session.swift +++ b/Sources/NIOHTTP2/NGHTTP2Session.swift @@ -628,6 +628,8 @@ class NGHTTP2Session { fatalError("not implemented") case .alternativeService: fatalError("not implemented") + case .origin(_): + fatalError("not implemented") } } @@ -675,7 +677,7 @@ class NGHTTP2Session { preconditionFailure("Attempting to send non-headers frame") } - let isEndStream = frame.endStream + let isEndStream = frame.flags.contains(.endStream) let flags = isEndStream ? UInt8(NGHTTP2_FLAG_END_STREAM.rawValue) : UInt8(0) guard let networkStreamID = frame.streamID.networkStreamID else { @@ -742,7 +744,7 @@ class NGHTTP2Session { streamState.dataProvider.bufferWrite(write: data, promise: promise) // If this has END_STREAM set, we do not expect trailers. - if frame.endStream { + if frame.flags.contains(.endStream) { streamState.dataProvider.bufferEOF(trailers: nil) } diff --git a/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift b/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift index 74c3a1c0..fedc3eb9 100644 --- a/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift +++ b/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift @@ -179,7 +179,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { let streamIDs = stride(from: 1, to: 100, by: 2).map { HTTP2StreamID(knownID: Int32($0)) } for streamID in streamIDs { var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(frame)) } XCTAssertEqual(completedChannelCount, 0) @@ -202,7 +202,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // First, set up the frames we want to send/receive. let streamID = HTTP2StreamID(knownID: Int32(1)) var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) let rstStreamFrame = HTTP2Frame(streamID: streamID, payload: .rstStream(.cancel)) let multiplexer = HTTP2StreamMultiplexer { (channel, _) in @@ -236,7 +236,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // First, set up the frames we want to send/receive. let streamID = HTTP2StreamID(knownID: Int32(1)) var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) let goAwayFrame = HTTP2Frame(streamID: .rootStream, payload: .goAway(lastStreamID: .rootStream, errorCode: .http11Required, opaqueData: nil)) let multiplexer = HTTP2StreamMultiplexer { (channel, _) in @@ -613,7 +613,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { let secondStreamID = HTTP2StreamID(knownID: 3) for streamID in [firstStreamID, secondStreamID] { var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(frame)) } XCTAssertEqual(channels.count, 2) @@ -649,7 +649,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // Let's open a stream. let streamID = HTTP2StreamID(knownID: 1) var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(frame)) XCTAssertNotNil(channel) @@ -680,7 +680,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // Let's open a stream. let streamID = HTTP2StreamID(knownID: 1) var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(frame)) XCTAssertNotNil(channel) @@ -711,7 +711,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // Let's open a stream. let streamID = HTTP2StreamID(knownID: 1) var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(frame)) XCTAssertNotNil(channel) @@ -949,7 +949,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // Let's open a stream. let streamID = HTTP2StreamID(knownID: 1) var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(frame)) // No handlerRemoved so far. @@ -980,7 +980,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // Let's open a stream. let streamID = HTTP2StreamID(knownID: 1) var frame = HTTP2Frame(streamID: streamID, payload: .headers(HTTPHeaders())) - frame.endStream = true + frame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(frame)) // No handlerRemoved so far. diff --git a/Tests/NIOHTTP2Tests/HTTP2ToHTTP1CodecTests.swift b/Tests/NIOHTTP2Tests/HTTP2ToHTTP1CodecTests.swift index 55bfc39f..550fddfc 100644 --- a/Tests/NIOHTTP2Tests/HTTP2ToHTTP1CodecTests.swift +++ b/Tests/NIOHTTP2Tests/HTTP2ToHTTP1CodecTests.swift @@ -93,7 +93,7 @@ final class HTTP2ToHTTP1CodecTests: XCTestCase { var bodyData = self.channel.allocator.buffer(capacity: 12) bodyData.write(staticString: "hello, world!") var dataFrame = HTTP2Frame(streamID: streamID, payload: .data(.byteBuffer(bodyData))) - dataFrame.endStream = true + dataFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(dataFrame)) self.channel.assertReceivedServerRequestPart(.body(bodyData)) self.channel.assertReceivedServerRequestPart(.end(nil)) @@ -108,7 +108,7 @@ final class HTTP2ToHTTP1CodecTests: XCTestCase { // A basic request. let requestHeaders = HTTPHeaders([(":path", "/get"), (":method", "GET"), (":scheme", "https"), (":authority", "example.org"), ("other", "header")]) var headersFrame = HTTP2Frame(streamID: streamID, payload: .headers(requestHeaders)) - headersFrame.endStream = true + headersFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(headersFrame)) var expectedRequestHead = HTTPRequestHead(version: HTTPVersion(major: 2, minor: 0), method: .GET, uri: "/get") @@ -137,7 +137,7 @@ final class HTTP2ToHTTP1CodecTests: XCTestCase { // Ok, we're going to send trailers. let trailers = HTTPHeaders([("a trailer", "yes"), ("another trailer", "also yes")]) var trailersFrame = HTTP2Frame(streamID: streamID, payload: .headers(trailers)) - trailersFrame.endStream = true + trailersFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(trailersFrame)) self.channel.assertReceivedServerRequestPart(.end(trailers)) @@ -280,7 +280,7 @@ final class HTTP2ToHTTP1CodecTests: XCTestCase { var bodyData = self.channel.allocator.buffer(capacity: 12) bodyData.write(staticString: "hello, world!") var dataFrame = HTTP2Frame(streamID: streamID, payload: .data(.byteBuffer(bodyData))) - dataFrame.endStream = true + dataFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(dataFrame)) self.channel.assertReceivedClientResponsePart(.body(bodyData)) self.channel.assertReceivedClientResponsePart(.end(nil)) @@ -295,7 +295,7 @@ final class HTTP2ToHTTP1CodecTests: XCTestCase { // A basic response. let responseHeaders = HTTPHeaders([(":status", "200"), ("other", "header")]) var headersFrame = HTTP2Frame(streamID: streamID, payload: .headers(responseHeaders)) - headersFrame.endStream = true + headersFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(headersFrame)) var expectedResponseHead = HTTPResponseHead(version: .init(major: 2, minor: 0), status: .ok) @@ -322,7 +322,7 @@ final class HTTP2ToHTTP1CodecTests: XCTestCase { // Ok, we're going to send trailers. let trailers = HTTPHeaders([("a trailer", "yes"), ("another trailer", "also yes")]) var trailersFrame = HTTP2Frame(streamID: streamID, payload: .headers(trailers)) - trailersFrame.endStream = true + trailersFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(trailersFrame)) self.channel.assertReceivedClientResponsePart(.end(trailers)) @@ -406,7 +406,7 @@ final class HTTP2ToHTTP1CodecTests: XCTestCase { // Now a response. let responseHeaders = HTTPHeaders([(":status", "200"), ("other", "header")]) var responseFrame = HTTP2Frame(streamID: streamID, payload: .headers(responseHeaders)) - responseFrame.endStream = true + responseFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.channel.writeInbound(responseFrame)) var expectedResponseHead = HTTPResponseHead(version: .init(major: 2, minor: 0), status: .ok) diff --git a/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift b/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift index fe830a9a..5b618332 100644 --- a/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift +++ b/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift @@ -201,14 +201,14 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame, reqBodyFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID // Let's send a quick response back. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) XCTAssertNoThrow(try self.clientChannel.finish()) @@ -232,7 +232,7 @@ class SimpleClientServerTests: XCTestCase { let streamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: streamID, payload: .headers(requestHeaders)) var reqBodyFrame = HTTP2Frame(streamID: streamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) self.clientChannel.write(reqFrame, promise: nil) self.clientChannel.write(reqBodyFrame, promise: nil) @@ -303,13 +303,13 @@ class SimpleClientServerTests: XCTestCase { var requestBody = self.clientChannel.allocator.buffer(capacity: 128) requestBody.write(staticString: "A simple HTTP/2 request.") var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [reqBodyFrame], sender: self.clientChannel, receiver: self.serverChannel) // The server will respond, closing this stream. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) // The server can now GOAWAY down to stream 1. We evaluate the bytes here ourselves becuase the client won't see this frame. @@ -355,7 +355,7 @@ class SimpleClientServerTests: XCTestCase { // Now we'll try to send this in a DATA frame. var dataFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(buffer))) - dataFrame.endStream = true + dataFrame.flags.insert(.endStream) self.clientChannel.writeAndFlush(dataFrame, promise: nil) self.interactInMemory(self.clientChannel, self.serverChannel) @@ -372,7 +372,7 @@ class SimpleClientServerTests: XCTestCase { // Now send a response from the server and shut things down. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) XCTAssertNoThrow(try self.clientChannel.finish()) @@ -392,14 +392,14 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.fileRegion(region))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame, reqBodyFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID // Let's send a quick response back. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) } @@ -432,7 +432,7 @@ class SimpleClientServerTests: XCTestCase { // Ok, we're gonna send the body here. This should create 4 streams. var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.fileRegion(region))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) self.clientChannel.writeAndFlush(reqBodyFrame, promise: nil) self.interactInMemory(self.clientChannel, self.serverChannel) @@ -449,7 +449,7 @@ class SimpleClientServerTests: XCTestCase { // Let's send a quick response back. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) // No frames left. @@ -500,12 +500,12 @@ class SimpleClientServerTests: XCTestCase { // Let's complete one of the streams by sending data for the first stream on the client, and responding on the server. var dataFrame = HTTP2Frame(streamID: clientStreamIDs.first!, payload: .data(.byteBuffer(requestBody))) - dataFrame.endStream = true + dataFrame.flags.insert(.endStream) self.clientChannel.writeAndFlush(dataFrame, promise: nil) let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamIDs.first!, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) self.serverChannel.writeAndFlush(respFrame, promise: nil) // Now we expect the following things to have happened: 1) the client will have seen the server's response, @@ -580,7 +580,7 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) self.clientChannel.write(reqFrame, promise: nil) var receivedError: Error? = nil @@ -610,7 +610,7 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) self.clientChannel.write(reqFrame, promise: nil) var receivedError: Error? = nil @@ -722,7 +722,7 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) self.clientChannel.write(reqFrame, promise: nil) self.clientChannel.write(reqBodyFrame, promise: nil) @@ -764,7 +764,7 @@ class SimpleClientServerTests: XCTestCase { for _ in 0..<63 { self.clientChannel.write(reqBodyFrame, promise: nil) } - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) self.clientChannel.writeAndFlush(reqBodyFrame, promise: nil) // Ok, we now want to send this data to the server. @@ -991,7 +991,7 @@ class SimpleClientServerTests: XCTestCase { let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) let reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) var trailerFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(trailers)) - trailerFrame.endStream = true + trailerFrame.flags.insert(.endStream) let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame, reqBodyFrame, trailerFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID @@ -999,7 +999,7 @@ class SimpleClientServerTests: XCTestCase { let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) let respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) var respTrailersFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(trailers)) - respTrailersFrame.endStream = true + respTrailersFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame, respTrailersFrame], sender: self.serverChannel, receiver: self.clientChannel) XCTAssertNoThrow(try self.clientChannel.finish()) @@ -1014,7 +1014,7 @@ class SimpleClientServerTests: XCTestCase { let headers = HTTPHeaders([(":path", "/"), (":method", "GET"), (":scheme", "https"), (":authority", "localhost")]) let clientStreamID = HTTP2StreamID() var reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) - reqFrame.endStream = true + reqFrame.flags.insert(.endStream) let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID @@ -1026,7 +1026,7 @@ class SimpleClientServerTests: XCTestCase { // Now we send the final response back. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) XCTAssertNoThrow(try self.clientChannel.finish()) @@ -1083,7 +1083,7 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(headers)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame, reqBodyFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID @@ -1094,7 +1094,7 @@ class SimpleClientServerTests: XCTestCase { // Let's send a quick response back. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) // Now the streams are closed, they should have seen user events. @@ -1250,13 +1250,13 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(requestHeaders)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame, reqBodyFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID // Let's send a quick response back. var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) } @@ -1284,13 +1284,13 @@ class SimpleClientServerTests: XCTestCase { let clientStreamID = HTTP2StreamID() let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(requestHeaders)) var reqBodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(requestBody))) - reqBodyFrame.endStream = true + reqBodyFrame.flags.insert(.endStream) let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame, reqBodyFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID // Let's send a quick response back. var respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel) } @@ -1309,7 +1309,7 @@ class SimpleClientServerTests: XCTestCase { // Clean it up with the server now. let serverFrames = serverStreamIDs.map { streamID -> HTTP2Frame in var respFrame = HTTP2Frame(streamID: streamID, payload: .headers(responseHeaders)) - respFrame.endStream = true + respFrame.flags.insert(.endStream) return respFrame } try self.assertFramesRoundTrip(frames: serverFrames, sender: self.serverChannel, receiver: self.clientChannel) diff --git a/Tests/NIOHTTP2Tests/TestUtilities.swift b/Tests/NIOHTTP2Tests/TestUtilities.swift index 745726d8..4ac6f733 100644 --- a/Tests/NIOHTTP2Tests/TestUtilities.swift +++ b/Tests/NIOHTTP2Tests/TestUtilities.swift @@ -150,6 +150,10 @@ extension EmbeddedChannel { } extension HTTP2Frame { + var ack: Bool { + return self.flags.contains(.ack) + } + /// Asserts that the given frame is a SETTINGS frame. func assertSettingsFrame(expectedSettings: [HTTP2Setting], ack: Bool, file: StaticString = #file, line: UInt = #line) { guard case .settings(let values) = self.payload else { @@ -196,7 +200,7 @@ extension HTTP2Frame { guard case .headers(let payload) = frame.payload else { preconditionFailure("Headers frames can never match non-headers frames") } - self.assertHeadersFrame(endStream: frame.endStream, + self.assertHeadersFrame(endStream: frame.flags.contains(.endStream), streamID: frame.streamID.networkStreamID!, payload: payload, file: file, @@ -211,8 +215,8 @@ extension HTTP2Frame { return } - XCTAssertEqual(self.endStream, endStream, - "Unexpected endStream: expected \(endStream), got \(self.endStream)", file: file, line: line) + XCTAssertEqual(self.flags.contains(.endStream), endStream, + "Unexpected endStream: expected \(endStream), got \(self.flags.contains(.endStream))", file: file, line: line) XCTAssertEqual(self.streamID.networkStreamID!, streamID, "Unexpected streamID: expected \(streamID), got \(self.streamID.networkStreamID!)", file: file, line: line) XCTAssertEqual(payload, actualPayload, "Non-equal payloads: expected \(payload), got \(actualPayload)", file: file, line: line) @@ -231,7 +235,7 @@ extension HTTP2Frame { preconditionFailure("Data frames can never match non-data frames") } - self.assertDataFrame(endStream: frame.endStream, + self.assertDataFrame(endStream: frame.flags.contains(.endStream), streamID: frame.streamID.networkStreamID!, payload: expectedPayload, file: file, @@ -245,8 +249,8 @@ extension HTTP2Frame { return } - XCTAssertEqual(self.endStream, endStream, - "Unexpected endStream: expected \(endStream), got \(self.endStream)", file: file, line: line) + XCTAssertEqual(self.flags.contains(.endStream), endStream, + "Unexpected endStream: expected \(endStream), got \(self.flags.contains(.endStream))", file: file, line: line) XCTAssertEqual(self.streamID.networkStreamID!, streamID, "Unexpected streamID: expected \(streamID), got \(self.streamID.networkStreamID!)", file: file, line: line) XCTAssertEqual(actualPayload, payload,