diff --git a/Sources/SwiftNetwork/Protocols/BridgeProtocol.swift b/Sources/SwiftNetwork/Protocols/BridgeProtocol.swift index 51ba1a6f..f69012b0 100644 --- a/Sources/SwiftNetwork/Protocols/BridgeProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/BridgeProtocol.swift @@ -67,6 +67,7 @@ public struct DatagramDrops: Equatable { } public typealias BridgeObserveFirstByteHandler = ((UInt8) -> Void)? +public typealias BridgeObserveFrameHandler = ((UInt8, Int) -> Void)? @_spi(Essentials) @available(Network 0.1.0, *) @@ -78,6 +79,7 @@ public struct BridgeDatagramProtocol: NetworkProtocol { public struct BridgeOptions: PerProtocolOptions { public var linkDelay: NetworkDuration = .zero public var observeFirstByteHandler: BridgeObserveFirstByteHandler = nil + public var observeFrameHandler: BridgeObserveFrameHandler = nil var datagramDrops: DatagramDrops? init() {} @@ -152,6 +154,7 @@ public struct BridgeDatagramProtocol: NetworkProtocol { var linkDelay: NetworkDuration = .zero var datagramDrops: DatagramDrops? = nil var observeFirstByteHandler: BridgeObserveFirstByteHandler = nil + var observeFrameHandler: BridgeObserveFrameHandler = nil private var timerSet = false func deliverInboundDataAvailableEvent() { @@ -194,6 +197,7 @@ public struct BridgeDatagramProtocol: NetworkProtocol { self.linkDelay = bridgeOptions.linkDelay self.datagramDrops = bridgeOptions.datagramDrops self.observeFirstByteHandler = bridgeOptions.observeFirstByteHandler + self.observeFrameHandler = bridgeOptions.observeFrameHandler } #endif @@ -294,6 +298,14 @@ public struct BridgeDatagramProtocol: NetworkProtocol { return true } } + if let observeFrameHandler { + datagrams.iterateMutableFrames { frame in + if let bytes = frame.bytes, bytes.byteCount > 0 { + observeFrameHandler(bytes[0], bytes.byteCount) + } + return true + } + } remoteInstance.incomingFrames.add(frames: datagrams) remoteInstance.deliverInboundDataAvailableEvent() } @@ -348,6 +360,11 @@ extension ProtocolOptions { set { perProtocolOptions!.observeFirstByteHandler = newValue } } + public var observeFrameHandler: BridgeObserveFrameHandler { + get { perProtocolOptions!.observeFrameHandler } + set { perProtocolOptions!.observeFrameHandler = newValue } + } + public var datagramDrops: DatagramDrops? { get { perProtocolOptions!.datagramDrops } set { perProtocolOptions!.datagramDrops = newValue } diff --git a/Sources/SwiftNetwork/Protocols/QUICConnectionProtocol.swift b/Sources/SwiftNetwork/Protocols/QUICConnectionProtocol.swift index 03993cc8..54fd8cf7 100644 --- a/Sources/SwiftNetwork/Protocols/QUICConnectionProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/QUICConnectionProtocol.swift @@ -211,7 +211,7 @@ public struct QUICConnectionProtocol: NetworkProtocol { public var enableL4S: Bool? internal var _maxUDPPayloadSize: UInt16 = UInt16.max - var maxUDPPayloadSize: UInt16 { + public var maxUDPPayloadSize: UInt16 { get { self._maxUDPPayloadSize } set { self._maxUDPPayloadSize = newValue } } @@ -235,7 +235,7 @@ public struct QUICConnectionProtocol: NetworkProtocol { } internal var _initialPacketSize: UInt16 = 0 - var initialPacketSize: UInt16 { + public var initialPacketSize: UInt16 { get { self._initialPacketSize } set { self._initialPacketSize = newValue } } diff --git a/Sources/SwiftNetwork/QUIC/QUICConnection.swift b/Sources/SwiftNetwork/QUIC/QUICConnection.swift index 55ecde96..abd715d8 100644 --- a/Sources/SwiftNetwork/QUIC/QUICConnection.swift +++ b/Sources/SwiftNetwork/QUIC/QUICConnection.swift @@ -526,6 +526,14 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, pmtudIgnoreCost = protocolOptions.quicConnectionOptions.pmtudIgnoreCost pmtudInterval = protocolOptions.quicConnectionOptions.pmtudUpdateInterval + // RFC 9000 - 14.1. Initial Datagram Size + // Datagrams containing Initial packets MAY exceed 1200 bytes if the sender + // believes that the network path and peer both support the size that it chooses + let requestedInitialPacketSize = Int(protocolOptions.quicConnectionOptions.initialPacketSize) + if requestedInitialPacketSize > Constants.initialMSS { + initialMSS = requestedInitialPacketSize + } + pacingEnabled = protocolOptions.quicConnectionOptions.enablePacing testSendingShortPackets = @@ -4540,6 +4548,13 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, log.debug( "Remote max datagram size \(remoteMaxDatagramFrameSize)" ) + // A flow opened before the peer's transport parameters sized itself + // against a limit of 0, so recompute now that the limit is known. + if let path = currentPath { + applyToAllSecondaryFlows { datagramFlow in + datagramFlow.updateUsableDatagramFrameSize(connection: self, path: path) + } + } } guard let remoteTPMaxDatagramFrameSize else { self.remoteMaxDatagramFrameSize = 0 @@ -5213,6 +5228,7 @@ extension QUICConnection { self.applicationCloseError = QUICApplicationError(frame.errorCode, frame.reason) receivedApplicationClose = true } + log.info("received APPLICATION_CLOSE code: \(frame.errorCode), reason: '\(frame.reason)'") close() return true } @@ -5222,6 +5238,7 @@ extension QUICConnection { self.closeError = QUICTransportError(frame.errorCode, frame.reason) receivedConnectionClose = true } + log.info("received CONNECTION_CLOSE code: \(frame.errorCode), reason: '\(frame.reason)'") close() return true } @@ -5853,13 +5870,21 @@ extension QUICConnection { logPrefixer: logPrefixer ) multiplexedSecondaryFlows[newFlowIdentifier] = newFlow - deliverNewInboundSecondaryFlowEvent(newFlow.reference) - - newFlow.log.debug("Created inbound datagram flow for \(newFlowIdentifier)") withCurrentPath { path in newFlow.updateUsableDatagramFrameSize(connection: self, path: path) } + let datagramMetadata = QUICProtocol.metadata() + datagramMetadata.perProtocolMetadata?.datagramFlowID = newFlow.flowID + datagramMetadata.perProtocolMetadata?.isDatagramFlow = true + datagramMetadata.perProtocolMetadata?.usableDatagramFrameSize = UInt16(newFlow.usableDatagramSize) + datagramMetadata.perProtocolMetadata?.quicConnectionMetadata = self.connectionMetadata + secondaryInboundFlowLinkage.deliverNewInboundFlowEvent( + reference, + flowReference: newFlow.reference, + flowMetadata: datagramMetadata + ) + newFlow.log.debug("Created inbound datagram flow for \(newFlowIdentifier)") matchingFlowIdentifier = newFlowIdentifier } diff --git a/Tests/SwiftNetworkTests/QUICTestHarness.swift b/Tests/SwiftNetworkTests/QUICTestHarness.swift index afcada53..9957b840 100644 --- a/Tests/SwiftNetworkTests/QUICTestHarness.swift +++ b/Tests/SwiftNetworkTests/QUICTestHarness.swift @@ -138,7 +138,8 @@ class QUICTestHarness { timeout: TimeInterval = 5.0, clientOptions: ProtocolOptions = QUICProtocol.options(), serverOptions: ProtocolOptions = QUICProtocol.options(), - bridgeObserveFirstByteHandler: BridgeObserveFirstByteHandler = nil + bridgeObserveFirstByteHandler: BridgeObserveFirstByteHandler = nil, + bridgeObserveFrameHandler: BridgeObserveFrameHandler = nil ) throws(NetworkError) { var clientConnected = false var serverConnected = false @@ -168,6 +169,7 @@ class QUICTestHarness { let clientBridge = BridgeDatagramProtocol.instance(context: self.context) let clientBridgeOptions = BridgeDatagramProtocol.options() clientBridgeOptions.observeFirstByteHandler = bridgeObserveFirstByteHandler + clientBridgeOptions.observeFrameHandler = bridgeObserveFrameHandler clientBridgeOptions.setProtocolInstance(clientBridge) clientBridgeOptions.linkDelay = clientLinkDelay clientBridgeOptions.datagramDrops = clientDrops @@ -196,6 +198,7 @@ class QUICTestHarness { let serverBridge = BridgeDatagramProtocol.instance(context: self.context) let serverBridgeOptions = BridgeDatagramProtocol.options() serverBridgeOptions.observeFirstByteHandler = bridgeObserveFirstByteHandler + serverBridgeOptions.observeFrameHandler = bridgeObserveFrameHandler serverBridgeOptions.setProtocolInstance(serverBridge) serverBridgeOptions.linkDelay = serverLinkDelay serverBridgeOptions.datagramDrops = serverDrops @@ -924,7 +927,8 @@ class QUICTestHarness { extraServerCIDs: [(QUICConnectionID, QUICStatelessResetToken)] = .init(), afterHandshake: ((QUICTestHarness) -> Void)? = nil, // Block to run after handshake is complete afterData: ((QUICTestHarness) -> Void)? = nil, // Block to run after handshake is complete - bridgeObserveFirstByteHandler: BridgeObserveFirstByteHandler = nil + bridgeObserveFirstByteHandler: BridgeObserveFirstByteHandler = nil, + bridgeObserveFrameHandler: BridgeObserveFrameHandler = nil ) { // Start with the handshake Logger.test.debug("Test phase: Handshake") @@ -945,7 +949,8 @@ class QUICTestHarness { timeout: timeout, clientOptions: clientOptions, serverOptions: serverOptions, - bridgeObserveFirstByteHandler: bridgeObserveFirstByteHandler + bridgeObserveFirstByteHandler: bridgeObserveFirstByteHandler, + bridgeObserveFrameHandler: bridgeObserveFrameHandler ) } catch { if expectHandshakeError == nil { diff --git a/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift b/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift index 911959bb..53daf95a 100644 --- a/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift +++ b/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift @@ -657,6 +657,29 @@ final class SwiftNetworkQUICHarnessTests: NetTestCase { QUICTestHarness().runQUICTest(datagram: true, blockSize: 1000, blockCount: 10) } + func testQUICDatagramWithLargeInitialPacketSize() { + let clientOptions = QUICProtocol.options() + clientOptions.connectionOptions.initialPacketSize = 1400 + + var observedInitialPacketSizes: [Int] = [] + let observeFrameHandler: BridgeObserveFrameHandler = { firstByte, byteCount in + // Verify initial packet + guard (firstByte & 0xF0) == 0xC0 else { return } + observedInitialPacketSizes.append(byteCount) + } + + QUICTestHarness().runQUICTest( + datagram: true, + blockSize: 1000, + blockCount: 10, + clientOptions: clientOptions, + bridgeObserveFrameHandler: observeFrameHandler + ) + + XCTAssertFalse(observedInitialPacketSizes.isEmpty, "Should have observed at least one Initial packet") + XCTAssertEqual(observedInitialPacketSizes.first, 1400) + } + func testQUICDatagramRemoteMaxDatagramFrameSize() { QUICTestHarness().runQUICTest( datagram: true,