Skip to content

Reduce and isolate unsafe code in TLS record layer - #19

Merged
kpodosin merged 4 commits into
apple:mainfrom
FranzBusch:pr-18-review
Oct 13, 2026
Merged

kpodosin merged 4 commits into
apple:mainfrom
FranzBusch:pr-18-review

Conversation

@FranzBusch

Copy link
Copy Markdown
Member

This PR reduces and isolates the unsafe code introduced in #18 to a minimum. This helps with ensuring that the TLS record layer implementation is as safe as possible.

This PR reduces and isolates the unsafe code introduced in apple#18 to a minimum. This helps with ensuring that the TLS record layer implementation is as safe as possible.

@kpodosin kpodosin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

}
initializedCount = bytes.byteCount
self.init(capacity: bytes.byteCount) { output in
output.append(contentsOf: bytes)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since append still has an unsafe operation and iterates in a for loop, is it worth changing this? Versus the single unsafe memcpy here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or could we change the append implementation to avoid a loop?

claude's suggestion is this, but I haven't checked if it works:

@available(SwiftTLS 0.1.0, *)
extension OutputSpan where Element == UInt8 {
    mutating func append(contentsOf bytes: RawSpan) {
        guard !bytes.isEmpty else { return }
        append(contentsOf: bytes.unsafeView(as: UInt8.self))   // Span<UInt8>, bulk overload
    }
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggestion doesn't work because unsafeView is not real public API and there are no other bulk operation methods on the spans right now. I did some measurement and on release builds the loop is fully vectorized and equal in performance to a memcpy. However, in debug builds it is significantly slower. Now while we don't optimize for debug build performance I think this here is important enough to also ensure it is fast in debug builds. I pushed a commit that switches back to memcpy

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some measurement

If there are specific use cases that would make sense to model, we could make benchmarking reproducible with https://github.com/ordo-one/benchmark

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we should totally setup benchmarking here. I have another follow up PR that I would like to open at some point that allows us to produce record views with ~Escapable types to avoid copying where possible.

@baumanl baumanl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, left a few comments but not blocking.

@KushalP KushalP left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor suggestions.

Comment thread Sources/ArraySpanUtilities.swift
Comment thread Sources/ArraySpanUtilities.swift
Comment thread Sources/ArraySpanUtilities.swift Outdated
Comment thread Sources/ArraySpanUtilities.swift Outdated
Comment thread Sources/ArraySpanUtilities.swift Outdated
Comment thread Sources/RecordProtocol/TLSCiphertextRecord.swift Outdated
Comment thread Sources/RecordProtocol/TLSCiphertextRecord.swift Outdated
@FranzBusch

Copy link
Copy Markdown
Member Author

@tfpauly @kpodosin can we get this merged?

// place. The tag is copied into stack storage so that it doesn't
// overlap the buffer being decrypted.
var message = [UInt8](copying: encryptedRecord.extracting(0..<ciphertextLength))
let tag = [16 of UInt8](copying: encryptedRecord.extracting(ciphertextLength..<encryptedRecord.byteCount))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we typealias [16 of UInt8] to make this more readable?

@kpodosin kpodosin added the 🔨 semver/patch No public API change. label Oct 9, 2026
@FranzBusch
FranzBusch requested a review from kpodosin October 13, 2026 12:55
@kpodosin
kpodosin merged commit 7325011 into apple:main Oct 13, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants