diff --git a/drivers/atwinc15x0/Makefile.dep b/drivers/atwinc15x0/Makefile.dep index f68a18760f..79f8936067 100644 --- a/drivers/atwinc15x0/Makefile.dep +++ b/drivers/atwinc15x0/Makefile.dep @@ -1,6 +1,18 @@ USEMODULE += netdev_eth USEMODULE += ztimer_msec USEMODULE += netdev_legacy_api + +ifeq (,$(filter atwinc15x0_dynamic_%,$(USEMODULE))) + # use static connect by default when no dynamic module is loaded + USEMODULE += atwinc15x0_static_connect +endif +ifneq (,$(filter atwinc15x0_dynamic_scan,$(USEMODULE))) + USEMODULE += wifi_scan_list +endif +ifneq (,$(filter atwinc15x0_static_connect,$(USEMODULE))) + USEMODULE += ztimer +endif + USEPKG += driver_atwinc15x0 FEATURES_REQUIRED += periph_gpio FEATURES_REQUIRED += periph_gpio_irq diff --git a/drivers/atwinc15x0/Makefile.include b/drivers/atwinc15x0/Makefile.include index ca765babdf..4e2ecc38bc 100644 --- a/drivers/atwinc15x0/Makefile.include +++ b/drivers/atwinc15x0/Makefile.include @@ -1,2 +1,9 @@ USEMODULE_INCLUDES_atwinc15x0 := $(LAST_MAKEFILEDIR)/include USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_atwinc15x0) + +# Try to connect to a preknown AP (WIFI_SSID, WIFI_PASS) +PSEUDOMODULES += atwinc15x0_static_connect +# Accept connection requests (NETOPT_CONNECT) +PSEUDOMODULES += atwinc15x0_dynamic_connect +# Accept scan requests (NETOPT_SCAN) +PSEUDOMODULES += atwinc15x0_dynamic_scan diff --git a/drivers/atwinc15x0/atwinc15x0_netdev.c b/drivers/atwinc15x0/atwinc15x0_netdev.c index b31cd1c3e2..855136e74f 100644 --- a/drivers/atwinc15x0/atwinc15x0_netdev.c +++ b/drivers/atwinc15x0/atwinc15x0_netdev.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Gunar Schorcht + * 2023 ML!PA Consulting GmbH * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level @@ -14,6 +15,7 @@ * @brief Netdev driver for the ATWINC15x0 WiFi module * * @author Gunar Schorcht + * @author Fabian Hüßler * * @} */ @@ -29,11 +31,19 @@ #include "driver/source/m2m_hif.h" #include "driver/include/m2m_wifi.h" +#include "thread.h" +#include "compiler_hints.h" #include "assert.h" #include "log.h" +#include "net/netopt.h" +#include "net/wifi.h" #include "net/netdev/eth.h" +#include "net/netdev/wifi.h" +#include "net/wifi_scan_list.h" #include "od.h" #include "ztimer.h" +#include "string_utils.h" +#include "net/netif.h" #define ENABLE_DEBUG 0 #define ENABLE_DEBUG_DUMP 0 @@ -47,20 +57,20 @@ #define ATWINC15X0_WAIT_RECONNECT_MS (5000) /** - * @brief Don't perform operations that would wake the device from sleep + * @brief Maximum number of scan list entries to deliver */ -#define _CHECK_SLEEP_STATE(dev) do { \ - if (dev->state != NETOPT_STATE_IDLE) { \ - return -EBUSY; \ - } \ - } while (0) +#define ATWINC15X0_SCAN_LIST_NUMOF CONFIG_ATWINC15X0_SCAN_LIST_NUMOF /* Forward function declarations */ static void _atwinc15x0_wifi_cb(uint8_t event, void *msg); static void _atwinc15x0_eth_cb(uint8_t type, void *msg, void *ctrl); -static int _atwinc15x0_connect(void); +static int _atwinc15x0_connect(const wifi_connect_request_t *req); +static int _atwinc15x0_disconnect(const wifi_disconnect_request_t *req); +static int _atwinc15x0_scan(const wifi_scan_request_t *req); static int _atwinc15x0_init(netdev_t *netdev); static int _set_state(atwinc15x0_t *dev, netopt_state_t state); +static netopt_state_t _get_state(const atwinc15x0_t *dev); +static void _atwinc15x0_isr(netdev_t *netdev); /** * The following buffer is required by the ATWINC15x0 vendor driver to store @@ -104,6 +114,175 @@ static tstrWifiInitParam atwinc15x0_wifi_params = { */ atwinc15x0_t *atwinc15x0 = NULL; +MAYBE_UNUSED +static struct { + wifi_scan_list_t head; + wifi_scan_list_node_t array[ATWINC15X0_SCAN_LIST_NUMOF]; +} _atwinc15x0_scan_list; + +static inline void _wifi_scan_list_empty(void) +{ +#if IS_USED(MODULE_WIFI_SCAN_LIST) + wifi_scan_list_empty(&_atwinc15x0_scan_list.head, + _atwinc15x0_scan_list.array, + ARRAY_SIZE(_atwinc15x0_scan_list.array)); +#endif +} + +static inline void _wifi_scan_list_insert(const wifi_scan_result_t *result) +{ + (void)result; +#if IS_USED(MODULE_WIFI_SCAN_LIST) + wifi_scan_list_insert(&_atwinc15x0_scan_list.head, + _atwinc15x0_scan_list.array, + ARRAY_SIZE(_atwinc15x0_scan_list.array), + result); +#endif +} + +MAYBE_UNUSED +static wifi_scan_request_t _atwinc15x0_scan_req; + +static inline void _wifi_scan_result_callback(const wifi_scan_list_t *scan_list) +{ + if (_atwinc15x0_scan_req.base.scan_cb) { + void *netif = netif_get_by_id(thread_getpid()); + ((wifi_on_scan_result_t)_atwinc15x0_scan_req.base.scan_cb)(netif, + scan_list); + } + _atwinc15x0_scan_req.base.scan_cb = NULL; +} + +/** + * @brief Internal next timeout type + */ +typedef enum { + /** + * @brief No / clear timeout + */ + ATWINC15X0_WIFI_STA_TIMEOUT_NONE = 0, + /** + * @brief Timeout to reconnect to + */ + ATWINC15X0_WIFI_STA_TIMEOUT_RECONNECT, +} atwinc15x0_wifi_sta_timeout_t; + +MAYBE_UNUSED +static union { + wifi_disconnect_request_t disconn_req; + wifi_connect_request_t conn_req; +} _atwinc15x0_connect_req; + +static inline void _wifi_connect_result_callback(const wifi_connect_result_t *result) +{ + if (_atwinc15x0_connect_req.conn_req.base.conn_cb) { + void *netif = netif_get_by_id(thread_getpid()); + ((wifi_on_connect_result_t)_atwinc15x0_connect_req.conn_req.base.conn_cb)(netif, + result); + } + _atwinc15x0_connect_req.conn_req.base.conn_cb = NULL; + /* _atwinc15x0_connect_req.conn_req.base.disconn_cb is called when connection is lost */ +} + +static inline void _wifi_disconnect_result_callback(const wifi_disconnect_result_t *result) +{ + if (_atwinc15x0_connect_req.conn_req.base.disconn_cb) { + void *netif = netif_get_by_id(thread_getpid()); + ((wifi_on_disconnect_result_t)_atwinc15x0_connect_req.conn_req.base.disconn_cb)(netif, + result); + } + _atwinc15x0_connect_req.conn_req.base.conn_cb = NULL; + _atwinc15x0_connect_req.conn_req.base.disconn_cb = NULL; +} + +MAYBE_UNUSED +static struct { + atwinc15x0_wifi_sta_timeout_t timeout; + ztimer_t timer; +} _atwinc15x0_timer; + +static void _atwinc15x0_reconnect_timer(void *arg) +{ + (void)arg; + _atwinc15x0_timer.timeout = ATWINC15X0_WIFI_STA_TIMEOUT_RECONNECT; + _atwinc15x0_timer.timer.callback = NULL; + atwinc15x0_irq(); +} + +static void _atwinc15x0_set_timer(void *arg, ztimer_callback_t cb, uint32_t timeout) +{ + ztimer_remove(ZTIMER_MSEC, &_atwinc15x0_timer.timer); + _atwinc15x0_timer.timer.arg = arg; + _atwinc15x0_timer.timer.callback = cb; + ztimer_set(ZTIMER_MSEC, &_atwinc15x0_timer.timer, timeout); +} + +static inline void _atwinc15x0_set_reconnect_timer(void) +{ + _atwinc15x0_set_timer(NULL, _atwinc15x0_reconnect_timer, ATWINC15X0_WAIT_RECONNECT_MS); +} + +static int _atwinc15x0_static_connect(void) +{ + if (!IS_USED(MODULE_ATWINC15X0_STATIC_CONNECT)) { + return 0; + } + tuniM2MWifiAuth auth_info; + tenuM2mSecType auth_type = M2M_WIFI_SEC_OPEN; + if (_atwinc15x0_is_busy(atwinc15x0)) { + return -EBUSY; + } + if (_atwinc15x0_is_connected(atwinc15x0)) { + return -EALREADY; + } + if (_atwinc15x0_is_sleeping(atwinc15x0)) { + return -ECANCELED; + } +#if !defined(MODULE_WIFI_ENTERPRISE) && defined(WIFI_PASS) + + strncpy((char *)auth_info.au8PSK, WIFI_PASS, M2M_MAX_PSK_LEN); + auth_type = M2M_WIFI_SEC_WPA_PSK; + +#elif defined(MODULE_WIFI_ENTERPRISE) + +#if defined(WIFI_USER) && defined(WIFI_PASS) + strncpy((char *)&auth_info.strCred1x.au8UserName, WIFI_USER, M2M_1X_USR_NAME_MAX); + strncpy((char *)&auth_info.strCred1x.au8Passwd, WIFI_PASS, M2M_1X_PWD_MAX); + auth_type = M2M_WIFI_SEC_802_1X; +#else /* defined(WIFI_USER) && defined(WIFI_PASS) */ +#error WIFI_EAP_USER and WIFI_EAP_PASS have to define the user name \ + and the password for EAP phase 2 authentication in wifi_enterprise +#endif /* defined(WIFI_USER) && defined(WIFI_PASS) */ + +#endif /* defined(MODULE_WIFI_ENTERPRISE) */ + /* connect */ + int8_t res; + if ((res = m2m_wifi_connect(WIFI_SSID, sizeof(WIFI_SSID), + auth_type, &auth_info, + M2M_WIFI_CH_ALL)) != M2M_SUCCESS) { + LOG_ERROR("[atwinc15x0] WiFi connect failed with %d\n", res); + return -EIO; + } + _atwinc15x0_set_connecting(atwinc15x0); + return 0; +} + +static inline int _atwinc15x0_get_sec_mode(tenuM2mSecType mode) +{ + switch (mode) { + case M2M_WIFI_SEC_OPEN: + return WIFI_SECURITY_MODE_OPEN; + case M2M_WIFI_SEC_WPA_PSK: + return WIFI_SECURITY_MODE_WPA2_PERSONAL; + case M2M_WIFI_SEC_WEP: + return WIFI_SECURITY_MODE_WEP_PSK; + case M2M_WIFI_SEC_802_1X: + return WIFI_SECURITY_MODE_WPA2_ENTERPRISE; + default: + return -1; + } +} + static void _atwinc15x0_eth_cb(uint8_t type, void *msg, void *ctrl_buf) { assert(atwinc15x0); @@ -115,7 +294,7 @@ static void _atwinc15x0_eth_cb(uint8_t type, void *msg, void *ctrl_buf) DEBUG("%s type=%u msg=%p len=%d remaining=%d\n", __func__, type, msg, ctrl->u16DataSize, ctrl->u16RemainigDataSize); - if (IS_ACTIVE(ENABLE_DEBUG) && IS_USED(MODULE_OD)) { + if (IS_ACTIVE(ENABLE_DEBUG_DUMP) && IS_USED(MODULE_OD)) { od_hex_dump(msg, ctrl->u16DataSize, 16); } @@ -144,6 +323,181 @@ typedef union { static bool _rssi_info_ready = false; +static void _atwinc15x0_handle_resp_scan_done(const tstrM2mScanDone* scan_done) +{ + DEBUG("%s scan done, %d APs found\n", __func__, scan_done->u8NumofCh); + if (scan_done->u8NumofCh > 0) { + /* read the first scan result record */ + m2m_wifi_req_scan_result(0); + } + else { + /* no results */ + _atwinc15x0_set_idle(atwinc15x0); + if (IS_USED(MODULE_ATWINC15X0_DYNAMIC_SCAN)) { + _wifi_scan_result_callback(&_atwinc15x0_scan_list.head); + } + } +} + +static void _atwinc15x0_handle_resp_scan_result(const tstrM2mWifiscanResult* scan_result) +{ + LOG_DEBUG("[atwinc15x0] %s: rssi %d, auth %d, ch %d, bssid " + ATWINC15X0_MAC_STR "\n", + scan_result->au8SSID, + scan_result->s8rssi, + scan_result->u8AuthType, + scan_result->u8ch, + ATWINC15X0_MAC_STR_ARG(scan_result->au8BSSID)); + if (_atwinc15x0_is_connected(atwinc15x0)) { + if (!memcmp(scan_result->au8BSSID, &atwinc15x0->ap, ETHERNET_ADDR_LEN)) { + /* use the results for current AP to set the current channel */ + atwinc15x0->channel = scan_result->u8ch; + } + } + if (IS_USED(MODULE_ATWINC15X0_DYNAMIC_SCAN)) { + int sec; + if ((sec = _atwinc15x0_get_sec_mode(scan_result->u8AuthType)) != -1) { + wifi_scan_result_t result = WIFI_SCAN_RESULT_INITIALIZER(scan_result->u8ch, + scan_result->s8rssi, sec); + memcpy(result.bssid, scan_result->au8BSSID, sizeof(result.bssid)); + strncpy(result.ssid, (const char *)scan_result->au8SSID, sizeof(result.ssid) - 1); + _wifi_scan_list_insert(&result); + } + } + if (scan_result->u8index < m2m_wifi_get_num_ap_found() - 1) { + /* read the next scan result record */ + m2m_wifi_req_scan_result(scan_result->u8index + 1); + } + else { + _atwinc15x0_set_idle(atwinc15x0); + if (IS_USED(MODULE_ATWINC15X0_DYNAMIC_SCAN)) { + _wifi_scan_result_callback(&_atwinc15x0_scan_list.head); + } + } +} + +static void _atwinc15x0_handle_resp_con_state_changed(const tstrM2mWifiStateChanged *state_changed) +{ + /** + * The logic here can be tested with the following test cases: + * 1. connect when disconnected + * 2. connect to another AP when connected + * 3. disconnect when connected + * 4. go to sleep when connected + */ + switch (state_changed->u8CurrState) { + case M2M_WIFI_DISCONNECTED: + LOG_INFO("[atwinc15x0] WiFi disconnected\n"); + /* We disconnect before we connect, so we will first get a disconnect event when we + were connected. After that when connection to the new AP fails we are already in a + disconnected state. */ + bool was_connected = _atwinc15x0_is_connected(atwinc15x0); + bool is_connecting = _atwinc15x0_is_connecting(atwinc15x0); + bool is_disconnecting = _atwinc15x0_is_disconnecting(atwinc15x0); + bool is_sleeping; + if (!(is_sleeping = _atwinc15x0_is_sleeping(atwinc15x0))) { + /* We requested to disconnect before sleep. + Don´t override the sleep state when the disconnect event is received. */ + _atwinc15x0_set_disconnected(atwinc15x0); + } + if (was_connected || is_disconnecting || is_sleeping) { + /* notify when connection state changed or when we disconnected due to sleep */ + DEBUG("atwinc15x0: notify upper layer about disconnect\n"); + atwinc15x0->netdev.event_callback(&atwinc15x0->netdev, NETDEV_EVENT_LINK_DOWN); + } + if (IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT)) { + if (!was_connected && !is_disconnecting && !is_sleeping) { + /* connection failed */ + DEBUG("atwinc15x0: notify about connection failure\n"); + wifi_disconnect_result_t disconn + = WIFI_DISCONNECT_RESULT_INITIALIZER( + _atwinc15x0_connect_req.conn_req.base.channel, + _atwinc15x0_connect_req.conn_req.ssid); + _wifi_disconnect_result_callback(&disconn); + } + else if ((was_connected || is_disconnecting || is_sleeping) && !is_connecting) { + /* disconnect from previous connection */ + DEBUG("atwinc15x0: notify about disconnect\n"); + wifi_disconnect_result_t disconn + = WIFI_DISCONNECT_RESULT_INITIALIZER( + atwinc15x0->channel, + _atwinc15x0_sta_get_current_ssid(atwinc15x0)); + _wifi_disconnect_result_callback(&disconn); + } + } + if (IS_USED(MODULE_ATWINC15X0_STATIC_CONNECT)) { + /* do not reconnect on sleep */ + if (!_atwinc15x0_is_sleeping(atwinc15x0)) { + /* schedule reconnect timer: + Not trying to reconnect immediately allows + other connect requests to get through. */ + _atwinc15x0_set_reconnect_timer(); + } + } + break; + case M2M_WIFI_CONNECTED: + LOG_INFO("[atwinc15x0] WiFi connected\n"); + _atwinc15x0_set_connected(atwinc15x0); + atwinc15x0->netdev.event_callback(&atwinc15x0->netdev, NETDEV_EVENT_LINK_UP); + /* get information about the current AP */ + m2m_wifi_get_connection_info(); + if (IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT)) { + _atwinc15x0_sta_set_current_ssid(atwinc15x0, + _atwinc15x0_connect_req.conn_req.ssid); + atwinc15x0->channel = _atwinc15x0_connect_req.conn_req.base.channel; + wifi_connect_result_t conn + = WIFI_CONNECT_RESULT_INITIALIZER( + atwinc15x0->channel, + _atwinc15x0_sta_get_current_ssid(atwinc15x0)); + if (_atwinc15x0_connect_req.conn_req.cred) { + if (*_atwinc15x0_connect_req.conn_req.cred == WIFI_SECURITY_MODE_WEP_PSK) { + conn.credentials.wep = *((const wifi_security_wep_psk_t *) + _atwinc15x0_connect_req.conn_req.cred); + } + else if (*_atwinc15x0_connect_req.conn_req.cred == WIFI_SECURITY_MODE_WPA2_PERSONAL) { + conn.credentials.wpa_psk = *((const wifi_security_wpa_psk_t *) + _atwinc15x0_connect_req.conn_req.cred); + } + else if (*_atwinc15x0_connect_req.conn_req.cred == WIFI_SECURITY_MODE_WPA2_ENTERPRISE) { + conn.credentials.wpa_enterprise = *((const wifi_security_wpa_enterprise_t *) + _atwinc15x0_connect_req.conn_req.cred); + } + } + _wifi_connect_result_callback(&conn); + } + if (IS_USED(MODULE_ATWINC15X0_STATIC_CONNECT)) { + /* start a scan for additional info, e.g. used channel */ + m2m_wifi_request_scan(M2M_WIFI_CH_ALL); + } + break; + } +} + +static void _atwinc15x0_handle_resp_conn_info(const tstrM2MConnInfo *conn_info) +{ + DEBUG("%s conn info %s, rssi %d, sec %u, bssid " + ATWINC15X0_MAC_STR "\n", __func__, + conn_info->acSSID, + conn_info->s8RSSI, + conn_info->u8SecType, + ATWINC15X0_MAC_STR_ARG(conn_info->au8MACAddress)); + + /* set the RSSI and BSSID of the current AP */ + atwinc15x0->rssi = conn_info->s8RSSI; + memcpy(atwinc15x0->ap, conn_info->au8MACAddress, ETHERNET_ADDR_LEN); + if (IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT)) { + _atwinc15x0_sta_set_current_ssid(atwinc15x0, conn_info->acSSID); + } +} + +static void _atwinc15x0_handle_resp_current_rssi(int8_t rssi) +{ + DEBUG("%s current rssi %d\n", __func__, rssi); + /* set the RSSI */ + atwinc15x0->rssi = rssi; + _rssi_info_ready = true; +} + static void _atwinc15x0_wifi_cb(uint8_t type, void *msg) { /** @@ -157,85 +511,19 @@ static void _atwinc15x0_wifi_cb(uint8_t type, void *msg) switch (type) { case M2M_WIFI_RESP_SCAN_DONE: - DEBUG("%s scan done, %d APs found\n", __func__, - event->scan_done.u8NumofCh); - /* read the first scan result record */ - m2m_wifi_req_scan_result(0); + _atwinc15x0_handle_resp_scan_done(&event->scan_done); break; - case M2M_WIFI_RESP_SCAN_RESULT: - LOG_DEBUG("[atwinc15x0] %s: rssi %d, auth %d, ch %d, bssid " - ATWINC15X0_MAC_STR "\n", - event->scan_result.au8SSID, - event->scan_result.s8rssi, - event->scan_result.u8AuthType, - event->scan_result.u8ch, - ATWINC15X0_MAC_STR_ARG(event->scan_result.au8BSSID)); - - if (memcmp(&event->scan_result.au8BSSID, - &atwinc15x0->ap, ETHERNET_ADDR_LEN) == 0) { - /* use the results for current AP to set the current channel */ - atwinc15x0->channel = event->scan_result.u8ch; - } - if (event->scan_result.u8index < m2m_wifi_get_num_ap_found()) { - /* read the next scan result record */ - m2m_wifi_req_scan_result(event->scan_result.u8index + 1); - } + _atwinc15x0_handle_resp_scan_result(&event->scan_result); break; - case M2M_WIFI_RESP_CON_STATE_CHANGED: - switch (event->state_changed.u8CurrState) { - case M2M_WIFI_DISCONNECTED: - LOG_INFO("[atwinc15x0] WiFi disconnected\n"); - atwinc15x0->connected = false; - atwinc15x0->netdev.event_callback(&atwinc15x0->netdev, - NETDEV_EVENT_LINK_DOWN); - /* do not reconnect on standby or sleep */ - if (atwinc15x0->state == NETOPT_STATE_STANDBY || - atwinc15x0->state == NETOPT_STATE_SLEEP) { - break; - } - /* wait and try to reconnect */ - ztimer_sleep(ZTIMER_MSEC, ATWINC15X0_WAIT_RECONNECT_MS); - _atwinc15x0_connect(); - break; - case M2M_WIFI_CONNECTED: - LOG_INFO("[atwinc15x0] WiFi connected\n"); - atwinc15x0->connected = true; - atwinc15x0->netdev.event_callback(&atwinc15x0->netdev, - NETDEV_EVENT_LINK_UP); - /* get information about the current AP */ - m2m_wifi_get_connection_info(); - /* start a scan for additional info, e.g. used channel */ - m2m_wifi_request_scan(M2M_WIFI_CH_ALL); - break; - default: - break; - } + _atwinc15x0_handle_resp_con_state_changed(&event->state_changed); break; - case M2M_WIFI_RESP_CONN_INFO: - DEBUG("%s conn info %s, rssi %d, sec %u, bssid " - ATWINC15X0_MAC_STR "\n", __func__, - event->conn_info.acSSID, - event->conn_info.s8RSSI, - event->conn_info.u8SecType, - ATWINC15X0_MAC_STR_ARG(event->conn_info.au8MACAddress)); - - /* set the RSSI and BSSID of the current AP */ - atwinc15x0->rssi = event->conn_info.s8RSSI; - memcpy(atwinc15x0->ap, - event->conn_info.au8MACAddress, ETHERNET_ADDR_LEN); + _atwinc15x0_handle_resp_conn_info(&event->conn_info); break; - case M2M_WIFI_RESP_CURRENT_RSSI: - DEBUG("%s current rssi %d\n", __func__, event->rssi); - /* set the RSSI */ - atwinc15x0->rssi = event->rssi; - _rssi_info_ready = true; - break; - - default: + _atwinc15x0_handle_resp_current_rssi(event->rssi); break; } } @@ -248,20 +536,15 @@ static int _atwinc15x0_send(netdev_t *netdev, const iolist_t *iolist) assert(dev == atwinc15x0); assert(iolist); - if (!dev->connected) { - DEBUG("%s WiFi is still not connected to AP, cannot send\n", __func__); - return -ENODEV; - } - /* send wakes from standby but not from sleep */ - if (dev->state == NETOPT_STATE_SLEEP) { + if (_atwinc15x0_is_sleeping(dev)) { DEBUG("%s WiFi is in SLEEP state, cannot send\n", __func__); return -ENODEV; } - if (dev->state == NETOPT_STATE_STANDBY) { - _set_state(dev, NETOPT_STATE_IDLE); + if (!_atwinc15x0_is_connected(dev)) { + DEBUG("%s WiFi is still not connected to AP, cannot send\n", __func__); + return -ENODEV; } - /* atwinc15x0_eth_buf should not be used for incoming packets here */ assert(dev->rx_buf == NULL); @@ -350,15 +633,7 @@ static int _atwinc15x0_recv(netdev_t *netdev, void *buf, size_t len, void *info) static netopt_enable_t _get_link_state(atwinc15x0_t *dev) { - if (dev->state != NETOPT_STATE_IDLE) { - return NETOPT_DISABLE; - } - - if (dev->connected) { - return NETOPT_ENABLE; - } - - return NETOPT_DISABLE; + return _atwinc15x0_is_connected(dev) ? NETOPT_ENABLE : NETOPT_DISABLE; } static int _atwinc15x0_get(netdev_t *netdev, netopt_t opt, void *val, @@ -367,7 +642,6 @@ static int _atwinc15x0_get(netdev_t *netdev, netopt_t opt, void *val, atwinc15x0_t *dev = (atwinc15x0_t *)netdev; (void)max_len; - assert(val); assert(dev); assert(dev == atwinc15x0); @@ -396,13 +670,15 @@ static int _atwinc15x0_get(netdev_t *netdev, netopt_t opt, void *val, case NETOPT_STATE: assert(max_len >= sizeof(netopt_state_t)); - *((netopt_state_t *)val) = dev->state; + *((netopt_state_t *)val) = _get_state(dev); return sizeof(netopt_state_t); case NETOPT_RSSI: assert(max_len == sizeof(int16_t)); _rssi_info_ready = false; - _CHECK_SLEEP_STATE(dev); + if (!_atwinc15x0_is_connected(dev)) { + return -ECANCELED; + } /* trigger the request current RSSI (asynchronous function) */ if (m2m_wifi_req_curr_rssi() != M2M_SUCCESS) { return 0; @@ -423,28 +699,39 @@ static int _atwinc15x0_get(netdev_t *netdev, netopt_t opt, void *val, static int _set_state(atwinc15x0_t *dev, netopt_state_t state) { + if (_atwinc15x0_is_busy(dev)) { + return -EBUSY; + } switch (state) { case NETOPT_STATE_SLEEP: - case NETOPT_STATE_STANDBY: - dev->state = state; + _atwinc15x0_set_sleeping(dev); m2m_wifi_disconnect(); m2m_wifi_set_sleep_mode(M2M_PS_MANUAL, CONFIG_ATWINC15X0_RECV_BCAST); m2m_wifi_request_sleep(UINT32_MAX); if (gpio_is_valid(atwinc15x0->params.wake_pin)) { gpio_clear(atwinc15x0->params.wake_pin); } - return sizeof(netopt_state_t); + return sizeof(netopt_state_t); case NETOPT_STATE_IDLE: - if (gpio_is_valid(atwinc15x0->params.wake_pin)) { - gpio_set(atwinc15x0->params.wake_pin); + if (_atwinc15x0_is_sleeping(dev)) { + if (gpio_is_valid(atwinc15x0->params.wake_pin)) { + gpio_set(atwinc15x0->params.wake_pin); + } + m2m_wifi_set_sleep_mode(M2M_PS_DEEP_AUTOMATIC, CONFIG_ATWINC15X0_RECV_BCAST); + _atwinc15x0_set_disconnected(dev); + if (IS_USED(MODULE_ATWINC15X0_STATIC_CONNECT)) { + _atwinc15x0_set_reconnect_timer(); + } } - m2m_wifi_set_sleep_mode(M2M_PS_DEEP_AUTOMATIC, CONFIG_ATWINC15X0_RECV_BCAST); - dev->state = state; - _atwinc15x0_connect(); return sizeof(netopt_state_t); case NETOPT_STATE_RESET: + if (_atwinc15x0_is_sleeping(dev)) { + _set_state(dev, NETOPT_STATE_IDLE); + } + else if (_atwinc15x0_is_connected(dev)) { + m2m_wifi_disconnect(); + } _atwinc15x0_init(&dev->netdev); - dev->state = NETOPT_STATE_IDLE; return sizeof(netopt_state_t); default: break; @@ -453,15 +740,25 @@ static int _set_state(atwinc15x0_t *dev, netopt_state_t state) return -ENOTSUP; } +static netopt_state_t _get_state(const atwinc15x0_t *dev) +{ + if (dev->state == ATWINC15X0_STATE_SLEEP) { + return NETOPT_STATE_SLEEP; + } + else { + return NETOPT_STATE_IDLE; + } +} + static int _atwinc15x0_set(netdev_t *netdev, netopt_t opt, const void *val, size_t max_len) { atwinc15x0_t *dev = (atwinc15x0_t *)netdev; - assert(val); DEBUG("%s dev=%p opt=%u val=%p max_len=%u\n", __func__, (void *)netdev, opt, val, max_len); + int ret; switch (opt) { case NETOPT_ADDRESS: assert(max_len == ETHERNET_ADDR_LEN); @@ -471,7 +768,15 @@ static int _atwinc15x0_set(netdev_t *netdev, netopt_t opt, const void *val, assert(max_len <= sizeof(netopt_state_t)); return _set_state(dev, *((const netopt_state_t *)val)); case NETOPT_L2_GROUP: - if (m2m_wifi_enable_mac_mcast((void *)val, 1)) { + /* sometimes m2m_wifi_enable_mac_mcast() fails with M2M_ERR_MEM_ALLOC */ + m2m_wifi_enable_mac_mcast((void *)val, 0); + /* sometimes it fails with M2M_ERR_BUS_FAIL */ + int tries = 5; + do { + ret = m2m_wifi_enable_mac_mcast((void *)val, 1); + DEBUG_PUTS("busy loop setting L2 multicast address on atwinc15x0"); + } while (--tries && ret == M2M_ERR_BUS_FAIL); + if (ret) { return -EINVAL; } else { return max_len; @@ -482,9 +787,53 @@ static int _atwinc15x0_set(netdev_t *netdev, netopt_t opt, const void *val, } else { return max_len; } + case NETOPT_SCAN: + if (!IS_USED(MODULE_ATWINC15X0_DYNAMIC_SCAN)) { + break; + } + if (max_len < sizeof(wifi_scan_request_t)) { + return -EINVAL; + } + if ((ret = _atwinc15x0_scan((const wifi_scan_request_t *)val)) != 0) { + return ret; + } + return sizeof(wifi_scan_request_t); + case NETOPT_CONNECT: + if (!IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT)) { + break; + } + if (max_len < sizeof(wifi_connect_request_t)) { + return -EINVAL; + } + if ((ret = _atwinc15x0_connect((const wifi_connect_request_t *)val)) != 0) { + return ret; + } + return sizeof(wifi_connect_request_t); + case NETOPT_DISCONNECT: + if (!IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT)) { + break; + } + if (max_len < sizeof(wifi_disconnect_request_t)) { + return -EINVAL; + } + if ((ret = _atwinc15x0_disconnect((const wifi_disconnect_request_t *)val)) != 0) { + return ret; + } + return sizeof(wifi_disconnect_request_t); default: - return netdev_eth_set(netdev, opt, val, max_len); + break; } + return netdev_eth_set(netdev, opt, val, max_len); +} + +static void _print_firmware_version(const tstrM2mRev *info) +{ + LOG_DEBUG("[atwinc15x0] CHIP ID: %lu\n", + info->u32Chipid); + LOG_DEBUG("[atwinc15x0] FIRMWARE: %u.%u.%u\n", + info->u8FirmwareMajor, info->u8FirmwareMinor, info->u8FirmwarePatch); + LOG_DEBUG("[atwinc15x0] DRIVER: %u.%u.%u\n", + info->u8DriverMajor, info->u8DriverMinor, info->u8DriverPatch); } static int _atwinc15x0_init(netdev_t *netdev) @@ -499,15 +848,13 @@ static int _atwinc15x0_init(netdev_t *netdev) atwinc15x0->bsp_isr = NULL; atwinc15x0->bsp_irq_enabled = true; - atwinc15x0->connected = false; - atwinc15x0->state = NETOPT_STATE_IDLE; + atwinc15x0->state = ATWINC15X0_STATE_DISCONNECTED; atwinc15x0->rx_len = 0; atwinc15x0->rx_buf = NULL; nm_bsp_init(); - int8_t res; - + int res; /* initialize the WINC Driver*/ if ((res = m2m_wifi_init(&atwinc15x0_wifi_params)) != M2M_SUCCESS) { DEBUG("m2m_wifi_init failed with code %d\n", res); @@ -521,6 +868,29 @@ static int _atwinc15x0_init(netdev_t *netdev) } } + /* get firmware version */ + if (IS_ACTIVE(ENABLE_DEBUG)) { + tstrM2mRev fw_ver; + if ((res = m2m_wifi_get_firmware_version(&fw_ver)) != M2M_SUCCESS) { + LOG_ERROR("[atwinc15x0] Could not read firmware version\n"); + } + else { + _print_firmware_version(&fw_ver); + } + } + /* set Wi-Fi region */ + if (WIFI_REGION == WIFI_REGION_EUROPE) { + res = m2m_wifi_set_scan_region(EUROPE); + } + else if (WIFI_REGION == WIFI_REGION_NORTH_AMERICA) { + res = m2m_wifi_set_scan_region(NORTH_AMERICA); + } + else if (WIFI_REGION == WIFI_REGION_ASIA ) { + res = m2m_wifi_set_scan_region(ASIA); + } + if (res != M2M_SUCCESS) { + return -ENOTSUP; + } /* disable the built-in DHCP client */ if ((res = m2m_wifi_enable_dhcp(false)) != M2M_SUCCESS) { LOG_ERROR("[atwinc15x0] m2m_wifi_enable_dhcp failed with %d\n", res); @@ -530,42 +900,145 @@ static int _atwinc15x0_init(netdev_t *netdev) /* enable automatic power saving */ m2m_wifi_set_sleep_mode(M2M_PS_DEEP_AUTOMATIC, CONFIG_ATWINC15X0_RECV_BCAST); - /* try to connect and return */ - return _atwinc15x0_connect(); + res = 0; + if (IS_USED(MODULE_ATWINC15X0_STATIC_CONNECT)) { + /* try to connect and return */ + res = _atwinc15x0_static_connect(); + } + return res; } -static int _atwinc15x0_connect(void) +static int _atwinc15x0_scan(const wifi_scan_request_t *req) { + assert(req); + (void)req; + if (!IS_USED(MODULE_ATWINC15X0_DYNAMIC_SCAN)) { + return 0; + } + if (_atwinc15x0_is_busy(atwinc15x0)) { + return -EBUSY; + } + if (req->base.channel != NETOPT_SCAN_REQ_ALL_CH) { + if (req->base.channel < WIFI_2_4_CH_MIN || + req->base.channel > WIFI_2_4_CH_MAX) { + return -EINVAL; + } + } + if (_atwinc15x0_is_sleeping(atwinc15x0)) { + _set_state(atwinc15x0, NETOPT_STATE_IDLE); + } + tstrM2MScanOption opt = { + .u8NumOfSlot = ATWINC1510_SCAN_SLOTS_DEF, + .u8ProbesPerSlot = ATWINC1510_SCAN_PROBES_NUMOF_DEF, + .u8SlotTime = ATWINC1510_SCAN_SLOT_TIME_MS_DEF, + .s8RssiThresh = ATWINC1510_SCAN_THRESHOLD_DBM_DEF, + }; + if (req->timeout_ms_per_ch) { + uint16_t ch_time_max = ATWINC1510_SCAN_SLOT_TIME_MS_MAX * opt.u8NumOfSlot; + if (req->timeout_ms_per_ch > ch_time_max) { + opt.u8SlotTime = ATWINC1510_SCAN_SLOT_TIME_MS_MAX; + } + else { + opt.u8SlotTime = req->timeout_ms_per_ch / opt.u8NumOfSlot; + } + } + int ret; + if ((ret = m2m_wifi_set_scan_options(&opt)) != M2M_SUCCESS) { + LOG_ERROR("[atwinc15x0] WiFi setting scan options failed with %d\n", ret); + return -EIO; + } + if ((ret = m2m_wifi_request_scan(req->base.channel == NETOPT_SCAN_REQ_ALL_CH + ? M2M_WIFI_CH_ALL + : req->base.channel)) != M2M_SUCCESS) { + LOG_ERROR("[atwinc15x0] WiFi scan failed with %d\n", ret); + return -EIO; + } + _atwinc15x0_set_scanning(atwinc15x0); + _atwinc15x0_scan_req = *req; + _wifi_scan_list_empty(); + return 0; +} + +static int _atwinc15x0_disconnect(const wifi_disconnect_request_t *req) +{ + assert(req); + if (_atwinc15x0_is_busy(atwinc15x0)) { + return -EBUSY; + } + if (!_atwinc15x0_is_connected(atwinc15x0)) { + /* also when sleeping */ + return -EALREADY; + } + int ret; + if ((ret = m2m_wifi_disconnect()) != M2M_SUCCESS) { + LOG_ERROR("[atwinc15x0] WiFi disconnect failed with %d\n", ret); + return -EIO; + } + _atwinc15x0_set_disconnecting(atwinc15x0); + _atwinc15x0_connect_req.disconn_req = *req; + return 0; +} + +static int _atwinc15x0_connect(const wifi_connect_request_t *req) +{ + assert(req); + if (!IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT)) { + return 0; + } tuniM2MWifiAuth auth_info; tenuM2mSecType auth_type = M2M_WIFI_SEC_OPEN; - -#if !defined(MODULE_WIFI_ENTERPRISE) && defined(WIFI_PASS) - - strncpy((char *)auth_info.au8PSK, WIFI_PASS, M2M_MAX_PSK_LEN); - auth_type = M2M_WIFI_SEC_WPA_PSK; - -#elif defined(MODULE_WIFI_ENTERPRISE) - -#if defined(WIFI_USER) && defined(WIFI_PASS) - strncpy((char *)&auth_info.strCred1x.au8UserName, WIFI_USER, M2M_1X_USR_NAME_MAX); - strncpy((char *)&auth_info.strCred1x.au8Passwd, WIFI_PASS, M2M_1X_PWD_MAX); - auth_type = M2M_WIFI_SEC_802_1X; -#else /* defined(WIFI_EAP_USER) && defined(WIFI_EAP_PASS) */ -#error WIFI_EAP_USER and WIFI_EAP_PASS have to define the user name \ - and the password for EAP phase 2 authentication in wifi_enterprise -#endif /* defined(WIFI_EAP_USER) && defined(WIFI_EAP_PASS) */ - -#endif /* defined(MODULE_ESP_WIFI_ENTERPRISE) */ - - /* connect */ + if (_atwinc15x0_is_busy(atwinc15x0)) { + return -EBUSY; + } + if (_atwinc15x0_is_connected(atwinc15x0)) { + if (!strcmp(req->ssid, _atwinc15x0_sta_get_current_ssid(atwinc15x0))) { + return -EALREADY; + } + } + else if (_atwinc15x0_is_sleeping(atwinc15x0)) { + _set_state(atwinc15x0, NETOPT_STATE_IDLE); + } + if (req->cred && *(req->cred) != WIFI_SECURITY_MODE_OPEN) { + if (*(req->cred) == WIFI_SECURITY_MODE_WEP_PSK) { + auth_type = M2M_WIFI_SEC_WEP; + const wifi_security_wep_psk_t *cred = (const wifi_security_wep_psk_t *)req->cred; + strscpy((char *)auth_info.au8PSK, cred->psk, sizeof(auth_info.au8PSK)); + } + else if (*(req->cred) == WIFI_SECURITY_MODE_WPA2_PERSONAL) { + auth_type = M2M_WIFI_SEC_WPA_PSK; + const wifi_security_wpa_psk_t *cred = (const wifi_security_wpa_psk_t *)req->cred; + strscpy((char *)auth_info.au8PSK, cred->psk, sizeof(auth_info.au8PSK)); + } + else if (*(req->cred) == WIFI_SECURITY_MODE_WPA2_ENTERPRISE) { + auth_type = M2M_WIFI_SEC_802_1X; + const wifi_security_wpa_enterprise_t *cred + = (const wifi_security_wpa_enterprise_t *)req->cred; + strscpy((char *)auth_info.strCred1x.au8UserName, cred->user, + sizeof(auth_info.strCred1x.au8UserName)); + strscpy((char *)auth_info.strCred1x.au8Passwd, cred->pwd, + sizeof(auth_info.strCred1x.au8Passwd)); + } + else { + return -ENOTSUP; + } + } int8_t res; - if ((res = m2m_wifi_connect(WIFI_SSID, sizeof(WIFI_SSID), + if (_atwinc15x0_is_connected(atwinc15x0)) { + /* late disconnect to not interrupt connection on errors before */ + if ((res = m2m_wifi_disconnect()) != M2M_SUCCESS) { + LOG_ERROR("[atwinc15x0] WiFi disconnect failed with %d\n", res); + return -EIO; + } + } + /* connect */ + if ((res = m2m_wifi_connect((char *)req->ssid, strlen(req->ssid), auth_type, &auth_info, M2M_WIFI_CH_ALL)) != M2M_SUCCESS) { LOG_ERROR("[atwinc15x0] WiFi connect failed with %d\n", res); return res; } - + _atwinc15x0_set_connecting(atwinc15x0); + _atwinc15x0_connect_req.conn_req = *req; return 0; } @@ -583,6 +1056,18 @@ static void _atwinc15x0_isr(netdev_t *netdev) DEBUG("%s handle events failed, reset device\n", __func__); _atwinc15x0_init(netdev); } + int err; + if (IS_USED(MODULE_ATWINC15X0_STATIC_CONNECT)) { + if (_atwinc15x0_timer.timeout == ATWINC15X0_WIFI_STA_TIMEOUT_RECONNECT) { + if (!_atwinc15x0_is_connected(atwinc15x0)) { + /* try again if device is busy or the Atmel firmware throws an error */ + if ((err = _atwinc15x0_static_connect()) == -EBUSY || err == -EIO) { + _atwinc15x0_set_reconnect_timer(); + } + } + _atwinc15x0_timer.timeout = ATWINC15X0_WIFI_STA_TIMEOUT_NONE; + } + } } const netdev_driver_t atwinc15x0_netdev_driver = { diff --git a/drivers/atwinc15x0/include/atwinc15x0_internal.h b/drivers/atwinc15x0/include/atwinc15x0_internal.h index d3a7c5ce27..4f846cf285 100644 --- a/drivers/atwinc15x0/include/atwinc15x0_internal.h +++ b/drivers/atwinc15x0/include/atwinc15x0_internal.h @@ -19,6 +19,10 @@ #ifndef ATWINC15X0_INTERNAL_H #define ATWINC15X0_INTERNAL_H +#include +#include + +#include "driver/include/m2m_types.h" #include "atwinc15x0.h" #ifdef __cplusplus @@ -34,6 +38,264 @@ extern "C" { */ extern atwinc15x0_t *atwinc15x0; +/** + * @brief Minimum number of slots to scan a channel + */ +#define ATWINC1510_SCAN_SLOTS_MIN 2 + +/** + * @brief Default number of slots to scan a channel + */ +#define ATWINC1510_SCAN_SLOTS_DEF M2M_SCAN_DEFAULT_NUM_SLOTS + +/** + * @brief Maximum number of slots to scan a channel + */ +#define ATWINC1510_SCAN_SLOTS_MAX 255 + +/** + * @brief Time in ms to scan a slot in a channel + */ +#define ATWINC1510_SCAN_SLOT_TIME_MS_MIN 10 + +/** + * @brief Default time in ms to scan a slot in a channel + */ +#define ATWINC1510_SCAN_SLOT_TIME_MS_DEF M2M_SCAN_DEFAULT_SLOT_TIME + +/** + * @brief Maximum time in ms to scan a slot in a channel + */ +#define ATWINC1510_SCAN_SLOT_TIME_MS_MAX 250 + +/** + * @brief Default number of probes to send to scan a channel + */ +#define ATWINC1510_SCAN_PROBES_NUMOF_DEF M2M_SCAN_DEFAULT_NUM_PROBE + +/** + * @brief Default threshold in dbm for an AP to pass + */ +#define ATWINC1510_SCAN_THRESHOLD_DBM_DEF (-99) + +/** + * @brief Check if @p dev is scanning + * + * @param[in] dev ATWINC15x0 device + * + * @returns true if @p dev is scanning + * @returns false if @p dev is not scanning + */ +static inline bool _atwinc15x0_is_scanning(const atwinc15x0_t *dev) { + return dev->state == ATWINC15X0_STATE_CONNECTED_SCANNING || + dev->state == ATWINC15X0_STATE_DISCONNECTED_SCANNING; +} + +/** + * @brief Set state to indicate that @p dev is scanning + * + * @param[in, out] dev ATWINC15x0 device + */ +static inline void _atwinc15x0_set_scanning(atwinc15x0_t *dev) { + if (dev->state == ATWINC15X0_STATE_CONNECTED) { + dev->state = ATWINC15X0_STATE_CONNECTED_SCANNING; + } + else if (dev->state == ATWINC15X0_STATE_DISCONNECTED) { + dev->state = ATWINC15X0_STATE_DISCONNECTED_SCANNING; + } + else { + assert(false); + } +} + +/** + * @brief Check if @p dev is connecting to an AP + * + * @param[in] dev ATWINC15x0 device + * + * @returns true if @p dev is connecting + * @returns false if @p dev is not connecting + */ +static inline bool _atwinc15x0_is_connecting(const atwinc15x0_t *dev) { + return dev->state == ATWINC15X0_STATE_CONNECTED_CONNECTING || + dev->state == ATWINC15X0_STATE_DISCONNECTED_CONNECTING; +} + +/** + * @brief Set state to indicate that @p dev is connecting + * to an AP + * + * @param[in, out] dev ATWINC15x0 device + */ +static inline void _atwinc15x0_set_connecting(atwinc15x0_t *dev) { + if (dev->state == ATWINC15X0_STATE_CONNECTED) { + dev->state = ATWINC15X0_STATE_CONNECTED_CONNECTING; + } + else if (dev->state == ATWINC15X0_STATE_DISCONNECTED) { + dev->state = ATWINC15X0_STATE_DISCONNECTED_CONNECTING; + } + else { + assert(false); + } +} + +/** + * @brief Check if @p dev is disconnecting from an AP + * + * @param[in] dev ATWINC15x0 device + * + * @returns true if @p dev is disconnecting + * @returns false if @p dev is not disconnecting + */ +static inline bool _atwinc15x0_is_disconnecting(const atwinc15x0_t *dev) { + return dev->state == ATWINC15X0_STATE_DISCONNECTING; +} + +/** + * @brief Set state to indicate that @p dev is disconnecting + * from an AP + * + * @param[in, out] dev ATWINC15x0 device + */ +static inline void _atwinc15x0_set_disconnecting(atwinc15x0_t *dev) { + if (dev->state == ATWINC15X0_STATE_CONNECTED) { + dev->state = ATWINC15X0_STATE_DISCONNECTING; + } +} + +/** + * @brief Check if @p dev is connected to an AP + * + * @param[in] dev ATWINC15x0 device + * + * @returns true if @p dev is connected + * @returns false if @p dev is not connected + */ +static inline bool _atwinc15x0_is_connected(const atwinc15x0_t *dev) { + return dev->state == ATWINC15X0_STATE_CONNECTED || + dev->state == ATWINC15X0_STATE_CONNECTED_SCANNING || + dev->state == ATWINC15X0_STATE_CONNECTED_CONNECTING; +} + +/** + * @brief Set state to indicate that @p dev is connected + * to an AP + * + * @param[in, out] dev ATWINC15x0 device + */ +static inline void _atwinc15x0_set_connected(atwinc15x0_t *dev) { + assert(dev->state == ATWINC15X0_STATE_DISCONNECTED || + dev->state == ATWINC15X0_STATE_DISCONNECTED_CONNECTING || + dev->state == ATWINC15X0_STATE_CONNECTED_SCANNING); + dev->state = ATWINC15X0_STATE_CONNECTED; +} + +/** + * @brief Set state to indicate that @p dev is disconnected + * + * @param[in, out] dev ATWINC15x0 device + */ +static inline void _atwinc15x0_set_disconnected(atwinc15x0_t *dev) { + dev->state = ATWINC15X0_STATE_DISCONNECTED; +} + +/** + * @brief Check if @p dev is currently performing an asynchronous operation + * + * @param[in] dev ATWINC15x0 device + * @returns true if @p dev is busy + * @returns false if @p dev is not busy + */ +static inline bool _atwinc15x0_is_busy(const atwinc15x0_t *dev) { + return dev->state == ATWINC15X0_STATE_DISCONNECTING || + dev->state == ATWINC15X0_STATE_DISCONNECTED_SCANNING || + dev->state == ATWINC15X0_STATE_DISCONNECTED_CONNECTING || + dev->state == ATWINC15X0_STATE_CONNECTED_SCANNING || + dev->state == ATWINC15X0_STATE_CONNECTED_CONNECTING; +} + +/** + * @brief Check is @p dev is currently not performing an asynchronous operation + * + * @param[in] dev ATWINC15x0 device + * + * @return true if @p dev is idle + * @return false if @p dev is not idle + */ +static inline bool _atwinc15x0_is_idle(const atwinc15x0_t *dev) { + return dev->state == ATWINC15X0_STATE_CONNECTED || + dev->state == ATWINC15X0_STATE_DISCONNECTED; +} + +/** + * @brief Return from any busy state to corresponding idle state + * + * @param[in] dev ATWINC15x0 device + */ +static inline void _atwinc15x0_set_idle(atwinc15x0_t *dev) { + if (dev->state == ATWINC15X0_STATE_CONNECTED_SCANNING || + dev->state == ATWINC15X0_STATE_CONNECTED_CONNECTING) { + _atwinc15x0_set_connected(dev); + } + else if (dev->state == ATWINC15X0_STATE_DISCONNECTED_SCANNING || + dev->state == ATWINC15X0_STATE_DISCONNECTED_CONNECTING) { + _atwinc15x0_set_disconnected(dev); + } +} + +/** + * @brief Set state to indicate that @p dev is sleeping + * + * @param[in] dev ATWINC15x0 device + */ +static inline void _atwinc15x0_set_sleeping(atwinc15x0_t *dev) { + assert(!_atwinc15x0_is_busy(dev)); + dev->state = ATWINC15X0_STATE_SLEEP; +} + +/** + * @brief Check if @p dev is currently sleeping + * + * @param[in] dev ATWINC15x0 device + * @return true if @p dev is sleeping + * @return false if @p dev is not sleeping + */ +static inline bool _atwinc15x0_is_sleeping(const atwinc15x0_t *dev) { + return dev->state == ATWINC15X0_STATE_SLEEP; +} + +/** + * @brief Set member of currently connected AP SSID + * + * @pre Module atwinc15x0_dynamic_connect is used + * + * @param[in, out] dev ATWINC15x0 device + * @param[in] ssid SSID + */ +static inline void _atwinc15x0_sta_set_current_ssid(atwinc15x0_t *dev, const char *ssid) { + (void)dev; (void)ssid; +#if IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT) + strcpy(atwinc15x0->ssid, ssid); +#endif +} + +/** + * @brief Get member of currently connected AP + * + * @pre Module atwinc15x0_dynamic_connect is used + * + * @param[in] dev ATWINC15x0 device + * + * @returns SSID member + */ +static inline const char *_atwinc15x0_sta_get_current_ssid(const atwinc15x0_t *dev) { + (void)dev; +#if IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT) + return dev->ssid; +#endif + return NULL; +} + /** * @brief ATWINC15x0 device driver ISR */ diff --git a/drivers/include/atwinc15x0.h b/drivers/include/atwinc15x0.h index 13ce1a4840..7309ed492c 100644 --- a/drivers/include/atwinc15x0.h +++ b/drivers/include/atwinc15x0.h @@ -22,6 +22,7 @@ #include "bsp/include/nm_bsp.h" #include "net/ethernet.h" +#include "net/wifi.h" #include "net/netdev.h" #include "periph/gpio.h" #include "periph/spi.h" @@ -41,6 +42,14 @@ extern "C" { #define CONFIG_ATWINC15X0_RECV_BCAST (1) #endif +/** + * @brief Maximum number of supported entries in a scan result of + * an ATWINC15x0 transceiver + */ +#ifndef CONFIG_ATWINC15X0_SCAN_LIST_NUMOF +#define CONFIG_ATWINC15X0_SCAN_LIST_NUMOF (3) +#endif + /** * @brief ATWINC15x0 hardware and global parameters */ @@ -54,16 +63,32 @@ typedef struct { gpio_t wake_pin; /**< WAKE pin */ } atwinc15x0_params_t; +/** + * @brief ATWINC15x0 internal states + */ +typedef enum { + ATWINC15X0_STATE_SLEEP, /**< Sleep state */ + ATWINC15X0_STATE_DISCONNECTING, /**< Disconnect received when connected before */ + ATWINC15X0_STATE_DISCONNECTED, /**< Disconnect state */ + ATWINC15X0_STATE_DISCONNECTED_SCANNING, /**< Scanning state when disconnected */ + ATWINC15X0_STATE_DISCONNECTED_CONNECTING, /**< Connecting state where disconnected before */ + ATWINC15X0_STATE_CONNECTED, /**< Connected state */ + ATWINC15X0_STATE_CONNECTED_SCANNING, /**< Scanning state when connected */ + ATWINC15X0_STATE_CONNECTED_CONNECTING, /**< Connecting state where disconnect + event is not yet received */ +} atwinc15x0_state_t; + /** * @brief ATWINC15x0 device descriptor type */ typedef struct atwinc15x0 { netdev_t netdev; /**< Pulls in the netdev fields */ atwinc15x0_params_t params; /**< Device initialization parameters */ - - bool connected; /**< Indicates whether connected to an AP */ - netopt_state_t state; /**< Current interface state, only sleep or idle */ + atwinc15x0_state_t state; /**< Device state */ char ap[ETHERNET_ADDR_LEN]; /**< BSSID of current AP */ +#if IS_USED(MODULE_ATWINC15X0_DYNAMIC_CONNECT) || defined(DOXYGEN) + char ssid[WIFI_SSID_LEN_MAX + 1]; /**< SSID of current AP */ +#endif uint8_t channel; /**< Channel used for current AP */ int8_t rssi; /**< RSSI last measured by the WiFi module */ diff --git a/drivers/include/net/netdev/wifi.h b/drivers/include/net/netdev/wifi.h new file mode 100644 index 0000000000..42c106ed17 --- /dev/null +++ b/drivers/include/net/netdev/wifi.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2023 Fabian Hüßler ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @defgroup drivers_netdev_wifi Wi-Fi drivers + * @ingroup drivers_netdev_api + * @{ + * + * @file + * @brief Definitions for netdev common Wi-Fi code + * + * @author Fabian Hüßler + */ + +#ifndef NET_NETDEV_WIFI_H +#define NET_NETDEV_WIFI_H + +#include "net/ethernet/hdr.h" +#include "net/netopt.h" +#include "net/wifi.h" +#include "net/l2scan_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief A Wi-Fi scan request + */ +typedef struct wifi_scan_request { + netopt_scan_request_t base; /**< Basic scan request */ + uint16_t timeout_ms_per_ch; /**< timeout for each channel to scan */ +} wifi_scan_request_t; + +/** + * @brief Static initializer for a @ref wifi_scan_request_t + * + * @param ch Channel to scan or @ref NETOPT_SCAN_REQ_ALL_CH + * @param cb Callback on scan result @ref wifi_on_scan_result_t + * @param ms Timeout for each channel to scan + */ +#define WIFI_SCAN_REQUEST_INITIALIZER(ch, cb, ms) \ + (wifi_scan_request_t) { \ + .base = NETOPT_SCAN_REQUEST_INITIALIZER(ch, cb), \ + .timeout_ms_per_ch = ms, \ + } + +/** + * @brief A Wi-Fi scan result + */ +typedef struct wifi_scan_result { + netopt_scan_result_t base; /**< Basic scan result */ + wifi_security_mode_t sec_mode; /**< Security mode of AP */ + uint8_t bssid[ETHERNET_ADDR_LEN]; /**< BSSID of AP */ + char ssid[WIFI_SSID_LEN_MAX + 1]; /**< SSID of AP */ +} wifi_scan_result_t; + +/** + * @brief Static initializer for a @ref wifi_scan_result_t + * + * @param ch Channel that was scanned + * @param str Received signal strength + * @param sec AP security mode + */ +#define WIFI_SCAN_RESULT_INITIALIZER(ch, str, sec) \ + (wifi_scan_result_t) { \ + .base = NETOPT_SCAN_RESULT_INITIALIZER(ch, str), \ + .sec_mode = sec, \ + .bssid = { 0 }, \ + .ssid = { 0 }, \ + } + +/** + * @brief A Wi-Fi scan list + */ +typedef l2scan_list_t wifi_scan_list_t; + +/** + * @brief Static initializer for a @ref wifi_scan_list_t + * + * @param list Pointer to first list node + */ +#define WIFI_SCAN_LIST_INITIALIZER(list) \ + (wifi_scan_list_t) { \ + .head = (list_node_t *)list, \ + } + +/** + * @brief Node type in a wifi scan list @ref wifi_scan_list_t + */ +typedef struct wifi_scan_list_node { + list_node_t node; /**< Basic list node */ + wifi_scan_result_t result; /**< Wi-Fi scan result */ +} wifi_scan_list_node_t; + +/** + * @brief Wi-Fi scan result callback prototype + */ +typedef void (*wifi_on_scan_result_t) (void *netif, const wifi_scan_list_t *res); + +/** + * @brief A Wi-Fi connect request + */ +typedef struct wifi_connect_request { + netopt_connect_request_t base; /**< Basic connect request */ + const wifi_security_mode_t *cred; /**< Pointer to credentials */ + char ssid[WIFI_SSID_LEN_MAX + 1]; /**< SSID of AP to connect to */ +} wifi_connect_request_t; + +/** + * @brief Static initializer for a @ref wifi_connect_request_t + * + * @param ch Channel to connect to + * @param ccb On connect callback + * @param dcb On disconnect callback + * @param cr Pointer to credentials + */ +#define WIFI_CONNECT_REQUEST_INITIALIZER(ch, ccb, dcb, cr) \ + (wifi_connect_request_t) { \ + .base = NETOPT_CONNECT_REQUEST_INITIALIZER(ch, ccb, dcb), \ + .cred = cr, \ + .ssid = { 0 }, \ + } + +/** + * @brief A Wi-Fi connect result + */ +typedef struct wifi_connect_result { + netopt_connect_result_t base; /**< Basic connect result */ + const char *ssid; /**< SSID of currently connected AP */ + union { + wifi_security_mode_t sec; /**< WiFi security mode */ + wifi_security_wpa_psk_t wpa_psk; /**< WPA2 PSK security mode*/ + wifi_security_wep_psk_t wep; /**< WEP security mode */ + wifi_security_wpa_enterprise_t wpa_enterprise; /**< WPA2 enterprise security mode */ + } credentials; /**< Credentials */ +} wifi_connect_result_t; + +/** + * @brief Static initializer for a @ref wifi_connect_result_t + * + * @param ch Channel of the connected AP + * @param p_ssid SSID of connected AP + */ +#define WIFI_CONNECT_RESULT_INITIALIZER(ch, p_ssid) \ + (wifi_connect_result_t) { \ + .base = NETOPT_CONNECT_RESULT_INITIALIZER(ch), \ + .ssid = p_ssid, \ + .credentials = { .sec = WIFI_SECURITY_MODE_OPEN }, \ + } + +/** + * @brief Wi-Fi connect result callback prototype + */ +typedef void (*wifi_on_connect_result_t) (void *netif, const wifi_connect_result_t *res); + +/** + * @brief A Wi-Fi disconnect request + */ +typedef struct wifi_disconnect_request { + netopt_disconnect_request_t base; /**< Basic disconnect request */ +} wifi_disconnect_request_t; + +/** + * @brief Static initializer for a @ref wifi_disconnect_request_t + * + * @param dcb On disconnect callback + */ +#define WIFI_DISCONNECT_REQUEST_INITIALIZER(dcb) \ + (wifi_disconnect_request_t) { \ + .base = NETOPT_DISCONNECT_REQUEST_INITIALIZER(dcb), \ + } + +/** + * @brief A Wi-Fi disconnect result + */ +typedef struct wifi_disconnect_result { + netopt_disconnect_result_t base; /**< Basic disconnect result */ + const char *ssid; /**< SSID of the AP no longer connected to */ +} wifi_disconnect_result_t; + +/** + * @brief Static initializer for a @ref wifi_disconnect_result_t + * + * @param ch Channel of no longer connected AP + * @param p_ssid SSID of no longer connected AP + */ +#define WIFI_DISCONNECT_RESULT_INITIALIZER(ch, p_ssid) \ + (wifi_disconnect_result_t) { \ + .base = NETOPT_DISCONNECT_RESULT_INITIALIZER(ch), \ + .ssid = p_ssid, \ + } + +/** + * @brief Wi-Fi disconnect result callback prototype + */ +typedef void (*wifi_on_disconnect_result_t) (void *netif, const wifi_disconnect_result_t *res); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_NETDEV_WIFI_H */ +/** @} */ diff --git a/examples/gnrc_border_router/Makefile.board.dep b/examples/gnrc_border_router/Makefile.board.dep index 4b1d06953a..9943c4f057 100644 --- a/examples/gnrc_border_router/Makefile.board.dep +++ b/examples/gnrc_border_router/Makefile.board.dep @@ -10,8 +10,6 @@ ifeq (,$(filter native,$(BOARD))) ifneq (ble, $(DOWNLINK)) USEMODULE += esp_now endif - else - $(error Only esp32 and esp8266 are currently supported) endif endif ifeq (ble, $(DOWNLINK)) diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 1c5ea3dab4..bf89174d1d 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -472,6 +472,7 @@ PSEUDOMODULES += shell_cmd_gnrc_sixlowpan_frag_stats PSEUDOMODULES += shell_cmd_gnrc_udp PSEUDOMODULES += shell_cmd_heap PSEUDOMODULES += shell_cmd_i2c_scan +PSEUDOMODULES += shell_cmd_iw PSEUDOMODULES += shell_cmd_lwip_netif PSEUDOMODULES += shell_cmd_mci PSEUDOMODULES += shell_cmd_md5sum @@ -580,6 +581,7 @@ PSEUDOMODULES += vfs_auto_mount PSEUDOMODULES += vfs_default PSEUDOMODULES += wakaama_objects_% +PSEUDOMODULES += wifi_scan_list PSEUDOMODULES += wifi_enterprise PSEUDOMODULES += xtimer_on_ztimer PSEUDOMODULES += xtimer_no_ztimer_default diff --git a/sys/Makefile b/sys/Makefile index b7724719a7..de159e8680 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -101,6 +101,9 @@ endif ifneq (,$(filter l2filter,$(USEMODULE))) DIRS += net/link_layer/l2filter endif +ifneq (,$(filter l2scan_list,$(USEMODULE))) + DIRS += net/link_layer/l2scan_list +endif ifneq (,$(filter l2util,$(USEMODULE))) DIRS += net/link_layer/l2util endif diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 3edf021097..783df5777e 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -694,4 +694,8 @@ ifneq (,$(filter auto_init%,$(USEMODULE))) USEMODULE += preprocessor_successor endif +ifneq (,$(filter wifi_scan_list,$(USEMODULE))) + USEMODULE += l2scan_list +endif + include $(RIOTBASE)/sys/test_utils/Makefile.dep diff --git a/sys/include/net/coap.h b/sys/include/net/coap.h index d14a0bfe07..dca1f4bae0 100644 --- a/sys/include/net/coap.h +++ b/sys/include/net/coap.h @@ -48,12 +48,46 @@ extern "C" { #define COAP_OPT_IF_NONE_MATCH (5) #define COAP_OPT_OBSERVE (6) #define COAP_OPT_LOCATION_PATH (8) +/** + * @brief OSCORE option + * + * Indicates that the CoAP message is an OSCORE message and that it contains a + * compressed COSE object. + * + * @see [RFC 8613](https://datatracker.ietf.org/doc/html/rfc8613) + */ +#define COAP_OPT_OSCORE (9) #define COAP_OPT_URI_PATH (11) #define COAP_OPT_CONTENT_FORMAT (12) #define COAP_OPT_MAX_AGE (14) #define COAP_OPT_URI_QUERY (15) +/** + * @brief Hop-Limit option + * + * Used to prevent infinite loops when communicating over multiple proxies. + * + * @see [RFC 8768](https://datatracker.ietf.org/doc/html/rfc8768) + */ +#define COAP_OPT_HOP_LIMIT (16) #define COAP_OPT_ACCEPT (17) +/** + * @brief Q-Block1 option + * + * Used for block-wise transfer supporting robust transmission in requests. + * + * @see [RFC 9177](https://datatracker.ietf.org/doc/html/rfc9177) + */ +#define COAP_OPT_Q_BLOCK1 (19) #define COAP_OPT_LOCATION_QUERY (20) +/** + * @brief EDHOC option + * + * Used in a CoAP request to signal that the request payload conveys both an + * EDHOC message_3 and OSCORE protected data, combined together. + * + * @see [draft-ietf-core-oscore-edhoc-02](https://datatracker.ietf.org/doc/draft-ietf-core-oscore-edhoc/02/) + */ +#define COAP_OPT_EDHOC (21) #define COAP_OPT_BLOCK2 (23) #define COAP_OPT_BLOCK1 (27) /** @@ -67,6 +101,14 @@ extern "C" { * @see [RFC 8613](https://datatracker.ietf.org/doc/html/rfc8613) */ #define COAP_OPT_SIZE2 (28) +/** + * @brief Q-Block2 option + * + * Used for block-wise transfer supporting robust transmission in responses. + * + * @see [RFC 9177](https://datatracker.ietf.org/doc/html/rfc9177) + */ +#define COAP_OPT_Q_BLOCK2 (31) #define COAP_OPT_PROXY_URI (35) #define COAP_OPT_PROXY_SCHEME (39) /** @@ -82,11 +124,29 @@ extern "C" { * @see [RFC 8613](https://datatracker.ietf.org/doc/html/rfc8613) */ #define COAP_OPT_SIZE1 (60) +/** + * @brief Echo option + * + * Enables a CoAP server to verify the freshness of a request or to force a + * client to demonstrate reachability at its claimed network address. + * + * @see [RFC 9175](https://datatracker.ietf.org/doc/html/rfc9175) + */ +#define COAP_OPT_ECHO (252) /** * @brief suppress CoAP response * @see [RFC 7968](https://datatracker.ietf.org/doc/html/rfc7967) */ #define COAP_OPT_NO_RESPONSE (258) +/** + * @brief Request-Tag option + * + * Allows a CoAP server to match block-wise message fragments belonging to the + * same request. + * + * @see [RFC 9175](https://datatracker.ietf.org/doc/html/rfc9175) + */ +#define COAP_OPT_REQUEST_TAG (292) /** @} */ /** @@ -177,6 +237,41 @@ extern "C" { * @{ */ #define COAP_FORMAT_TEXT (0) +/** + * @brief Content-Type `application/cose; cose-type="cose-encrypt0"` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_ENCRYPT0 (16) +/** + * @brief Content-Type `application/cose; cose-type="cose-mac0"` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_MAC0 (17) +/** + * @brief Content-Type `application/cose; cose-type="cose-sign1"` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_SIGN1 (18) +/** + * @brief Content-Type `application/ace+cbor` + * @see [RFC 9200](https://datatracker.ietf.org/doc/html/rfc9200) + */ +#define COAP_FORMAT_ACE_CBOR (19) +/** + * @brief Content-Type `image/gif` + * @see https://www.w3.org/Graphics/GIF/spec-gif89a.txt + */ +#define COAP_FORMAT_IMAGE_GIF (21) +/** + * @brief Content-Type `image/jpeg` + * @see [ISO/IEC 10918-5](https://www.itu.int/rec/T-REC-T.871-201105-I/en) + */ +#define COAP_FORMAT_IMAGE_JPEG (22) +/** + * @brief Content-Type `image/png` + * @see [RFC 2083](https://datatracker.ietf.org/doc/html/rfc2083) + */ +#define COAP_FORMAT_IMAGE_PNG (23) #define COAP_FORMAT_LINK (40) #define COAP_FORMAT_XML (41) #define COAP_FORMAT_OCTET (42) @@ -185,14 +280,226 @@ extern "C" { #define COAP_FORMAT_JSON_PATCH_JSON (51) #define COAP_FORMAT_MERGE_PATCH_JSON (52) #define COAP_FORMAT_CBOR (60) +/** + * @brief Content-Type `application/cwt` + * @see [RFC 8392](https://datatracker.ietf.org/doc/html/rfc8392) + */ +#define COAP_FORMAT_CWT (61) +/** + * @brief Content-Type `application/multipart-core` + * @see [RFC 8710](https://datatracker.ietf.org/doc/html/rfc8710) + */ +#define COAP_FORMAT_MULTIPART_CORE (62) +/** + * @brief Content-Type `application/cbor-seq` + * @see [RFC 8742](https://datatracker.ietf.org/doc/html/rfc8742) + */ +#define COAP_FORMAT_CBOR_SEQ (63) +/** + * @brief Content-Type `application/cose; cose-type="cose-encrypt"` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_ENCRYPT (96) +/** + * @brief Content-Type `application/cose; cose-type="cose-mac"` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_MAC (97) +/** + * @brief Content-Type `application/cose; cose-type="cose-sign"` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_SIGN (98) +/** + * @brief Content-Type `application/cose-key` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_KEY (101) +/** + * @brief Content-Type `application/cose-key-set` + * @see [RFC 9052](https://datatracker.ietf.org/doc/html/rfc9052) + */ +#define COAP_FORMAT_COSE_KEY_SET (102) #define COAP_FORMAT_SENML_JSON (110) #define COAP_FORMAT_SENSML_JSON (111) #define COAP_FORMAT_SENML_CBOR (112) #define COAP_FORMAT_SENSML_CBOR (113) #define COAP_FORMAT_SENML_EXI (114) #define COAP_FORMAT_SENSML_EXI (115) +/** + * @brief Content-Type `application/yang-data+cbor; id=sid` + * @see [RFC 9254](https://datatracker.ietf.org/doc/html/rfc9254) + */ +#define COAP_FORMAT_YANG_DATA_CBOR_SID (140) +/** + * @brief Content-Type `application/coap-group+json` + * @see [RFC 7390](https://datatracker.ietf.org/doc/html/rfc7390) + */ +#define COAP_FORMAT_COAP_GROUP_JSON (256) +/** + * @brief Content-Type `application/concise-problem-details+cbor` + * @see [RFC 9290](https://datatracker.ietf.org/doc/html/rfc9290) + */ +#define COAP_FORMAT_PROBLEM_DETAILS_CBOR (257) +/** + * @brief Content-Type `application/swid+cbor` + * @see [RFC 9393](https://datatracker.ietf.org/doc/html/rfc9393) + */ +#define COAP_FORMAT_SWID_CBOR (258) +/** + * @brief Content-Type `application/pkixcmp` + * @see [draft-ietf-ace-cmpv2-coap-transport](https://datatracker.ietf.org/doc/draft-ietf-ace-cmpv2-coap-transport/) + * @see [RFC 4210](https://datatracker.ietf.org/doc/html/rfc4210) + */ +#define COAP_FORMAT_PKIXCMP (259) +/** + * @brief Content-Type `application/dots+cbor` + * @see [RFC 9132](https://datatracker.ietf.org/doc/html/rfc9132) + */ +#define COAP_FORMAT_DOTS_CBOR (271) +/** + * @brief Content-Type `application/missing-blocks+cbor-seq` + * @see [RFC 9177](https://datatracker.ietf.org/doc/html/rfc9177) + */ +#define COAP_FORMAT_MISSING_BLOCKS_CBOR_SEQ (272) +/** + * @brief Content-Type `application/pkcs7-mime; smime-type=server-generated-key` + * @see [RFC 7030](https://datatracker.ietf.org/doc/html/rfc7030) + * @see [RFC 8551](https://datatracker.ietf.org/doc/html/rfc8551) + * @see [RFC 9148](https://datatracker.ietf.org/doc/html/rfc9148) + */ +#define COAP_FORMAT_PKCS7_MIME_SERVER_GEN (280) +/** + * @brief Content-Type `application/pkcs7-mime; smime-type=certs-only` + * @see [RFC 8551](https://datatracker.ietf.org/doc/html/rfc8551) + * @see [RFC 9148](https://datatracker.ietf.org/doc/html/rfc9148) + */ +#define COAP_FORMAT_PKCS7_MIME_CERTS_ONLY (281) +/** + * @brief Content-Type `application/pkcs8` + * @see [RFC 5958](https://datatracker.ietf.org/doc/html/rfc5958) + * @see [RFC 8551](https://datatracker.ietf.org/doc/html/rfc8551) + * @see [RFC 9148](https://datatracker.ietf.org/doc/html/rfc9148) + */ +#define COAP_FORMAT_PKCS8 (284) +/** + * @brief Content-Type `application/csrattrs` + * @see [RFC 7030](https://datatracker.ietf.org/doc/html/rfc7030) + * @see [RFC 9148](https://datatracker.ietf.org/doc/html/rfc9148) + */ +#define COAP_FORMAT_CSRATTRS (285) +/** + * @brief Content-Type `application/pkcs10` + * @see [RFC 5967](https://datatracker.ietf.org/doc/html/rfc5967) + * @see [RFC 8551](https://datatracker.ietf.org/doc/html/rfc8551) + * @see [RFC 9148](https://datatracker.ietf.org/doc/html/rfc9148) + */ +#define COAP_FORMAT_PKCS10 (286) +/** + * @brief Content-Type `application/pkix-cert` + * @see [RFC 2585](https://datatracker.ietf.org/doc/html/rfc2585) + * @see [RFC 9148](https://datatracker.ietf.org/doc/html/rfc9148) + */ +#define COAP_FORMAT_PKIX_CERT (287) +/** + * @brief Content-Type `application/aif+cbor` + * @see [RFC 9237](https://datatracker.ietf.org/doc/html/rfc9237) + */ +#define COAP_FORMAT_AIF_CBOR (290) +/** + * @brief Content-Type `application/aif+json` + * @see [RFC 9237](https://datatracker.ietf.org/doc/html/rfc9237) + */ +#define COAP_FORMAT_AIF_JSON (291) #define COAP_FORMAT_SENML_XML (310) #define COAP_FORMAT_SENSML_XML (311) +/** + * @brief Content-Type `application/senml-etch+json` + * @see [RFC 8790](https://datatracker.ietf.org/doc/html/rfc8790) + */ +#define COAP_FORMAT_SNML_ETCH_JSON (320) +/** + * @brief Content-Type `application/senml-etch+cbor` + * @see [RFC 8790](https://datatracker.ietf.org/doc/html/rfc8790) + */ +#define COAP_FORMAT_SNML_ETCH_CBOR (322) +/** + * @brief Content-Type `application/yang-data+cbor` + * @see [RFC 9254](https://datatracker.ietf.org/doc/html/rfc9254) + */ +#define COAP_FORMAT_YAML_DATA_CBOR (340) +/** + * @brief Content-Type `application/yang-data+cbor; id=name` + * @see [RFC 9254](https://datatracker.ietf.org/doc/html/rfc9254) + */ +#define COAP_FORMAT_YAML_DATA_CBOR_ID_NAME (341) +/** + * @brief Content-Type `application/td+json` + * @see [Web of Things (WoT) Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/) + */ +#define COAP_FORMAT_TD_JSON (432) +/** + * @brief Content-Type `application/tm+json` + * @see [Web of Things (WoT) Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/) + */ +#define COAP_FORMAT_TM_JSON (433) +/** + * @brief Content-Type `application/voucher-cose+cbor` + * @see [draft-ietf-anima-constrained-voucher](https://datatracker.ietf.org/doc/draft-ietf-anima-constrained-voucher/) + * @note Temporary registration until April 12, 2024. + */ +#define COAP_FORMAT_VOUCER_COSE_CBOR (836) +/** + * @brief Content-Type `application/vnd.ocf+cbor` + */ +#define COAP_FORMAT_VND_OCF_CBOR (10000) +/** + * @brief Content-Type `application/oscore` + * @see [RFC 8613](https://datatracker.ietf.org/doc/html/rfc8613) + */ +#define COAP_FORMAT_OSCORE (10001) +/** + * @brief Content-Type `application/javascript` + * @see [RFC 4329](https://datatracker.ietf.org/doc/html/rfc4329) + */ +#define COAP_FORMAT_JAVASCRIPT (10002) +/** + * @brief Content-Type `application/json` with Content Coding `deflate` + * @see [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259) + * @see [RFC 9110, Section 8.4.1.2](https://datatracker.ietf.org/doc/html/rfc9110) + */ +#define COAP_FORMAT_JSON_DEFLATE (11050) +/** + * @brief Content-Type `application/cbor` with Content Coding `deflate` + * @see [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949) + * @see [RFC 9110, Section 8.4.1.2](https://datatracker.ietf.org/doc/html/rfc9110) + */ +#define COAP_FORMAT_CBOR_DEFLATE (11060) +/** + * @brief Content-Type `application/vnd.oma.lwm2m+tlv` + * @see [OMA-TS-LightweightM2M-V1_0](https://www.openmobilealliance.org/release/LightweightM2M/V1_0-20170208-A/OMA-TS-LightweightM2M-V1_0-20170208-A.pdf) + */ +#define COAP_FORMAT_VND_OMA_LWM2M_TLV (11542) +/** + * @brief Content-Type `application/vnd.oma.lwm2m+json` + * @see [OMA-TS-LightweightM2M-V1_0](https://www.openmobilealliance.org/release/LightweightM2M/V1_0-20170208-A/OMA-TS-LightweightM2M-V1_0-20170208-A.pdf) + */ +#define COAP_FORMAT_VND_OMA_LWM2M_JSON (11543) +/** + * @brief Content-Type `application/vnd.oma.lwm2m+cbor` + * @see [OMA-TS-LightweightM2M-V1_2](https://www.openmobilealliance.org/release/LightweightM2M/V1_2-20201110-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_2-20201110-A.html) + */ +#define COAP_FORMAT_VND_OMA_LWM2M_CBOR (11544) +/** + * @brief Content-Type `text/css` + * @see https://datatracker.ietf.org/doc/html/rfc2318 + */ +#define COAP_FORMAT_TEXT_CSS (20000) +/** + * @brief Content-Type `image/svg+xml` + * @see [RFC 2318](https://www.w3.org/TR/SVG/mimereg.html) + */ +#define COAP_FORMAT_IMAGE_SVG_XML (30000) #define COAP_FORMAT_DNS_MESSAGE (65053) /**< NON STANDARD! */ /** @} */ diff --git a/sys/include/net/l2scan_list.h b/sys/include/net/l2scan_list.h new file mode 100644 index 0000000000..7d88e3e220 --- /dev/null +++ b/sys/include/net/l2scan_list.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup net_l2scanlist Scan List - List of wireless access points + * @ingroup net + * @brief Internal list data structure of scanned access points + * @ref NETOPT_SCAN + * @{ + * + * @file + * @brief L2 Scan list API + * + * @author Fabian Hüßler + */ + +#ifndef NET_L2SCAN_LIST_H +#define NET_L2SCAN_LIST_H + +#include + +#include "list.h" +#include "net/netopt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of a Link Layer scan list + */ +typedef struct l2scan_list { + list_node_t head; /**< List head, where head->next is the first element */ + /* items */ +} l2scan_list_t; + +/** + * @brief Empty the list to start a new scan + * + * @param[in, out] list Pointer to list + * @param[in, out] nodes Pointer to nodes array + * @param[in] nodes_numof Number of nodes in the array + * @param[in] node_size Size of one node element in the array + */ +void l2scan_list_empty(l2scan_list_t *list, + list_node_t *nodes, unsigned nodes_numof, + size_t node_size); + +/** + * @brief Insert a new scan result into the list + * + * @param[in, out] list Pointer to list + * @param[in, out] nodes Pointer to nodes array + * @param[in] nodes_numof Number of nodes in the array + * @param[in] node_size Size of one node element in the array + * @param[in] result New result to insert + */ +void l2scan_list_insert(l2scan_list_t *list, + list_node_t *nodes, unsigned nodes_numof, + size_t node_size, + const netopt_scan_result_t *result); + +/** + * @brief Copy the content of a L2 scan list to an array to get rid of the list overhead + * + * @param[in] list Pointer to list + * @param[out] nodes_array Buffer of nodes to store the result + * @param[in] nodes_numof Maximum number of nodes that can be copied + * @param[in] node_size Size of one node element in the array + * + * @return Number of copied nodes + */ +unsigned l2scan_list_to_array(const l2scan_list_t *list, + void *nodes_array, unsigned nodes_numof, + size_t node_size); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_L2SCAN_LIST_H */ +/** @} */ diff --git a/sys/include/net/netopt.h b/sys/include/net/netopt.h index 5a06f98c81..6385e68145 100644 --- a/sys/include/net/netopt.h +++ b/sys/include/net/netopt.h @@ -25,6 +25,9 @@ #ifndef NET_NETOPT_H #define NET_NETOPT_H +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -783,6 +786,35 @@ typedef enum { */ NETOPT_RSSI, + /** + * @brief (@ref netopt_scan_request_t) Instruct the interface to do a network scan + * + * This netopt triggers an asynchronous network scan. + * The result is a list of reachable access points @ref l2scan_list_t. + * Notification happens by a callback @ref netopt_on_scan_result_t. + */ + NETOPT_SCAN, + + /** + * @brief (@ref netopt_connect_request_t) Instructs the interface to connect to a network + * + * This netopt triggers an asynchronous connection attempt to a network. + * The result is a derivative of @ref netopt_connect_result_t or + * @ref netopt_disconnect_result_t. Notification happens through a callback + * @ref netopt_on_connect_result_t or @ref netopt_on_disconnect_result_t respectively. + */ + NETOPT_CONNECT, + + /** + * @brief (@ref netopt_disconnect_request_t) Instructs the interface to disconnect + * from a network + * + * This netopt triggers a disconnect procedure from a network. + * The result is a derivative of @ref netopt_disconnect_result_t. + * Notification happens through a callback @ref netopt_on_disconnect_result_t. + */ + NETOPT_DISCONNECT, + /** * @brief (uint16_t) Set the battery monitor voltage (in mV). * @@ -877,6 +909,157 @@ typedef enum { NETOPT_RF_TESTMODE_CTX_PRBS9, /**< PRBS9 continuous tx mode */ } netopt_rf_testmode_t; +/** + * @brief Netopt RF channel type + */ +typedef uint16_t netopt_channel_t; + +/** + * @brief Netopt RSSI type + */ +typedef int16_t netopt_rssi_t; + +/** + * @brief Request to scan all channels + */ +#define NETOPT_SCAN_REQ_ALL_CH ((netopt_channel_t)(-1)) + +/** + * @brief Basic network scan result + */ +typedef struct netopt_scan_result { + netopt_channel_t channel; /**< Scanned channel */ + netopt_rssi_t strength; /**< Received signal strength */ +} netopt_scan_result_t; + +/** + * @brief Static initializer for a @ref netopt_scan_result_t + * + * @param ch Scanned channel + * @param str Received signal strength + */ +#define NETOPT_SCAN_RESULT_INITIALIZER(ch, str) \ + (netopt_scan_result_t) { \ + .channel = ch, \ + .strength = str, \ + } + +/** + * @brief Forward declaration of a list of network scan results + * + * This prevents a recursive include. + */ +struct l2scan_list; + +/** + * @brief Basic callback type on network scan @ref NETOPT_CONNECT + */ +typedef void (*netopt_on_scan_result_t) (void *netif, const struct l2scan_list *res); + +/** + * @brief Basic network scan request + */ +typedef struct netopt_scan_request { + netopt_on_scan_result_t scan_cb; /**< Scan result callback */ + netopt_channel_t channel; /**< Channel to scan */ +} netopt_scan_request_t; + +/** + * @brief Static initializer for a @ref netopt_scan_request_t + * + * @param ch Channel to be scanned + * @param cb Scan result callback + */ +#define NETOPT_SCAN_REQUEST_INITIALIZER(ch, cb) \ + (netopt_scan_request_t) { \ + .channel = ch, \ + .scan_cb = (netopt_on_scan_result_t)cb, \ + } + +/** + * @brief Basic network connect result + */ +typedef struct netopt_connect_result { + netopt_channel_t channel; /**< Connected channel */ +} netopt_connect_result_t; + +/** + * @brief Static initializer for a @ref netopt_connect_result_t + * + * @param ch Connected channel + */ +#define NETOPT_CONNECT_RESULT_INITIALIZER(ch) \ + (netopt_connect_result_t) { \ + .channel = ch, \ + } + +/** + * @brief Basic disconnect result + */ +typedef struct netopt_disconnect_result { + netopt_channel_t channel; /**< Channel of the disconnected AP */ +} netopt_disconnect_result_t; + +/** + * @brief Static initializer for a @ref netopt_disconnect_result_t + * + * @param ch Channel of the disconnected AP + */ +#define NETOPT_DISCONNECT_RESULT_INITIALIZER(ch) \ + (netopt_disconnect_result_t) { \ + .channel = ch, \ + } + +/** + * @brief Basic callback type on network connection @ref NETOPT_CONNECT + */ +typedef void (*netopt_on_connect_result_t) (void *netif, const struct netopt_connect_result *res); + +/** + * @brief Basic callback type on network disconnection @ref NETOPT_CONNECT + */ +typedef void (*netopt_on_disconnect_result_t) (void *netif, const struct netopt_disconnect_result *res); + +/** + * @brief Basic network connect request + */ +typedef struct netopt_connect_request { + netopt_on_disconnect_result_t disconn_cb; /**< On disconnect callback */ + netopt_on_connect_result_t conn_cb; /**< On connect callback */ + netopt_channel_t channel; /**< Channel of the network to connect to */ +} netopt_connect_request_t; + +/** + * @brief Static initializer for a @ref netopt_connect_request_t + * + * @param ch Channel of the network to connect to + * @param ccb On connect callback + * @param dcb On disconnect callback + */ +#define NETOPT_CONNECT_REQUEST_INITIALIZER(ch, ccb, dcb) \ + (netopt_connect_request_t) { \ + .disconn_cb = (netopt_on_disconnect_result_t)dcb, \ + .conn_cb = (netopt_on_connect_result_t)ccb, \ + .channel = ch, \ + } + +/** + * @brief Basic network disconnect request + */ +typedef struct netopt_disconnect_request { + netopt_on_disconnect_result_t disconn_cb; /**< On disconnect callback */ +} netopt_disconnect_request_t; + +/** + * @brief Static initializer for a @ref netopt_disconnect_request_t + * + * @param dcb On disconnect callback + */ +#define NETOPT_DISCONNECT_REQUEST_INITIALIZER(dcb) \ + (netopt_disconnect_request_t) { \ + .disconn_cb = (netopt_on_disconnect_result_t)dcb, \ + } + /** * @brief Get a string ptr corresponding to opt, for debugging * diff --git a/sys/include/net/wifi.h b/sys/include/net/wifi.h new file mode 100644 index 0000000000..74ce48dfdd --- /dev/null +++ b/sys/include/net/wifi.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2023 Fabian Hüßler ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @defgroup net_wifi Wi-Fi + * @ingroup net + * @brief Provides Wi-Fi definitions + * @{ + * + * @file + * @brief Definitions for Wi-Fi + * + * @author Fabian Hüßler + */ + +#ifndef NET_WIFI_H +#define NET_WIFI_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Wi-Fi region Europe + */ +#define WIFI_REGION_EUROPE 0 + +/** + * @brief Wi-Fi region North America + */ +#define WIFI_REGION_NORTH_AMERICA 1 + +/** + * @brief Wi-Fi region Asia + */ +#define WIFI_REGION_ASIA 2 + +#if !defined(CONFIG_WIFI_REGION) || defined(DOXYGEN) +/** + * @brief Used to select regional Wi-Fi restrictions + */ +#define CONFIG_WIFI_REGION WIFI_REGION_EUROPE +#endif + +/** + * @brief Current Wi-Fi region + */ +#define WIFI_REGION CONFIG_WIFI_REGION + +/** + * @brief 2.4 GHz Channels + */ +typedef enum { + WIFI_2_4_CH_1 = 1, /**< 2.4 GHz channel 1 */ + WIFI_2_4_CH_2 = 2, /**< 2.4 GHz channel 2 */ + WIFI_2_4_CH_3 = 3, /**< 2.4 GHz channel 3 */ + WIFI_2_4_CH_4 = 4, /**< 2.4 GHz channel 4 */ + WIFI_2_4_CH_5 = 5, /**< 2.4 GHz channel 5 */ + WIFI_2_4_CH_6 = 6, /**< 2.4 GHz channel 6 */ + WIFI_2_4_CH_7 = 7, /**< 2.4 GHz channel 7 */ + WIFI_2_4_CH_8 = 8, /**< 2.4 GHz channel 8 */ + WIFI_2_4_CH_9 = 9, /**< 2.4 GHz channel 9 */ + WIFI_2_4_CH_10 = 10, /**< 2.4 GHz channel 10 */ + WIFI_2_4_CH_11 = 11, /**< 2.4 GHz channel 11 */ + WIFI_2_4_CH_12 = 12, /**< 2.4 GHz channel 12 */ + WIFI_2_4_CH_13 = 13, /**< 2.4 GHz channel 13 */ + WIFI_2_4_CH_14 = 14, /**< 2.4 GHz channel 14 */ +} wifi_2_4_channel_t; + +#if WIFI_REGION == WIFI_REGION_EUROPE +/** + * @brief Lowest Wi-Fi channel + */ +#define WIFI_2_4_CH_MIN 1 + +/** + * @brief Highest Wi-Fi channel + */ +#define WIFI_2_4_CH_MAX 13 +#elif WIFI_REGION == WIFI_REGION_NORTH_AMERICA +#define WIFI_2_4_CH_MIN 1 +#define WIFI_2_4_CH_MAX 11 +#elif WIFI_REGION == WIFI_REGION_ASIA +#define WIFI_2_4_CH_MIN 1 +#define WIFI_2_4_CH_MAX 14 +#else +#error "WIFI_REGION undefined" +#endif + +/** + * @brief Number of 2.4 GHz channels + */ +#define WIFI_2_4_CH_NUMOF (2 + (WIFI_2_4_CH_MAX - WIFI_2_4_CH_MIN)) + +/** + * @brief Type to express a Wi-Fi security mode + */ +typedef enum { + WIFI_SECURITY_MODE_OPEN, /**< AP is open */ + WIFI_SECURITY_MODE_WEP_PSK, /**< AP is protected with WEP */ + WIFI_SECURITY_MODE_WPA2_PERSONAL, /**< AP is protected with WPA2 Personal mode */ + WIFI_SECURITY_MODE_WPA2_ENTERPRISE, /**< AP is protected with WPA2 Enterprise mode */ +} wifi_security_mode_t; + +/** + * @brief Maximum length of an SSID + */ +#define WIFI_SSID_LEN_MAX 32 + +/** + * @brief Maximum length of a WPA key + */ +#define WIFI_WPA_PSK_LEN_MAX 63 + +/** + * @brief Maximum length of a WEP key + */ +#define WIFI_WEP_PSK_LEN_MAX 16 + +/** + * @brief Maximum username length for WPA2 Enterprise + */ +#define WIFI_EAP_USER_LEN_MAX 20 + +/** + * @brief Maximum password length for WPA2 Enterprise + */ +#define WIFI_EAP_PWD_LEN_MAX 40 + +/** + * @brief A WPA pre-shared-key + */ +typedef struct wifi_security_wpa_psk { + wifi_security_mode_t sec; /**< @ref WIFI_SECURITY_MODE_WPA2_PERSONAL */ + char psk[WIFI_WPA_PSK_LEN_MAX + 1]; /**< Key data */ +} wifi_security_wpa_psk_t; + +/** + * @brief A WEP pre-shared-key + */ +typedef struct wifi_security_wep_psk { + wifi_security_mode_t sec; /**< @ref WIFI_SECURITY_MODE_WEP_PSK */ + char psk[WIFI_WEP_PSK_LEN_MAX + 1]; /**< Key data */ +} wifi_security_wep_psk_t; + +/** + * @brief WPA2 Enterprise credentials + */ +typedef struct wifi_security_wpa_enterprise { + wifi_security_mode_t sec; /**< @ref WIFI_SECURITY_MODE_WPA2_ENTERPRISE */ + char user[WIFI_EAP_USER_LEN_MAX + 1]; /**< Username */ + char pwd[WIFI_EAP_PWD_LEN_MAX + 1]; /**< Password */ +} wifi_security_wpa_enterprise_t; + +#ifdef __cplusplus +} +#endif + +#endif /* NET_WIFI_H */ +/** @} */ diff --git a/sys/include/net/wifi_scan_list.h b/sys/include/net/wifi_scan_list.h new file mode 100644 index 0000000000..d5eb774561 --- /dev/null +++ b/sys/include/net/wifi_scan_list.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @defgroup net_wifi_scan_list List of scanned WiFis access points + * @ingroup net + * @brief Wrapper around l2scan list for WiFi scan results + * @{ + * + * @file + * @brief Thin wrapper around l2scan list to support WiFi scan results + * + * @author Fabian Hüßler + */ + +#ifndef NET_WIFI_SCAN_LIST_H +#define NET_WIFI_SCAN_LIST_H + +#include + +#include "l2scan_list.h" +#include "net/netdev/wifi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Empty the WiFi scan list to start a new scan + * + * @param[in, out] list Pointer to WiFi scan result list + * @param[in, out] nodes Pointer to WiFi scan result nodes array + * @param[in] nodes_numof Number of nodes in the array + */ +static inline void wifi_scan_list_empty(wifi_scan_list_t *list, + wifi_scan_list_node_t *nodes, + unsigned nodes_numof) +{ + l2scan_list_empty(list, &nodes->node, nodes_numof, + sizeof(wifi_scan_list_node_t)); +} + +/** + * @brief Insert a new WiFi scan result into the list + * + * @param[in, out] list Pointer to WiFi scan result list + * @param[in, out] nodes Pointer to WiFi scan result nodes array + * @param[in] nodes_numof Number of nodes in the array + * @param[in] result New WiFi scan result to insert + */ +static inline void wifi_scan_list_insert(wifi_scan_list_t *list, + wifi_scan_list_node_t *nodes, + unsigned nodes_numof, + const wifi_scan_result_t *result) +{ + l2scan_list_insert(list, &nodes->node, nodes_numof, + sizeof(wifi_scan_list_node_t), &result->base); +} + +/** + * @brief Copy the content of a WiFi scan list to an array to get rid of the list overhead + * + * @param[in] list Pointer to list + * @param[in] array Buffer of nodes to store the result + * @param[in] numof Maximum number of nodes that can be copied + * + * @return Number of copied nodes + */ +static inline unsigned wifi_scan_list_to_array(const wifi_scan_list_t *list, + wifi_scan_result_t *array, + unsigned numof) +{ + return l2scan_list_to_array(list, array, numof, + sizeof(wifi_scan_list_node_t)); +} + +#ifdef __cplusplus +} +#endif + +#endif /* NET_WIFI_SCAN_LIST_H */ +/** @} */ diff --git a/sys/net/crosslayer/netopt/netopt.c b/sys/net/crosslayer/netopt/netopt.c index 57e2b5bb4a..9c66cbc0b4 100644 --- a/sys/net/crosslayer/netopt/netopt.c +++ b/sys/net/crosslayer/netopt/netopt.c @@ -129,6 +129,9 @@ static const char *_netopt_strmap[] = { [NETOPT_NUM_GATEWAYS] = "NETOPT_NUM_GATEWAYS", [NETOPT_LINK_CHECK] = "NETOPT_LINK_CHECK", [NETOPT_RSSI] = "NETOPT_RSSI", + [NETOPT_SCAN] = "NETOPT_SCAN", + [NETOPT_CONNECT] = "NETOPT_CONNECT", + [NETOPT_DISCONNECT] = "NETOPT_DISCONNECT", [NETOPT_BATMON] = "NETOPT_BATMON", [NETOPT_L2_GROUP] = "NETOPT_L2_GROUP", [NETOPT_L2_GROUP_LEAVE] = "NETOPT_L2_GROUP_LEAVE", diff --git a/sys/net/link_layer/l2scan_list/Kconfig b/sys/net/link_layer/l2scan_list/Kconfig new file mode 100644 index 0000000000..accb7e2595 --- /dev/null +++ b/sys/net/link_layer/l2scan_list/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2023 ML!PA Consulting Gmbh +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config MODULE_L2SCAN_LIST + bool "List of scanned Link Layer access points" diff --git a/sys/net/link_layer/l2scan_list/Makefile b/sys/net/link_layer/l2scan_list/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/net/link_layer/l2scan_list/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/link_layer/l2scan_list/l2scan_list.c b/sys/net/link_layer/l2scan_list/l2scan_list.c new file mode 100644 index 0000000000..c4c92b67b7 --- /dev/null +++ b/sys/net/link_layer/l2scan_list/l2scan_list.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup net_l2scan_list + * @{ + * + * @file + * @brief List to store the result of a network scan + * + * @author Fabian Hüßler + * + * @} + */ + +#include + +#include "list.h" +#include "net/netopt.h" +#include "net/l2scan_list.h" + +#define SCAN_LIST_NODE_AT(array, pos, size) ((void *)(((uint8_t *)(array)) + ((pos) * (size)))) + +/** + * @brief Basic type of a scan result in a list + */ +typedef struct scan_list_node { + list_node_t node; /* Basic node */ + netopt_scan_result_t result; /* Basic scan result */ +} scan_list_node_t; + +static scan_list_node_t *_scan_list_get_insert(l2scan_list_t *list, + list_node_t *array, unsigned array_numof, + size_t item_size) +{ + scan_list_node_t *lowest = (scan_list_node_t *)array; + for (unsigned i = 0; i < array_numof; i++) { + /* look for free slot or lowest element */ + scan_list_node_t *result = SCAN_LIST_NODE_AT(array, i, item_size); + if (!result->result.strength) { + return result; /* free slot */ + } + if (result->result.strength < lowest->result.strength) { + lowest = result; /* override lowest element */ + } + } + list_remove(&list->head, &lowest->node); + return lowest; +} + +void l2scan_list_empty(l2scan_list_t *list, + list_node_t *nodes, unsigned nodes_numof, + size_t node_size) +{ + list->head.next = NULL; + memset(nodes, 0, nodes_numof * node_size); +} + +void l2scan_list_insert(l2scan_list_t *list, + list_node_t *nodes, unsigned nodes_numof, + size_t node_size, + const netopt_scan_result_t *result) +{ + scan_list_node_t *insert = _scan_list_get_insert(list, nodes, + nodes_numof, node_size); + *insert = (scan_list_node_t) { .node = { .next = NULL }, }; + memcpy(&insert->result, result, node_size - sizeof(list_node_t)); + const scan_list_node_t *next; + list_node_t *before = &list->head; + while ((next = (scan_list_node_t *)before->next) && + next->result.strength > result->strength) { + before = before->next; + } + list_add(before, &insert->node); +} + +unsigned l2scan_list_to_array(const l2scan_list_t *list, + void *nodes_array, unsigned nodes_numof, + size_t node_size) +{ + list_node_t *node = list->head.next; + uint8_t *buf = nodes_array; + size_t size = node_size - sizeof(*node); + unsigned i; + for (i = 0; i < nodes_numof && node; i++, buf += size, node = node->next) { + memcpy(buf, &node[1], size); + } + return i; +} diff --git a/sys/shell/Makefile.dep b/sys/shell/Makefile.dep index 0254da82b2..862ada7b93 100644 --- a/sys/shell/Makefile.dep +++ b/sys/shell/Makefile.dep @@ -208,6 +208,9 @@ endif ifneq (,$(filter shell_cmd_i2c_scan,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c endif +ifneq (,$(filter shell_cmd_iw,$(USEMODULE))) + USEMODULE += ztimer_sec +endif ifneq (,$(filter shell_cmd_lwip_netif,$(USEMODULE))) USEMODULE += lwip_netif endif diff --git a/sys/shell/cmds/Kconfig b/sys/shell/cmds/Kconfig index 794bca53d0..1a663693f6 100644 --- a/sys/shell/cmds/Kconfig +++ b/sys/shell/cmds/Kconfig @@ -176,6 +176,10 @@ config MODULE_SHELL_CMD_I2C_SCAN depends on MODULE_SHELL_CMDS depends on MODULE_PERIPH_I2C +config MODULE_SHELL_CMD_IW + bool "Command to interact with WiFi interfaces" + depends on MODULE_ZTIMER_SEC + config MODULE_SHELL_CMD_LWIP_NETIF bool "Command to manage lwIP network interfaces (ifconfig)" default y if MODULE_SHELL_CMDS_DEFAULT diff --git a/sys/shell/cmds/iw.c b/sys/shell/cmds/iw.c new file mode 100644 index 0000000000..c46312b03f --- /dev/null +++ b/sys/shell/cmds/iw.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_shell_commands + * @{ + * + * @file + * @brief Shell commands for interacting with Wi-Fi interfaces + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include + +#include "kernel_defines.h" +#include "mutex.h" +#include "net/wifi_scan_list.h" +#include "net/netdev/wifi.h" +#include "net/netopt.h" +#include "net/wifi.h" +#include "sched.h" +#include "shell.h" +#include "net/netif.h" +#include "net/netdev.h" +#include "fmt.h" +#include "string_utils.h" +#include "ztimer.h" + +#define SC_IW_AP_NUMOF 10 +#define SC_IW_AP_SCAN_TIMEOUT_SEC_MAX 30 +#define SC_IW_AP_CONNECT_TIMEOUT_SEC_MAX 60 + +static mutex_t _sync = MUTEX_INIT; + +static struct { + unsigned numof; + wifi_scan_result_t ap[SC_IW_AP_NUMOF]; +} _aps; + +static union { + wifi_security_wep_psk_t wep; + wifi_security_wpa_psk_t wpa; + wifi_security_wpa_enterprise_t eap; +} _cred; + +static const wifi_scan_result_t *_get_ap(const char *ssid) +{ + for (unsigned i = 0; i < _aps.numof; i++) { + if (!strcmp(_aps.ap[i].ssid, ssid)) { + return &_aps.ap[i]; + } + } + return NULL; +} + +static const char *_ssec(wifi_security_mode_t mode) +{ + switch (mode) { + case WIFI_SECURITY_MODE_OPEN: + return "open"; + case WIFI_SECURITY_MODE_WEP_PSK: + return "WEP"; + case WIFI_SECURITY_MODE_WPA2_PERSONAL: + return "WPA"; + case WIFI_SECURITY_MODE_WPA2_ENTERPRISE: + return "WPA2 Enterprise"; + default: + return "unknown"; + } +} + +static void _list_ap(void) +{ + puts(" SSID | SEC | RSSI | CHANNEL"); + puts("---------------------------------+-----------------+-------+--------"); + for (unsigned i = 0; i < _aps.numof; i++) { + printf(" %-31s | %-15s | %-5"PRId16" | %-6d \n", + _aps.ap[i].ssid, + _ssec(_aps.ap[i].sec_mode), + _aps.ap[i].base.strength, + _aps.ap[i].base.channel); + } +} + +static void _wifi_scan_cb(void *netif, const wifi_scan_list_t *result) +{ + (void)netif; + _aps.numof = wifi_scan_list_to_array(result, _aps.ap, ARRAY_SIZE(_aps.ap)); + _list_ap(); + mutex_unlock(&_sync); +} + +static void _wifi_connect_cb(void *netif, const wifi_connect_result_t *result) +{ + (void)netif; + if (result) { + printf("connected to %s\n", result->ssid); + } + mutex_unlock(&_sync); +} + +static void _wifi_disconnect_cb(void *netif, const wifi_disconnect_result_t *result) +{ + (void)netif; + if (result) { + printf("could not connect to %s\n", result->ssid); + } + mutex_unlock(&_sync); +} + +static int _iw_probe(netif_t *iface) +{ + long ret; + uint16_t val16; + if ((ret = netif_get_opt(iface, NETOPT_IS_WIRED, 0, &val16, sizeof(&val16))) < 0) { + if (ret != -ENOTSUP) { /* -ENOTSUP means wireless */ + return -EIO; + } + } + else { + return -ENOTSUP; /* wired */ + } + if ((ret = netif_get_opt(iface, NETOPT_DEVICE_TYPE, 0, &val16, sizeof(val16))) < 0) { + return -EIO; + } + if (val16 != NETDEV_TYPE_ETHERNET) { + return -ENOTSUP; + } + return 0; +} + +static int _iw_disconnect(netif_t *iface) +{ + long ret; + wifi_disconnect_request_t request = WIFI_DISCONNECT_REQUEST_INITIALIZER(NULL); + if ((ret = netif_set_opt(iface, NETOPT_DISCONNECT, 0, &request, sizeof(request))) < 0) { + return ret; + } + return 0; +} + +static int _iw_cmd_disconnect(netif_t *iface, int argc, char **argv) +{ + (void)argc; (void)argv; + return _iw_disconnect(iface); +} + +static int _iw_connect(netif_t *iface, wifi_connect_request_t *request) +{ + long ret; + /* this should not block! */ + mutex_lock(&_sync); + if ((ret = netif_set_opt(iface, NETOPT_CONNECT, 0, request, sizeof(*request))) < 0) { + mutex_unlock(&_sync); + return ret; + } + /* callback unlocks mutex */ + ztimer_mutex_lock_timeout(ZTIMER_SEC, &_sync, SC_IW_AP_CONNECT_TIMEOUT_SEC_MAX); + mutex_unlock(&_sync); + return 0; +} + +static int _iw_cmd_connect(netif_t *iface, int argc, char **argv) +{ + (void)iface; (void)argc; (void)argv; + if (argc < 1) { + return -EINVAL; + } + wifi_connect_request_t request = WIFI_CONNECT_REQUEST_INITIALIZER(0, _wifi_connect_cb, + _wifi_disconnect_cb, NULL); + if (strlen(argv[0]) > sizeof(request.ssid) - 1) { + printf("SSID too long\n"); + return -EINVAL; + } + strcpy(request.ssid, argv[0]); + argc--; + argv++; + const char *user = NULL; + const char *pwd = NULL; + const char *psk = NULL; + bool wep = false; + for (int i = 0; i < argc; i++) { + if (argv[i][0] != '-') { + return -EINVAL; + } + if (!strcmp("-u", argv[i])) { + if (++i >= argc) { + printf("-u requires an argument\n"); + return -EINVAL; + } + user = argv[i]; + } + else if (!strcmp("-p", argv[i])) { + if (++i >= argc) { + printf("-p requires an argument\n"); + return -EINVAL; + } + pwd = argv[i]; + } + else if (!strcmp("-k", argv[i])) { + if (++i >= argc) { + printf("-k requires an argument\n"); + return -EINVAL; + } + if (!strcmp("WEP", argv[i])) { + if (++i >= argc) { + printf("-k requires an argument\n"); + return -EINVAL; + } + wep = true; + } + psk = argv[i]; + } + else { + printf("unknown option %s\n", argv[i]); + return -EINVAL; + } + } + const wifi_scan_result_t *ap = _get_ap(request.ssid); + if (ap) { + if (ap->sec_mode == WIFI_SECURITY_MODE_WEP_PSK) { + if (!psk) { + printf("%s requires -k WEP\n", request.ssid); + return -EPERM; + } + } + else if (ap->sec_mode == WIFI_SECURITY_MODE_WPA2_PERSONAL) { + if (!psk) { + printf("%s requires -k\n", request.ssid); + return -EPERM; + } + } + else if (ap->sec_mode == WIFI_SECURITY_MODE_WPA2_ENTERPRISE) { + if (!user || !pwd) { + printf("%s requires -u and -p\n", request.ssid); + return -EPERM; + } + } + request.base.channel = ap->base.channel; + } + if (psk) { + if (strlen(psk) > sizeof(_cred.wpa.psk) - 1) { + printf("Key too long\n"); + return -EINVAL; + } + /* also copies to WEP key */ + strcpy(_cred.wpa.psk, psk); + if (wep) { + _cred.wep.sec = WIFI_SECURITY_MODE_WEP_PSK; + request.cred = &_cred.wep.sec; + } + else { + _cred.wpa.sec = WIFI_SECURITY_MODE_WPA2_PERSONAL; + request.cred = &_cred.wpa.sec; + } + } + if (user) { + if (strlen(user) > sizeof(_cred.eap.user) - 1) { + printf("username too long\n"); + return -EINVAL; + } + strcpy(_cred.eap.user, user); + _cred.eap.sec = WIFI_SECURITY_MODE_WPA2_ENTERPRISE; + request.cred = &_cred.eap.sec; + explicit_bzero(_cred.eap.pwd, sizeof(_cred.eap.pwd)); + } + if (pwd) { + if (strlen(pwd) > sizeof(_cred.eap.pwd) - 1) { + printf("password too long\n"); + return -EINVAL; + } + strcpy(_cred.eap.pwd, pwd); + } + return _iw_connect(iface, &request); +} + +static int _iw_scan(netif_t *iface, wifi_scan_request_t *request) +{ + int ret; + /* this should not block! */ + mutex_lock(&_sync); + if ((ret = netif_set_opt(iface, NETOPT_SCAN, 0, request, sizeof(*request))) < 0) { + mutex_unlock(&_sync); + return ret; + } + /* callback unlocks mutex */ + ztimer_mutex_lock_timeout(ZTIMER_SEC, &_sync, SC_IW_AP_SCAN_TIMEOUT_SEC_MAX); + mutex_unlock(&_sync); + return 0; +} + +static int _iw_cmd_scan(netif_t *iface, int argc, char **argv) +{ + long ret; + netopt_channel_t ch = NETOPT_SCAN_REQ_ALL_CH; + uint32_t timeout_ms = 0; + for (int i = 0; i < argc; i++) { + if (argv[i][0] != '-') { + return -EINVAL; + } + if (!strcmp("-c", argv[i])) { + if (++i >= argc) { + printf("-c requires an argument\n"); + return -EINVAL; + } + if (!fmt_is_number(argv[i]) || + (ret = strtol(argv[i], NULL, 10)) < 0) { + printf("-c argument %s is not a number\n", argv[i]); + return -EINVAL; + } + ch = (netopt_channel_t)ret; + } + else if (!strcmp("-t", argv[i])) { + if (++i >= argc) { + printf("-t requires an argument\n"); + return -EINVAL; + } + if (!fmt_is_number(argv[i]) || + (ret = strtol(argv[i], NULL, 10)) < 0) { + printf("-t argument %s is not a number\n", argv[i]); + return -EINVAL; + } + timeout_ms = (uint32_t)ret; + } + else { + printf("unknown option %s\n", argv[i]); + return -EINVAL; + } + } + wifi_scan_request_t request = WIFI_SCAN_REQUEST_INITIALIZER(ch, _wifi_scan_cb, timeout_ms); + return _iw_scan(iface, &request); +} + +static void _iw_usage(const char *cmd) +{ + printf("usage: %s \n", cmd); + printf("commands:\n" + " scan [-c ] [-t ]\n" + " connect [-u -p ] | -k [WEP] ]\n" + " disconnect\n" + ); +} + +static void _iw_error(const char *cmd, int error) +{ + printf("%s: error (%d) %s\n", cmd, error, strerror(error)); +} + +int _iw_cmd(int argc, char **argv) +{ + if (argc < 3) { + goto exit_help; + } + int ret = -EINVAL; + netif_t *iface = netif_get_by_name(argv[1]); + if (!iface) { + printf("%s: invalid interface given\n", argv[0]); + goto exit_failure; + } + if ((ret = _iw_probe(iface)) != 0) { + if (ret == -ENOTSUP) { + printf("%s: interface is not Wi-Fi\n", argv[0]); + goto exit_failure; + } + else { + printf("%s: interface communication error\n", argv[0]); + goto exit_failure; + } + } + + if (!strcmp(argv[2], "scan")) { + if ((ret = _iw_cmd_scan(iface, argc - 3, argv + 3)) != 0) { + goto exit_failure; + } + } + else if (!strcmp(argv[2], "connect")) { + if ((ret = _iw_cmd_connect(iface, argc - 3, argv + 3)) != 0) { + goto exit_failure; + } + } + else if (!strcmp(argv[2], "disconnect")) { + if ((ret = _iw_cmd_disconnect(iface, argc - 3, argv + 3)) != 0) { + goto exit_failure; + } + } + else { + goto exit_help; + } + + printf("%s: ok\n", argv[0]); + return EXIT_SUCCESS; + +exit_help: + _iw_usage(argv[0]); + return EXIT_FAILURE; + +exit_failure: + _iw_error(argv[0], ret); + return EXIT_FAILURE; + +} + +SHELL_COMMAND(iw, "Control Wi-Fi interfaces", _iw_cmd); diff --git a/tests/net/l2scan_list/Makefile b/tests/net/l2scan_list/Makefile new file mode 100644 index 0000000000..c5a959c8db --- /dev/null +++ b/tests/net/l2scan_list/Makefile @@ -0,0 +1,6 @@ +include ../Makefile.net_common + +USEMODULE += embunit +USEMODULE += l2scan_list + +include $(RIOTBASE)/Makefile.include diff --git a/tests/net/l2scan_list/main.c b/tests/net/l2scan_list/main.c new file mode 100644 index 0000000000..f94784ab75 --- /dev/null +++ b/tests/net/l2scan_list/main.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + + /** + * @ingroup tests + * @{ + * + * @file + * @brief Unit tests for l2scan list + * + * @author Fabian Hüßler + */ + +#include +#include + +#include "embUnit.h" +#include "container.h" +#include "net/netopt.h" +#include "net/l2scan_list.h" + +typedef struct my_scan_result_node { + list_node_t node; + netopt_scan_result_t result; +} my_scan_result_node_t; + +static const netopt_scan_result_t _test_results[] = { + [0] = { + .channel = 11, + .strength = -40, + }, + [1] = { + .channel = 11, + .strength = -52, + }, + [2] = { + .channel = 6, + .strength = -53, + }, + [3] = { + .channel = 6, + .strength = -54, + }, + [4] = { + .channel = 6, + .strength = -55, + }, + [5] = { + .channel = 1, + .strength = -60, + }, + [6] = { + .channel = 1, + .strength = -70, + }, + [7] = { + .channel = 1, + .strength = -80, + } +}; + +struct { + my_scan_result_node_t *head; + my_scan_result_node_t result[ARRAY_SIZE(_test_results)]; +} _scan_list; + +static void test_l2scan_list_empty(void) +{ + l2scan_list_empty((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t)); + TEST_ASSERT(!_scan_list.head); +} + +static void test_l2scan_list_not_empty(void) +{ + l2scan_list_empty((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t)); + TEST_ASSERT(!_scan_list.head); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[0]); + TEST_ASSERT(_scan_list.head); +} + +static void test_l2scan_list_insert(void) +{ + l2scan_list_empty((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t)); + TEST_ASSERT(!_scan_list.head); + /* random order insert */ + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[6]); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[0]); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[3]); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[5]); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[1]); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[2]); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[7]); + l2scan_list_insert((l2scan_list_t *)&_scan_list, (list_node_t *)_scan_list.result, + ARRAY_SIZE(_scan_list.result), sizeof(my_scan_result_node_t), + &_test_results[4]); + /* now expect it to be sorted by strength as it is in the test data */ + my_scan_result_node_t *node = _scan_list.head; + for (unsigned i = 0; i < ARRAY_SIZE(_test_results); i++) { + TEST_ASSERT(node); + TEST_ASSERT(node->result.channel == _test_results[i].channel); + TEST_ASSERT(node->result.strength == _test_results[i].strength); + node = (my_scan_result_node_t *)node->node.next; + } +} + +static void _setup(void) +{ + memset(&_scan_list, 0, sizeof(_scan_list)); +} + +Test* test_l2scan_list(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_l2scan_list_empty), + new_TestFixture(test_l2scan_list_not_empty), + new_TestFixture(test_l2scan_list_insert), + }; + + EMB_UNIT_TESTCALLER(tests_l2scan_list, _setup, NULL, fixtures); + return (Test *)&tests_l2scan_list; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(test_l2scan_list()); + TESTS_END(); +}