Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import PackageDescription
var swiftSettings: [PackageDescription.SwiftSetting] = [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InternalImportsByDefault"),
.enableExperimentalFeature("AnyAppleOSAvailability"),
]

let package = Package(
name: "swift-nio-http3",
platforms: [.macOS("26.0"), .iOS("26.0"), .tvOS("26.0"), .watchOS("26.0"), .visionOS("26.0")],
products: [
.library(name: "NIOHTTP3", targets: ["NIOHTTP3"])
],
Expand Down
1 change: 1 addition & 0 deletions Sources/NIOHTTP3/HTTP3ConnectionHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ public final class HTTP3ConnectionHandler<StreamCreator: QUICStreamCreator & Sen
/// - connection: An instance of ``HTTP3ServerConnection`` which inbound connections can be vended to.
/// - Returns: A ``HTTP3ConnectionHandler``.
@_spi(HTTP3AsyncInterface)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public static func server<Output: Sendable>(
eventLoop: any EventLoop,
configuration: HTTP3ServerConfiguration,
Expand Down
6 changes: 6 additions & 0 deletions Sources/NIOHTTP3/HTTP3ConnectionMultiplexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public import NIOQUICHelpers
// Allow creating outbound push streams.
/// A HTTP/3 connection from a servers side. Allows iterating inbound streams.
@_spi(HTTP3AsyncInterface)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public struct HTTP3ServerConnection<Output: Sendable, StreamCreator: QUICStreamCreator>: Sendable {
/// Channel initializer called for each new inbound stream.
private let inboundStreamInitializer: @Sendable (HTTP3StreamInitializerParameters) -> EventLoopFuture<Output>
Expand Down Expand Up @@ -85,6 +86,7 @@ extension HTTP3ServerConnection.InboundStreams.AsyncIterator: Sendable {}
// Allow iterating incoming push streams.
/// A HTTP/3 connection from a clients side. Allows creating outbound request streams.
@_spi(HTTP3AsyncInterface)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public struct HTTP3ClientConnection<
Output: Sendable,
StreamCreator: QUICStreamCreator & SendableMetatype
Expand Down Expand Up @@ -144,6 +146,7 @@ package protocol StreamMultiplexerContinuation: Sendable {
func finish()
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTP3ServerConnection: StreamMultiplexerContinuation {
package func initialize(parameters: HTTP3StreamInitializerParameters) -> EventLoopFuture<any Sendable> {
self.inboundStreamInitializer(parameters).map { $0 as any Sendable }
Expand All @@ -163,6 +166,7 @@ extension HTTP3ServerConnection: StreamMultiplexerContinuation {
/// You can use this type to wrap a QUIC implementation. Have the QUIC implementation call ``yield(connection:)`` and ``finish()`` as appropriate.
/// Then, you can easily iterate through incoming connections and handle them.
@_spi(HTTP3AsyncInterface)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public struct HTTP3ServerConnectionMultiplexer<Output: Sendable, StreamCreator: QUICStreamCreator>: Sendable {
/// The inboundConnections' continuation.
private let inboundConnectionsContinuation: AsyncStream<HTTP3ServerConnection<Output, StreamCreator>>.Continuation
Expand Down Expand Up @@ -219,6 +223,7 @@ extension HTTP3ServerConnectionMultiplexer.InboundConnections.AsyncIterator: Sen

/// Implementations of QUIC should implement this protocol to be able to use the HTTP3ClientConnectionMultiplexer.
@_spi(HTTP3AsyncInterface)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol HTTP3ConnectionCreator {
/// Create a new QUIC connection using the parameters provided.
/// - Parameters:
Expand All @@ -235,6 +240,7 @@ public protocol HTTP3ConnectionCreator {

/// Allows you to create outbound connections as a client.
@_spi(HTTP3AsyncInterface)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public struct HTTP3ClientConnectionMultiplexer<
ConnectionCreator: HTTP3ConnectionCreator & SendableMetatype,
StreamCreator: QUICStreamCreator
Expand Down
34 changes: 8 additions & 26 deletions Tests/H3IntegrationTests/AsyncEndToEndTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,16 @@ import struct NIOQUIC.QUICStreamCreator
struct AsyncEndToEndTests {
private let eventLoopGroup = MultiThreadedEventLoopGroup.singleton

private static func supportsAsyncInterface() -> Bool {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, visionOS 1.0, macCatalyst 13.0, *) {
return true
} else {
return false
}
}

private static let standardAuthenticationConfigurations: [AuthenticationConfiguration] = {
[.keys, .certs]
}()

@Test(
.enabled(if: Self.supportsAsyncInterface()),
arguments: Self.standardAuthenticationConfigurations
)
@Test(arguments: Self.standardAuthenticationConfigurations)
@available(anyAppleOS 26, *)
func asyncRoundtrip(authenticationConfiguration: AuthenticationConfiguration) async throws {
let serverLogger = Logger(label: "Server")
let clientLogger = Logger(label: "Client")

guard #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, visionOS 1.0, macCatalyst 13.0, *) else {
fatalError("Test shouldn't be enabled on this platform")
}

let credentials = try TestCertificates.makeCredentials(for: authenticationConfiguration)
let serverName: String
switch credentials {
Expand Down Expand Up @@ -138,16 +124,13 @@ struct AsyncEndToEndTests {
}
}

@Test(.enabled(if: Self.supportsAsyncInterface()), arguments: Self.standardAuthenticationConfigurations)
@Test(arguments: Self.standardAuthenticationConfigurations)
@available(anyAppleOS 26, *)
func goawayMidRequestWithHighID(authenticationConfiguration: AuthenticationConfiguration) async throws {
// Send a goaway to the client in the middle of a request. See that the client can not make further requests.
let serverLogger = Logger(label: "Server")
let clientLogger = Logger(label: "Client")

guard #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, visionOS 1.0, macCatalyst 13.0, *) else {
fatalError("Test shouldn't be enabled on this platform")
}

let credentials = try TestCertificates.makeCredentials(for: authenticationConfiguration)
let serverName: String
switch credentials {
Expand Down Expand Up @@ -245,17 +228,14 @@ struct AsyncEndToEndTests {
try await clientChannel.close()
}

@Test(.enabled(if: Self.supportsAsyncInterface()), arguments: Self.standardAuthenticationConfigurations)
@Test(arguments: Self.standardAuthenticationConfigurations)
@available(anyAppleOS 26, *)
func goawayMidRequest(authenticationConfiguration: AuthenticationConfiguration) async throws {
// Send a goaway to a client during a request, such that the goaway affects the request in flight.
// See that the server rejects the request.
let serverLogger = Logger(label: "Server")
let clientLogger = Logger(label: "Client")

guard #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, visionOS 1.0, macCatalyst 13.0, *) else {
fatalError("Test shouldn't be enabled on this platform")
}

let credentials = try TestCertificates.makeCredentials(for: authenticationConfiguration)
let serverName: String
switch credentials {
Expand Down Expand Up @@ -385,6 +365,7 @@ struct AsyncEndToEndTests {

// MARK: - Helper Functions

@available(anyAppleOS 26, *)
private func makeHTTP3Server(
credentials: Credentials,
settings: HTTP3Settings,
Expand Down Expand Up @@ -473,6 +454,7 @@ struct AsyncEndToEndTests {
}
}

@available(anyAppleOS 26, *)
private func makeHTTP3Client(
credentials: Credentials,
settings: HTTP3Settings,
Expand Down
25 changes: 22 additions & 3 deletions Tests/H3IntegrationTests/EndToEndTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ struct EndToEndTests {
}()

@Test(arguments: Self.standardAuthenticationConfigurations)
@available(anyAppleOS 26, *)
func testSettingsFrame(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -304,6 +305,7 @@ struct EndToEndTests {
.disabled("See DynamicTable document in NIOHTTP3 module"),
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testRoundtripWithSlowDynamicTable(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -524,6 +526,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testRoundtripWithoutDynamicTable(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -629,6 +632,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testConnectionError(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -685,6 +689,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testServerClosesConnection(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -732,6 +737,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testClosingConnectionAlsoClosesStreams(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -786,6 +792,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testServerClosesConnectionWithActiveStream(
authenticationConfiguration: AuthenticationConfiguration
) async throws {
Expand Down Expand Up @@ -853,6 +860,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testStreamError(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -924,6 +932,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testUnknownIncomingStream(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -977,10 +986,11 @@ struct EndToEndTests {
try await serverChannel.close()
}

// This is always an error; clients must not open push streams to servers.
@Test(
arguments: Self.standardAuthenticationConfigurations
)
// This is always an error; clients must not open push streams to servers.
@available(anyAppleOS 26, *)
func testIncomingPushStreamOnServer(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -1034,10 +1044,11 @@ struct EndToEndTests {
try await serverChannel.close()
}

// This is an error because the client hasn't specified that it wants to receive push (by sending a MAX_PUSH_ID frame)
@Test(
arguments: Self.standardAuthenticationConfigurations
)
// This is an error because the client hasn't specified that it wants to receive push (by sending a MAX_PUSH_ID frame)
@available(anyAppleOS 26, *)
func testIncomingPushStreamOnClient(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -1124,6 +1135,7 @@ struct EndToEndTests {
.disabled("See DynamicTable document in NIOHTTP3 module"),
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testBadQPACKInstruction(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -1192,10 +1204,11 @@ struct EndToEndTests {
try await serverChannel.close()
}

// If the connection handler gets a shouldquiesce event then the stream handlers should also
@Test(
arguments: Self.standardAuthenticationConfigurations
)
// If the connection handler gets a shouldquiesce event then the stream handlers should also
@available(anyAppleOS 26, *)
func testForwardShouldQuiesce(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -1254,6 +1267,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testNoOutboundRequestsAfterGoawayReceived(
authenticationConfiguration: AuthenticationConfiguration
) async throws {
Expand Down Expand Up @@ -1377,6 +1391,7 @@ struct EndToEndTests {
@Test(
arguments: Self.standardAuthenticationConfigurations
)
@available(anyAppleOS 26, *)
func testControlStreamClosed(authenticationConfiguration: AuthenticationConfiguration) async throws {
let host = "127.0.0.1"

Expand Down Expand Up @@ -1461,6 +1476,7 @@ struct EndToEndTests {

// MARK: - Helper Functions

@available(anyAppleOS 26, *)
private func makeServer(
credentials: Credentials,
host: String,
Expand Down Expand Up @@ -1491,6 +1507,7 @@ struct EndToEndTests {
)
}

@available(anyAppleOS 26, *)
private func makeClient(
credentials: Credentials,
host: String,
Expand All @@ -1515,6 +1532,7 @@ struct EndToEndTests {
)
}

@available(anyAppleOS 26, *)
private static func createHTTP3ClientChannel(
eventLoopGroup: any EventLoopGroup,
host: String,
Expand Down Expand Up @@ -1563,6 +1581,7 @@ struct EndToEndTests {
return try await httpChannelFuture.get()
}

@available(anyAppleOS 26, *)
private static func createHTTP3ServerChannel(
eventLoopGroup: any EventLoopGroup,
host: String,
Expand Down
2 changes: 2 additions & 0 deletions Tests/H3IntegrationTests/HTTP3+NIOQUIC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import struct NIOQUIC.QUICStreamCreator
typealias QUICHTTP3ConnectionHandler = HTTP3ConnectionHandler<QUICStreamCreator>

// MARK: Configure with async interface
@available(anyAppleOS 26, *)
extension ChannelPipeline.SynchronousOperations {
/// Setup a HTTP/3 server pipeline on a UDP channel.
///
Expand Down Expand Up @@ -201,6 +202,7 @@ extension ChannelPipeline.SynchronousOperations {

// MARK: Configure without async interface

@available(anyAppleOS 26, *)
extension ChannelPipeline.SynchronousOperations {
/// Setup a HTTP/3 server pipeline on a UDP channel.
///
Expand Down
1 change: 1 addition & 0 deletions Tests/H3IntegrationTests/QUICConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import class Foundation.FileManager
import struct Foundation.UUID
import struct NIOQUIC.QUICConfiguration

@available(anyAppleOS 26, *)
extension QUICConfiguration {
static func makeH3ServerConfig(
serverName: String,
Expand Down
1 change: 1 addition & 0 deletions Tests/H3IntegrationTests/QUICConnectionCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import NIOQUICHelpers

typealias ConcreteQUICStreamCreator = NIOQUIC.QUICStreamCreator

@available(anyAppleOS 26, *)
struct QUICConnectionCreator: HTTP3ConnectionCreator {
let quicHandler: QUICHandler
let connectionInitializer: @Sendable (any Channel, ConcreteQUICStreamCreator) -> EventLoopFuture<any Channel>
Expand Down
Loading