blob: 8d23951605902facb57c034eec2d01ccdd60bba7 [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/net/net_if.h>
#include <zephyr/net/conn_mgr_connectivity.h>
#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, K_SECONDS(SIMULATED_EVENT_DELAY_SECONDS));
k_mutex_unlock(&simulated_event_mutex);
}
static void simulate_timeout(struct net_if *target)
{
simulate_event(target, 0);
}
static void simulate_fatal_error(struct net_if *target, int reason)
{
simulate_event(target, reason);
}
/* 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);