Skip to content
Open
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
4 changes: 4 additions & 0 deletions Sources/SwiftNetwork/Protocols/Frame.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public struct Frame: ~Copyable {
return effectiveBufferLength - (startOffset + endOffset)
}

@inline(__always)
public var span: Span<UInt8>? {
@_lifetime(borrow self)
get {
Expand All @@ -151,6 +152,7 @@ public struct Frame: ~Copyable {
}
}

@inline(__always)
public var bytes: RawSpan? {
@_lifetime(borrow self)
get {
Expand All @@ -167,6 +169,7 @@ public struct Frame: ~Copyable {
}
}

@inline(__always)
public var mutableSpan: MutableSpan<UInt8>? {
@_lifetime(&self)
mutating get {
Expand All @@ -187,6 +190,7 @@ public struct Frame: ~Copyable {
}
}

@inline(__always)
var allBytes: RawSpan? {
guard isValid else { return nil }
switch buffer {
Expand Down
80 changes: 57 additions & 23 deletions Sources/SwiftNetwork/Utilities/Deserializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
private var currentSpan: RawSpan
private var currentSpanByteCount = 0
private var availableByteCount: Int
private var scratchSpace = [16 of UInt8](repeating: 0)
private var scratchSpace: [16 of UInt8]? // Initialized lazily
private var cursor = 0
private var previousSpanAggregateByteCount = 0
private(set) var internalResult: DeserializationResult = .success
Expand All @@ -98,6 +98,16 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
self.refill()
}

// Simple initializer for single span case with empty span factory
@_lifetime(copy span)
init(_ span: RawSpan) where Factory == EmptySpanFactory {
let byteCount = span.byteCount
self.availableByteCount = byteCount
self.currentSpanByteCount = byteCount
self.currentSpan = span
self.factory = EmptySpanFactory()
}

/// Refills the deserializer with the next span from the factory, and resets the cursor and internal result so deserialization can continue.
///
/// - Returns: A Boolean value that indicates whether the factory had another span available; returns `false` when no more spans remain.
Expand All @@ -112,6 +122,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
currentSpanByteCount = currentSpan.byteCount
return true
}
@inline(__always)
private var remaining: Int {
// This will never be negative since cursor is only advanced
// by moveCursor, which checks to ensure that cursor never
Expand All @@ -138,36 +149,48 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
case .error: return internalResult
}
}

@inline(__always)
private func hasRoom(_ length: Int) -> Bool {
internalResult.isValid && remaining >= length
(currentSpanByteCount - cursor) >= length
}

mutating func invalidate(_ error: DeserializationError) throws(DeserializationError) -> Never {
internalResult = .error(error)
throw error
}

private mutating func moveCursor(_ amount: Int) throws(DeserializationError) {
guard amount <= remaining else {
try invalidate(.bufferTooShort)
}
@inline(__always)
private mutating func moveCursorUnchecked(_ amount: Int) {
// It is safe to always add the amount to the cursor, since the length
// was already checked. So, we use &+= which skips the more expensive
// overflow check.
cursor &+= amount
}

@inline(__always)
private mutating func moveCursor(_ amount: Int) throws(DeserializationError) {
guard amount <= remaining else {
try invalidate(.bufferTooShort)
}
moveCursorUnchecked(amount)
}

/// Reads a fixed-size value across span boundaries, using the stored scratch space and refilling as needed.
///
/// Call this method when `hasRoom` fails but `internalResult` is still valid.
private mutating func readFragmented<T: BitwiseCopyable>(_ value: inout T) throws(DeserializationError) {
let length = MemoryLayout<T>.size
precondition(length <= 16)
if scratchSpace == nil {
// Lazy initialize on first use
scratchSpace = .init(repeating: 0)
}
var filled = 0
while filled < length {
let available = min(remaining, length - filled)
for i in 0..<available {
scratchSpace[filled + i] = currentSpan[cursor + i]
scratchSpace![filled + i] = currentSpan[cursor + i]
}
try moveCursor(available)
filled += available
Expand All @@ -177,10 +200,11 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
}
}
}
value = scratchSpace.span.bytes.unsafeLoadUnaligned(as: T.self)
value = scratchSpace!.span.bytes.unsafeLoadUnaligned(as: T.self)
}

/// Reads a fixed-size value across span boundaries, with optional network-to-host byte order conversion.
@inline(__always)
private mutating func readFragmented<T: BitwiseCopyable & FixedWidthInteger>(
_ value: inout T,
networkByteOrder: Bool
Expand All @@ -195,19 +219,21 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
///
/// Reads a fixed-size `BitwiseCopyable` value, using the fast path when the current
/// span has enough data, or falling back to `readFragmented(_:)`.
@inline(__always)
private mutating func readFixedSize<T: BitwiseCopyable>(_ value: inout T) throws(DeserializationError) {
let length = MemoryLayout<T>.size
guard hasRoom(length) else {
try readFragmented(&value)
return
}
value = currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: T.self)
try moveCursor(length)
moveCursorUnchecked(length)
}

/// Reads an optional fixed-size, bitwise-copyable value.
///
/// Reads an optional fixed-size `BitwiseCopyable` value.
@inline(__always)
private mutating func readFixedSize<T: BitwiseCopyable & FixedWidthInteger>(
_ value: inout T?
) throws(DeserializationError) {
Expand All @@ -217,6 +243,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
}

/// Reads a fixed-size integer value with optional network-to-host byte order conversion.
@inline(__always)
private mutating func readFixedSize<T: BitwiseCopyable & FixedWidthInteger>(
_ value: inout T,
networkByteOrder: Bool
Expand All @@ -228,6 +255,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
}

/// Reads an optional fixed-size integer value with optional network-to-host byte order conversion.
@inline(__always)
private mutating func readFixedSize<T: BitwiseCopyable & FixedWidthInteger>(
_ value: inout T?,
networkByteOrder: Bool
Expand All @@ -249,7 +277,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
switch firstBits {
case 0:
let value = UInt64(currentSpan[cursor])
try moveCursor(MemoryLayout<UInt8>.size)
moveCursorUnchecked(MemoryLayout<UInt8>.size)
return (value, 1)
case 1:
var raw: UInt16 = 0
Expand Down Expand Up @@ -308,7 +336,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
try invalidate(.validationFailed)
}

try moveCursor(length)
moveCursorUnchecked(length)
}

public mutating func uint8(_ value: inout UInt8) throws(DeserializationError) {
Expand Down Expand Up @@ -529,7 +557,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
value = truncatedString
}

try moveCursor(byteCount)
moveCursorUnchecked(byteCount)
}

