0

So i have the code below. If i directly call signKey() function in the loopTask, the free heap is stable. However if i use that function to create task using xTaskCreate(), the free heap is always decreasing. Am i missing something?

Here is the output on the Serial Monitor using direct call:

FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312
FreeHeap: 358312

Here is the output on the Serial Monitor using task:

FreeHeap: 338364
FreeHeap: 337776
FreeHeap: 337228
FreeHeap: 336632
FreeHeap: 336080
FreeHeap: 335508
FreeHeap: 334960
FreeHeap: 334372
FreeHeap: 333812
FreeHeap: 333220
FreeHeap: 332672
FreeHeap: 332108
FreeHeap: 331552
FreeHeap: 330964
FreeHeap: 330416
FreeHeap: 329856
FreeHeap: 329304
FreeHeap: 328716
FreeHeap: 328152

And here is the code:

#include "mbedtls/pk.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"

static String privateKey = "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCID48Vp/9ydCMU\nzZ4n4iQ6pzr6eK+VCL7LcpY8j/tBwLWk3jZ3ETmMm6Ua4MhUMc3ABN84uz0RgDi9\nb4RhOUy2SVbvfm5WZhlvpPmDD0yeTgWEyM3h3fBYPnRzMn/6KjsmCUgcfTWjdA+C\nj9vTL41j7bVCkU0/glBh5Rk3rKeImeX/lf4KfstguvJ+OlznCn039+tDlsCNrRgw\ni+L2AeI4UDZEDGWYRbONzU2WgJ11fr9J+Y6rSCwommDcxhdTtdlorZj2CFEtxRp5\nFCDm6Tvu0+aur8zpuhC+NFUWmYEHxztz18X1I4Pcy5cJpuqKL5t/Hxy5Yu7y32Ek\nE8DZv1DFAgMBAAECggEBAIBF+uW13tSuvSwttf9v6iwJ4UamZRKijg4MV6t9KqoQ\n3q8yeDLE4Ha5fmzaosMNuSZg8XnwvGA1fEjMTAfFF5d7iSR9E9UMqMpixIFU+Sz9\n7aIEFmXs8VygdPTuFU1qZx0y/vMs8FbLYpv6uIpfeHNPdeXuSt+nIdVJQf8FHWVg\nH7EJHJPCh0SoQZHQhV0M/n7K6iacXdC2k4AW2f2KtwPBOV19S+4Ymq7S6ycIyghd\nmMzMhWLgZYoPycMYQRDErEZOSHQHs+BqvKYqp/UVJNKAVihiqYXHmT8hnWMaaj8d\nQzVmcsq7dZO7vX+0Zfjd+krj9gbpCG30ESeNOx5sLAECgYEA0wDiGFyFAu6fVhm5\np4pHhGhFK765Ys3b5bapEgD1uN92BttPat2Qj11TKGdAERa8X0uuBB8McGcWUcvV\nFUIKYE0qj4k/oo7OEw5KmrQVlQLsWv4rls8XTEJcUnoq2lCTXZEfuwEJFBkk4Bkb\nhioZE3CU0cV/Jg78vl37SJZbE4ECgYEApRNmRPCDWH8Jeo7OSqgee8Qy46R+JpBb\nmaaQ9dCO8pp5MYLQSA4C4BeHEpKq4v6C3HL7gf+Z6N/6/WU1X1bqDtXPdito5Zxc\nQ0Sa6URIPu7/txNU4Nc4GOfB6nG0/bxiQRDkxf7Kwk2E8xpmoCAQwDhg58Y1k61q\nO/iTjIJOj0UCgYEAo7yDtrPU47mYG5BK6R/871qakp+l7G4ivddIy5fDFnsRc7Cr\nqBnXG+knpqq4pIooEyr/FmOhm3fjcgXijGR6+M/ovwmaP+LhNxhX/ETSmpdyIgoq\neRSq15qHWdlDd7YfJPSA0vSyvs3kN6JEIZB5dQRf94hyam4m4vK7FFDYzAECgYEA\nlBiva7ILZF20d0ufL8NcddUzgp+UvaxNQa/55U7SsDx99jlR+xL26WyyNat3vGZx\nqK1PjvVtc0tete8SzxH+soiHs5CGb1i0PXVTNWuZFTz+FZU2VPFA1rc1dcvFgM59\np7osRKWt6lv5ptBMueOKo6jw538fmfm+kUcVuL0/FbECgYBx9ADNvyUaPq/3rQEa\n4Nzp+yyBL8r7iguLvI8EAYbCil9lACL+xXtScQ7mCY8EW8D8w/0cqA3Wjamb5ntS\n4T9Id1Iqq6MvzgJlTjNAYrsgoEC+fmU8iPnHNjxXrf74j4Mlh9pm5j6yeYsHQnMI\nv60FP7rcRiL2XnDJ5/ev/BWXaQ==\n-----END PRIVATE KEY-----";
static bool isTask = false;
static bool printSerial = false;

const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

static String base64_encode(const uint8_t *bytesToEncode, uint16_t len) {
  String ret;
  int i = 0;
  int j = 0;
  unsigned char char_array_3[3];
  unsigned char char_array_4[4];

  while (len--) {
    char_array_3[i++] = *(bytesToEncode++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;
      for (i = 0; (i < 4); i++) {
        ret += base64_chars[char_array_4[i]];
      }
      i = 0;
    }
  }

  if (i) {
    for (j = i; j < 3; j++) {
      char_array_3[j] = '\0';
    }
    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;
    for (j = 0; (j < i + 1); j++) {
      ret += base64_chars[char_array_4[j]];
    }
  }
  return ret;
}

