embassy-rs/embassy · error
Additional associated data must be processed first!
Error message
Additional associated data must be processed first!
What it means
During the payload phase, if AAD was supplied but not finalized (aad_complete false, header_len > 0), the driver panics: payload processing started while pending AAD is unfinished. The pending AAD's last block must be processed before payload blocks.
Solutions
- Pass last_aad_block=true on the final additional_header() call (or make AAD length a block multiple) before starting the payload.
- Verify every AAD byte was written — check header_len equals total AAD length.
- Restart with a fresh context if the phase state is already inconsistent.
Example fix
// before ctx.additional_header(aad, false); // never finalized ctx.payload(data, &mut out, false); // after ctx.additional_header(aad, true); ctx.payload(data, &mut out, false);
Defensive patterns
Strategy: type-guard
Validate before calling
if ctx.header_len > 0 && !ctx.aad_complete { /* finalize AAD before payload */ } Type guard
fn aad_ready_for_payload(ctx: &CipherContext) -> bool { ctx.aad_complete || ctx.header_len == 0 } Prevention
- Always mark the final AAD block with last=true
- Assert AAD totals match expected header length in protocol code
- Wrap header+payload phases in a helper that enforces ordering
When it happens
Trigger: Calling payload()/decrypt when header_len > 0 and aad_complete == false — e.g. additional_header() was called with last_aad_block=false and never finalized, then the payload phase starts.
Common situations: Forgetting last=true on the final additional_header() call; AAD not a block multiple with no final block; streaming AAD asynchronously and racing with payload start.
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
- Cannot update AAD after starting payload!
- 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/db222acdfd8989cc.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-stm32/src/cryp/mod.rs:1404
/// This function panics under various mismatches of parameters.
/// Output buffer must be at least as long as the input buffer.
/// Data must be a multiple of block size (128-bits for AES, 64-bits for DES) for CBC and ECB modes.
/// Padding or ciphertext stealing must be managed by the application for these modes.
/// Data must also be a multiple of block size unless `last_block` is `true`.
pub fn payload_blocking<'c, C: Cipher<'c> + CipherSized + IVSized>(
&self,
ctx: &mut Context<'c, C>,
input: &[u8],
output: &mut [u8],
last_block: bool,
) {
self.load_context(ctx);
let last_block_remainder = input.len() % C::BLOCK_SIZE;
// Perform checks for correctness.
if !ctx.aad_complete && ctx.header_len > 0 {
panic!("Additional associated data must be processed first!");
} else if !ctx.aad_complete {
#[cfg(any(cryp_v2, cryp_v3, cryp_v4))]
{
ctx.aad_complete = true;
T::regs().cr().modify(|w| w.set_crypen(false));
T::regs().cr().modify(|w| w.set_gcm_ccmph(2));
T::regs().cr().modify(|w| w.set_fflush(true));
T::regs().cr().modify(|w| w.set_crypen(true));
}
}
if ctx.last_block_processed {
panic!("The last block has already been processed!");
}
if input.len() > output.len() {
panic!("Output buffer length must match input length.");
}
if !last_block {
if last_block_remainder != 0 {View on GitHub (pinned to 463a07b963)