public mutating func fixedLengthUTF8(_ value: inout String?, byteCount: Int) throws(DeserializationError) {
Expand Down Expand Up @@ -571,7 +599,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
source.withUnsafeBytes { buffer in
value.append(contentsOf: buffer)
}
cursor &+= length
moveCursorUnchecked(length)
}

public mutating func span(expect value: RawSpan) throws(DeserializationError) {
Expand Down Expand Up @@ -625,7 +653,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
try invalidate(.validationFailed)
}

try moveCursor(length)
moveCursorUnchecked(length)
}

@_optimize(speed)
Expand Down Expand Up @@ -679,7 +707,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
}
}

try moveCursor(lengthToCopy)
moveCursorUnchecked(lengthToCopy)
}

public mutating func string(_ value: inout String) throws(DeserializationError) {
Expand Down Expand Up @@ -729,7 +757,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
return
}

try moveCursor(length)
moveCursorUnchecked(length)
}

private static func deserialize(
Expand Down Expand Up @@ -761,30 +789,36 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
}

public static func deserialize(
_ bytes: RawSpan,
_ span: RawSpan,
_ builder: (_ buffer: inout Deserializer) throws(DeserializationError) -> Void
) -> DeserializationResult where Factory == SingleSpanFactory {
deserialize(SingleSpanFactory(bytes), builder)
) -> DeserializationResult where Factory == EmptySpanFactory {
var deserializer = Deserializer(span)
do {
try builder(&deserializer)
} catch {
// Error already recorded in internalResult via invalidate
}
return deserializer.finalResult
}

