From fa031c95dce1f8a26b47c755f19b02d25e2529ed Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 13 Aug 2026 02:27:05 -0500 Subject: [PATCH] perf(crypto): stop heap-allocating a cipher object per packet (#11462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(crypto): stop heap-allocating a cipher object per packet encryptAESCtr() constructed a fresh CTR on the heap for every call - once per encrypted transmit and once per channel decrypt attempt on every received encrypted packet. On the platforms that use this base implementation (STM32WL, RP2040, nRF54L15, portduino) that is avoidable per-packet malloc/free churn on small heaps. Reuse lazily-created singletons instead. Safe for the same reason the function's static scratch buffer already is: every caller serializes under cryptLock, and setKey/setIV reinitialize the cipher state each call. Lazy heap pointers rather than static objects so ESP32/nRF52 (which override this method) never reserve the RAM. * Improve comments in encryptAESCtr function Refactor comments for clarity and conciseness in AES-CTR encryption implementation. --------- Co-authored-by: Thomas Göttgens --- src/mesh/CryptoEngine.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index f2c966cc7..bd199e8fd 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -403,11 +403,20 @@ void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes // Generic implementation of AES-CTR encryption. void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) { - std::unique_ptr ctr; - if (_key.length == 16) - ctr = std::unique_ptr(new CTR()); - else - ctr = std::unique_ptr(new CTR()); + // Reused instead of reallocated per packet: safe because all callers hold cryptLock and setKey/setIV reset the + // full cipher state. Lazy so overriding platforms reserve nothing; key material now lives until the next call. + static CTR *ctr128 = nullptr; + static CTR *ctr256 = nullptr; + CTRCommon *ctr; + if (_key.length == 16) { + if (!ctr128) + ctr128 = new CTR(); + ctr = ctr128; + } else { + if (!ctr256) + ctr256 = new CTR(); + ctr = ctr256; + } ctr->setKey(_key.bytes, _key.length); static uint8_t scratch[MAX_BLOCKSIZE]; memcpy(scratch, bytes, numBytes);