static String base64_encode(String str) {
  return base64_encode((const unsigned char *) str.c_str(), str.length());
}

uint8_t buffer[2000];
static void signKey(void *parameter) {
  String header = "{\"typ\":\"JWT\",\"alg\":\"RS256\"}";
  String payload = "{\"payload\":\"my-payload\"}";

  String content = base64_encode(header) + "." + base64_encode(payload);

  mbedtls_pk_context pk_context;
  mbedtls_pk_init(&pk_context);
  int rc = mbedtls_pk_parse_key(&pk_context, (uint8_t*)privateKey.c_str(), privateKey.length() + 1, NULL, 0);
  if (rc != 0) {
    mbedtls_pk_free(&pk_context);
    if (printSerial)Serial.println("Failed to parse private key (err_code: 0x" + String(rc, HEX) + ")");
    if (isTask)vTaskDelete(NULL);
  }

  mbedtls_entropy_context entropy;
  mbedtls_entropy_init(&entropy);
  mbedtls_ctr_drbg_context ctr_drbg;
  mbedtls_ctr_drbg_init(&ctr_drbg);
  const char* pers = "firebase-jwt";
  mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char*)pers, strlen(pers));

  uint8_t digest[32];
  rc = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), (uint8_t*)content.c_str(), content.length(), digest);
  if (rc != 0) {
    mbedtls_pk_free(&pk_context);
    mbedtls_entropy_free(&entropy);
    mbedtls_ctr_drbg_free(&ctr_drbg);
    if (printSerial)Serial.println("Failed to digest content (err_code: 0x" + String(rc, HEX) + ")");
    if (isTask)vTaskDelete(NULL);
  }

  size_t retSize;
  rc = mbedtls_pk_sign(&pk_context, MBEDTLS_MD_SHA256, digest, sizeof(digest), buffer, &retSize, mbedtls_ctr_drbg_random, &ctr_drbg);
  if (rc != 0) {
    mbedtls_pk_free(&pk_context);
    mbedtls_entropy_free(&entropy);
    mbedtls_ctr_drbg_free(&ctr_drbg);
    if (printSerial)Serial.println("Failed to sign content (err_code: 0x" + String(rc, HEX) + ")");
    if (isTask)vTaskDelete(NULL);
  }

  String signature = base64_encode(buffer, retSize);
  if (printSerial)Serial.println(signature);
  mbedtls_pk_free(&pk_context);
  mbedtls_entropy_free(&entropy);
  mbedtls_ctr_drbg_free(&ctr_drbg);
  if (isTask)vTaskDelete(NULL);
}

void tryTask() {
  isTask = true;
  xTaskCreate(signKey, "signKey", 4096, NULL, 1, NULL);
}

void tryDirectCall() {
  isTask = false;
  signKey(NULL);
}

void setup() {
  Serial.begin(115200);
}

void loop() {
  tryTask();
  //tryDirectCall();
  Serial.println("FreeHeap: " + String(ESP.getFreeHeap()));
  delay(5000);
}

UPDATE: I tried to create task on the setup() and made the task looping continuously, the decreasing free heap didn't occured! So the issue only exist when i dynamically creating and deleting task. Why would this happen?

Wachid Susilo
  • 496
  • 4
  • 11

1 Answers1

2

The ESP32 has two types of memory - data and instruction. The Arduino wrapper returns rather liberal interpretation of free heap - the queried cabability MALLOC_CAP_INTERNAL probably includes both types. So it might be just code cache being filled with garbage from single use threads.

That, or you're leaking. In any case, single-use threads are not a commonly used practice on micros - they make everything harder, including such analysis. Standard approach is to create a permanent FreeRTOS task at startup and pass it work items through a message queue whenever it has to do something. I can recommend the FreeRTOS tutorial as an excellent resource.

Tarmo
  • 3,728
  • 1
  • 8
  • 25
  • thanks for your answer, especially for the resources. It's very helpful! Anyway, what you mean by "code cache being filled with garbage"? And can i create my own thread in arduino IDE? – Wachid Susilo Jan 09 '21 at 01:28
  • Look at the memory map in https://medium.com/the-esp-journal/esp32-programmers-memory-model-259444d89387. When you run a program, most parts of it execute directly from Flash. Some parts of the program, however, are loaded into instruction RAM (which is faster than Flash) and are executed from there. `ESP.getFreeHeap()` probably counts remaining heap from both data and shared data/instruction RAM, so it's not a super useful method. Anyway, you're already creating threads. That's what `xTaskCreate()` does in FreeRTOS. I don't know much about Arduino, so can't help in this aspect. – Tarmo Jan 09 '21 at 10:44
  • Thank you so much for your valuable time. I'm still digging into this issue. i'll posted here once i got answer or solution. Anyway, i tried using queue as your suggestion. No luck yet, queue only support fixed size data type, while i need string to be passed on. I also tried ring buffer, it's not satisfy what i needed thought. – Wachid Susilo Jan 10 '21 at 06:31
  • There's a bunch of ways to pass strings through a queue to another thread. If you stick to the C way of doing things, then you can allocate your string in heap, pass a pointer to it through the queue to processing thread and release the memory there; or keep a fixed length array of strings somewhere and pass an index into this array. If you're in C++"mode" then you can build a queue of Arduino String or the stl::string classes. – Tarmo Jan 10 '21 at 12:35