/* * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "test_conn_impl.h" /* Event simulation */ static int simulated_event; static struct net_if *simulated_event_iface; static K_MUTEX_DEFINE(simulated_event_mutex); /* Static storage for fatal error info */ static int fatal_error; void simulate_event_handler(struct k_work *work) { ARG_UNUSED(*work); k_mutex_lock(&simulated_event_mutex, K_FOREVER); if (simulated_event == 0) { net_mgmt_event_notify( NET_EVENT_CONN_IF_TIMEOUT, simulated_event_iface ); } else { fatal_error = simulated_event; net_mgmt_event_notify_with_info( NET_EVENT_CONN_IF_FATAL_ERROR, simulated_event_iface, &fatal_error, sizeof(fatal_error) ); } k_mutex_unlock(&simulated_event_mutex); } static K_WORK_DELAYABLE_DEFINE(simulate_event_work, simulate_event_handler); /** * @brief Simulates an event on the target iface. * * Do not attempt to simulate multiple events simultaneously -- only the last event requested * will be fired. * * @param target - iface to simulate the event on. * @param event - Event to simulate. * If 0, simulate a timeout. * Otherwise, simulate a fatal error with this value as the reason/info. */ static void simulate_event(struct net_if *target, int event) { k_mutex_lock(&simulated_event_mutex, K_FOREVER); simulated_event = event; simulated_event_iface = target; k_work_reschedule(&simulate_event_work, SIMULATED_EVENT_DELAY_TIME); k_mutex_unlock(&simulated_event_mutex); } static void simulate_timeout(struct net_if *target) { simulate_event(target, 0); } void simulate_fatal_error(struct net_if *target, int reason) { simulate_event(target, reason); } void simulate_connection_loss(struct net_if *target) { net_if_dormant_on(target); } /* Connectivity implementations */ static void inc_call_count(struct test_conn_data *data, bool a) { if (a) { data->call_cnt_a += 1; } else { data->call_cnt_b += 1; } } static int test_connect(struct conn_mgr_conn_binding *const binding, bool a) { struct test_conn_data *data = binding->ctx; inc_call_count(data, a); /* Fail immediately if requested */ if (data->api_err != 0) { return data->api_err; } /* Fail after a delay if requested */ if (data->fatal_error) { simulate_fatal_error(binding->iface, data->fatal_error); return 0; } if (data->timeout) { simulate_timeout(binding->iface); return 0; } /* Succeed otherwise */ data->conn_bal += 1; /* Mark iface as connected */ net_if_dormant_off(binding->iface); return 0; } static int test_disconnect(struct conn_mgr_conn_binding *const binding, bool a) { struct test_conn_data *data = binding->ctx; inc_call_count(data, a); if (data->api_err != 0) { return data->api_err; } data->conn_bal -= 1; /* Mark iface as dormant (disconnected) */ net_if_dormant_on(binding->iface); return 0; } char *opt_pointer(struct test_conn_data *data, int optname) { switch (optname) { case TEST_CONN_OPT_X: return data->data_x; case TEST_CONN_OPT_Y: return data->data_y; } return NULL; } int test_set_opt_a(struct conn_mgr_conn_binding *const binding, int optname, const void *optval, size_t optlen) { struct test_conn_data *data = binding->ctx; char *target = opt_pointer(data, optname); int len = MIN(optlen, TEST_CONN_DATA_LEN); /* get/set opt are only implemented for implementation A */ inc_call_count(data, true); if (target == NULL) { return -ENOPROTOOPT; } if (data->api_err) { return data->api_err; } (void)memset(target, 0, TEST_CONN_DATA_LEN); (void)memcpy(target, optval, len); return 0; } int test_get_opt_a(struct conn_mgr_conn_binding *const binding, int optname, void *optval, size_t *optlen) { struct test_conn_data *data = binding->ctx; char *target = opt_pointer(data, optname); int len; /* get/set opt are only implemented for implementation A */ inc_call_count(data, true); if (target == NULL) { *optlen = 0; return -ENOPROTOOPT; } len = MIN(strlen(target) + 1, *optlen); if (data->api_err) { *optlen = 0; return data->api_err; } *optlen = len; (void)memset(optval, 0, len); (void)memcpy(optval, target, len-1); return 0; } static void test_init(struct conn_mgr_conn_binding *const binding, bool a) { struct test_conn_data *data = binding->ctx; if (a) { data->init_calls_a += 1; } else { data->init_calls_b += 1; } /* Mark the iface dormant (disconnected) on initialization */ net_if_dormant_on(binding->iface); } static void test_init_a(struct conn_mgr_conn_binding *const binding) { test_init(binding, true); } static void test_init_b(struct conn_mgr_conn_binding *const binding) { test_init(binding, false); } static int test_connect_a(struct conn_mgr_conn_binding *const binding) { return test_connect(binding, true); } static int test_connect_b(struct conn_mgr_conn_binding *const binding) { return test_connect(binding, false); } static int test_disconnect_a(struct conn_mgr_conn_binding *const binding) { return test_disconnect(binding, true); } static int test_disconnect_b(struct conn_mgr_conn_binding *const binding) { return test_disconnect(binding, false); } static struct conn_mgr_conn_api test_conn_api_a = { .connect = test_connect_a, .disconnect = test_disconnect_a, .init = test_init_a, .get_opt = test_get_opt_a, .set_opt = test_set_opt_a }; static struct conn_mgr_conn_api test_conn_api_b = { .connect = test_connect_b, .disconnect = test_disconnect_b, .init = test_init_b, }; static struct conn_mgr_conn_api test_conn_api_ni = { .connect = test_connect_a, .disconnect = test_disconnect_a, }; /* Equivalent but distinct implementations */ CONN_MGR_CONN_DEFINE(TEST_L2_CONN_IMPL_A, &test_conn_api_a); CONN_MGR_CONN_DEFINE(TEST_L2_CONN_IMPL_B, &test_conn_api_b); /* Implementation without init */ CONN_MGR_CONN_DEFINE(TEST_L2_CONN_IMPL_NI, &test_conn_api_ni); /* Bad implementation, should be handled gracefully */ CONN_MGR_CONN_DEFINE(TEST_L2_CONN_IMPL_N, NULL);