{"record":{"id":"253f2a5d97c7b1f0","repo":"embassy-rs/embassy","slug":"the-last-block-has-already-been-processed","errorCode":null,"errorMessage":"The last block has already been processed!","messagePattern":"The last block has already been processed!","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"embassy-stm32/src/cryp/mod.rs","lineNumber":1416,"sourceCode":"        self.load_context(ctx);\n\n        let last_block_remainder = input.len() % C::BLOCK_SIZE;\n\n        // Perform checks for correctness.\n        if !ctx.aad_complete && ctx.header_len > 0 {\n            panic!(\"Additional associated data must be processed first!\");\n        } else if !ctx.aad_complete {\n            #[cfg(any(cryp_v2, cryp_v3, cryp_v4))]\n            {\n                ctx.aad_complete = true;\n                T::regs().cr().modify(|w| w.set_crypen(false));\n                T::regs().cr().modify(|w| w.set_gcm_ccmph(2));\n                T::regs().cr().modify(|w| w.set_fflush(true));\n                T::regs().cr().modify(|w| w.set_crypen(true));\n            }\n        }\n        if ctx.last_block_processed {\n            panic!(\"The last block has already been processed!\");\n        }\n        if input.len() > output.len() {\n            panic!(\"Output buffer length must match input length.\");\n        }\n        if !last_block {\n            if last_block_remainder != 0 {\n                panic!(\"Input length must be a multiple of {} bytes.\", C::BLOCK_SIZE);\n            }\n        }\n        if C::REQUIRES_PADDING {\n            if last_block_remainder != 0 {\n                panic!(\n                    \"Input must be a multiple of {} bytes in ECB and CBC modes. Consider padding or ciphertext stealing.\",\n                    C::BLOCK_SIZE\n                );\n            }\n        }\n        if last_block {","sourceCodeStart":1398,"sourceCodeEnd":1434,"githubUrl":"https://github.com/embassy-rs/embassy/blob/463a07b963419a1bfe61d5d597c44acb810afb8b/embassy-stm32/src/cryp/mod.rs#L1398-L1434","documentation":"The payload phase refuses further blocks once the final block has been processed: after a call with last_block=true sets ctx.last_block_processed, any subsequent payload call panics. This protects the tag computation and CBC/ECB tail handling from corruption by post-final data.","triggerScenarios":"Calling payload()/decrypt again after a call with last_block=true; accidentally passing last_block=true on a non-final chunk then continuing; restoring a saved context whose last_block_processed flag is already set.","commonSituations":"Streaming loops that don't break after the final chunk; off-by-one chunking producing a trailing empty call; retry logic re-invoking payload on a finished context.","solutions":["Stop calling payload() once a last_block=true call returns; finalize and drop the context.","Fix chunking so last_block=true is passed only for the actual final chunk.","Create a new cipher context if more data genuinely remains."],"exampleFix":"// before\nctx.payload(final_chunk, &mut out, true);\nctx.payload(leftover, &mut out2, true); // panics\n// after\nctx.payload(final_chunk, &mut out, true);\n// verify tag; do not reuse ctx","handlingStrategy":"type-guard","validationCode":"if !ctx.last_block_processed { ctx.payload(chunk, &mut out, is_last); }","typeGuard":"fn can_process_payload(ctx: &CipherContext) -> bool { !ctx.last_block_processed }","tryCatchPattern":null,"preventionTips":["Compute last_block once from the chunk index in streaming loops","Consume the context (move into a finalizer) after the last block","Never retry payload calls without resetting state"],"tags":["embedded","crypto","cryp","panic","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"463a07b963419a1bfe61d5d597c44acb810afb8b","analyzedAt":"2026-09-10T13:38:26.660Z","contentChangedAt":"2026-09-10T13:38:26.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}