#include "unity.h" #include "celrs/crsf_param.h" #include "Mockserial_internal.h" #include #include /* Global state for mock read callbacks */ static uint8_t s_mock_read_buf[260]; static size_t s_mock_read_len = 0; static int s_mock_read_calls = 0; static int s_mock_read_zero_until = 0; /* return 0 for first N calls */ static size_t mock_read_cb(cel_serial_platform_handle handle, uint8_t* buf, size_t len, int call_instance) { (void)handle; (void)call_instance; s_mock_read_calls++; if (s_mock_read_calls <= s_mock_read_zero_until) return 0; size_t to_copy = s_mock_read_len < len ? s_mock_read_len : len; if (to_copy > 0) memcpy(buf, s_mock_read_buf, to_copy); return to_copy; } void setUp(void) { Mockserial_internal_Init(); s_mock_read_calls = 0; s_mock_read_zero_until = 0; s_mock_read_len = 0; } void tearDown(void) { Mockserial_internal_Verify(); Mockserial_internal_Destroy(); } /* Helper: build a valid CRSF frame (payload includes dest+src+data) */ static size_t build_frame(uint8_t* dst, uint8_t type, uint8_t const* payload, uint8_t payload_len) { uint8_t length = 1 + payload_len + 1; dst[0] = CEL_CRSF_FRAME_HEADER; dst[1] = length; dst[2] = type; memcpy(dst + 3, payload, payload_len); uint8_t crc = cel_crsf_crc(dst + 2, 1 + payload_len); dst[2 + length - 1] = crc; return 2 + length; } /* cel_crsf_param_parse tests */ void test_param_parse_null_args(void) { uint8_t payload[16] = {0}; TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_parse(NULL, payload, sizeof(payload))); TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_parse(NULL, NULL, 0)); } void test_param_parse_too_short(void) { cel_crsf_param param; uint8_t payload[5] = {0}; TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_parse(¶m, payload, sizeof(payload))); } void test_param_parse_text_select(void) { cel_crsf_param param; /* dest=0x10, src=0xEE, index=5, chunks=0, parent=0, type=TEXT_SELECT */ /* name="TX Power", options="10 mW;25 mW;100 mW;500 mW;1000 mW", value=2, min=0, max=4, default=0 */ uint8_t payload[] = { 0x10, /* dest */ 0xEE, /* src */ 0x05, /* index */ 0x00, /* chunks_remaining */ 0x00, /* parent */ 0x09, /* type = CEL_PARAM_TEXT_SELECT */ 'T', 'X', ' ', 'P', 'o', 'w', 'e', 'r', '\0', /* name */ '1', '0', ' ', 'm', 'W', ';', /* options */ '2', '5', ' ', 'm', 'W', ';', '1', '0', '0', ' ', 'm', 'W', ';', '5', '0', '0', ' ', 'm', 'W', ';', '1', '0', '0', '0', ' ', 'm', 'W', '\0', /* end of options */ 0x02, /* value */ 0x00, /* min */ 0x04, /* max */ 0x00, /* default */ }; TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload))); TEST_ASSERT_EQUAL_UINT8(5, param.index); TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_TEXT_SELECT, param.type); TEST_ASSERT_EQUAL_UINT8(0, param.hidden); TEST_ASSERT_EQUAL_STRING("TX Power", param.name); TEST_ASSERT_EQUAL_STRING("10 mW;25 mW;100 mW;500 mW;1000 mW", param.options); TEST_ASSERT_EQUAL_UINT8(2, param.value); TEST_ASSERT_EQUAL_UINT8(0, param.min_val); TEST_ASSERT_EQUAL_UINT8(4, param.max_val); TEST_ASSERT_EQUAL_UINT8(0, param.default_val); } void test_param_parse_hidden_flag(void) { cel_crsf_param param; uint8_t payload[] = { 0x10, 0xEE, 0x00, 0x00, 0x00, 0x88, /* type with hidden bit set (0x80 | 0x08) */ 'H', 'i', 'd', 'd', 'e', 'n', '\0', 'A', ';', 'B', '\0', 0x00, 0x00, 0x01, 0x00, }; TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload))); TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_FLOAT, param.type); TEST_ASSERT_EQUAL_UINT8(1, param.hidden); } void test_param_parse_uint8_type(void) { cel_crsf_param param; uint8_t payload[] = { 0x10, 0xEE, 0x10, 0x00, 0x00, 0x00, /* type = CEL_PARAM_UINT8 */ 'V', 'a', 'l', '\0', /* name */ 0x00, /* min */ 0xFF, /* max */ 0x80, /* default */ 0x42, /* value */ }; TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload))); TEST_ASSERT_EQUAL_UINT8(0x10, param.index); TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_UINT8, param.type); TEST_ASSERT_EQUAL_STRING("Val", param.name); TEST_ASSERT_EQUAL_UINT8(0x42, param.value); TEST_ASSERT_EQUAL_UINT8(0x00, param.min_val); TEST_ASSERT_EQUAL_UINT8(0xFF, param.max_val); TEST_ASSERT_EQUAL_UINT8(0x80, param.default_val); } void test_param_parse_int8_type(void) { cel_crsf_param param; uint8_t payload[] = { 0x10, 0xEE, 0x01, 0x00, 0x00, 0x01, /* type = CEL_PARAM_INT8 */ 'S', '\0', 0x80, /* min (-128) */ 0x7F, /* max (127) */ 0x00, /* default */ 0x10, /* value */ }; TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload))); TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_INT8, param.type); TEST_ASSERT_EQUAL_UINT8(0x10, param.value); } void test_param_parse_folder(void) { cel_crsf_param param; uint8_t payload[] = { 0x10, 0xEE, 0xFF, 0x00, 0x00, 0x0B, /* type = CEL_PARAM_FOLDER */ 'F', 'o', 'l', 'd', 'e', 'r', '\0', }; TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload))); TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_FOLDER, param.type); TEST_ASSERT_EQUAL_STRING("Folder", param.name); } void test_param_parse_name_truncation(void) { cel_crsf_param param; /* Name longer than 63 chars (should be truncated) */ uint8_t payload[128] = {0}; payload[0] = 0x10; /* dest */ payload[1] = 0xEE; /* src */ payload[2] = 0x00; /* index */ payload[3] = 0x00; /* chunks */ payload[4] = 0x00; /* parent */ payload[5] = 0x0B; /* type = FOLDER */ /* Fill name with 70 'A' chars */ for (int i = 0; i < 70; i++) payload[6 + i] = 'A'; payload[6 + 70] = '\0'; TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload))); TEST_ASSERT_EQUAL_UINT8(63, strlen(param.name)); /* truncated to 63 */ } void test_param_parse_options_truncation(void) { cel_crsf_param param; /* Options longer than 255 chars (should be truncated) */ uint8_t payload[400] = {0}; payload[0] = 0x10; /* dest */ payload[1] = 0xEE; /* src */ payload[2] = 0x00; /* index */ payload[3] = 0x00; /* chunks */ payload[4] = 0x00; /* parent */ payload[5] = 0x09; /* type = TEXT_SELECT */ strcpy((char*)(payload + 6), "Name"); payload[6 + 4] = '\0'; /* Fill options with 300 'O' chars */ size_t opts_start = 6 + 5; /* after name */ for (int i = 0; i < 299; i++) payload[opts_start + i] = 'O'; payload[opts_start + 299] = '\0'; /* Add type-specific data after options */ size_t after_opts = opts_start + 300; payload[after_opts] = 0x00; /* value */ payload[after_opts + 1] = 0x00; /* min */ payload[after_opts + 2] = 0x00; /* max */ payload[after_opts + 3] = 0x00; /* default */ TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload))); TEST_ASSERT_EQUAL_UINT8(255, strlen(param.options)); /* truncated to 255 */ } /* cel_crsf_param_write tests */ void test_param_write_null_port(void) { TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_write(NULL, 0, 0)); } void test_param_write_success(void) { cel_serial_platform_open_ExpectAndReturn("COM3", 400000, (cel_serial_platform_handle)42); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); cel_serial_platform_write_ExpectAnyArgsAndReturn(8); TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_write(port, 5, 3)); cel_serial_platform_close_Expect((cel_serial_platform_handle)42); cel_serial_close(port); } void test_param_write_partial_write(void) { cel_serial_platform_open_ExpectAndReturn("COM3", 400000, (cel_serial_platform_handle)42); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); cel_serial_platform_write_ExpectAnyArgsAndReturn(4); TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_write(port, 5, 3)); cel_serial_platform_close_Expect((cel_serial_platform_handle)42); cel_serial_close(port); } /* cel_crsf_param_ping tests */ void test_param_ping_null_port(void) { TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_ping(NULL, 1.0f)); } void test_param_ping_success(void) { /* Build a DEVICE_INFO frame (type 0x29) */ uint8_t payload[] = {0x10, 0xEE, 0x00}; /* dest, src, type */ size_t frame_len = build_frame(s_mock_read_buf, CEL_CRSF_TYPE_DEVICE_INFO, payload, sizeof(payload)); s_mock_read_len = frame_len; s_mock_read_zero_until = 0; cel_serial_platform_open_ExpectAndReturn("COM3", 400000, (cel_serial_platform_handle)42); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); cel_serial_platform_write_ExpectAnyArgsAndReturn(6); cel_serial_platform_read_StubWithCallback(mock_read_cb); TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_ping(port, 1.0f)); cel_serial_platform_close_Expect((cel_serial_platform_handle)42); cel_serial_close(port); } void test_param_ping_timeout(void) { cel_serial_platform_open_ExpectAndReturn("COM3", 400000, (cel_serial_platform_handle)42); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); cel_serial_platform_write_ExpectAnyArgsAndReturn(6); cel_serial_platform_read_StubWithCallback(mock_read_cb); /* mock_read_cb always returns 0 since s_mock_read_zero_until defaults to 0 * and s_mock_read_len is 0 */ /* Use a short timeout so test doesn't hang */ TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_ping(port, 0.05f)); cel_serial_platform_close_Expect((cel_serial_platform_handle)42); cel_serial_close(port); } /* cel_crsf_param_read tests */ void test_param_read_null_port(void) { cel_crsf_param param; TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_read(NULL, 0, ¶m, 1.0f)); } void test_param_read_null_out(void) { cel_serial_platform_open_ExpectAndReturn("COM3", 400000, (cel_serial_platform_handle)42); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); /* No write expected since out is NULL */ TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_read(port, 5, NULL, 1.0f)); cel_serial_platform_close_Expect((cel_serial_platform_handle)42); cel_serial_close(port); } void test_param_read_success(void) { /* Build a PARAM_ENTRY frame (type 0x2B) matching index 5 */ uint8_t payload[] = { 0x10, 0xEE, 0x05, 0x00, 0x00, 0x00, /* dest,src,idx,chunks,parent,type */ 'V', 'a', 'l', '\0', /* name */ 0x00, 0xFF, 0x80, 0x42, /* min,max,default,value */ }; size_t frame_len = build_frame(s_mock_read_buf, CEL_CRSF_TYPE_PARAM_ENTRY, payload, sizeof(payload)); s_mock_read_len = frame_len; s_mock_read_zero_until = 0; cel_serial_platform_open_ExpectAndReturn("COM3", 400000, (cel_serial_platform_handle)42); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); cel_serial_platform_write_ExpectAnyArgsAndReturn(8); cel_serial_platform_read_StubWithCallback(mock_read_cb); cel_crsf_param param; TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_read(port, 5, ¶m, 1.0f)); TEST_ASSERT_EQUAL_UINT8(5, param.index); TEST_ASSERT_EQUAL_STRING("Val", param.name); TEST_ASSERT_EQUAL_UINT8(0x42, param.value); cel_serial_platform_close_Expect((cel_serial_platform_handle)42); cel_serial_close(port); } void test_param_read_timeout(void) { cel_serial_platform_open_ExpectAndReturn("COM3", 400000, (cel_serial_platform_handle)42); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); cel_serial_platform_write_ExpectAnyArgsAndReturn(8); cel_serial_platform_read_StubWithCallback(mock_read_cb); cel_crsf_param param; TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_read(port, 5, ¶m, 0.05f)); cel_serial_platform_close_Expect((cel_serial_platform_handle)42); cel_serial_close(port); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_param_parse_null_args); RUN_TEST(test_param_parse_too_short); RUN_TEST(test_param_parse_text_select); RUN_TEST(test_param_parse_hidden_flag); RUN_TEST(test_param_parse_uint8_type); RUN_TEST(test_param_parse_int8_type); RUN_TEST(test_param_parse_folder); RUN_TEST(test_param_parse_name_truncation); RUN_TEST(test_param_parse_options_truncation); RUN_TEST(test_param_write_null_port); RUN_TEST(test_param_write_success); RUN_TEST(test_param_write_partial_write); RUN_TEST(test_param_ping_null_port); RUN_TEST(test_param_ping_success); RUN_TEST(test_param_ping_timeout); RUN_TEST(test_param_read_null_port); RUN_TEST(test_param_read_null_out); RUN_TEST(test_param_read_success); RUN_TEST(test_param_read_timeout); return UNITY_END(); }