feat: remove Rc<RefCell<Context>> from hp::Key#50
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors QUIC header-protection key handling (hp::Key) to avoid Rc<RefCell<Context>> by storing the AES SymKey and (re)creating the AES-ECB PK11Context lazily after cloning, which removes RefCell::borrow_mut overhead on each mask() call.
Changes:
- Replace
Key::Aes(Rc<RefCell<Context>>)withKey::Aes { ctx: Option<Context>, key: SymKey }and implement a customClonethat references theSymKeyand resetsctx. - Change
Key::maskto take&mut selfand lazily initialize the AES context whenctxisNone. - Update the HP tests to use mutable keys when calling
mask().
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/hp.rs | Refactors AES HP key storage/clone behavior and makes mask() mutable with lazy AES context initialization. |
| tests/hp.rs | Adjusts tests to accommodate mask(&mut self, ...) and validates cloned keys still produce identical masks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Clean, well-motivated change. Replacing Rc<RefCell<Context>> with owned Option<Context> + SymKey is the right trade-off — it removes interior-mutability overhead, eliminates the RefCell::borrow_mut on every mask call, and makes the API honestly &mut self. The lazy context recreation via make_aes_ctx is correct, and the switch from SECItemBorrowed::wrap(&ZERO[..0]) to SECItemBorrowed::make_empty() is a nice cleanup.
No correctness or security issues found. Two minor style suggestions inline (neither blocking). One additional note:
Re @Copilot's observation: since Key is a pub enum with public variants, callers can construct Key::Aes { ctx: None, .. } directly. The doc on line 60 ("ctx is None only between clone() and the first call to mask") is therefore not an enforceable invariant. Consider rewording to "mask lazily initializes ctx when it is None" to match the actual guarantee. Making the variants #[non_exhaustive] would harden this further, though it may be overkill for the current consumer base.
There was a problem hiding this comment.
Well-motivated change. Replacing Rc<RefCell<Context>> with owned Option<Context> + SymKey is correct: it removes interior-mutability overhead, honestly expresses the &mut self requirement, and the lazy context recreation via make_aes_ctx is sound. The version bump to 0.10.0 is appropriate for the &self → &mut self breaking change on mask.
Verified:
- Thread safety:
ContextandSymKeyare!Send/!Sync(raw pointers fromscoped_ptr!), same as the oldRc-based type — no auto-trait regression. - Clone correctness:
SymKey::cloneusesPK11_ReferenceSymKey(O(1) ref-count bump);Contextintentionally has noClone, so lazy recreation is the right approach. - Error semantics: If
make_aes_ctxfails after clone,ctxstaysNoneand the nextmaskcall will retry — correct behaviour for transient NSS errors. - SAFETY: The "Fixes" commit adopted the earlier suggestion to ground the
unsafeargument in aliasing (Derefyields a pointer copy,&mut selfguarantees exclusion) rather than AES-ECB statefulness. This is the right framing.
Two minor style suggestions inline (neither blocking). The Option::insert cleanup from the prior review round looks good.
There was a problem hiding this comment.
Clean, well-motivated change. Replacing Rc<RefCell<Context>> with owned Option<Context> + SymKey is the right trade-off: it removes interior-mutability overhead, makes the &mut self requirement honest, and the lazy context recreation via make_aes_ctx is sound. All feedback from prior review rounds has been addressed (SAFETY comment rewording, Option::insert, doc trimming, #[non_exhaustive]).
A few notes on the overall shape:
-
#[non_exhaustive]is an additional API break beyond the&self→&mut selfchange onmask. It prevents external construction and exhaustive destructuring of both variants. This is the right call for a crypto API (prevents constructing aKey::Aeswith arbitrary state), but the PR description only mentions themasksignature change. Worth calling out in release notes since downstream code that pattern-matches onKey::Chacha(k)will also need updating. -
Version bump to 0.10.0 is appropriate for the breaking changes.
-
Test coverage is adequate:
hp_testexercises the normal path, lazy-init-after-clone path, and continued use of the original key post-clone, across all three cipher suites.
Two minor doc/style suggestions inline — neither blocking.
martinthomson
left a comment
There was a problem hiding this comment.
I'm going to suggest that this attempt to protect the clone() implementation is an error. Instead, I suggest that you remove the Option around the context and make a fallible clone method. We only invoke the clone in one place in neqo and that could be replaced by a fallible function.
Then, you can construct the context when creating the thing. And when duplicating it. That one place in next() where clone() is called can be replaced with an invocation of the fallible duplication function. Then the mask function doesn't need &mut self and we're good.
Replace `Aes(Rc<RefCell<Context>>)` with `Aes { ctx: Option<Context>, key: SymKey }`,
eliminating the `RefCell::borrow_mut` overhead on every call to `Key::mask`.
`PK11_CloneContext` is not supported for AES-ECB contexts, so `Clone` stores
the `SymKey` (via `PK11_ReferenceSymKey`, O(1)) and sets `ctx` to `None`.
The context is recreated lazily on the first call to `mask` after a clone,
surfacing any NSS error there rather than panicking in `Clone`.
`mask` now takes `&mut self`; neqo will need a corresponding update.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
699ca9d to
474fad0
Compare
Replace
Aes(Rc<RefCell<Context>>)withAes { ctx: Option<Context>, key: SymKey }, eliminating theRefCell::borrow_mutoverhead on every call toKey::mask.PK11_CloneContextis not supported for AES-ECB contexts, soClonestores theSymKey(viaPK11_ReferenceSymKey, O(1)) and setsctxtoNone. The context is recreated lazily on the first call tomaskafter a clone, surfacing any NSS error there rather than panicking inClone.masknow takes&mut self; neqo will need a corresponding update.