public static func deserialize(
_ buffer: Span<UInt8>,
_ builder: (_ buffer: inout Deserializer) throws(DeserializationError) -> Void
) -> DeserializationResult where Factory == SingleSpanFactory {
) -> DeserializationResult where Factory == EmptySpanFactory {
deserialize(buffer.bytes, builder)
}

public static func deserialize(
_ bytes: [UInt8],
_ builder: (_ buffer: inout Deserializer) throws(DeserializationError) -> Void
) -> DeserializationResult where Factory == SingleSpanFactory {
) -> DeserializationResult where Factory == EmptySpanFactory {
deserialize(bytes.span.bytes, builder)
}

static func deserialize(
_ bytes: inout [UInt8],
_ builder: (_ buffer: inout Deserializer) throws(DeserializationError) -> Void
) -> DeserializationResult where Factory == SingleSpanFactory {
) -> DeserializationResult where Factory == EmptySpanFactory {
let result = deserialize(bytes.span.bytes, builder)
if case .success(let parsedBytes, _) = result {
bytes = Array(bytes[parsedBytes...])
Expand All @@ -797,7 +831,7 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
_ frame: inout Frame,
claim: Bool,
_ builder: (_ buffer: inout Deserializer) throws(DeserializationError) -> Void
) -> DeserializationResult where Factory == SingleSpanFactory {
) -> DeserializationResult where Factory == EmptySpanFactory {
var result: DeserializationResult = .success
if let bytes = frame.bytes {
result = deserialize(bytes, builder)
Expand Down
19 changes: 6 additions & 13 deletions Sources/SwiftNetwork/Utilities/SerializationHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,22 @@ public protocol DeserializerSpanFactory: ~Copyable, ~Escapable {
var availableByteCount: Int { get }
}

/// A factory that stores a single span.
/// A factory that stores no spans.
///
/// Use this factory when initializing a `Deserializer` directly from a `RawSpan`.
@_spi(ProtocolProvider)
@available(Network 0.1.0, *)
public struct SingleSpanFactory: ~Escapable, DeserializerSpanFactory {
private var span: RawSpan
private var consumed: Bool = false

@_lifetime(copy span)
init(_ span: RawSpan) {
self.span = span
}
public struct EmptySpanFactory: ~Escapable, DeserializerSpanFactory {
@_lifetime(immortal)
init() {}

@_lifetime(&self)
public mutating func nextSpan() -> RawSpan? {
guard !consumed else { return nil }
consumed = true
return span
nil
}

public var availableByteCount: Int {
span.byteCount
0
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftNetwork/Utilities/StreamDeserializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ extension StreamDeserializer where T: ~Copyable, Factory == FrameArraySpanFactor
}

@available(Network 0.1.0, *)
extension StreamDeserializer where T: ~Copyable, Factory == SingleSpanFactory {
extension StreamDeserializer where T: ~Copyable, Factory == EmptySpanFactory {
public mutating func handleSpan(_ span: RawSpan) throws(DeserializationError) -> T? {
try handleInputInternal(
{ builder, value in
Expand Down Expand Up @@ -523,5 +523,5 @@ public typealias FrameArrayStreamDeserializer<T: StreamDeserializerState & ~Copy
@_spi(ProtocolProvider)
@available(Network 0.1.0, *)
public typealias SpanStreamDeserializer<T: StreamDeserializerState & ~Copyable> = StreamDeserializer<
T, T.StateMachineStepIdentifier, SingleSpanFactory
T, T.StateMachineStepIdentifier, EmptySpanFactory
>
Loading