perf(crypto): stop heap-allocating a cipher object per packet (#11462)

* perf(crypto): stop heap-allocating a cipher object per packet

encryptAESCtr() constructed a fresh CTR<AES128/256> 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 <tgoettgens@gmail.com>
This commit is contained in:
Ben Meadors
2026-08-13 09:27:05 +02:00
committed by GitHub
co-authored by Thomas Göttgens
parent a65d9aef39
commit fa031c95dc
+14 -5
View File
@@ -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<CTRCommon> ctr;
if (_key.length == 16)
ctr = std::unique_ptr<CTRCommon>(new CTR<AES128>());
else
ctr = std::unique_ptr<CTRCommon>(new CTR<AES256>());
// 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<AES128> *ctr128 = nullptr;
static CTR<AES256> *ctr256 = nullptr;
CTRCommon *ctr;
if (_key.length == 16) {
if (!ctr128)
ctr128 = new CTR<AES128>();
ctr = ctr128;
} else {
if (!ctr256)
ctr256 = new CTR<AES256>();
ctr = ctr256;
}
ctr->setKey(_key.bytes, _key.length);
static uint8_t scratch[MAX_BLOCKSIZE];
memcpy(scratch, bytes, numBytes);