embassy-rs/embassy · error
Cannot update AAD after starting payload!
Error message
Cannot update AAD after starting payload!
What it means
The CRYP AES driver enforces GCM/CCM phase ordering: once the AAD (header) phase is complete (aad_complete set, payload phase started), additional_header() can no longer be called. AAD must be fully supplied before any payload data; interleaving AAD after payload is not supported by this hardware flow.
Solutions
- Collect all AAD first and call additional_header() for all of it (last block flagged), then start the payload phase.
- Buffer payload bytes until AAD is complete if AAD arrives late.
- Use a fresh cipher context — phase state cannot be rewound.
Example fix
// before ctx.additional_header(aad1, false); ctx.payload(chunk, &mut out, false); ctx.additional_header(aad2, true); // panics // after ctx.additional_header(aad1, false); ctx.additional_header(aad2, true); ctx.payload(chunk, &mut out, false);
Defensive patterns
Strategy: type-guard
Validate before calling
if !ctx.aad_complete { ctx.additional_header(more_aad, false); } else { /* buffer or error */ } Type guard
fn can_add_aad(ctx: &CipherContext) -> bool { !ctx.aad_complete } Prevention
- Structure AEAD usage AAD-first, payload-second
- Track AAD completion in your own protocol code
- Do not expose interleaved-AAD APIs over this sequential driver
When it happens
Trigger: Calling additional_header() on a context after the payload phase has begun (ctx.aad_complete == true), e.g. appending more AAD after encrypting a chunk.
Common situations: Protocols that interleave header and body; AEAD callers using frameworks that allow AAD/payload interleaving mapped onto this sequential driver; reusing a context across calls without tracking phase.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Additional associated data must be processed first!
- Message is too large for given IV size.
- The last block has already been processed!
- Output buffer length must match input length.
- Input length must be a multiple of
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/b0436fb1dc398581.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-stm32/src/cryp/mod.rs:1306
/// This function is only valid for authenticated ciphers including GCM, CCM, and GMAC.
/// All additional associated data (AAD) must be supplied to this function prior to starting the payload phase with `payload_blocking`.
/// The AAD must be supplied in multiples of the block size (128-bits for AES, 64-bits for DES), except when supplying the last block.
/// When supplying the last block of AAD, `last_aad_block` must be `true`.
pub fn aad_blocking<
'c,
const TAG_SIZE: usize,
C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated<TAG_SIZE>,
>(
&self,
ctx: &mut Context<'c, C>,
aad: &[u8],
last_aad_block: bool,
) {
self.load_context(ctx);
// Perform checks for correctness.
if ctx.aad_complete {
panic!("Cannot update AAD after starting payload!")
}
ctx.header_len += aad.len() as u64;
// Header phase
T::regs().cr().modify(|w| w.set_crypen(false));
T::regs().cr().modify(|w| w.set_gcm_ccmph(1));
T::regs().cr().modify(|w| w.set_crypen(true));
// First write the header B1 block if not yet written.
if !ctx.header_processed {
ctx.header_processed = true;
let header = ctx.cipher.get_header_block();
ctx.aad_buffer[0..header.len()].copy_from_slice(header);
ctx.aad_buffer_len += header.len();
}
// Fill the header block to make a full block.View on GitHub (pinned to 463a07b963)