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

  1. Pass last_aad_block=true on the final additional_header() call (or make AAD length a block multiple) before starting the payload.
  2. Verify every AAD byte was written — check header_len equals total AAD length.
  3. 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

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


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)