Conversation
`Protector` derives every packet protection key, IV and header protection key with an HKDF expansion whose label it encodes first. The encoding went into a fresh array each time, though it only has to last for the one expansion, and a connection derives around two dozen of them across its initial, handshake and application keys. `withEncodedLabel` builds it in temporary storage instead. Measured on my Mac against `main` with the package's QUIC benchmark tools. Allocation counts come from full malloc stack logging, which records every allocation: QUICTransfer -size 1200, per message 24.37 -> 24.34 QUICTransfer, per 500 KB transfer 6,187.6 -> 6,182.4 QUICHandshake, per connection 1,947.4 -> 1,892.0 QUICStreamLoad, per stream 111.8 -> 112.0 The expansions themselves still allocate inside CryptoKit. Wall-clock time comes from running `main`, this change and the other changes measured alongside it in a rotating order for 9 rounds, and comparing each run with `main`'s in the same round. Changes moved paths they do not touch by up to about 1.3%, so differences that size count as noise. Nothing changed beyond noise.
rnro
requested review from
agnosticdev,
kkuk24,
rpaulo and
tfpauly
as code owners
October 1, 2026 19:54
rpaulo
reviewed
Oct 2, 2026
| result[index] = 0 | ||
|
|
||
| return result | ||
| return withUnsafeTemporaryAllocation(byteCount: totalLength, alignment: 1) { result in |
Contributor
There was a problem hiding this comment.
To clarify: there's a maximum size of the label, so I thought we could use InlineArray.
Contributor
There was a problem hiding this comment.
On the other hand, withUnsafeTemporaryAllocation() may already be allocating on the stack, so 🤷🏼
Contributor
Author
There was a problem hiding this comment.
withUnsafeTemporaryAllocation does allocate on the stack, but it is 'unsafe'. Let me investigate the InlineArray
rpaulo
approved these changes
Oct 2, 2026
TLS caps an HKDF label at 255 bytes, so the encoding fits a fixed 259-byte inline array, and building it no longer writes through an unsafe buffer: every store is bounds-checked. Only handing the finished label to `HKDF.expand`, which takes `DataProtocol`, still reads it through a scoped unsafe buffer. The allocation count is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Protectorderives every packet protection key, IV and header protection keywith an HKDF expansion whose label it encodes first. The encoding went into a
fresh array each time, though it only has to last for the one expansion, and a
connection derives around two dozen of them across its initial, handshake and
application keys.
withEncodedLabelbuilds it in temporary storage instead.Measured on my Mac against
mainwith the package's QUIC benchmark tools.Allocation counts come from full malloc stack logging, which records every
allocation:
The expansions themselves still allocate inside CryptoKit.
Wall-clock time comes from running
main, this change and the other changesmeasured alongside it in a rotating order for 9 rounds, and comparing each run
with
main's in the same round. Changes moved paths they do not touch by up toabout 1.3%, so differences that size count as noise. Nothing changed beyond
noise.