diff --git a/helper.mk b/helper.mk index dead32b..f252068 100755 --- a/helper.mk +++ b/helper.mk @@ -22,7 +22,7 @@ docker_arch_i386=i386 docker_arch?=$(docker_arch_${target_debian_arch}) packages?=make cmake time file git sudo -packages+=build-essential pkg-config bison flex python +packages+=build-essential pkg-config bison flex python ruby packages+=libusb-1.0-0-dev libssl-dev libxml2-dev libjson-c-dev packages+=doxygen xsltproc plantuml roffit packages+=radvd parprouted bridge-utils net-tools zip unzip diff --git a/libs2/test/CMakeLists.txt b/libs2/test/CMakeLists.txt index 0f132fc..a712798 100644 --- a/libs2/test/CMakeLists.txt +++ b/libs2/test/CMakeLists.txt @@ -32,6 +32,148 @@ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "C51") target_link_libraries(new_test_t2 zipgateway-lib) endif() add_test(test_transport_service2 new_test_t2) + + set(CMOCK_RB "" CACHE FILEPATH "Path to CMock lib/cmock.rb used for local mock regeneration") + set(CMOCK_VERSION "2.4.6" CACHE STRING "CMock version used for regeneration") + set(CMOCK_ARCHIVE "${CMAKE_BINARY_DIR}/tools/cmock-v${CMOCK_VERSION}.tar.gz") + set(CMOCK_ARCHIVE_SHA256 "7b4b0a1ca9ecd4d461e3a442b4b2c98c37e451d8966e636483142fdf49667ed7" + CACHE STRING "Expected SHA256 for CMock archive") + set(CMOCK_ROOT "${CMAKE_BINARY_DIR}/tools/CMock-${CMOCK_VERSION}") + set(CMOCK_RB_DEFAULT "${CMOCK_ROOT}/lib/cmock.rb") + if(CMOCK_RB STREQUAL "") + if(NOT EXISTS "${CMOCK_RB_DEFAULT}") + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/tools") + if(NOT EXISTS "${CMOCK_ARCHIVE}") + message(STATUS "Downloading CMock v${CMOCK_VERSION} for mock regeneration") + file(DOWNLOAD + "https://github.com/ThrowTheSwitch/CMock/archive/refs/tags/v${CMOCK_VERSION}.tar.gz" + "${CMOCK_ARCHIVE}" + SHOW_PROGRESS + STATUS CMOCK_DOWNLOAD_STATUS + TLS_VERIFY ON) + list(GET CMOCK_DOWNLOAD_STATUS 0 CMOCK_DOWNLOAD_CODE) + list(GET CMOCK_DOWNLOAD_STATUS 1 CMOCK_DOWNLOAD_MESSAGE) + if(NOT CMOCK_DOWNLOAD_CODE EQUAL 0) + message(FATAL_ERROR "Failed to download CMock archive: ${CMOCK_DOWNLOAD_MESSAGE}") + endif() + endif() + + file(SHA256 "${CMOCK_ARCHIVE}" CMOCK_ARCHIVE_SHA256_ACTUAL) + if(NOT CMOCK_ARCHIVE_SHA256_ACTUAL STREQUAL CMOCK_ARCHIVE_SHA256) + message(FATAL_ERROR + "CMock archive SHA256 mismatch. Expected ${CMOCK_ARCHIVE_SHA256}, got ${CMOCK_ARCHIVE_SHA256_ACTUAL}") + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf "${CMOCK_ARCHIVE}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/tools" + RESULT_VARIABLE CMOCK_EXTRACT_RESULT) + if(NOT CMOCK_EXTRACT_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to extract ${CMOCK_ARCHIVE}") + endif() + + file(MAKE_DIRECTORY "${CMOCK_ROOT}/vendor/unity/auto") + file(COPY "${CMAKE_SOURCE_DIR}/libs2/TestFramework/unity/auto/type_sanitizer.rb" + DESTINATION "${CMOCK_ROOT}/vendor/unity/auto") + endif() + set(CMOCK_RB "${CMOCK_RB_DEFAULT}" CACHE FILEPATH "Path to CMock lib/cmock.rb used by regen_mock_zw_transport_api" FORCE) + endif() + + set(CMOCK_RUNTIME_C "${CMOCK_ROOT}/src/cmock.c" + CACHE FILEPATH "Path to CMock runtime cmock.c used by unit tests") + if(NOT EXISTS "${CMOCK_RUNTIME_C}") + message(FATAL_ERROR "CMock runtime source not found: ${CMOCK_RUNTIME_C}") + endif() + + set(CMOCK_GEN_DIR "${CMAKE_BINARY_DIR}/cmock_gen") + set(CMOCK_INPUT_DIR "${CMAKE_BINARY_DIR}/cmock-input") + set(CMOCK_INPUT_HEADER "${CMOCK_INPUT_DIR}/ZW_transport_api.h") + set(MOCK_ZW_TRANSPORT_API_C "${CMOCK_GEN_DIR}/MockZW_transport_api.c") + set(MOCK_ZW_TRANSPORT_API_H "${CMOCK_GEN_DIR}/MockZW_transport_api.h") + + add_custom_command( + OUTPUT "${CMOCK_INPUT_HEADER}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMOCK_INPUT_DIR}" + COMMAND "${CMAKE_C_COMPILER}" -E -P + -DZW_CONTROLLER_BRIDGE + -I"${CMAKE_SOURCE_DIR}/Z-Wave/include" + "${CMAKE_SOURCE_DIR}/Z-Wave/include/ZW_transport_api.h" + -o "${CMOCK_INPUT_HEADER}" + DEPENDS "${CMAKE_SOURCE_DIR}/Z-Wave/include/ZW_transport_api.h" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Preprocessing ZW_transport_api.h for CMock generation") + + add_custom_command( + OUTPUT "${MOCK_ZW_TRANSPORT_API_C}" "${MOCK_ZW_TRANSPORT_API_H}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMOCK_GEN_DIR}" + COMMAND ${CMAKE_COMMAND} -E env ruby "${CMOCK_RB}" + -o"${CMAKE_CURRENT_SOURCE_DIR}/cmock/cmock_config.yml" + "${CMOCK_INPUT_HEADER}" + DEPENDS "${CMOCK_INPUT_HEADER}" "${CMAKE_CURRENT_SOURCE_DIR}/cmock/cmock_config.yml" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Generating MockZW_transport_api in build directory") + set_source_files_properties("${MOCK_ZW_TRANSPORT_API_C}" "${MOCK_ZW_TRANSPORT_API_H}" PROPERTIES GENERATED TRUE) + + add_custom_target(regen_mock_zw_transport_api + DEPENDS "${MOCK_ZW_TRANSPORT_API_C}" "${MOCK_ZW_TRANSPORT_API_H}") + + add_unity_test(NAME test_t2_zipgw_runtime_nodeid FILES + test_t2_zipgw_runtime_nodeid.c + "${CMOCK_RUNTIME_C}" + "${MOCK_ZW_TRANSPORT_API_C}" + ${CMAKE_CURRENT_SOURCE_DIR}/../transport_service/transport_service2.c + ${CMAKE_CURRENT_SOURCE_DIR}/../transport_service/transport2_fsm.c) + add_dependencies(test_t2_zipgw_runtime_nodeid regen_mock_zw_transport_api) + target_compile_definitions(test_t2_zipgw_runtime_nodeid PRIVATE ZIPGW) + + # Explicitly undefine NEW_TEST_T2 here so this regression test follows the + # production TS code path. + target_compile_options(test_t2_zipgw_runtime_nodeid PRIVATE -U NEW_TEST_T2) + # Generated CMock code may reference legacy Unity detail macros that are + # not available in the Unity variant used by this tree. + target_compile_options(test_t2_zipgw_runtime_nodeid PRIVATE + -include "${CMAKE_CURRENT_SOURCE_DIR}/cmock/cmock_legacy_unity_compat.h") + target_include_directories(test_t2_zipgw_runtime_nodeid PRIVATE + ${CMAKE_BINARY_DIR} + "${CMOCK_GEN_DIR}" + "${CMOCK_ROOT}/src" + ${CMAKE_CURRENT_SOURCE_DIR}/../transport_service/ + ${CMAKE_CURRENT_SOURCE_DIR}/../include/ + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/transport + ${CMAKE_SOURCE_DIR}/src/utls + ${CMAKE_SOURCE_DIR}/contiki/core + ${CMAKE_SOURCE_DIR}/contiki/core/sys + ${CMAKE_SOURCE_DIR}/contiki/platform/linux + ${CMAKE_SOURCE_DIR}/Z-Wave/include) +endif() + +# ZW_SendDataAppl.c is compiled directly into the test (not taken from the +# pre-built zipgateway-lib) so that the -DUNIT_TEST flag (set globally by +# add_definitions at the top of this file) is in effect. This is required +# to compile the UNIT_TEST seam functions that expose private state. +# +# The remaining zipgateway-lib symbols that ZW_SendDataAppl.c needs are +# provided either by linking zipgateway-lib or by --wrap stubs in the test. +# --------------------------------------------------------------------------- +if (${CMAKE_PROJECT_NAME} MATCHES "zipgateway") + add_unity_test(NAME test_send_data_callback + FILES test_send_data_callback.c + ${CMAKE_SOURCE_DIR}/src/transport/ZW_SendDataAppl.c + ${CMAKE_SOURCE_DIR}/test/zipgateway_main_stubs.c + LIBRARIES zipgateway-lib) + + set_target_properties(test_send_data_callback PROPERTIES LINK_FLAGS + "-Wl,-wrap=process_post \ + -Wl,-wrap=process_exit \ + -Wl,-wrap=process_start \ + -Wl,-wrap=get_queue_state \ + -Wl,-wrap=zw_frame_buffer_free \ + -Wl,-wrap=etimer_stop \ + -Wl,-wrap=etimer_set \ + -Wl,-wrap=etimer_expired \ + -Wl,-wrap=sec0_abort_all_tx_sessions \ + -Wl,-wrap=ima_send_data_done") endif() add_definitions( -DRANDLEN=64 ) diff --git a/libs2/test/cmock/cmock_config.yml b/libs2/test/cmock/cmock_config.yml new file mode 100644 index 0000000..e727431 --- /dev/null +++ b/libs2/test/cmock/cmock_config.yml @@ -0,0 +1,7 @@ +:cmock: + :mock_path: cmock_gen + :plugins: + - :ignore + - :ignore_arg + - :expect + :treat_externs: :include diff --git a/libs2/test/cmock/cmock_legacy_unity_compat.h b/libs2/test/cmock/cmock_legacy_unity_compat.h new file mode 100644 index 0000000..90dbd1a --- /dev/null +++ b/libs2/test/cmock/cmock_legacy_unity_compat.h @@ -0,0 +1,16 @@ +#ifndef CMOCK_LEGACY_UNITY_COMPAT_H +#define CMOCK_LEGACY_UNITY_COMPAT_H + +#ifndef UNITY_SET_DETAIL +#define UNITY_SET_DETAIL(msg) ((void)(msg)) +#endif + +#ifndef UNITY_CLR_DETAILS +#define UNITY_CLR_DETAILS() ((void)0) +#endif + +#ifndef UNITY_SET_DETAILS +#define UNITY_SET_DETAILS(msg1, msg2) ((void)(msg1), (void)(msg2)) +#endif + +#endif diff --git a/libs2/test/test_send_data_callback.c b/libs2/test/test_send_data_callback.c new file mode 100644 index 0000000..602edd8 --- /dev/null +++ b/libs2/test/test_send_data_callback.c @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: LicenseRef-MSLA + * SPDX-FileCopyrightText: Silicon Laboratories Inc. https://www.silabs.com + */ + +#include +#include +#include + +#include "unity.h" + +#include "ZW_SendDataAppl.h" /* public API + ts_param_t */ +#include "ZW_transport_api.h" /* TRANSMIT_COMPLETE_*, TX_STATUS_TYPE */ +#include "node_queue.h" /* en_queue_state, QS_IDLE */ +#include "zw_frame_buffer.h" /* zw_frame_buffer_element_t */ + +/* + * Test-seam declarations + * + * These are compiled into ZW_SendDataAppl.c only when -DUNIT_TEST is set + * (which libs2/test/CMakeLists.txt sets globally with add_definitions). + * -------------------------------------------------------------------------*/ +void ZW_SendDataAppl_set_lock_ll(uint8_t v); +void ZW_SendDataAppl_set_resend_counter(uint8_t v); +uint8_t ZW_SendDataAppl_get_lock_ll(void); +void *ZW_SendDataAppl_alloc_session(void); +void ZW_SendDataAppl_push_session(void *s); +void *ZW_SendDataAppl_list_head(void); +void ZW_SendDataAppl_trigger_fail_for_test(void); + +typedef struct { + void *next; + zw_frame_buffer_element_t *fb; + void *user; + ZW_SendDataAppl_Callback_t callback; +} test_session_t; + +static int app_cb_count; +static uint8_t app_cb_status; +static void *app_cb_user; + +static void spy_app_callback(uint8_t status, void *user, TX_STATUS_TYPE *tx) { + (void)tx; + app_cb_count++; + app_cb_status = status; + app_cb_user = user; +} + +static int fb_free_count; +static zw_frame_buffer_element_t *fb_free_ptr; + +void __wrap_zw_frame_buffer_free(zw_frame_buffer_element_t *e) { + fb_free_count++; + fb_free_ptr = e; +} + +static int pp_count; +static process_event_t pp_last_event; + +#define EXPECTED_SEND_NEXT_LL ((process_event_t)1) + +int __wrap_process_post(struct process *p, process_event_t ev, void *data) { + (void)p; + (void)data; + pp_count++; + pp_last_event = ev; + return 0; +} + +enum en_queue_state __wrap_get_queue_state(void) { return QS_IDLE; } + +void __wrap_process_exit(struct process *p) { (void)p; } +void __wrap_process_start(struct process *p, const char *arg) { + (void)p; + (void)arg; +} + +void __wrap_etimer_stop(struct etimer *t) { (void)t; } +void __wrap_etimer_set(struct etimer *t, unsigned long i) { + (void)t; + (void)i; +} +int __wrap_etimer_expired(struct etimer *t) { + (void)t; + return 1; +} + +void __wrap_sec0_abort_all_tx_sessions(void) {} +void __wrap_ima_send_data_done(uint16_t n, uint8_t s, TX_STATUS_TYPE *t) { + (void)n; + (void)s; + (void)t; +} + +static void reset_spies(void) { + app_cb_count = 0; + app_cb_status = 0xFF; + app_cb_user = NULL; + + fb_free_count = 0; + fb_free_ptr = NULL; + + pp_count = 0; + pp_last_event = 0xFF; +} + +/* ------------------------------------------------------------------------- + * ZW_SendDataAppl_init() sets lock=FALSE, lock_ll=FALSE, clears the list + * and reinitialises the memb pool. It also (re)starts the Contiki process, + * which is stubbed to a no-op here. + * -------------------------------------------------------------------------*/ +void setUp(void) { + reset_spies(); + ZW_SendDataAppl_init(); +} + +void tearDown(void) {} + +void test_ts_should_pop_session_and_advance_when_transmit_complete_fail_and_resend_zero(void) { + static zw_frame_buffer_element_t fb_a, fb_b; + int sentinel = 0xBEEF; + + test_session_t *session_a = (test_session_t *)ZW_SendDataAppl_alloc_session(); + test_session_t *session_b = (test_session_t *)ZW_SendDataAppl_alloc_session(); + TEST_ASSERT_NOT_NULL_MESSAGE(session_a, "memb_alloc failed — session pool exhausted"); + TEST_ASSERT_NOT_NULL_MESSAGE(session_b, "memb_alloc failed — session pool exhausted"); + + session_a->fb = &fb_a; + session_a->user = &sentinel; + session_a->callback = spy_app_callback; + + session_b->fb = &fb_b; + session_b->user = NULL; + session_b->callback = NULL; + + /* head = A, A->next = B */ + ZW_SendDataAppl_push_session(session_b); + ZW_SendDataAppl_push_session(session_a); + + TEST_ASSERT_EQUAL_PTR_MESSAGE(session_a, ZW_SendDataAppl_list_head(), + "Pre-condition: A must be at the head before the failure"); + + ZW_SendDataAppl_set_lock_ll(1); + ZW_SendDataAppl_set_resend_counter(0); + + ZW_SendDataAppl_trigger_fail_for_test(); + + TEST_ASSERT_EQUAL_PTR_MESSAGE(session_b, ZW_SendDataAppl_list_head(), + "Session B must be the head after session A is popped on FAIL"); + + TEST_ASSERT_EQUAL_INT_MESSAGE(1, fb_free_count, "Exactly one buffer must be freed (session A's)"); + TEST_ASSERT_EQUAL_PTR_MESSAGE(&fb_a, fb_free_ptr, + "The freed buffer must be session A's, not B's"); + + TEST_ASSERT_EQUAL_INT_MESSAGE(1, app_cb_count, + "Application callback must be called exactly once"); + TEST_ASSERT_EQUAL_UINT8_MESSAGE(TRANSMIT_COMPLETE_FAIL, app_cb_status, + "Application callback must receive TRANSMIT_COMPLETE_FAIL"); + TEST_ASSERT_EQUAL_PTR_MESSAGE(&sentinel, app_cb_user, + "Application callback must receive the original user pointer"); + + TEST_ASSERT_EQUAL_INT_MESSAGE(1, pp_count, + "SEND_EVENT_SEND_NEXT_LL must be posted after A is removed"); + TEST_ASSERT_EQUAL_MESSAGE(EXPECTED_SEND_NEXT_LL, pp_last_event, + "The posted event must be SEND_EVENT_SEND_NEXT_LL"); +} + +void test_ts_should_not_pop_session_when_resend_is_needed() { + static zw_frame_buffer_element_t fb; + + test_session_t *s = (test_session_t *)ZW_SendDataAppl_alloc_session(); + TEST_ASSERT_NOT_NULL(s); + s->fb = &fb; + s->user = NULL; + s->callback = spy_app_callback; + ZW_SendDataAppl_push_session(s); + + ZW_SendDataAppl_set_lock_ll(1); + ZW_SendDataAppl_set_resend_counter(1); + + ZW_SendDataAppl_trigger_fail_for_test(); + + TEST_ASSERT_EQUAL_PTR_MESSAGE(s, ZW_SendDataAppl_list_head(), + "Session must remain in send_data_list when resend_counter > 0"); + + TEST_ASSERT_EQUAL_INT_MESSAGE(0, fb_free_count, + "zw_frame_buffer_free must NOT be called when resend_counter > 0"); + + TEST_ASSERT_EQUAL_INT_MESSAGE(0, app_cb_count, + "Application callback must NOT be called when resend_counter > 0"); + + TEST_ASSERT_EQUAL_INT_MESSAGE(0, pp_count, + "process_post must NOT be called when resend_counter > 0"); + + TEST_ASSERT_EQUAL_UINT8_MESSAGE(0, ZW_SendDataAppl_get_lock_ll(), + "lock_ll must be FALSE after the callback (retry path)"); +} diff --git a/libs2/test/test_t2_zipgw_runtime_nodeid.c b/libs2/test/test_t2_zipgw_runtime_nodeid.c new file mode 100644 index 0000000..ddca1f3 --- /dev/null +++ b/libs2/test/test_t2_zipgw_runtime_nodeid.c @@ -0,0 +1,77 @@ +/* Copyright Silicon Laboratories Inc. + * + * Build Transport Service with ZIPGW and without NEW_TEST_T2, then verify + * that LR node IDs are preserved on the TS -> ZW_SendData_Bridge call path. + */ + +#include +#include + +// clang-format off +#include "unity.h" +#include "MockZW_transport_api.h" +// clang-format on + +#include "S2.h" +#include "transport_service2.h" + +BYTE MyNodeID = 1; + +void setUp(void) { MockZW_transport_api_Init(); } + +void tearDown(void) { + MockZW_transport_api_Verify(); + MockZW_transport_api_Destroy(); +} + +struct ctimer; +typedef unsigned long clock_time_t; +void ctimer_set(struct ctimer *c, clock_time_t t, void (*f)(void *), void *ptr) { + (void)c; + (void)t; + (void)f; + (void)ptr; +} + +void ctimer_stop(struct ctimer *c) { (void)c; } + +clock_time_t clock_time(void) { return 0; } + +uint16_t zgw_crc16(uint16_t crc16, uint8_t *data, unsigned long data_len) { + (void)data; + (void)data_len; + return crc16; +} + +static void app_cmd_handler(ts_param_t *p, ZW_APPLICATION_TX_BUFFER *pCmd, uint16_t cmdLength) { + (void)p; + (void)pCmd; + (void)cmdLength; +} + +static void status_cb(uint8_t txStatus, TX_STATUS_TYPE *txStatusReport) { + (void)txStatus; + (void)txStatusReport; +} + +void test_zipgw_ts_preserves_lr_destination_nodeid(void) { + ts_param_t p; + uint8_t payload[64]; + + memset(&p, 0, sizeof(p)); + memset(payload, 0xAA, sizeof(payload)); + + p.snode = 0x00ff; + p.dnode = 257; + p.tx_flags = TRANSMIT_OPTION_ACK | TRANSMIT_OPTION_AUTO_ROUTE | TRANSMIT_OPTION_EXPLORE; + + ZW_SendData_Bridge_ExpectAndReturn(p.snode, p.dnode, NULL, 0, 0, NULL, 1); + ZW_SendData_Bridge_IgnoreArg_pData(); + ZW_SendData_Bridge_IgnoreArg_dataLength(); + ZW_SendData_Bridge_IgnoreArg_txOptions(); + ZW_SendData_Bridge_IgnoreArg_completedFunc(); + + ZW_TransportService_Init(app_cmd_handler); + TEST_ASSERT_TRUE_MESSAGE(ZW_TransportService_SendData(&p, payload, sizeof(payload), status_cb), + "ZW_TransportService_SendData returned false"); +} diff --git a/libs2/transport_service/transport_service2.c b/libs2/transport_service/transport_service2.c index e972959..d816997 100644 --- a/libs2/transport_service/transport_service2.c +++ b/libs2/transport_service/transport_service2.c @@ -107,7 +107,7 @@ typedef void (*ZW_CommandHandler_Callback_t)(ts_param_t* p, ZW_APPLICATION_TX_BU #endif #ifndef __C51__ -extern uint8_t ZW_SendData_Bridge(uint8_t, uint8_t, uint8_t *, uint8_t, uint8_t, VOID_CALLBACKFUNC(completedFunc)(uint8_t, TX_STATUS_TYPE*)); +extern uint8_t ZW_SendData_Bridge(uint16_t, uint16_t, uint8_t *, uint8_t, uint8_t, VOID_CALLBACKFUNC(completedFunc)(uint8_t, TX_STATUS_TYPE*)); #endif /* ifndef __C51__ */ #endif diff --git a/src/transport/S2_wrap.c b/src/transport/S2_wrap.c index 385ba03..f602a4c 100644 --- a/src/transport/S2_wrap.c +++ b/src/transport/S2_wrap.c @@ -603,7 +603,7 @@ uint8_t S2_send_frame(struct S2* ctxt,const s2_connection_t* conn, uint8_t* buf, p.snode = conn->l_node; p.dnode = conn->r_node; p.tx_flags = conn->zw_tx_options; - LOG_PRINTF(" Sending S2_send_frame %i %d -> %d\n", len, p.snode, p.dnode); + LOG_PRINTF("Sending S2_send_frame %i %d -> %d\n", len, p.snode, p.dnode); transmit_start_time = clock_time(); return send_data(&p, buf, len,S2_send_frame_callback,ctxt); } diff --git a/src/transport/ZW_SendDataAppl.c b/src/transport/ZW_SendDataAppl.c index 5106c54..5ad09ce 100644 --- a/src/transport/ZW_SendDataAppl.c +++ b/src/transport/ZW_SendDataAppl.c @@ -288,25 +288,36 @@ send_data_callback_func(u8_t status, TX_STATUS_TYPE* ts) return; } - - // if (status == TRANSMIT_COMPLETE_NO_ACK) + // TS and other senders report TRANSMIT_COMPLETE_FAIL. Pop and + // free the session so the queue advances; the original hold-for-retry + // behaviour (previously on NO_ACK) is preserved only when the resend watchdog + // is active. if (status == TRANSMIT_COMPLETE_FAIL) { etimer_stop(&emergency_timer); if(resend_counter == 0) { - send_data_appl_session_t *s = list_head(send_data_list); + send_data_appl_session_t *s = list_pop(send_data_list); if (s) { if (s->callback) { s->callback(status, s->user, ts); } + zw_frame_buffer_free(s->fb); + + char refcount = memb_free(&session_memb, s); + if(refcount == -1) { + ERR_PRINTF("attempt to deallocate illegal memory block\n"); + ASSERT(0); + } } else { + DBG_PRINTF("event in the send data list is NULL\n"); ASSERT(0); } + process_post(&ZW_SendDataAppl_process, SEND_EVENT_SEND_NEXT_LL, NULL); } lock_ll = FALSE; @@ -810,7 +821,12 @@ PROCESS_THREAD(ZW_SendDataAppl_process, ev, data) if (SupportsCmdClass(current_session_ll->fb->param.dnode, COMMAND_CLASS_TRANSPORT_SERVICE)) { - DBG_PRINTF("SEND_EVENT_SEND_NEXT_LL | SupportsCmdClass | ZW_TransportService_SendData \n"); + DBG_PRINTF("SEND_EVENT_SEND_NEXT_LL | SupportsCmdClass | ZW_TransportService_SendData\n" + "snode=%u dnode=%u frame_len=%u dnode_low8=%u\n", + (unsigned)current_session_ll->fb->param.snode, + (unsigned)current_session_ll->fb->param.dnode, + (unsigned)current_session_ll->fb->frame_len, + (unsigned)(current_session_ll->fb->param.dnode & 0xFFu)); //ASSERT(current_session_ll->param.snode == MyNodeID); //TODO make transport service bridge aware rc = ZW_TransportService_SendData(¤t_session_ll->fb->param, (u8_t*) current_session_ll->fb->frame_data, @@ -896,3 +912,24 @@ PROCESS_THREAD(ZW_SendDataAppl_process, ev, data) } PROCESS_END(); } + +#ifdef UNIT_TEST +void ZW_SendDataAppl_set_lock_ll(uint8_t value) { lock_ll = value; } +void ZW_SendDataAppl_set_resend_counter(uint8_t value) { resend_counter = value; } +uint8_t ZW_SendDataAppl_get_lock_ll(void) { return lock_ll; } + +void *ZW_SendDataAppl_alloc_session(void) { return memb_alloc(&session_memb); } +void ZW_SendDataAppl_push_session(void *s) { list_push(send_data_list, s); } +void *ZW_SendDataAppl_list_head(void) { return list_head(send_data_list); } + +/* + * Drive send_data_callback_func(TRANSMIT_COMPLETE_FAIL, NULL) directly. + * This is the same call the emergency timer makes when the NCP misses a + * callback. Exposing it here lets tests trigger the FAIL path without + * needing a running Contiki scheduler. + */ +void ZW_SendDataAppl_trigger_fail_for_test(void) +{ + send_data_callback_func(TRANSMIT_COMPLETE_FAIL, NULL); +} +#endif /* UNIT_TEST */