diff --git a/components/bootloader_support/test/component.mk b/components/bootloader_support/test/component.mk deleted file mode 100644 index 8c6eb513..00000000 --- a/components/bootloader_support/test/component.mk +++ /dev/null @@ -1,4 +0,0 @@ -# -#Component Makefile -# -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/bootloader_support/test/test_verify_image.c b/components/bootloader_support/test/test_verify_image.c deleted file mode 100644 index 7994667f..00000000 --- a/components/bootloader_support/test/test_verify_image.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Tests for bootloader_support esp_load(ESP_IMAGE_VERIFY, ...) - */ - -#include -#include -#include "string.h" -#include "rom/ets_sys.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "freertos/queue.h" -#include "freertos/xtensa_api.h" -#include "unity.h" -#include "bootloader_common.h" -#include "esp_partition.h" -#include "esp_ota_ops.h" -#include "esp_image_format.h" - -TEST_CASE("Verify bootloader image in flash", "[bootloader_support]") -{ - const esp_partition_pos_t fake_bootloader_partition = { - .offset = ESP_BOOTLOADER_OFFSET, - .size = ESP_PARTITION_TABLE_OFFSET - ESP_BOOTLOADER_OFFSET, - }; - esp_image_metadata_t data = { 0 }; - TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_load(ESP_IMAGE_VERIFY, &fake_bootloader_partition, &data)); - TEST_ASSERT_NOT_EQUAL(0, data.image_len); - - uint32_t bootloader_length = 0; - TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_verify_bootloader(&bootloader_length)); - TEST_ASSERT_EQUAL(data.image_len, bootloader_length); -} - -TEST_CASE("Verify unit test app image", "[bootloader_support]") -{ - esp_image_metadata_t data = { 0 }; - const esp_partition_t *running = esp_ota_get_running_partition(); - TEST_ASSERT_NOT_EQUAL(NULL, running); - const esp_partition_pos_t running_pos = { - .offset = running->address, - .size = running->size, - }; - - TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_load(ESP_IMAGE_VERIFY, &running_pos, &data)); - TEST_ASSERT_NOT_EQUAL(0, data.image_len); - TEST_ASSERT_TRUE(data.image_len <= running->size); -} - -void check_label_search (int num_test, const char *list, const char *t_label, bool result) -{ - // gen_esp32part.py trims up to 16 characters - // and the string may not have a null terminal symbol. - // below is cutting as it does the generator. - char label[16 + 1] = {0}; - strncpy(label, t_label, sizeof(label) - 1); - - bool ret = bootloader_common_label_search(list, label); - if (ret != result) { - printf("%d) %s | %s \n", num_test, list, label); - } - TEST_ASSERT_MESSAGE(ret == result, "Test failed"); -} - -TEST_CASE("Test label_search", "[bootloader_support]") -{ - TEST_ASSERT_FALSE(bootloader_common_label_search(NULL, NULL)); - TEST_ASSERT_FALSE(bootloader_common_label_search("nvs", NULL)); - - check_label_search(1, "nvs", "nvs", true); - check_label_search(2, "nvs, ", "nvs", true); - check_label_search(3, "nvs1", "nvs", false); - check_label_search(3, "nvs1, ", "nvs", false); - check_label_search(4, "nvs1nvs1, phy", "nvs1", false); - check_label_search(5, "nvs1, nvs1, phy", "nvs1", true); - check_label_search(6, "nvs12, nvs12, phy", "nvs1", false); - check_label_search(7, "nvs12, nvs1, phy", "nvs1", true); - check_label_search(8, "nvs12, nvs3, phy, nvs1","nvs1", true); - check_label_search(9, "nvs1nvs1, phy, nvs", "nvs", true); - check_label_search(10, "nvs1nvs1, phy, nvs1", "nvs", false); - check_label_search(11, "nvs1, nvs, phy, nvs1", "nvs", true); - check_label_search(12, "nvs1, nvs2, phy, nvs","nvs", true); - check_label_search(13, "ota_data, backup_nvs", "nvs", false); - check_label_search(14, "nvs1, nvs2, ota, nvs", "vs1", false); - - check_label_search(20, "12345678901234, phy, nvs1", "12345678901234", true); - check_label_search(21, "123456789012345, phy, nvs1", "123456789012345", true); - check_label_search(22, "1234567890123456, phy, nvs1", "1234567890123456", true); - check_label_search(23, "12345678901234567, phy, nvs1", "12345678901234567", false); - check_label_search(24, "1234567890123456, phy, nvs1", "12345678901234567", true); - check_label_search(25, "phy, 1234567890123456, nvs1", "12345678901234567", true); - -} diff --git a/components/esp8266/include/esp_clk.h b/components/esp8266/include/esp_clk.h new file mode 100644 index 00000000..7298b090 --- /dev/null +++ b/components/esp8266/include/esp_clk.h @@ -0,0 +1,25 @@ +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/** + * @brief Return current CPU clock frequency + * When frequency switching is performed, this frequency may change. + * However it is guaranteed that the frequency never changes with a critical + * section. + * + * @return CPU clock frequency, in Hz + */ +int esp_clk_cpu_freq(void); diff --git a/components/esp8266/include/rom/ets_sys.h b/components/esp8266/include/rom/ets_sys.h index 9b18e2cb..4af67fe8 100644 --- a/components/esp8266/include/rom/ets_sys.h +++ b/components/esp8266/include/rom/ets_sys.h @@ -43,6 +43,14 @@ extern "C" { #define ETS_WDT_INUM 8 #define ETS_FRC_TIMER1_INUM 9 +typedef enum { + OK = 0, + FAIL, + PENDING, + BUSY, + CANCEL, +} STATUS; + extern char NMIIrqIsOn; extern uint32_t WDEV_INTEREST_EVENT; diff --git a/components/esp8266/include/rom/uart.h b/components/esp8266/include/rom/uart.h new file mode 100644 index 00000000..28eb365c --- /dev/null +++ b/components/esp8266/include/rom/uart.h @@ -0,0 +1,127 @@ +// Copyright 2010-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ROM_UART_H_ +#define _ROM_UART_H_ + +#include "esp_types.h" +#include "esp_attr.h" +#include "ets_sys.h" + +#include "esp8266/uart_struct.h" +#include "esp8266/uart_register.h" +#include "esp8266/pin_mux_register.h" +#include "esp8266/eagle_soc.h" +#include "esp8266/rom_functions.h" + +#include "driver/soc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup uart_apis, uart configuration and communication related apis + * @brief uart apis + */ + +/** @addtogroup uart_apis + * @{ + */ + +/** + * @brief Wait until uart tx full empty and the last char send ok. + * + * @param uart_no : 0 for UART0, 1 for UART1, 2 for UART2 + * + * The function defined in ROM code has a bug, so we define the correct version + * here for compatibility. + */ +static inline void IRAM_ATTR uart_tx_wait_idle(uint8_t uart_no) { + uint32_t tx_bytes; + uint32_t baudrate, byte_delay_us; + uart_dev_t *const UART[2] = {&uart0, &uart1}; + uart_dev_t *const uart = UART[uart_no]; + + baudrate = (UART_CLK_FREQ / (uart->clk_div.val & 0xFFFFF)); + byte_delay_us = (uint32_t)(10000000 / baudrate); + + do { + tx_bytes = uart->status.txfifo_cnt; + /* either tx count or state is non-zero */ + } while (tx_bytes); + + ets_delay_us(byte_delay_us); +} + +/** + * @brief Output a char to printf channel, wait until fifo not full. + * + * @param None + * + * @return OK. + */ +STATUS uart_tx_one_char(uint8_t TxChar); + +/** + * @brief Get an input char from message channel. + * Please do not call this function in SDK. + * + * @param uint8_t *pRxChar : the pointer to store the char. + * + * @return OK for successful. + * FAIL for failed. + */ +STATUS uart_rx_one_char(uint8_t *pRxChar); + +/** + * @brief Get an input string line from message channel. + * Please do not call this function in SDK. + * + * @param uint8_t *pString : the pointer to store the string. + * + * @param uint8_t MaxStrlen : the max string length, incude '\0'. + * + * @return OK. + */ +static inline STATUS UartRxString(uint8_t *pString, uint8_t MaxStrlen) +{ + int rx_bytes = 0; + + while(1) { + uint8_t data; + + while (uart_rx_one_char(&data) != OK); + + if (data == '\n' || data == '\r') + data = '\0'; + + pString[rx_bytes++] = data; + if (data == '\0') + return OK; + if (rx_bytes >= MaxStrlen) + return FAIL; + } + + return OK; +} + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _ROM_UART_H_ */ diff --git a/components/esp8266/include/soc/cpu.h b/components/esp8266/include/soc/cpu.h new file mode 100644 index 00000000..2249e06b --- /dev/null +++ b/components/esp8266/include/soc/cpu.h @@ -0,0 +1,40 @@ +// Copyright 2010-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _SOC_CPU_H +#define _SOC_CPU_H + +#include +#include +#include +#include "xtensa/corebits.h" +#include "xtensa/config/core.h" + +/* C macros for xtensa special register read/write/exchange */ + +#define RSR(reg, curval) asm volatile ("rsr %0, " #reg : "=r" (curval)); +#define WSR(reg, newval) asm volatile ("wsr %0, " #reg : : "r" (newval)); +#define XSR(reg, swapval) asm volatile ("xsr %0, " #reg : "+r" (swapval)); + +/** @brief Read current stack pointer address + * + */ +static inline void *get_sp() +{ + void *sp; + asm volatile ("mov %0, sp;" : "=r" (sp)); + return sp; +} + +#endif diff --git a/components/esp8266/ld/esp8266.rom.ld b/components/esp8266/ld/esp8266.rom.ld index 49f6dd71..b51d44b7 100644 --- a/components/esp8266/ld/esp8266.rom.ld +++ b/components/esp8266/ld/esp8266.rom.ld @@ -57,4 +57,5 @@ PROVIDE ( gpio_input_get = 0x40004cf0 ); PROVIDE ( gpio_pin_wakeup_disable = 0x40004ed4 ); PROVIDE ( gpio_pin_wakeup_enable = 0x40004e90 ); -PROVIDE ( ets_io_vprintf = 0x40001f00 ); \ No newline at end of file +PROVIDE ( ets_io_vprintf = 0x40001f00 ); +PROVIDE ( uart_rx_one_char = 0x40003b8c ); \ No newline at end of file diff --git a/components/esp_http_server/test/CMakeLists.txt b/components/esp_http_server/test/CMakeLists.txt deleted file mode 100644 index 7587213b..00000000 --- a/components/esp_http_server/test/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -set(COMPONENT_SRCDIRS ".") -set(COMPONENT_ADD_INCLUDEDIRS ".") - -set(COMPONENT_REQUIRES unity esp_http_server) - -register_component() diff --git a/components/esp_http_server/test/component.mk b/components/esp_http_server/test/component.mk deleted file mode 100644 index ce464a21..00000000 --- a/components/esp_http_server/test/component.mk +++ /dev/null @@ -1 +0,0 @@ -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/esp_http_server/test/test_http_server.c b/components/esp_http_server/test/test_http_server.c deleted file mode 100644 index e343c99c..00000000 --- a/components/esp_http_server/test/test_http_server.c +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2018 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include -#include - -#include "unity.h" -#include "test_utils.h" - -int pre_start_mem, post_stop_mem, post_stop_min_mem; -bool basic_sanity = true; - -esp_err_t null_func(httpd_req_t *req) -{ - return ESP_OK; -} - -httpd_uri_t handler_limit_uri (char* path) -{ - httpd_uri_t uri = { - .uri = path, - .method = HTTP_GET, - .handler = null_func, - .user_ctx = NULL, - }; - return uri; -}; - -static inline unsigned num_digits(unsigned x) -{ - unsigned digits = 1; - while ((x = x/10) != 0) { - digits++; - } - return digits; -} - -#define HTTPD_TEST_MAX_URI_HANDLERS 8 - -void test_handler_limit(httpd_handle_t hd) -{ - int i; - char x[HTTPD_TEST_MAX_URI_HANDLERS+1][num_digits(HTTPD_TEST_MAX_URI_HANDLERS)+1]; - httpd_uri_t uris[HTTPD_TEST_MAX_URI_HANDLERS+1]; - - for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS + 1; i++) { - sprintf(x[i],"%d",i); - uris[i] = handler_limit_uri(x[i]); - } - - /* Register multiple instances of the same handler for MAX URI Handlers */ - for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS; i++) { - TEST_ASSERT(httpd_register_uri_handler(hd, &uris[i]) == ESP_OK); - } - - /* Register the MAX URI + 1 Handlers should fail */ - TEST_ASSERT(httpd_register_uri_handler(hd, &uris[HTTPD_TEST_MAX_URI_HANDLERS]) != ESP_OK); - - /* Unregister the one of the Handler should pass */ - TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[0].uri, uris[0].method) == ESP_OK); - - /* Unregister non added Handler should fail */ - TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[0].uri, uris[0].method) != ESP_OK); - - /* Register the MAX URI Handler should pass */ - TEST_ASSERT(httpd_register_uri_handler(hd, &uris[0]) == ESP_OK); - - /* Reregister same instance of handler should fail */ - TEST_ASSERT(httpd_register_uri_handler(hd, &uris[0]) != ESP_OK); - - /* Register the MAX URI + 1 Handlers should fail */ - TEST_ASSERT(httpd_register_uri_handler(hd, &uris[HTTPD_TEST_MAX_URI_HANDLERS]) != ESP_OK); - - /* Unregister the same handler for MAX URI Handlers */ - for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS; i++) { - TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[i].uri, uris[i].method) == ESP_OK); - } - basic_sanity = false; -} - -/********************* Test Handler Limit End *******************/ - -httpd_handle_t test_httpd_start(uint16_t id) -{ - httpd_handle_t hd; - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - config.max_uri_handlers = HTTPD_TEST_MAX_URI_HANDLERS; - config.server_port += id; - config.ctrl_port += id; - TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK) - return hd; -} - -#define SERVER_INSTANCES 2 - -/* Currently this only tests for the number of tasks. - * Heap leakage is not tested as LWIP allocates memory - * which may not be freed immedietly causing erroneous - * evaluation. Another test to implement would be the - * monitoring of open sockets, but LWIP presently provides - * no such API for getting the number of open sockets. - */ -TEST_CASE("Leak Test", "[HTTP SERVER]") -{ - httpd_handle_t hd[SERVER_INSTANCES]; - unsigned task_count; - bool res = true; - - test_case_uses_tcpip(); - - task_count = uxTaskGetNumberOfTasks(); - printf("Initial task count: %d\n", task_count); - - pre_start_mem = esp_get_free_heap_size(); - - for (int i = 0; i < SERVER_INSTANCES; i++) { - hd[i] = test_httpd_start(i); - vTaskDelay(10); - unsigned num_tasks = uxTaskGetNumberOfTasks(); - task_count++; - if (num_tasks != task_count) { - printf("Incorrect task count (starting): %d expected %d\n", - num_tasks, task_count); - res = false; - } - } - - for (int i = 0; i < SERVER_INSTANCES; i++) { - if (httpd_stop(hd[i]) != ESP_OK) { - printf("Failed to stop httpd task %d\n", i); - res = false; - } - vTaskDelay(10); - unsigned num_tasks = uxTaskGetNumberOfTasks(); - task_count--; - if (num_tasks != task_count) { - printf("Incorrect task count (stopping): %d expected %d\n", - num_tasks, task_count); - res = false; - } - } - post_stop_mem = esp_get_free_heap_size(); - TEST_ASSERT(res == true); -} - -TEST_CASE("Basic Functionality Tests", "[HTTP SERVER]") -{ - httpd_handle_t hd; - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - - test_case_uses_tcpip(); - - TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK); - test_handler_limit(hd); - TEST_ASSERT(httpd_stop(hd) == ESP_OK); -} diff --git a/components/freertos/port/esp8266/port.c b/components/freertos/port/esp8266/port.c index 988b032c..db384023 100644 --- a/components/freertos/port/esp8266/port.c +++ b/components/freertos/port/esp8266/port.c @@ -148,6 +148,14 @@ void TASK_SW_ATTR xPortSysTickHandle(void) } } +/** + * @brief Return current CPU clock frequency + */ +int esp_clk_cpu_freq(void) +{ + return _xt_tick_divisor * XT_TICK_PER_SEC; +} + /* * See header file for description. */ diff --git a/components/partition_table/test/component.mk b/components/partition_table/test/component.mk deleted file mode 100644 index 5dd172bd..00000000 --- a/components/partition_table/test/component.mk +++ /dev/null @@ -1,5 +0,0 @@ -# -#Component Makefile -# - -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/partition_table/test/test_partition.c b/components/partition_table/test/test_partition.c deleted file mode 100644 index 5267b5ba..00000000 --- a/components/partition_table/test/test_partition.c +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include "unity.h" -#include "test_utils.h" -#include "esp_partition.h" - - -TEST_CASE("Can read partition table", "[partition]") -{ - - const esp_partition_t *p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL); - TEST_ASSERT_NOT_NULL(p); - TEST_ASSERT_EQUAL(0x10000, p->address); - TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_FACTORY, p->subtype); - - esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, NULL); - TEST_ASSERT_NOT_NULL(it); - int count = 0; - const esp_partition_t* prev = NULL; - for (; it != NULL; it = esp_partition_next(it)) { - const esp_partition_t *p = esp_partition_get(it); - TEST_ASSERT_NOT_NULL(p); - if (prev) { - TEST_ASSERT_TRUE_MESSAGE(prev->address < p->address, "incorrect partition order"); - } - prev = p; - ++count; - } - esp_partition_iterator_release(it); - TEST_ASSERT_EQUAL(4, count); -} - -TEST_CASE("Can write, read, mmap partition", "[partition][ignore]") -{ - const esp_partition_t *p = get_test_data_partition(); - printf("Using partition %s at 0x%x, size 0x%x\n", p->label, p->address, p->size); - TEST_ASSERT_NOT_NULL(p); - const size_t max_size = 2 * SPI_FLASH_SEC_SIZE; - uint8_t *data = (uint8_t *) malloc(max_size); - TEST_ASSERT_NOT_NULL(data); - - TEST_ASSERT_EQUAL(ESP_OK, esp_partition_erase_range(p, 0, p->size)); - - srand(0); - size_t block_size; - for (size_t offset = 0; offset < p->size; offset += block_size) { - block_size = ((rand() + 4) % max_size) & (~0x3); - size_t left = p->size - offset; - if (block_size > left) { - block_size = left; - } - for (size_t i = 0; i < block_size / 4; ++i) { - ((uint32_t *) (data))[i] = rand(); - } - TEST_ASSERT_EQUAL(ESP_OK, esp_partition_write(p, offset, data, block_size)); - } - - srand(0); - for (size_t offset = 0; offset < p->size; offset += block_size) { - block_size = ((rand() + 4) % max_size) & (~0x3); - size_t left = p->size - offset; - if (block_size > left) { - block_size = left; - } - TEST_ASSERT_EQUAL(ESP_OK, esp_partition_read(p, offset, data, block_size)); - for (size_t i = 0; i < block_size / 4; ++i) { - TEST_ASSERT_EQUAL(rand(), ((uint32_t *) data)[i]); - } - } - - free(data); - - const uint32_t *mmap_data; - spi_flash_mmap_handle_t mmap_handle; - size_t begin = 3000; - size_t size = 64000; //chosen so size is smaller than 64K but the mmap straddles 2 MMU blocks - TEST_ASSERT_EQUAL(ESP_OK, esp_partition_mmap(p, begin, size, SPI_FLASH_MMAP_DATA, - (const void **)&mmap_data, &mmap_handle)); - srand(0); - for (size_t offset = 0; offset < p->size; offset += block_size) { - block_size = ((rand() + 4) % max_size) & (~0x3); - size_t left = p->size - offset; - if (block_size > left) { - block_size = left; - } - for (size_t i = 0; i < block_size / 4; ++i) { - size_t pos = offset + i * 4; - uint32_t expected = rand(); - if (pos < begin || pos >= (begin + size)) { - continue; - } - TEST_ASSERT_EQUAL(expected, mmap_data[(pos - begin) / 4]); - } - } - - spi_flash_munmap(mmap_handle); -} diff --git a/components/protocomm/test/CMakeLists.txt b/components/protocomm/test/CMakeLists.txt deleted file mode 100644 index f25cb01a..00000000 --- a/components/protocomm/test/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(COMPONENT_SRCDIRS ".") -set(COMPONENT_ADD_INCLUDEDIRS ".") -set(COMPONENT_PRIV_INCLUDEDIRS "../proto-c/") - -set(COMPONENT_REQUIRES unity mbedtls protocomm protobuf-c) - -register_component() diff --git a/components/protocomm/test/component.mk b/components/protocomm/test/component.mk deleted file mode 100644 index 7a27118a..00000000 --- a/components/protocomm/test/component.mk +++ /dev/null @@ -1,2 +0,0 @@ -COMPONENT_PRIV_INCLUDEDIRS := ../proto-c/ -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/protocomm/test/test_protocomm.c b/components/protocomm/test/test_protocomm.c deleted file mode 100644 index d9a3a71a..00000000 --- a/components/protocomm/test/test_protocomm.c +++ /dev/null @@ -1,1134 +0,0 @@ -// Copyright 2018 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "session.pb-c.h" - -#ifdef CONFIG_HEAP_TRACING - #include - #define NUM_RECORDS 100 - static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM -#endif - -#define PUBLIC_KEY_LEN 32 -#define SZ_RANDOM 16 - -typedef struct { - uint32_t id; - uint8_t sec_ver; - uint8_t weak; - const protocomm_security_pop_t *pop; - uint8_t device_pubkey[PUBLIC_KEY_LEN]; - uint8_t client_pubkey[PUBLIC_KEY_LEN]; - uint8_t sym_key[PUBLIC_KEY_LEN]; - uint8_t rand[SZ_RANDOM]; - - /* mbedtls context data for Curve25519 */ - mbedtls_ecdh_context ctx_client; - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - - /* mbedtls context data for AES */ - mbedtls_aes_context ctx_aes; - unsigned char stb[16]; - size_t nc_off; -} session_t; - -static const char *TAG = "protocomm_test"; - -static protocomm_t *test_pc = NULL; -static const protocomm_security_t *test_sec = NULL; -static uint32_t test_priv_data = 1234; - -static void flip_endian(uint8_t *data, size_t len) -{ - uint8_t swp_buf; - for (int i = 0; i < len/2; i++) { - swp_buf = data[i]; - data[i] = data[len - i - 1]; - data[len - i - 1] = swp_buf; - } -} - -static void hexdump(const char *msg, uint8_t *buf, int len) -{ - ESP_LOGI(TAG, "%s:", msg); - ESP_LOG_BUFFER_HEX(TAG, buf, len); -} - -static esp_err_t prepare_command0(session_t *session, SessionData *req) -{ - Sec1Payload *in = (Sec1Payload *) malloc(sizeof(Sec1Payload)); - if (in == NULL) { - ESP_LOGE(TAG, "Error allocating memory for request"); - return ESP_ERR_NO_MEM; - } - - SessionCmd0 *in_req = (SessionCmd0 *) malloc(sizeof(SessionCmd0)); - if (in_req == NULL) { - ESP_LOGE(TAG, "Error allocating memory for request"); - free(in); - return ESP_ERR_NO_MEM; - } - - sec1_payload__init(in); - session_cmd0__init(in_req); - - in_req->client_pubkey.data = session->client_pubkey; - in_req->client_pubkey.len = PUBLIC_KEY_LEN; - - in->msg = SEC1_MSG_TYPE__Session_Command0; - in->payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; - in->sc0 = in_req; - - req->proto_case = SESSION_DATA__PROTO_SEC1; - req->sec_ver = protocomm_security1.ver; - req->sec1 = in; - - return ESP_OK; -} - -static void cleanup_command0(SessionData *req) -{ - free(req->sec1->sc0); - free(req->sec1); -} - -static esp_err_t verify_response0(session_t *session, SessionData *resp) -{ - if ((resp->proto_case != SESSION_DATA__PROTO_SEC1) || - (resp->sec1->msg != SEC1_MSG_TYPE__Session_Response0)) { - ESP_LOGE(TAG, "Invalid response type"); - return ESP_ERR_INVALID_ARG; - } - - int ret; - Sec1Payload *in = (Sec1Payload *) resp->sec1; - - if (in->sr0->device_pubkey.len != PUBLIC_KEY_LEN) { - ESP_LOGE(TAG, "Device public key length as not as expected"); - return ESP_FAIL; - } - - if (in->sr0->device_random.len != SZ_RANDOM) { - ESP_LOGE(TAG, "Device random data length is not as expected"); - return ESP_FAIL; - } - - uint8_t *cli_pubkey = session->client_pubkey; - uint8_t *dev_pubkey = session->device_pubkey; - memcpy(session->device_pubkey, in->sr0->device_pubkey.data, in->sr0->device_pubkey.len); - - hexdump("Device pubkey", dev_pubkey, PUBLIC_KEY_LEN); - hexdump("Client pubkey", cli_pubkey, PUBLIC_KEY_LEN); - - ret = mbedtls_mpi_lset(&session->ctx_client.Qp.Z, 1); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_mpi_lset with error code : %d", ret); - return ESP_FAIL; - } - - flip_endian(session->device_pubkey, PUBLIC_KEY_LEN); - ret = mbedtls_mpi_read_binary(&session->ctx_client.Qp.X, dev_pubkey, PUBLIC_KEY_LEN); - flip_endian(session->device_pubkey, PUBLIC_KEY_LEN); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_mpi_read_binary with error code : %d", ret); - return ESP_FAIL; - } - - ret = mbedtls_ecdh_compute_shared(&session->ctx_client.grp, - &session->ctx_client.z, - &session->ctx_client.Qp, - &session->ctx_client.d, - mbedtls_ctr_drbg_random, - &session->ctr_drbg); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_ecdh_compute_shared with error code : %d", ret); - return ESP_FAIL; - } - - ret = mbedtls_mpi_write_binary(&session->ctx_client.z, session->sym_key, PUBLIC_KEY_LEN); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : %d", ret); - return ESP_FAIL; - } - flip_endian(session->sym_key, PUBLIC_KEY_LEN); - - const protocomm_security_pop_t *pop = session->pop; - if (pop != NULL && pop->data != NULL && pop->len != 0) { - ESP_LOGD(TAG, "Adding proof of possession"); - uint8_t sha_out[PUBLIC_KEY_LEN]; - - ret = mbedtls_sha256_ret((const unsigned char *) pop->data, pop->len, sha_out, 0); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_sha256_ret with error code : %d", ret); - return ESP_FAIL; - } - - for (int i = 0; i < PUBLIC_KEY_LEN; i++) { - session->sym_key[i] ^= sha_out[i]; - } - } - - hexdump("Shared key", session->sym_key, PUBLIC_KEY_LEN); - - memcpy(session->rand, in->sr0->device_random.data, in->sr0->device_random.len); - hexdump("Dev random", session->rand, sizeof(session->rand)); - return ESP_OK; -} - -static esp_err_t prepare_command1(session_t *session, SessionData *req) -{ - int ret; - uint8_t *outbuf = (uint8_t *) malloc(PUBLIC_KEY_LEN); - if (!outbuf) { - ESP_LOGE(TAG, "Error allocating ciphertext buffer"); - return ESP_ERR_NO_MEM; - } - - /* Initialise crypto context */ - mbedtls_aes_init(&session->ctx_aes); - memset(session->stb, 0, sizeof(session->stb)); - session->nc_off = 0; - - ret = mbedtls_aes_setkey_enc(&session->ctx_aes, session->sym_key, - sizeof(session->sym_key)*8); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_aes_setkey_enc with erro code : %d", ret); - free(outbuf); - return ESP_FAIL; - } - - ret = mbedtls_aes_crypt_ctr(&session->ctx_aes, PUBLIC_KEY_LEN, - &session->nc_off, session->rand, - session->stb, session->device_pubkey, outbuf); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_aes_crypt_ctr with erro code : %d", ret); - free(outbuf); - return ESP_FAIL; - } - - Sec1Payload *out = (Sec1Payload *) malloc(sizeof(Sec1Payload)); - if (!out) { - ESP_LOGE(TAG, "Error allocating out buffer"); - free(outbuf); - return ESP_ERR_NO_MEM; - } - sec1_payload__init(out); - - SessionCmd1 *out_req = (SessionCmd1 *) malloc(sizeof(SessionCmd1)); - if (!out_req) { - ESP_LOGE(TAG, "Error allocating out_req buffer"); - free(outbuf); - free(out); - return ESP_ERR_NO_MEM; - } - session_cmd1__init(out_req); - - out_req->client_verify_data.data = outbuf; - out_req->client_verify_data.len = PUBLIC_KEY_LEN; - hexdump("Client verify data", outbuf, PUBLIC_KEY_LEN); - - out->msg = SEC1_MSG_TYPE__Session_Command1; - out->payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; - out->sc1 = out_req; - - req->proto_case = SESSION_DATA__PROTO_SEC1; - req->sec_ver = protocomm_security1.ver; - req->sec1 = out; - - return ESP_OK; -} - -static void cleanup_command1(SessionData *req) -{ - free(req->sec1->sc1->client_verify_data.data); - free(req->sec1->sc1); - free(req->sec1); -} - -static esp_err_t verify_response1(session_t *session, SessionData *resp) -{ - uint8_t *cli_pubkey = session->client_pubkey; - uint8_t *dev_pubkey = session->device_pubkey; - - hexdump("Device pubkey", dev_pubkey, PUBLIC_KEY_LEN); - hexdump("Client pubkey", cli_pubkey, PUBLIC_KEY_LEN); - - if ((resp->proto_case != SESSION_DATA__PROTO_SEC1) || - (resp->sec1->msg != SEC1_MSG_TYPE__Session_Response1)) { - ESP_LOGE(TAG, "Invalid response type"); - return ESP_ERR_INVALID_ARG; - } - - uint8_t check_buf[PUBLIC_KEY_LEN]; - Sec1Payload *in = (Sec1Payload *) resp->sec1; - - int ret = mbedtls_aes_crypt_ctr(&session->ctx_aes, PUBLIC_KEY_LEN, - &session->nc_off, session->rand, session->stb, - in->sr1->device_verify_data.data, check_buf); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_aes_crypt_ctr with erro code : %d", ret); - return ESP_FAIL; - } - hexdump("Dec Device verifier", check_buf, sizeof(check_buf)); - - if (memcmp(check_buf, session->client_pubkey, sizeof(session->client_pubkey)) != 0) { - ESP_LOGE(TAG, "Key mismatch. Close connection"); - return ESP_FAIL; - } - - return ESP_OK; -} - -static esp_err_t test_new_session(session_t *session) -{ - if (session->sec_ver == 0) { - return ESP_OK; - } - - if (!test_sec || !test_sec->new_transport_session) { - return ESP_ERR_INVALID_STATE; - } - - uint32_t session_id = session->id; - if (test_sec->new_transport_session(session_id) != ESP_OK) { - ESP_LOGE(TAG, "Failed to launch new transport session"); - return ESP_FAIL; - } - return ESP_OK; -} - -static esp_err_t test_sec_endpoint(session_t *session) -{ - if (session->sec_ver == 0) { - return ESP_OK; - } - - uint32_t session_id = session->id; - - int ret = ESP_FAIL; - SessionData req; - SessionData *resp; - ssize_t inlen = 0; - uint8_t *inbuf = NULL; - ssize_t outlen = 0; - uint8_t *outbuf = NULL; - - mbedtls_ecdh_init(&session->ctx_client); - mbedtls_ctr_drbg_init(&session->ctr_drbg); - - mbedtls_entropy_init(&session->entropy); - ret = mbedtls_ctr_drbg_seed(&session->ctr_drbg, mbedtls_entropy_func, - &session->entropy, NULL, 0); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_ctr_drbg_seed with error code : %d", ret); - goto abort_test_sec_endpoint; - } - - ret = mbedtls_ecp_group_load(&session->ctx_client.grp, MBEDTLS_ECP_DP_CURVE25519); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_ecp_group_load with error code : %d", ret); - goto abort_test_sec_endpoint; - } - - ret = mbedtls_ecdh_gen_public(&session->ctx_client.grp, - &session->ctx_client.d, - &session->ctx_client.Q, - mbedtls_ctr_drbg_random, - &session->ctr_drbg); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_ecdh_gen_public with error code : %d", ret); - goto abort_test_sec_endpoint; - } - - if (session->weak) { - /* Read zero client public key */ - ret = mbedtls_mpi_read_binary(&session->ctx_client.Q.X, - session->client_pubkey, - PUBLIC_KEY_LEN); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_mpi_read_binary with error code : %d", ret); - goto abort_test_sec_endpoint; - } - } - ret = mbedtls_mpi_write_binary(&session->ctx_client.Q.X, - session->client_pubkey, - PUBLIC_KEY_LEN); - if (ret != 0) { - ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : %d", ret); - goto abort_test_sec_endpoint; - } - flip_endian(session->client_pubkey, PUBLIC_KEY_LEN); - - /*********** Transaction0 = SessionCmd0 + SessionResp0 ****************/ - session_data__init(&req); - if (prepare_command0(session, &req) != ESP_OK) { - ESP_LOGE(TAG, "Failed in prepare_command0"); - goto abort_test_sec_endpoint; - } - - inlen = session_data__get_packed_size(&req); - inbuf = (uint8_t *) malloc(inlen); - if (!inbuf) { - ESP_LOGE(TAG, "Failed to allocate inbuf"); - goto abort_test_sec_endpoint; - } - - session_data__pack(&req, inbuf); - cleanup_command0(&req); - - outlen = 0; - outbuf = NULL; - ret = protocomm_req_handle(test_pc, "test-sec", session_id, - inbuf, inlen, &outbuf, &outlen); - - free(inbuf); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "test-sec handler failed"); - free(outbuf); - goto abort_test_sec_endpoint; - } - - resp = session_data__unpack(NULL, outlen, outbuf); - free(outbuf); - if (!resp) { - ESP_LOGE(TAG, "Unable to unpack SessionResp0"); - goto abort_test_sec_endpoint; - } - - if (verify_response0(session, resp) != ESP_OK) { - ESP_LOGE(TAG, "Invalid response 0"); - session_data__free_unpacked(resp, NULL); - goto abort_test_sec_endpoint; - } - - session_data__free_unpacked(resp, NULL); - - /*********** Transaction1 = SessionCmd1 + SessionResp1 ****************/ - session_data__init(&req); - if (prepare_command1(session, &req) != ESP_OK) { - ESP_LOGE(TAG, "Failed in prepare_command1"); - goto abort_test_sec_endpoint; - } - - inlen = session_data__get_packed_size(&req); - inbuf = (uint8_t *) malloc(inlen); - if (!inbuf) { - ESP_LOGE(TAG, "Failed to allocate inbuf"); - goto abort_test_sec_endpoint; - } - - session_data__pack(&req, inbuf); - cleanup_command1(&req); - - outlen = 0; - outbuf = NULL; - ret = protocomm_req_handle(test_pc, "test-sec", session_id, - inbuf, inlen, &outbuf, &outlen); - - free(inbuf); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "test-sec handler failed"); - free(outbuf); - goto abort_test_sec_endpoint; - } - - resp = session_data__unpack(NULL, outlen, outbuf); - free(outbuf); - if (!resp) { - ESP_LOGE(TAG, "Unable to unpack SessionResp0"); - goto abort_test_sec_endpoint; - } - - if (verify_response1(session, resp) != ESP_OK) { - ESP_LOGE(TAG, "Invalid response 1"); - session_data__free_unpacked(resp, NULL); - goto abort_test_sec_endpoint; - } - - session_data__free_unpacked(resp, NULL); - mbedtls_ecdh_free(&session->ctx_client); - mbedtls_ctr_drbg_free(&session->ctr_drbg); - mbedtls_entropy_free(&session->entropy); - return ESP_OK; - -abort_test_sec_endpoint: - mbedtls_ecdh_free(&session->ctx_client); - mbedtls_ctr_drbg_free(&session->ctr_drbg); - mbedtls_entropy_free(&session->entropy); - return ESP_FAIL; -} - -#define TEST_VER_STR "" - -static esp_err_t test_ver_endpoint(session_t *session) -{ - ssize_t ver_data_len = 0; - uint8_t *ver_data = NULL; - - esp_err_t ret = protocomm_req_handle(test_pc, "test-ver", session->id, - NULL, 0, &ver_data, &ver_data_len); - - if (ret != ESP_OK) { - ESP_LOGE(TAG, "test-ver handler failed"); - return ESP_FAIL; - } - - if (ver_data_len != strlen(TEST_VER_STR) || memcmp(TEST_VER_STR, ver_data, ver_data_len)) { - ESP_LOGE(TAG, "incorrect response data from test-ver"); - free(ver_data); - return ESP_FAIL; - } - free(ver_data); - return ESP_OK; -} - -static esp_err_t test_req_endpoint(session_t *session) -{ - uint32_t session_id = session->id; - - uint8_t rand_test_data[512], enc_test_data[512]; - getrandom(rand_test_data, sizeof(rand_test_data), 0); - - if (session->sec_ver == 0) { - memcpy(enc_test_data, rand_test_data, sizeof(rand_test_data)); - } - else if (session->sec_ver == 1) { - mbedtls_aes_crypt_ctr(&session->ctx_aes, sizeof(rand_test_data), &session->nc_off, - session->rand, session->stb, rand_test_data, enc_test_data); - } - - ssize_t verify_data_len = 0; - uint8_t *enc_verify_data = NULL; - - esp_err_t ret = protocomm_req_handle(test_pc, "test-ep", session_id, - enc_test_data, sizeof(enc_test_data), - &enc_verify_data, &verify_data_len); - - if (ret != ESP_OK || !verify_data_len) { - ESP_LOGE(TAG, "test-ep handler failed"); - return ESP_FAIL; - } - - uint8_t *verify_data = malloc(verify_data_len); - if (!verify_data) { - ESP_LOGE(TAG, "error allocating memory for decrypted data"); - free(enc_verify_data); - return ESP_FAIL; - } - - if (session->sec_ver == 0) { - memcpy(verify_data, enc_verify_data, verify_data_len); - } - else if (session->sec_ver == 1) { - mbedtls_aes_crypt_ctr(&session->ctx_aes, verify_data_len, &session->nc_off, - session->rand, session->stb, enc_verify_data, verify_data); - } - free(enc_verify_data); - - hexdump("Sent data", rand_test_data, sizeof(rand_test_data)); - hexdump("Recv data", verify_data, verify_data_len); - - ESP_LOGI(TAG, "verify data len : %d", verify_data_len); - ESP_LOGI(TAG, "expected data len : %d", sizeof(rand_test_data)); - - if (verify_data_len != sizeof(rand_test_data)) { - ESP_LOGE(TAG, "incorrect response length from test-ep"); - free(verify_data); - return ESP_FAIL; - } - if (memcmp(rand_test_data, verify_data, verify_data_len)) { - ESP_LOGE(TAG, "incorrect response data from test-ep"); - free(verify_data); - return ESP_FAIL; - } - free(verify_data); - return ESP_OK; -} - -esp_err_t test_req_handler (uint32_t session_id, - const uint8_t *inbuf, ssize_t inlen, - uint8_t **outbuf, ssize_t *outlen, - void *priv_data) -{ - *outbuf = malloc(inlen); - if (*outbuf) { - *outlen = inlen; - memcpy(*outbuf, inbuf, inlen); - } else { - ESP_LOGE(TAG, "Error allocating response outbuf"); - *outbuf = NULL; - *outlen = 0; - } - - uint32_t *priv = (uint32_t *) priv_data; - if ((&test_priv_data != priv) || (test_priv_data != *priv)) { - ESP_LOGE(TAG, "Handler private data doesn't match"); - return ESP_FAIL; - } - return ESP_OK; -} - -static esp_err_t start_test_service(uint8_t sec_ver, const protocomm_security_pop_t *pop) -{ - test_pc = protocomm_new(); - if (test_pc == NULL) { - ESP_LOGE(TAG, "Failed to create new protocomm instance"); - return ESP_FAIL; - } - - if (sec_ver == 0) { - if (protocomm_set_security(test_pc, "test-sec", &protocomm_security0, NULL) != ESP_OK) { - ESP_LOGE(TAG, "Failed to set Security0"); - return ESP_FAIL; - } - test_sec = &protocomm_security0; - } else if (sec_ver == 1) { - if (protocomm_set_security(test_pc, "test-sec", &protocomm_security1, pop) != ESP_OK) { - ESP_LOGE(TAG, "Failed to set Security1"); - return ESP_FAIL; - } - test_sec = &protocomm_security1; - } - - if (protocomm_set_version(test_pc, "test-ver", TEST_VER_STR) != ESP_OK) { - ESP_LOGE(TAG, "Failed to set version"); - return ESP_FAIL; - } - - if (protocomm_add_endpoint(test_pc, "test-ep", - test_req_handler, - (void *) &test_priv_data) != ESP_OK) { - ESP_LOGE(TAG, "Failed to set test-ep endpoint handler"); - return ESP_FAIL; - } - return ESP_OK; -} - -static void stop_test_service(void) -{ - test_sec = NULL; - protocomm_delete(test_pc); - test_pc = NULL; -} - -static esp_err_t test_security1_no_encryption (void) -{ - ESP_LOGI(TAG, "Starting Security 1 no encryption test"); - - const char *pop_data = "test pop"; - protocomm_security_pop_t pop = { - .data = (const uint8_t *)pop_data, - .len = strlen(pop_data) - }; - - session_t *session = calloc(1, sizeof(session_t)); - if (session == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - return ESP_ERR_NO_MEM; - } - - session->id = 1; - session->sec_ver = 1; - session->pop = &pop; - - // Start protocomm service - if (start_test_service(1, &pop) != ESP_OK) { - ESP_LOGE(TAG, "Error starting test"); - free(session); - return ESP_ERR_INVALID_STATE; - } - - // Intialise protocomm session with zero public keys - if (test_new_session(session) != ESP_OK) { - ESP_LOGE(TAG, "Error creating new session"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - // Perform 25519 security handshake to set public keys - if (test_sec_endpoint(session) != ESP_OK) { - ESP_LOGE(TAG, "Error testing security endpoint"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - // Force endpoint with un-encrypted data - session->sec_ver = 0; - - // Send unencrypted request data to echo endpoint. - // Response would be encrypted causing echoed back - // data to not match that which was sent, hence failing. - if (test_req_endpoint(session) == ESP_OK) { - ESP_LOGE(TAG, "Error testing request endpoint"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - stop_test_service(); - free(session); - ESP_LOGI(TAG, "Protocomm test successful"); - return ESP_OK; -} - -static esp_err_t test_security1_session_overflow (void) -{ - ESP_LOGI(TAG, "Starting Security 1 session overflow test"); - - const char *pop_data = "test pop"; - protocomm_security_pop_t pop = { - .data = (const uint8_t *)pop_data, - .len = strlen(pop_data) - }; - - session_t *session1 = calloc(1, sizeof(session_t)); - if (session1 == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - return ESP_ERR_NO_MEM; - } - - session1->id = 2; - session1->sec_ver = 1; - session1->pop = &pop; - - session_t *session2 = calloc(1, sizeof(session_t)); - if (session2 == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - free(session1); - return ESP_ERR_NO_MEM; - } - - session2->id = 3; - session2->sec_ver = 1; - session2->pop = NULL; - - // Start protocomm service - if (start_test_service(1, &pop) != ESP_OK) { - ESP_LOGE(TAG, "Error starting test"); - free(session1); - free(session2); - return ESP_FAIL; - } - - // Intialise protocomm session with zero public keys - if (test_new_session(session1) != ESP_OK) { - ESP_LOGE(TAG, "Error creating new session"); - stop_test_service(); - free(session1); - free(session2); - return ESP_FAIL; - } - - // Perform 25519 security handshake to set public keys - if (test_sec_endpoint(session1) != ESP_OK) { - ESP_LOGE(TAG, "Error testing security endpoint"); - stop_test_service(); - free(session1); - free(session2); - return ESP_FAIL; - } - - // Try to perform security handshake again with different - // session ID without registering new session, hence failing - if (test_sec_endpoint(session2) == ESP_OK) { - ESP_LOGE(TAG, "Error testing security endpoint"); - stop_test_service(); - free(session1); - free(session2); - return ESP_FAIL; - } - - stop_test_service(); - free(session1); - free(session2); - - ESP_LOGI(TAG, "Protocomm test successful"); - return ESP_OK; -} - -static esp_err_t test_security1_wrong_pop (void) -{ - ESP_LOGI(TAG, "Starting Security 1 wrong auth test"); - - const char *pop_data = "test pop"; - protocomm_security_pop_t pop = { - .data = (const uint8_t *)pop_data, - .len = strlen(pop_data) - }; - - session_t *session = calloc(1, sizeof(session_t)); - if (session == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - return ESP_ERR_NO_MEM; - } - - session->id = 4; - session->sec_ver = 1; - session->pop = &pop; - - // Start protocomm service - if (start_test_service(1, &pop) != ESP_OK) { - ESP_LOGE(TAG, "Error starting test"); - free(session); - return ESP_FAIL; - } - - // Intialise protocomm session with zero public keys - if (test_new_session(session) != ESP_OK) { - ESP_LOGE(TAG, "Error creating new session"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - const char *wrong_pop_data = "wrong pop"; - protocomm_security_pop_t wrong_pop = { - .data = (const uint8_t *)wrong_pop_data, - .len = strlen(wrong_pop_data) - }; - - // Force wrong pop during authentication - session->pop = &wrong_pop; - - // Perform 25519 security handshake with - // wrong pop, hence failing - if (test_sec_endpoint(session) == ESP_OK) { - ESP_LOGE(TAG, "Error testing security endpoint"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - stop_test_service(); - free(session); - - ESP_LOGI(TAG, "Protocomm test successful"); - return ESP_OK; -} - -static esp_err_t test_security1_insecure_client (void) -{ - ESP_LOGI(TAG, "Starting Security 1 insecure client test"); - - const char *pop_data = "test pop"; - protocomm_security_pop_t pop = { - .data = (const uint8_t *)pop_data, - .len = strlen(pop_data) - }; - - session_t *session = calloc(1, sizeof(session_t)); - if (session == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - return ESP_ERR_NO_MEM; - } - - session->id = 5; - session->sec_ver = 1; - session->pop = &pop; - - // Start protocomm service - if (start_test_service(1, &pop) != ESP_OK) { - ESP_LOGE(TAG, "Error starting test"); - free(session); - return ESP_FAIL; - } - - // Perform 25519 security handshake without - // initialising session, hence failing - if (test_sec_endpoint(session) == ESP_OK) { - ESP_LOGE(TAG, "Error testing security endpoint"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - // Communicating with request endpoint without - // initialising session, hence failing - if (test_req_endpoint(session) == ESP_OK) { - ESP_LOGE(TAG, "Error testing request endpoint"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - stop_test_service(); - free(session); - - ESP_LOGI(TAG, "Protocomm test successful"); - return ESP_OK; -} - -static esp_err_t test_security1_weak_session (void) -{ - ESP_LOGI(TAG, "Starting Security 1 weak session test"); - - const char *pop_data = "test pop"; - protocomm_security_pop_t pop = { - .data = (const uint8_t *)pop_data, - .len = strlen(pop_data) - }; - - session_t *session = calloc(1, sizeof(session_t)); - if (session == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - return ESP_ERR_NO_MEM; - } - - session->id = 6; - session->sec_ver = 1; - session->pop = &pop; - session->weak = 1; - - // Start protocomm service - if (start_test_service(1, &pop) != ESP_OK) { - ESP_LOGE(TAG, "Error starting test"); - free(session); - return ESP_FAIL; - } - - // Intialise protocomm session with zero public keys - if (test_new_session(session) != ESP_OK) { - ESP_LOGE(TAG, "Error creating new session"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - // Perform 25519 security handshake with weak (zero) - // client public key, hence failing - if (test_sec_endpoint(session) == ESP_OK) { - ESP_LOGE(TAG, "Error testing security endpoint"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - // Sending request data to echo endpoint encrypted with zero - // public keys on both client and server side should fail - if (test_req_endpoint(session) == ESP_OK) { - ESP_LOGE(TAG, "Error testing request endpoint"); - stop_test_service(); - free(session); - return ESP_FAIL; - } - - stop_test_service(); - free(session); - - ESP_LOGI(TAG, "Protocomm test successful"); - return ESP_OK; -} - -static esp_err_t test_protocomm (session_t *session) -{ - ESP_LOGI(TAG, "Starting Protocomm test"); - - // Start protocomm service - if (start_test_service(session->sec_ver, session->pop) != ESP_OK) { - ESP_LOGE(TAG, "Error starting test"); - return ESP_FAIL; - } - - // Check version endpoint - if (test_ver_endpoint(session) != ESP_OK) { - ESP_LOGE(TAG, "Error testing version endpoint"); - stop_test_service(); - return ESP_FAIL; - } - - // Intialise protocomm session with zero public keys - if (test_new_session(session) != ESP_OK) { - ESP_LOGE(TAG, "Error creating new session"); - stop_test_service(); - return ESP_FAIL; - } - - // Perform 25519 security handshake to set public keys - if (test_sec_endpoint(session) != ESP_OK) { - ESP_LOGE(TAG, "Error testing security endpoint"); - stop_test_service(); - return ESP_FAIL; - } - - // Send request data to echo endpoint encrypted with - // the set public keys on both client and server side - if (test_req_endpoint(session) != ESP_OK) { - ESP_LOGE(TAG, "Error testing request endpoint"); - stop_test_service(); - return ESP_FAIL; - } - - // Stop protocomm service - stop_test_service(); - ESP_LOGI(TAG, "Protocomm test successful"); - return ESP_OK; -} - -static esp_err_t test_security1 (void) -{ - ESP_LOGI(TAG, "Starting Sec1 test"); - - const char *pop_data = "test pop"; - protocomm_security_pop_t pop = { - .data = (const uint8_t *)pop_data, - .len = strlen(pop_data) - }; - - session_t *session = calloc(1, sizeof(session_t)); - if (session == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - return ESP_ERR_NO_MEM; - } - - session->id = 7; - session->sec_ver = 1; - session->pop = &pop; - - if (test_protocomm (session) != ESP_OK) { - ESP_LOGE(TAG, "Sec1 test failed"); - free(session); - return ESP_FAIL; - } - - ESP_LOGI(TAG, "Sec1 test successful"); - free(session); - return ESP_OK; -} - -static esp_err_t test_security0 (void) -{ - ESP_LOGI(TAG, "Starting Sec0 test"); - - session_t *session = calloc(1, sizeof(session_t)); - if (session == NULL) { - ESP_LOGE(TAG, "Error allocating session"); - return ESP_ERR_NO_MEM; - } - - session->id = 8; - session->sec_ver = 0; - session->pop = NULL; - - if (test_protocomm (session) != ESP_OK) { - ESP_LOGE(TAG, "Sec0 test failed"); - free(session); - return ESP_FAIL; - } - - ESP_LOGI(TAG, "Sec0 test successful"); - free(session); - return ESP_OK; -} - -TEST_CASE("leak test", "[PROTOCOMM]") -{ -#ifdef CONFIG_HEAP_TRACING - heap_trace_init_standalone(trace_record, NUM_RECORDS); - heap_trace_start(HEAP_TRACE_LEAKS); -#endif - - /* Run basic tests for the first time to allow for internal long - * time allocations to happen (not related to protocomm) */ - test_security0(); - test_security1(); - usleep(1000); - -#ifdef CONFIG_HEAP_TRACING - heap_trace_stop(); - heap_trace_dump(); -#endif - - /* Run all tests passively. Any leaks due - * to protocomm should show up now */ - unsigned pre_start_mem = esp_get_free_heap_size(); - - test_security0(); - test_security1(); - test_security1_no_encryption(); - test_security1_session_overflow(); - test_security1_wrong_pop(); - test_security1_insecure_client(); - test_security1_weak_session(); - - usleep(1000); - - unsigned post_stop_mem = esp_get_free_heap_size(); - - if (pre_start_mem != post_stop_mem) { - ESP_LOGE(TAG, "Mismatch in free heap size : %d bytes", post_stop_mem - pre_start_mem); - } - - TEST_ASSERT(pre_start_mem == post_stop_mem); -} - -TEST_CASE("security 0 basic test", "[PROTOCOMM]") -{ - TEST_ASSERT(test_security0() == ESP_OK); -} - -TEST_CASE("security 1 basic test", "[PROTOCOMM]") -{ - TEST_ASSERT(test_security1() == ESP_OK); -} - -TEST_CASE("security 1 no encryption test", "[PROTOCOMM]") -{ - TEST_ASSERT(test_security1_no_encryption() == ESP_OK); -} - -TEST_CASE("security 1 session overflow test", "[PROTOCOMM]") -{ - TEST_ASSERT(test_security1_session_overflow() == ESP_OK); -} - -TEST_CASE("security 1 wrong pop test", "[PROTOCOMM]") -{ - TEST_ASSERT(test_security1_wrong_pop() == ESP_OK); -} - -TEST_CASE("security 1 insecure client test", "[PROTOCOMM]") -{ - TEST_ASSERT(test_security1_insecure_client() == ESP_OK); -} - -TEST_CASE("security 1 weak session test", "[PROTOCOMM]") -{ - TEST_ASSERT(test_security1_weak_session() == ESP_OK); -} diff --git a/tools/unit-test-app/CMakeLists.txt b/tools/unit-test-app/CMakeLists.txt new file mode 100644 index 00000000..95932b0c --- /dev/null +++ b/tools/unit-test-app/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(unit-test-app) \ No newline at end of file diff --git a/tools/unit-test-app/Makefile b/tools/unit-test-app/Makefile index 05f1dc35..006bf975 100644 --- a/tools/unit-test-app/Makefile +++ b/tools/unit-test-app/Makefile @@ -5,9 +5,11 @@ PROJECT_NAME := unit-test-app -NON_INTERACTIVE_TARGET += ut-apply-config-% ut-clean-% +ifeq ($(MAKELEVEL),0) +# Set default target +all: -include $(IDF_PATH)/make/project.mk +# Define helper targets only when not recursing # List of unit-test-app configurations. # Each file in configs/ directory defines a configuration. The format is the @@ -21,8 +23,9 @@ CONFIG_CLEAN_TARGETS := $(addprefix ut-clean-,$(CONFIG_NAMES)) CONFIG_APPLY_TARGETS := $(addprefix ut-apply-config-,$(CONFIG_NAMES)) # Build (intermediate) and output (artifact) directories -BUILDS_DIR := $(PROJECT_PATH)/builds -BINARIES_DIR := $(PROJECT_PATH)/output +PROJECT_DIR := $(abspath $(dir $(firstword $(MAKEFILE_LIST)))) +BUILDS_DIR := $(PROJECT_DIR)/builds +BINARIES_DIR := $(PROJECT_DIR)/output # This generates per-config targets (clean, build, apply-config). define GenerateConfigTargets @@ -56,18 +59,30 @@ $(BINARIES_DIR)/%/$(PROJECT_NAME).bin: configs/% mkdir -p $(BINARIES_DIR)/$*/bootloader mkdir -p $(BUILDS_DIR)/$* # Prepare configuration: top-level sdkconfig.defaults file plus the current configuration (configs/$*) - $(summary) CONFIG $(BUILDS_DIR)/$*/sdkconfig + echo CONFIG $(BUILDS_DIR)/$*/sdkconfig rm -f $(BUILDS_DIR)/$*/sdkconfig cat sdkconfig.defaults > $(BUILDS_DIR)/$*/sdkconfig.defaults echo "" >> $(BUILDS_DIR)/$*/sdkconfig.defaults # in case there is no trailing newline in sdkconfig.defaults cat configs/$* >> $(BUILDS_DIR)/$*/sdkconfig.defaults + # Build, tweaking paths to sdkconfig and sdkconfig.defaults - $(summary) BUILD_CONFIG $(BUILDS_DIR)/$* - $(MAKE) defconfig all \ + echo BUILD_CONFIG $(BUILDS_DIR)/$* + # 'TEST_COMPONENTS=names' option can be added to configs/$* to limit the set + # of tests to build for given configuration. + # Build all tests if this option is not present. + test_components=`sed -n 's/^TEST_COMPONENTS=\(.*\)/\1/p' configs/$*`; \ + test_exclude_components=`sed -n 's/^TEST_EXCLUDE_COMPONENTS=\(.*\)/\1/p' configs/$*`; \ + tests_all=`test -n "$${test_components}"; echo $${?}`; \ + exclude_components=`sed -n 's/^EXCLUDE_COMPONENTS=\(.*\)/\1/p' configs/$*`; \ + $(MAKE) defconfig list-components all \ BUILD_DIR_BASE=$(BUILDS_DIR)/$* \ SDKCONFIG=$(BUILDS_DIR)/$*/sdkconfig \ - SDKCONFIG_DEFAULTS=$(BUILDS_DIR)/$*/sdkconfig.defaults - $(MAKE) print_flash_cmd \ + SDKCONFIG_DEFAULTS=$(BUILDS_DIR)/$*/sdkconfig.defaults \ + TEST_COMPONENTS="$${test_components}" \ + TEST_EXCLUDE_COMPONENTS="$${test_exclude_components}" \ + TESTS_ALL=$${tests_all} \ + EXCLUDE_COMPONENTS="$${exclude_components}" + $(MAKE) --silent print_flash_cmd \ BUILD_DIR_BASE=$(BUILDS_DIR)/$* \ SDKCONFIG=$(BUILDS_DIR)/$*/sdkconfig \ | sed -e 's:'$(BUILDS_DIR)/$*/'::g' \ @@ -77,7 +92,7 @@ $(BINARIES_DIR)/%/$(PROJECT_NAME).bin: configs/% cp $(BUILDS_DIR)/$*/$(PROJECT_NAME).elf $(BINARIES_DIR)/$*/ cp $(BUILDS_DIR)/$*/$(PROJECT_NAME).bin $(BINARIES_DIR)/$*/ cp $(BUILDS_DIR)/$*/$(PROJECT_NAME).map $(BINARIES_DIR)/$*/ - cp $(BUILDS_DIR)/$*/partition_table*.bin $(BINARIES_DIR)/$*/ + cp $(BUILDS_DIR)/$*/*.bin $(BINARIES_DIR)/$*/ cp $(BUILDS_DIR)/$*/sdkconfig $(BINARIES_DIR)/$*/ @@ -87,17 +102,44 @@ ut-help: @echo "make ut-build-NAME - Build unit-test-app with configuration provided in configs/NAME." @echo " Build directory will be builds/NAME/, output binaries will be" @echo " under output/NAME/" - @echo "make ut-clean-NAME - Remove build and output directories for configuration NAME." @echo "" @echo "make ut-build-all-configs - Build all configurations defined in configs/ directory." @echo "" + @echo "Above targets determine list of components to be built from configs/NAME files." + @echo "To build custom subset of components use 'make ut-apply-config-NAME' and then 'make all'." + @echo "" @echo "make ut-apply-config-NAME - Generates configuration based on configs/NAME in sdkconfig" @echo " file. After this, normal all/flash targets can be used." @echo " Useful for development/debugging." @echo "" + @echo "make ut-clean-NAME - Remove build and output directories for configuration NAME." + @echo "" help: ut-help -.PHONY: ut-build-all-configs ut-clean-all-configs \ - $(CONFIG_BUILD_TARGETS) $(CONFIG_CLEAN_TARGETS) $(CONFIG_APPLY_TARGETS) \ +LOCAL_TARGETS := ut-build-all-configs ut-clean-all-configs \ + $(CONFIG_BUILD_TARGETS) $(CONFIG_CLEAN_TARGETS) \ ut-help + +.PHONY: $(LOCAL_TARGETS) + +NON_INTERACTIVE_TARGET += ut-apply-config-% ut-clean-% ut-build-% \ + ut-build-all-configs ut-clean-all-configs + +endif # MAKELEVEL == 0 + + +# When targets defined in this makefile are built, don't need to include the main project makefile. +# This prevents some variables which depend on build directory from being set erroneously. +ifeq ($(filter $(LOCAL_TARGETS),$(MAKECMDGOALS)),) + +include $(IDF_PATH)/make/project.mk + +endif + +# If recursing, print the actual list of tests being built +ifneq ($(MAKELEVEL),0) + +$(info TESTS $(foreach comp,$(TEST_COMPONENT_NAMES),$(patsubst %_test,%,$(comp)))) + +endif # MAKELEVEL != 0 diff --git a/tools/unit-test-app/README.md b/tools/unit-test-app/README.md index d59e71f7..9e197dfd 100644 --- a/tools/unit-test-app/README.md +++ b/tools/unit-test-app/README.md @@ -4,6 +4,8 @@ ESP-IDF unit tests are run using Unit Test App. The app can be built with the un # Building Unit Test App +## GNU Make + * Follow the setup instructions in the top-level esp-idf README. * Set IDF_PATH environment variable to point to the path to the esp-idf top-level directory. * Change into `tools/unit-test-app` directory @@ -12,11 +14,21 @@ ESP-IDF unit tests are run using Unit Test App. The app can be built with the un * Follow the printed instructions to flash, or run `make flash`. * Unit test have a few preset sdkconfigs. It provides command `make ut-clean-config_name` and `make ut-build-config_name` (where `config_name` is the file name under `unit-test-app/configs` folder) to build with preset configs. For example, you can use `make ut-build-default TESTS_ALL=1` to build with config file `unit-test-app/configs/default`. Built binary for this config will be copied to `unit-test-app/output/config_name` folder. +## CMake + +* Follow the setup instructions in the top-level esp-idf README. +* Set IDF_PATH environment variable to point to the path to the esp-idf top-level directory. +* Change into `tools/unit-test-app` directory +* `idf.py menuconfig` to configure the Unit Test App. +* `idf.py build -T ...` with `component` set to names of the components to be included in the test app. Or `idf.py build -T all` to build the test app with all the tests for components having `test` subdirectory. +* Follow the printed instructions to flash, or run `idf.py flash -p PORT`. +* Unit test have a few preset sdkconfigs. It provides command `idf.py ut-clean-config_name` and `idf.py ut-build-config_name` (where `config_name` is the file name under `unit-test-app/configs` folder) to build with preset configs. For example, you can use `idf.py ut-build-default -T all` to build with config file `unit-test-app/configs/default`. Built binary for this config will be copied to `unit-test-app/output/config_name` folder. + # Flash Size -The unit test partition table assumes a 4MB flash size. When testing `TESTS_ALL=1`, this additional factory app partition size is required. +The unit test partition table assumes a 4MB flash size. When testing `TESTS_ALL=1` (Make) or `-T all` (CMake), this additional factory app partition size is required. -If building unit tests to run on a smaller flash size, edit `partition_table_unit_tests_app.csv` and use `TEST_COMPONENTS=` instead of `TESTS_ALL` if tests don't fit in a smaller factory app partition (exact size will depend on configured options). +If building unit tests to run on a smaller flash size, edit `partition_table_unit_tests_app.csv` and use `TEST_COMPONENTS=` (Make) or `-T ...` (CMake) instead of `TESTS_ALL` or `-T all` if tests don't fit in a smaller factory app partition (exact size will depend on configured options). # Running Unit Tests @@ -68,7 +80,7 @@ Unit test jobs will do reset before running each case (because some cases do not Gitlab CI do not support create jobs at runtime. We must maunally add all jobs to CI config file. To make test running in parallel, we limit the number of cases running on each job. When add new unit test cases, it could exceed the limitation that current unit test jobs support. In this case, assign test job will raise error, remind you to add jobs to `.gitlab-ci.yml`. ``` -Please add the following jobs to .gitlab-ci.yml with specific tags: +Too many test cases vs jobs to run. Please add the following jobs to .gitlab-ci.yml with specific tags: * Add job with: UT_T1_1, ESP32_IDF, psram * Add job with: UT_T1_1, ESP32_IDF ``` @@ -103,9 +115,31 @@ If you want to reproduce locally, you need to: 2. Check the following print in CI job to get the config name: `Running unit test for config: config_name`. Then flash the binary of this config to your board. 3. Run the failed case on your board (refer to Running Unit Tests section). * There're some special UT cases (multiple stages case, multiple devices cases) which requires user interaction: - * You can refer to [unit test document](https://esp-idf.readthedocs.io/en/latest/api-guides/unit-tests.html#running-unit-tests) to run test manually. + * You can refer to [unit test document](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/unit-tests.html#running-unit-tests) to run test manually. * Or, you can use `tools/unit-test-app/unit_test.py` to run the test cases: * read document of tiny-test-fw, set correct `TEST_FW_PATH` and `IDF_PATH` - * modify `unit_test.py`, pass the test cases need to test as parameter (refer to test function doc string for supported parameter format) to test functions. - * use `python unit_test.py` to run test + * run `unit_test.py` (see examples below) * You can also use `tools/tiny-test-fw/Runner.py` to run test cases (it will be the same as what Runner do). Please use `python Runner.py -c $CONFIG_FILE $IDF_PATH/tools/unit-test-app` command, where `CONFIG_FILE` is a YAML file with same name with CI job in `components/idf_test/unit_test/CIConfigs` (artifacts, need to be download from `assign_test` job). + +## Running unit tests on local machine by `unit_test.py` + +A couple of examples follow for running unit tests on local machine. + +```bash +# run a simple unit test +./unit_test.py "UART can do select()" +# repeat the tests two times +./unit_test.py -r 2 "UART can do select()" +# use custom environment config file +./unit_test.py -e /tmp/EnvConfigTemplate.yml "UART can do select()" +# use custom application binary +./unit_test.py -b /tmp/app.bin "UART can do select()" +# run a list of unit tests +./unit_test.py "UART can do select()" "concurent selects work" +# add some options for unit tests +./unit_test.py "UART can do select()",timeout:10 "concurent selects work",config:release,env_tag:UT_T2_1 +# run a multi stage test (type of test and child case numbers are autodetected) +./unit_test.py "check a time after wakeup from deep sleep" +# run a list of different unit tests (one simple and one multi stage test) +./unit_test.py "concurent selects work" "NOINIT attributes behavior" +``` diff --git a/tools/unit-test-app/components/unity/CMakeLists.txt b/tools/unit-test-app/components/unity/CMakeLists.txt new file mode 100644 index 00000000..98d8fa6d --- /dev/null +++ b/tools/unit-test-app/components/unity/CMakeLists.txt @@ -0,0 +1,10 @@ +set(COMPONENT_SRCDIRS .) +set(COMPONENT_ADD_INCLUDEDIRS include) + +set(COMPONENT_REQUIRES spi_flash idf_test) + +register_component() + +if(GCC_NOT_5_2_0) + component_compile_options(-Wno-unused-const-variable) +endif() \ No newline at end of file diff --git a/tools/unit-test-app/components/unity/Kconfig b/tools/unit-test-app/components/unity/Kconfig new file mode 100644 index 00000000..642d76f9 --- /dev/null +++ b/tools/unit-test-app/components/unity/Kconfig @@ -0,0 +1,15 @@ +menu "Unity test framework" + +config UNITY_FREERTOS_PRIORITY + int "Priority of Unity test task" + default 5 + +config UNITY_FREERTOS_CPU + int "CPU to run Unity test task on" + default 0 + +config UNITY_FREERTOS_STACK_SIZE + int "Stack size of Unity test task, in bytes" + default 8192 + +endmenu diff --git a/tools/unit-test-app/components/unity/component.mk b/tools/unit-test-app/components/unity/component.mk index ebd7a7d5..c3c44cc0 100644 --- a/tools/unit-test-app/components/unity/component.mk +++ b/tools/unit-test-app/components/unity/component.mk @@ -1,3 +1,7 @@ # # Component Makefile # + +ifeq ($(GCC_NOT_5_2_0), 1) +unity.o: CFLAGS += -Wno-unused-const-variable +endif \ No newline at end of file diff --git a/tools/unit-test-app/components/unity/include/idf_performance.h b/tools/unit-test-app/components/unity/include/idf_performance.h new file mode 100644 index 00000000..60040303 --- /dev/null +++ b/tools/unit-test-app/components/unity/include/idf_performance.h @@ -0,0 +1,32 @@ + +/* @brief macro to print IDF performance + * @param mode : performance item name. a string pointer. + * @param value_fmt: print format and unit of the value, for example: "%02fms", "%dKB" + * @param value : the performance value. +*/ +#define IDF_LOG_PERFORMANCE(item, value_fmt, value) \ + printf("[Performance][%s]: "value_fmt"\n", item, value) + + +/* declare the performance here */ +#define IDF_PERFORMANCE_MAX_HTTPS_REQUEST_BIN_SIZE 800 +#define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP 200 +#define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP_PSRAM 300 +#define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP_UNICORE 130 +#define IDF_PERFORMANCE_MAX_ESP_TIMER_GET_TIME_PER_CALL 1000 +#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING 30 +#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING_NO_DMA 27 +#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_POLLING 15 +#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_POLLING_NO_DMA 15 +/* Due to code size & linker layout differences interacting with cache, VFS + microbenchmark currently runs slower with PSRAM enabled. */ +#define IDF_PERFORMANCE_MAX_VFS_OPEN_WRITE_CLOSE_TIME 50000 +#define IDF_PERFORMANCE_MAX_VFS_OPEN_WRITE_CLOSE_TIME_PSRAM 40000 +// throughput performance by iperf +#define IDF_PERFORMANCE_MIN_TCP_RX_THROUGHPUT 50 +#define IDF_PERFORMANCE_MIN_TCP_TX_THROUGHPUT 40 +#define IDF_PERFORMANCE_MIN_UDP_RX_THROUGHPUT 80 +#define IDF_PERFORMANCE_MIN_UDP_TX_THROUGHPUT 50 +// events dispatched per second by event loop library +#define IDF_PERFORMANCE_MIN_EVENT_DISPATCH 25000 +#define IDF_PERFORMANCE_MIN_EVENT_DISPATCH_PSRAM 21000 diff --git a/tools/unit-test-app/components/unity/include/test_utils.h b/tools/unit-test-app/components/unity/include/test_utils.h new file mode 100644 index 00000000..68e8e81d --- /dev/null +++ b/tools/unit-test-app/components/unity/include/test_utils.h @@ -0,0 +1,109 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// Utilities for esp-idf unit tests + +#include +#include + +/* Return the 'flash_test' custom data partition (type 0x55) + defined in the custom partition table. +*/ +const esp_partition_t *get_test_data_partition(); + +/** + * @brief Initialize reference clock + * + * Reference clock provides timestamps at constant 1 MHz frequency, even when + * the APB frequency is changing. + */ +void ref_clock_init(); + +/** + * @brief Deinitialize reference clock + */ +void ref_clock_deinit(); + + +/** + * @brief Get reference clock timestamp + * @return number of microseconds since the reference clock was initialized + */ +uint64_t ref_clock_get(); + + +/** + * @brief Reset automatic leak checking which happens in unit tests. + * + * Updates recorded "before" free memory values to the free memory values + * at time of calling. Resets leak checker if tracing is enabled in + * config. + * + * This can be called if a test case does something which allocates + * memory on first use, for example. + * + * @note Use with care as this can mask real memory leak problems. + */ +void unity_reset_leak_checks(void); + + +/** + * @brief Call this function from a test case which requires TCP/IP or + * LWIP functionality. + * + * @note This should be the first function the test case calls, as it will + * allocate memory on first use (and also reset the test case leak checker). + */ +void test_case_uses_tcpip(void); + + +/** + * @brief wait for signals. + * + * for multiple devices test cases, DUT might need to wait for other DUTs before continue testing. + * As all DUTs are independent, need user (or test script) interaction to make test synchronized. + * + * Here we provide signal functions for this. + * For example, we're testing GPIO, DUT1 has one pin connect to with DUT2. + * DUT2 will output high level and then DUT1 will read input. + * DUT1 should call `unity_wait_for_signal("output high level");` before it reads input. + * DUT2 should call `unity_send_signal("output high level");` after it finished setting output high level. + * According to the console logs: + * + * DUT1 console: + * + * ``` + * Waiting for signal: [output high level]! + * Please press "Enter" key to once any board send this signal. + * ``` + * + * DUT2 console: + * + * ``` + * Send signal: [output high level]! + * ``` + * + * Then we press Enter key on DUT1's console, DUT1 starts to read input and then test success. + * + * @param signal_name signal name which DUT expected to wait before proceed testing + */ +void unity_wait_for_signal(const char* signal_name); + +/** + * @brief DUT send signal. + * + * @param signal_name signal name which DUT send once it finished preparing. + */ +void unity_send_signal(const char* signal_name); diff --git a/tools/unit-test-app/components/unity/include/unity.h b/tools/unit-test-app/components/unity/include/unity.h index 5dc0725b..596c806c 100644 --- a/tools/unit-test-app/components/unity/include/unity.h +++ b/tools/unit-test-app/components/unity/include/unity.h @@ -16,6 +16,9 @@ extern "C" #define UNITY_INCLUDE_CONFIG_H #include "unity_internals.h" +/* include performance pass standards header file */ +#include "idf_performance.h" + void setUp(void); void tearDown(void); diff --git a/tools/unit-test-app/components/unity/include/unity_config.h b/tools/unit-test-app/components/unity/include/unity_config.h index 94caca15..19f73b1c 100644 --- a/tools/unit-test-app/components/unity/include/unity_config.h +++ b/tools/unit-test-app/components/unity/include/unity_config.h @@ -1,17 +1,19 @@ #ifndef UNITY_CONFIG_H #define UNITY_CONFIG_H -#include - // This file gets included from unity.h via unity_internals.h // It is inside #ifdef __cplusplus / extern "C" block, so we can // only use C features here // Adapt Unity to our environment, disable FP support +#include +#include + /* Some definitions applicable to Unity running in FreeRTOS */ -#define UNITY_FREERTOS_PRIORITY 5 -#define UNITY_FREERTOS_CPU 0 +#define UNITY_FREERTOS_PRIORITY CONFIG_UNITY_FREERTOS_PRIORITY +#define UNITY_FREERTOS_CPU CONFIG_UNITY_FREERTOS_CPU +#define UNITY_FREERTOS_STACK_SIZE CONFIG_UNITY_FREERTOS_STACK_SIZE #define UNITY_EXCLUDE_FLOAT #define UNITY_EXCLUDE_DOUBLE @@ -49,7 +51,7 @@ #define UNITY_TEST_FN_SET(...) \ static test_func UNITY_TEST_UID(test_functions)[] = {__VA_ARGS__}; \ - static char* UNITY_TEST_UID(test_fn_name)[] = FN_NAME_SET(PP_NARG(__VA_ARGS__), __VA_ARGS__) + static const char* UNITY_TEST_UID(test_fn_name)[] = FN_NAME_SET(PP_NARG(__VA_ARGS__), __VA_ARGS__) typedef void (* test_func)(void); @@ -62,7 +64,7 @@ struct test_desc_t const char* file; int line; uint8_t test_fn_count; - char ** test_fn_name; + const char ** test_fn_name; struct test_desc_t* next; }; diff --git a/tools/unit-test-app/components/unity/test_utils.c b/tools/unit-test-app/components/unity/test_utils.c new file mode 100644 index 00000000..36aae4c2 --- /dev/null +++ b/tools/unit-test-app/components/unity/test_utils.c @@ -0,0 +1,86 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "unity.h" +#include "test_utils.h" +#include "rom/ets_sys.h" +#include "rom/uart.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "tcpip_adapter.h" +#include "lwip/sockets.h" + +const esp_partition_t *get_test_data_partition() +{ + /* This finds "flash_test" partition defined in partition_table_unit_test_app.csv */ + const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, "flash_test"); + TEST_ASSERT_NOT_NULL(result); /* means partition table set wrong */ + return result; +} + +// wait user to send "Enter" key +static void wait_user_control() +{ + char sign[5] = {0}; + while(strlen(sign) == 0) + { + /* Flush anything already in the RX buffer */ + while(uart_rx_one_char((uint8_t *) sign) == OK) { + } + /* Read line */ + UartRxString((uint8_t*) sign, sizeof(sign) - 1); + } +} + +void test_case_uses_tcpip() +{ + // Can be called more than once, does nothing on subsequent calls + tcpip_adapter_init(); + + // Allocate all sockets then free them + // (First time each socket is allocated some one-time allocations happen.) + int sockets[CONFIG_LWIP_MAX_SOCKETS]; + for (int i = 0; i < CONFIG_LWIP_MAX_SOCKETS; i++) { + int type = (i % 2 == 0) ? SOCK_DGRAM : SOCK_STREAM; + int family = (i % 3 == 0) ? PF_INET6 : PF_INET; + sockets[i] = socket(family, type, IPPROTO_IP); + } + for (int i = 0; i < CONFIG_LWIP_MAX_SOCKETS; i++) { + close(sockets[i]); + } + + // Allow LWIP tasks to finish initialising themselves + vTaskDelay(25 / portTICK_RATE_MS); + + printf("Note: tcpip_adapter_init() has been called. Until next reset, TCP/IP task will periodicially allocate memory and consume CPU time.\n"); + + // Reset the leak checker as LWIP allocates a lot of memory on first run + unity_reset_leak_checks(); +} + +// signal functions, used for sync between unity DUTs for multiple devices cases +void unity_wait_for_signal(const char* signal_name) +{ + printf("Waiting for signal: [%s]!\n" + "Please press \"Enter\" key to once any board send this signal.\n", signal_name); + wait_user_control(); +} + +void unity_send_signal(const char* signal_name) +{ + printf("Send signal: [%s]!\n", signal_name); +} + diff --git a/tools/unit-test-app/components/unity/unity_platform.c b/tools/unit-test-app/components/unity/unity_platform.c index 49cae755..0eda486e 100644 --- a/tools/unit-test-app/components/unity/unity_platform.c +++ b/tools/unit-test-app/components/unity/unity_platform.c @@ -2,16 +2,20 @@ #include #include #include - #include "unity.h" - +#include "rom/ets_sys.h" +#include "rom/uart.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" - -#include "driver/uart.h" - #include "esp_log.h" -#include "esp_system.h" +#include "esp_clk.h" +#include "soc/cpu.h" +#include "esp_heap_caps.h" +#include "test_utils.h" + +#ifdef CONFIG_HEAP_TRACING +#include "esp_heap_trace.h" +#endif // Pointers to the head and tail of linked list of test description structs: static struct test_desc_t* s_unity_tests_first = NULL; @@ -20,6 +24,10 @@ static struct test_desc_t* s_unity_tests_last = NULL; // Inverse of the filter static bool s_invert = false; + +static size_t before_free_8bit; +static size_t before_free_32bit; + /* Each unit test is allowed to "leak" this many bytes. TODO: Make this value editable by the test. @@ -29,12 +37,52 @@ static bool s_invert = false; const size_t WARN_LEAK_THRESHOLD = 256; const size_t CRITICAL_LEAK_THRESHOLD = 4096; -extern int uart_rx_one_char(char *c); +void unity_reset_leak_checks(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + +#ifdef CONFIG_HEAP_TRACING + heap_trace_start(HEAP_TRACE_LEAKS); +#endif +} /* setUp runs before every test */ void setUp(void) { +// If heap tracing is enabled in kconfig, leak trace the test +#ifdef CONFIG_HEAP_TRACING + const size_t num_heap_records = 80; + static heap_trace_record_t *record_buffer; + if (!record_buffer) { + record_buffer = malloc(sizeof(heap_trace_record_t) * num_heap_records); + assert(record_buffer); + heap_trace_init_standalone(record_buffer, num_heap_records); + } +#endif + printf("%s", ""); /* sneakily lazy-allocate the reent structure for this test task */ + get_test_data_partition(); /* allocate persistent partition table structures */ + + unity_reset_leak_checks(); +} + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + if (before_free <= after_free) { + return; + } + size_t leaked = before_free - after_free; + if (leaked < WARN_LEAK_THRESHOLD) { + return; + } + + printf("MALLOC_CAP_%s %s leak: Before %u bytes free, After %u bytes free (delta %u)\n", + type, + leaked < CRITICAL_LEAK_THRESHOLD ? "potential" : "critical", + before_free, after_free, leaked); + fflush(stdout); + TEST_ASSERT_MESSAGE(leaked < CRITICAL_LEAK_THRESHOLD, "The test leaked too much memory"); } /* tearDown runs after every test */ @@ -47,6 +95,20 @@ void tearDown(void) const char *real_testfile = Unity.TestFile; Unity.TestFile = __FILE__; + /* check if unit test has caused heap corruption in any heap */ + //TEST_ASSERT_MESSAGE( heap_caps_check_integrity(MALLOC_CAP_INVALID, true), "The test has corrupted the heap"); + + /* check for leaks */ +#ifdef CONFIG_HEAP_TRACING + heap_trace_stop(); + heap_trace_dump(); +#endif + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); + Unity.TestFile = real_testfile; // go back to the real filename } @@ -54,43 +116,21 @@ void unity_putc(int c) { if (c == '\n') { - putchar('\r'); - putchar('\n'); + uart_tx_one_char('\r'); + uart_tx_one_char('\n'); } else if (c == '\r') { } else { - putchar(c); + uart_tx_one_char(c); } } void unity_flush() { -// uart_tx_wait_idle(0); // assume that output goes to UART0 -} - -static int UART_RxString(char *s, size_t len) -{ - size_t i = 1; - char *s_local = s; - - while (i < len) { - while (uart_rx_one_char(s_local) != 0); - - if ((*s_local == '\n') || (*s_local == '\r')) { - break; - } - - s_local++; - i++; - } - - s_local++; - *s_local = '\0'; - - return 0; + uart_tx_wait_idle(0); // assume that output goes to UART0 } void unity_testcase_register(struct test_desc_t* desc) @@ -132,8 +172,10 @@ void multiple_function_option(const struct test_desc_t* test_ms) while(strlen(cmdline) == 0) { /* Flush anything already in the RX buffer */ - while(uart_rx_one_char(cmdline) == 0); - UART_RxString(cmdline, sizeof(cmdline) - 1); + while(uart_rx_one_char((uint8_t *) cmdline) == OK) { + + } + UartRxString((uint8_t*) cmdline, sizeof(cmdline) - 1); if(strlen(cmdline) == 0) { /* if input was newline, print a new menu */ print_multiple_function_test_menu(test_ms); @@ -152,6 +194,7 @@ static void unity_run_single_test(const struct test_desc_t* test) printf("Running %s...\n", test->name); // Unit test runner expects to see test name before the test starts fflush(stdout); + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); Unity.TestFile = test->file; Unity.CurrentDetail1 = test->desc; @@ -185,14 +228,12 @@ static void unity_run_single_test_by_index_parse(const char* filter, int index_m int test_index = strtol(filter, NULL, 10); if (test_index >= 1 && test_index <= index_max) { - extern uint32_t system_get_cpu_freq(void); - uint32_t start; - asm volatile ("rsr %0, CCOUNT" : "=r" (start)); + RSR(CCOUNT, start); unity_run_single_test_by_index(test_index - 1); uint32_t end; - asm volatile ("rsr %0, CCOUNT" : "=r" (end)); - uint32_t ms = (end - start) / (system_get_cpu_freq() * 1000000 / 1000); + RSR(CCOUNT, end); + uint32_t ms = (end - start) / (esp_clk_cpu_freq() / 1000); printf("Test ran in %dms\n", ms); } } @@ -273,6 +314,7 @@ static int print_test_menu(void) } } } + printf("\nEnter test for running.\n"); /* unit_test.py needs it for finding the end of test menu */ return test_counter; } @@ -298,9 +340,10 @@ void unity_run_menu() while(strlen(cmdline) == 0) { /* Flush anything already in the RX buffer */ - while(uart_rx_one_char(cmdline) == 0); + while(uart_rx_one_char((uint8_t *) cmdline) == OK) { + } /* Read input */ - UART_RxString(cmdline, sizeof(cmdline) - 1); + UartRxString((uint8_t*) cmdline, sizeof(cmdline) - 1); trim_trailing_space(cmdline); if(strlen(cmdline) == 0) { /* if input was newline, print a new menu */ diff --git a/tools/unit-test-app/main/CMakeLists.txt b/tools/unit-test-app/main/CMakeLists.txt new file mode 100644 index 00000000..47f681d3 --- /dev/null +++ b/tools/unit-test-app/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "app_main.c") +set(COMPONENT_ADD_INCLUDEDIRS "") + +register_component() diff --git a/tools/unit-test-app/main/app_main.c b/tools/unit-test-app/main/app_main.c index dab076a8..73a8201f 100644 --- a/tools/unit-test-app/main/app_main.c +++ b/tools/unit-test-app/main/app_main.c @@ -1,11 +1,6 @@ #include -#include - -#include "esp_system.h" - #include "freertos/FreeRTOS.h" #include "freertos/task.h" - #include "unity.h" #include "unity_config.h" @@ -15,7 +10,10 @@ void unityTask(void *pvParameters) unity_run_menu(); /* Doesn't return */ } -void app_main(void) +void app_main() { - xTaskCreate(unityTask, "unityTask", 8192, NULL, UNITY_FREERTOS_PRIORITY, NULL); + // Note: if unpinning this task, change the way run times are calculated in + // unity_platform + xTaskCreatePinnedToCore(unityTask, "unityTask", UNITY_FREERTOS_STACK_SIZE, NULL, + UNITY_FREERTOS_PRIORITY, NULL, UNITY_FREERTOS_CPU); } diff --git a/tools/unit-test-app/partition_table_unit_test_app.csv b/tools/unit-test-app/partition_table_unit_test_app.csv new file mode 100644 index 00000000..e597a011 --- /dev/null +++ b/tools/unit-test-app/partition_table_unit_test_app.csv @@ -0,0 +1,14 @@ +# Special partition table for unit test app +# +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, 0xd000, 0x2000 +phy_init, data, phy, 0xf000, 0x1000 +factory, 0, 0, 0x10000, 0xF0000 +ota_0, 0, ota_0, , 64K +ota_1, 0, ota_1, , 64K +# flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests +flash_test, data, fat, , 528K + +# Note: still 1MB of a 4MB flash left free for some other purpose diff --git a/tools/unit-test-app/sdkconfig.defaults b/tools/unit-test-app/sdkconfig.defaults new file mode 100644 index 00000000..ab100149 --- /dev/null +++ b/tools/unit-test-app/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table_unit_test_app.csv" +CONFIG_PARTITION_TABLE_FILENAME="partition_table_unit_test_app.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_TASK_WDT= +CONFIG_ENABLE_PTHREAD=y +