From eb3ba9cf8ee57426636b3321bf1e5aaa8420db4b Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Tue, 28 Jul 2026 08:58:23 -0500 Subject: [PATCH 1/6] SwiftQUIC: PERF: Reduce CPU by 63% parsing QUIC header --- Sources/SwiftNetwork/Protocols/Frame.swift | 20 +++++++++++++++-- Sources/SwiftNetwork/QUIC/PacketParser.swift | 22 +++++++++---------- .../SwiftNetwork/QUIC/QUICConnectionID.swift | 20 ++++++++++++----- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftNetwork/Protocols/Frame.swift b/Sources/SwiftNetwork/Protocols/Frame.swift index f4b8500..1ef1d18 100644 --- a/Sources/SwiftNetwork/Protocols/Frame.swift +++ b/Sources/SwiftNetwork/Protocols/Frame.swift @@ -52,12 +52,12 @@ public struct Frame: ~Copyable { set { _endOffset = UInt32(newValue) } } private var _effectiveBufferLength: UInt32 = 0 - var effectiveBufferLength: Int { + @usableFromInline var effectiveBufferLength: Int { get { Int(_effectiveBufferLength) } set { _effectiveBufferLength = UInt32(newValue) } } private var _aggregateBufferLength: UInt32 = 0 - var aggregateBufferLength: Int { + @usableFromInline var aggregateBufferLength: Int { get { Int(_aggregateBufferLength) } set { _aggregateBufferLength = UInt32(newValue) } } @@ -212,6 +212,11 @@ public struct Frame: ~Copyable { } } + @inline(__always) + public var firstOctet: UInt8? { + bytes?.unsafeLoad(fromUncheckedByteOffset: 0, as: UInt8.self) + } + // Only unclaimed bytes in frame, for unsafe types private var unsafeUnclaimedBuffer: UnsafeMutableRawBufferPointer? { switch buffer { @@ -261,6 +266,7 @@ public struct Frame: ~Copyable { } } + @inline(__always) public mutating func claim(fromStart: Int, fromEnd: Int = 0, adjustSingleIPAggregate: Bool = true) -> Bool { if adjustSingleIPAggregate && isSingleIPAggregate { guard fromEnd == 0 else { @@ -786,6 +792,16 @@ extension Frame { @available(Network 0.1.0, *) extension Frame { + + @inline(__always) + func copyInto(inlineArray: inout [20 of UInt8], length: Int) { + guard startOffset + length <= self._bytes.count else { + return + } + for i in 0.. Packet { var firstOctet: UInt8 = 0 let originalLength = frame.unclaimedLength - let result = Deserializer.deserialize(&frame, claim: true) { read throws(DeserializationError) in - try read.uint8(&firstOctet) + guard let firstFrameOctet = frame.firstOctet else { + throw QUICError.packet(QUICPacketError.deserializationError) } - try validateDeserializationResult(result) - + guard frame.claim(fromStart: 1) else { + throw QUICError.packet(QUICPacketError.deserializationError) + } + firstOctet = firstFrameOctet // Common short/long header bits let longHeader = (firstOctet & 0x80) != 0 @@ -342,7 +344,6 @@ struct PacketParser: ~Copyable, PrefixedLoggable { originalLength: originalLength ) } - packet.framesReceived.reserveCapacity(1) return packet } @@ -523,16 +524,13 @@ struct PacketParser: ~Copyable, PrefixedLoggable { log.error("Short header fixed bit is zero") throw QUICError.packet(QUICPacketError.deserializationError) } - var dcidStorage = QUICConnectionIDStorage.empty - let result = Deserializer.deserialize(&frame, claim: true) { read throws(DeserializationError) in - try read.connectionID(&dcidStorage, length: dcidLength) + frame.copyInto(inlineArray: &dcidStorage, length: Int(dcidLength)) + guard frame.claim(fromStart: dcidLength) else { + throw QUICError.packet(QUICPacketError.deserializationError) } - try validateDeserializationResult(result) - - let destinationConnectionID = QUICConnectionID(storage: dcidStorage, size: Int(dcidLength)) return Packet( - destinationConnectionID: destinationConnectionID, + destinationConnectionID: QUICConnectionID(storage: dcidStorage, size: Int(dcidLength)), headerLength: UInt16(originalLength - frame.unclaimedLength), spin: spinValue ) diff --git a/Sources/SwiftNetwork/QUIC/QUICConnectionID.swift b/Sources/SwiftNetwork/QUIC/QUICConnectionID.swift index cbc9766..c0ead4e 100644 --- a/Sources/SwiftNetwork/QUIC/QUICConnectionID.swift +++ b/Sources/SwiftNetwork/QUIC/QUICConnectionID.swift @@ -88,8 +88,10 @@ public struct QUICConnectionID: Sendable, Equatable, CustomStringConvertible { // Creates a QUICConnectionID from an array. public init?(_ connectionID: [UInt8]) { guard connectionID.count <= QUICConnectionID.maximumSize else { + #if !DisableErrorLogging let connectionIDCount = connectionID.count - Logger.proto.fault("Invalid QUICConnectionID length \(connectionIDCount)") + Logger.proto.error("Invalid QUICConnectionID length \(connectionIDCount)") + #endif return nil } actualLength = connectionID.count @@ -101,15 +103,19 @@ public struct QUICConnectionID: Sendable, Equatable, CustomStringConvertible { if size <= QUICConnectionID.maximumSize { actualLength = size } else { - Logger.proto.fault("Invalid QUICConnectionID length \(size)") + #if !DisableErrorLogging + Logger.proto.error("Invalid QUICConnectionID length \(size)") + #endif actualLength = QUICConnectionID.maximumSize } } public init?(_ connectionID: Span) { guard connectionID.count <= QUICConnectionID.maximumSize else { + #if !DisableErrorLogging let connectionIDCount = connectionID.count - Logger.proto.fault("Invalid QUICConnectionID length \(connectionIDCount)") + Logger.proto.error("Invalid QUICConnectionID length \(connectionIDCount)") + #endif return nil } actualLength = connectionID.count @@ -120,7 +126,9 @@ public struct QUICConnectionID: Sendable, Equatable, CustomStringConvertible { public init(_ size: Int) { var size = size if size > QUICConnectionID.maximumSize { - Logger.proto.fault("Invalid QUICConnectionID length \(size)") + #if !DisableErrorLogging + Logger.proto.error("Invalid QUICConnectionID length \(size)") + #endif size = QUICConnectionID.maximumSize } if size != 0 && size < 4 { @@ -133,7 +141,9 @@ public struct QUICConnectionID: Sendable, Equatable, CustomStringConvertible { // Creates a QUICConnectionID from a buffer with a specific size. init?(_ buffer: [UInt8], size: Int) { guard size <= QUICConnectionID.maximumSize, buffer.count >= size else { - Logger.proto.fault("Invalid QUICConnectionID length \(size)") + #if !DisableErrorLogging + Logger.proto.error("Invalid QUICConnectionID length \(size)") + #endif return nil } let cidBytes = Array(buffer[0.. Date: Tue, 28 Jul 2026 16:24:54 -0500 Subject: [PATCH 2/6] InlineDeserializer --- Sources/SwiftNetwork/Protocols/Frame.swift | 26 ++++++------ Sources/SwiftNetwork/QUIC/PacketParser.swift | 17 ++++---- .../SwiftNetwork/Utilities/Deserializer.swift | 41 +++++++++++++++++++ 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftNetwork/Protocols/Frame.swift b/Sources/SwiftNetwork/Protocols/Frame.swift index 1ef1d18..f892dec 100644 --- a/Sources/SwiftNetwork/Protocols/Frame.swift +++ b/Sources/SwiftNetwork/Protocols/Frame.swift @@ -212,10 +212,10 @@ public struct Frame: ~Copyable { } } - @inline(__always) - public var firstOctet: UInt8? { - bytes?.unsafeLoad(fromUncheckedByteOffset: 0, as: UInt8.self) - } + // @inline(__always) + // public var firstOctet: UInt8? { + // bytes?.unsafeLoad(fromUncheckedByteOffset: 0, as: UInt8.self) + // } // Only unclaimed bytes in frame, for unsafe types private var unsafeUnclaimedBuffer: UnsafeMutableRawBufferPointer? { @@ -793,15 +793,15 @@ extension Frame { @available(Network 0.1.0, *) extension Frame { - @inline(__always) - func copyInto(inlineArray: inout [20 of UInt8], length: Int) { - guard startOffset + length <= self._bytes.count else { - return - } - for i in 0.. Packet { - var firstOctet: UInt8 = 0 let originalLength = frame.unclaimedLength - guard let firstFrameOctet = frame.firstOctet else { - throw QUICError.packet(QUICPacketError.deserializationError) - } - guard frame.claim(fromStart: 1) else { + guard let firstOctet = try? InlineDeserializer.uint8(frame: &frame, claim: true) else { throw QUICError.packet(QUICPacketError.deserializationError) } - firstOctet = firstFrameOctet // Common short/long header bits let longHeader = (firstOctet & 0x80) != 0 @@ -525,8 +520,14 @@ struct PacketParser: ~Copyable, PrefixedLoggable { throw QUICError.packet(QUICPacketError.deserializationError) } var dcidStorage = QUICConnectionIDStorage.empty - frame.copyInto(inlineArray: &dcidStorage, length: Int(dcidLength)) - guard frame.claim(fromStart: dcidLength) else { + guard + let _ = try? InlineDeserializer.connectionID( + frame: &frame, + storage: &dcidStorage, + length: Int(dcidLength), + claim: true + ) + else { throw QUICError.packet(QUICPacketError.deserializationError) } return Packet( diff --git a/Sources/SwiftNetwork/Utilities/Deserializer.swift b/Sources/SwiftNetwork/Utilities/Deserializer.swift index 95bb0dc..9d5a35e 100644 --- a/Sources/SwiftNetwork/Utilities/Deserializer.swift +++ b/Sources/SwiftNetwork/Utilities/Deserializer.swift @@ -72,6 +72,47 @@ public enum DeserializationResult: CustomStringConvertible, Equatable, Sendable } } +@_spi(ProtocolProvider) +@available(Network 0.1.0, *) +public struct InlineDeserializer {} + +@available(Network 0.1.0, *) +extension InlineDeserializer { + @inline(__always) + static func uint8(frame: inout Frame, claim: Bool = false) throws(DeserializationError) -> UInt8 { + guard frame._bytes.count > 0 else { + throw DeserializationError.bufferTooShort + } + let value: UInt8 = frame._bytes[0] + if claim { + guard frame.claim(fromStart: 1) else { + throw DeserializationError.bufferTooShort + } + } + return value + } + + @inline(__always) + static func connectionID( + frame: inout Frame, + storage: inout [20 of UInt8], + length: Int, + claim: Bool = false + ) throws(DeserializationError) { + guard frame.startOffset + length <= frame._bytes.count else { + return + } + for i in 0..: ~Copyable, ~Escapable { From 819307092e79f15b2abfe852fab22f68c270fe26 Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Tue, 28 Jul 2026 16:33:20 -0500 Subject: [PATCH 3/6] Fix for InlineDeserializer --- Sources/SwiftNetwork/Utilities/Deserializer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftNetwork/Utilities/Deserializer.swift b/Sources/SwiftNetwork/Utilities/Deserializer.swift index 9d5a35e..0c26502 100644 --- a/Sources/SwiftNetwork/Utilities/Deserializer.swift +++ b/Sources/SwiftNetwork/Utilities/Deserializer.swift @@ -83,7 +83,7 @@ extension InlineDeserializer { guard frame._bytes.count > 0 else { throw DeserializationError.bufferTooShort } - let value: UInt8 = frame._bytes[0] + let value: UInt8 = frame._bytes[frame.startOffset] if claim { guard frame.claim(fromStart: 1) else { throw DeserializationError.bufferTooShort From 7b6687a61988d813d429fc39c18828f9934619ed Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Tue, 28 Jul 2026 16:34:53 -0500 Subject: [PATCH 4/6] Removed unused code --- Sources/SwiftNetwork/Protocols/Frame.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Sources/SwiftNetwork/Protocols/Frame.swift b/Sources/SwiftNetwork/Protocols/Frame.swift index f892dec..d84e205 100644 --- a/Sources/SwiftNetwork/Protocols/Frame.swift +++ b/Sources/SwiftNetwork/Protocols/Frame.swift @@ -793,15 +793,6 @@ extension Frame { @available(Network 0.1.0, *) extension Frame { - // @inline(__always) - // func copyInto(inlineArray: inout [20 of UInt8], length: Int) { - // guard startOffset + length <= self._bytes.count else { - // return - // } - // for i in 0.. Date: Tue, 28 Jul 2026 16:36:56 -0500 Subject: [PATCH 5/6] Remove unused code --- Sources/SwiftNetwork/Protocols/Frame.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/SwiftNetwork/Protocols/Frame.swift b/Sources/SwiftNetwork/Protocols/Frame.swift index d84e205..fd8e045 100644 --- a/Sources/SwiftNetwork/Protocols/Frame.swift +++ b/Sources/SwiftNetwork/Protocols/Frame.swift @@ -212,11 +212,6 @@ public struct Frame: ~Copyable { } } - // @inline(__always) - // public var firstOctet: UInt8? { - // bytes?.unsafeLoad(fromUncheckedByteOffset: 0, as: UInt8.self) - // } - // Only unclaimed bytes in frame, for unsafe types private var unsafeUnclaimedBuffer: UnsafeMutableRawBufferPointer? { switch buffer { From 2b716c27b79825280bf1f734478d382754982421 Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Wed, 29 Jul 2026 11:01:16 -0700 Subject: [PATCH 6/6] Build out FrameDeserializer a bit more --- Sources/SwiftNetwork/QUIC/PacketParser.swift | 11 ++- .../SwiftNetwork/Utilities/Deserializer.swift | 83 ++++++++++++++++- .../SwiftNetworkFrameDeserializerTests.swift | 92 +++++++++++++++++++ 3 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 Tests/SwiftNetworkTests/SwiftNetworkFrameDeserializerTests.swift diff --git a/Sources/SwiftNetwork/QUIC/PacketParser.swift b/Sources/SwiftNetwork/QUIC/PacketParser.swift index acd5ce6..f36a497 100644 --- a/Sources/SwiftNetwork/QUIC/PacketParser.swift +++ b/Sources/SwiftNetwork/QUIC/PacketParser.swift @@ -317,8 +317,11 @@ struct PacketParser: ~Copyable, PrefixedLoggable { } private func parseHeader(frame: inout Frame, dcidLength: Int) throws(QUICError) -> Packet { + var firstOctet: UInt8 = 0 let originalLength = frame.unclaimedLength - guard let firstOctet = try? InlineDeserializer.uint8(frame: &frame, claim: true) else { + do throws(DeserializationError) { + firstOctet = try FrameDeserializer.uint8(frame: &frame, claim: true) + } catch { throw QUICError.packet(QUICPacketError.deserializationError) } // Common short/long header bits @@ -520,14 +523,14 @@ struct PacketParser: ~Copyable, PrefixedLoggable { throw QUICError.packet(QUICPacketError.deserializationError) } var dcidStorage = QUICConnectionIDStorage.empty - guard - let _ = try? InlineDeserializer.connectionID( + do throws(DeserializationError) { + try FrameDeserializer.connectionID( frame: &frame, storage: &dcidStorage, length: Int(dcidLength), claim: true ) - else { + } catch { throw QUICError.packet(QUICPacketError.deserializationError) } return Packet( diff --git a/Sources/SwiftNetwork/Utilities/Deserializer.swift b/Sources/SwiftNetwork/Utilities/Deserializer.swift index 0c26502..a5e601e 100644 --- a/Sources/SwiftNetwork/Utilities/Deserializer.swift +++ b/Sources/SwiftNetwork/Utilities/Deserializer.swift @@ -74,10 +74,10 @@ public enum DeserializationResult: CustomStringConvertible, Equatable, Sendable @_spi(ProtocolProvider) @available(Network 0.1.0, *) -public struct InlineDeserializer {} +public struct FrameDeserializer {} @available(Network 0.1.0, *) -extension InlineDeserializer { +extension FrameDeserializer { @inline(__always) static func uint8(frame: inout Frame, claim: Bool = false) throws(DeserializationError) -> UInt8 { guard frame._bytes.count > 0 else { @@ -92,6 +92,81 @@ extension InlineDeserializer { return value } + @inline(__always) + static func uint16(frame: inout Frame, claim: Bool = false) throws(DeserializationError) -> UInt16 { + guard frame.startOffset + 2 <= frame._bytes.count else { + throw DeserializationError.bufferTooShort + } + let value = frame._bytes.span.bytes.unsafeLoadUnaligned( + fromByteOffset: frame.startOffset, + as: UInt16.self + ) + if claim { + guard frame.claim(fromStart: 2) else { + throw DeserializationError.bufferTooShort + } + } + return value + } + + @inline(__always) + static func uint16NetworkByteOrder( + frame: inout Frame, + claim: Bool = false + ) throws(DeserializationError) -> UInt16 { + UInt16(bigEndian: try uint16(frame: &frame, claim: claim)) + } + + @inline(__always) + static func uint32(frame: inout Frame, claim: Bool = false) throws(DeserializationError) -> UInt32 { + guard frame.startOffset + 4 <= frame._bytes.count else { + throw DeserializationError.bufferTooShort + } + let value = frame._bytes.span.bytes.unsafeLoadUnaligned( + fromByteOffset: frame.startOffset, + as: UInt32.self + ) + if claim { + guard frame.claim(fromStart: 4) else { + throw DeserializationError.bufferTooShort + } + } + return value + } + + @inline(__always) + static func uint32NetworkByteOrder( + frame: inout Frame, + claim: Bool = false + ) throws(DeserializationError) -> UInt32 { + UInt32(bigEndian: try uint32(frame: &frame, claim: claim)) + } + + @inline(__always) + static func uint64(frame: inout Frame, claim: Bool = false) throws(DeserializationError) -> UInt64 { + guard frame.startOffset + 8 <= frame._bytes.count else { + throw DeserializationError.bufferTooShort + } + let value = frame._bytes.span.bytes.unsafeLoadUnaligned( + fromByteOffset: frame.startOffset, + as: UInt64.self + ) + if claim { + guard frame.claim(fromStart: 8) else { + throw DeserializationError.bufferTooShort + } + } + return value + } + + @inline(__always) + static func uint64NetworkByteOrder( + frame: inout Frame, + claim: Bool = false + ) throws(DeserializationError) -> UInt64 { + UInt64(bigEndian: try uint64(frame: &frame, claim: claim)) + } + @inline(__always) static func connectionID( frame: inout Frame, @@ -111,6 +186,10 @@ extension InlineDeserializer { } } } + + static func claim(frame: inout Frame, length: Int) -> Bool { + frame.claim(fromStart: length) + } } @_spi(ProtocolProvider) diff --git a/Tests/SwiftNetworkTests/SwiftNetworkFrameDeserializerTests.swift b/Tests/SwiftNetworkTests/SwiftNetworkFrameDeserializerTests.swift new file mode 100644 index 0000000..07b7d1a --- /dev/null +++ b/Tests/SwiftNetworkTests/SwiftNetworkFrameDeserializerTests.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import XCTest + +#if canImport(SwiftNetwork) +@_spi(Essentials) @_spi(ProtocolProvider) @testable import SwiftNetwork +#elseif canImport(Network) +@_spi(Essentials) @_spi(ProtocolProvider) @testable import Network +#endif + +@available(Network 0.1.0, *) +final class SwiftNetworkFrameDeserializerTests: NetTestCase { + + func testUInt8InlineValue() throws { + var frame = Frame(copyBuffer: [0xAB] as [UInt8]) + defer { frame.finalize(success: false) } + do throws(DeserializationError) { + let value = try FrameDeserializer.uint8(frame: &frame, claim: true) + XCTAssertEqual(value, 0xAB) + } catch { + XCTFail("Unexpected deserialization error: \(error)") + } + } + + func testUInt8PeekDoesNotAdvanceOffset() throws { + var frame = Frame(copyBuffer: [0xCD, 0xEF] as [UInt8]) + defer { frame.finalize(success: false) } + do throws(DeserializationError) { + let firstUnclaimed = try FrameDeserializer.uint8(frame: &frame, claim: false) + let nextClaimed = try FrameDeserializer.uint8(frame: &frame, claim: true) + XCTAssertEqual(firstUnclaimed, 0xCD) + XCTAssertEqual(nextClaimed, 0xCD) + XCTAssertEqual(frame.unclaimedLength, 1) + } catch { + XCTFail("Unexpected deserialization error: \(error)") + } + } + + func testUInt64InlineValue() throws { + let bytes: [UInt8] = [0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41] + var frame = Frame(copyBuffer: bytes) + defer { frame.finalize(success: false) } + do throws(DeserializationError) { + let value = try FrameDeserializer.uint64(frame: &frame, claim: true) + XCTAssertEqual(value, 0x4141_4141_4141_4141) + XCTAssertEqual(frame.unclaimedLength, 0) + } catch { + XCTFail("Unexpected deserialization error: \(error)") + } + } + + func testUInt64NetworkByteOrderInlineValue() throws { + let bytes: [UInt8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + var frame = Frame(copyBuffer: bytes) + defer { frame.finalize(success: false) } + do throws(DeserializationError) { + let value = try FrameDeserializer.uint64NetworkByteOrder(frame: &frame, claim: true) + XCTAssertEqual(value, 0x0102_0304_0506_0708) + XCTAssertEqual(frame.unclaimedLength, 0) + } catch { + XCTFail("Unexpected deserialization error: \(error)") + } + } + + func testUInt64NetworkByteOrderThenUInt8Sequential() throws { + let bytes: [UInt8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x42] + var frame = Frame(copyBuffer: bytes) + defer { frame.finalize(success: false) } + do throws(DeserializationError) { + let high = try FrameDeserializer.uint64NetworkByteOrder(frame: &frame, claim: true) + let low = try FrameDeserializer.uint8(frame: &frame, claim: true) + XCTAssertEqual(high, 0x0000_0000_0000_00FF) + XCTAssertEqual(low, 0x42) + XCTAssertEqual(frame.unclaimedLength, 0) + } catch { + XCTFail("Unexpected deserialization error: \(error)") + } + } + +}