Skip to content

WifiClientSecure runs out of memory after multiple MQTT publish operations when WiFiClientSecure::loadCACert is used #9636

Closed
@the-butcher

Description

@the-butcher

Board

featheresp32-s2

Device Description

Adafruit ESP32-S2 Feather with BME280 Sensor
https://www.adafruit.com/product/5303

platformio.ini as follows:

[env:featheresp32-s2]
platform = espressif32
board = featheresp32-s2
framework = arduino
board_build.f_cpu = 80000000L
board_build.f_flash = 80000000L
board_build.flash_mode = qio
board_build.partitions = min_spiffs.csv
monitor_speed = 115200
lib_deps =
adafruit/Adafruit NeoPixel@^1.12.0
adafruit/Adafruit BusIO@^1.15.0
SPI@^2.0.0
adafruit/SdFat - Adafruit Fork@^2.2.3
sensirion/Sensirion I2C SCD4x@^0.4.0
adafruit/Adafruit EPD@^4.5.4
adafruit/Adafruit BME280 Library@^2.2.4
adafruit/Adafruit LC709203F@^1.3.4
adafruit/RTClib@^2.1.3
bblanchon/ArduinoJson@5.13.4
ottowinter/ESPAsyncWebServer-esphome@^3.1.0
ricmoo/QRCode@^0.0.1
knolleary/PubSubClient@^2.8
build_flags = -DCORE_DEBUG_LEVEL=0

Hardware Configuration

Yes

  • Adafruit 2.9" Grayscale eInk / ePaper Display FeatherWing
  • Adafruit SCD-41 - True CO2 Temperature and Humidity Sensor
  • Adafruit PCF8523 Real Time Clock Breakout Board
    image

Version

other

IDE Name

PlatformIO

Operating System

Windows 10

Flash frequency

80mHz

PSRAM enabled

no

Upload speed

not specified

Description

A CO2 measuring device tries to publish it's measurements through MQTT in certain intervals, i.e. 3 minutes. The device deep-sleeps between measurements, therefore WifiClient and PubSubClient need to be instantiated and destroyed every time new measurements need to be published.

Since the user can upload the mqtt broker certificate it needs to be kept in a file on SD-card. Each tim a new instance of WifiClientSecure is created, the certificate file is opened and passed to that client through:
WiFiClientSecure::loadCACert(Stream& stream, size_t size)

ESP.getFreeHeap() returns ~70000bytes when first measurements are published, but reduces with each publish operation, until at ~60.000bytes the following error shows:

[start_ssl_client():273]: (-32512) SSL - Memory allocation failed

With SSL not correctly set up any more, further connections fail and subsequently mqtt publish fails too.


Workaround: free heap roughly reduces by the size of the certificate file each time. Looking at the code of WifiClientSecure it seems that a certificate, once loaded, never is freed when the client instance is destroyed.

When loading the certificate manually, then using.
WiFiClientSecure::setCACert (const char *rootCA)
rather than
WiFiClientSecure::loadCACert(Stream& stream, size_t size)
and freeing the loaded data before destroying the client, the memory leak appears to be fixed.

Sketch

//--------------------------------------------------------------------------------
// first solution (memory leak)

// instantiate wifi-client
WiFiClient* wifiClient;
char* certFileData = NULL;
if (crt != "" && ModuleCard::existsPath(crt)) {
	wifiClient = new WiFiClientSecure();
	File32 certFile;
	certFile.open(crt, O_RDONLY);
	((WiFiClientSecure*)wifiClient)->loadCACert(certFile, certFile.size());
	certFile.close();
} else {
	wifiClient = new WiFiClient();
}

// instantiate mqtt-client
PubSubClient* mqttClient;
mqttClient = new PubSubClient(*wifiClient);
mqttClient->setServer(srv, prt);
if (usr != "" && pwd != "") {
	mqttClient->connect(cli, usr, pwd, 0, 0, 0, 0, 0);
} else {
	mqttClient->connect(cli);
}

if (mqttClient->connected()) {
	
	// mqtt publishing happening here, around 100bytes/measurement, 300bytes total
	
	mqttClient->disconnect();  // calls stop() on wificlient
	
}

// redundant call to wifi-client stop
wifiClient->stop();

delete mqttClient; // releases some buffer
delete wifiClient; // calls stop (again) and deletes an internal sslclient instance
mqttClient = NULL;
wifiClient = NULL;

//--------------------------------------------------------------------------------
// alternative solution (no memory leak)

// instantiate wifi-client
WiFiClient* wifiClient;
char* certFileData = NULL;
if (mqtt.crt != "" && ModuleCard::existsPath(mqtt.crt)) {
	wifiClient = new WiFiClientSecure();
	File32 certFile;
	certFile.open(mqtt.crt, O_RDONLY);
	certFileData = (char*)malloc(certFile.size() + 1);
	certFile.readBytes(certFileData, certFile.size());
	((WiFiClientSecure*)wifiClient)->setCACert(certFileData);
	certFile.close();
} else {
	wifiClient = new WiFiClient();
}

// instantiate mqtt-client
PubSubClient* mqttClient;
mqttClient = new PubSubClient(*wifiClient);
mqttClient->setServer(mqtt.srv, mqtt.prt);
if (mqtt.usr != "" && mqtt.pwd != "") {
	mqttClient->connect(mqtt.cli, mqtt.usr, mqtt.pwd, 0, 0, 0, 0, 0);
} else {
	mqttClient->connect(mqtt.cli);  // connect without credentials
}

if (mqttClient->connected()) {

	// mqtt publishing happening here, around 100bytes/measurement, 300bytes total

	mqttClient->disconnect();  // calls stop() on wificlient
	
}

// redundant call to wifi-client stop
wifiClient->stop(); 
if (certFileData != NULL) {
	free(const_cast<char*>(certFileData));  // free the memory used to hold the certificate
	certFileData = NULL;
}

delete mqttClient;  // releases some buffer
delete wifiClient;  // calls stop (again) and deletes an internal sslclient instance
mqttClient = NULL;
wifiClient = NULL;

Debug Message

[start_ssl_client():273]: (-32512) SSL - Memory allocation failed

Other Steps to Reproduce

No response

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions