/* * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ /** @file * @brief Utility functions to be used by the Wi-Fi subsystem. */ #include LOG_MODULE_REGISTER(net_wifi_utils, CONFIG_NET_L2_WIFI_MGMT_LOG_LEVEL); #include #include #include #include #include #include #include #include #include /* Ensure 'strtok_r' is available even with -std=c99. */ char *strtok_r(char *str, const char *delim, char **saveptr); static const uint8_t valid_5g_chans_20mhz[] = {32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177}; static enum wifi_frequency_bands wifi_utils_map_band_str_to_idx(char *band_str) { enum wifi_frequency_bands band = WIFI_FREQ_BAND_UNKNOWN; if (!strcmp(band_str, "2")) { band = WIFI_FREQ_BAND_2_4_GHZ; } else if (!strcmp(band_str, "5")) { band = WIFI_FREQ_BAND_5_GHZ; } else if (!strcmp(band_str, "6")) { band = WIFI_FREQ_BAND_6_GHZ; } return band; } bool wifi_utils_validate_chan_2g(uint16_t chan) { if ((chan >= 1) && (chan <= 14)) { return true; } return false; } bool wifi_utils_validate_chan_5g(uint16_t chan) { uint16_t i; for (i = 0; i < ARRAY_SIZE(valid_5g_chans_20mhz); i++) { if (chan == valid_5g_chans_20mhz[i]) { return true; } } return false; } bool wifi_utils_validate_chan_6g(uint16_t chan) { if (((chan >= 1) && (chan <= 233) && (!((chan - 1) % 4))) || (chan == 2)) { return true; } return false; } bool wifi_utils_validate_chan(uint8_t band, uint16_t chan) { bool result = false; switch (band) { case WIFI_FREQ_BAND_2_4_GHZ: result = wifi_utils_validate_chan_2g(chan); break; case WIFI_FREQ_BAND_5_GHZ: result = wifi_utils_validate_chan_5g(chan); break; case WIFI_FREQ_BAND_6_GHZ: result = wifi_utils_validate_chan_6g(chan); break; default: NET_ERR("Unknown band: %d", band); break; } return result; } /** * @brief Get the next Wi-Fi 6GHz channel based on the given (valid) channel. * The function handles the initial edge cases (1 -> 2, 2 -> 5) and then increments by 4. * * @param chan Current channel number. * @return Next valid channel number. */ uint16_t wifi_utils_get_next_chan_6g(uint16_t chan) { if (chan == 1) { return 2; } else if (chan == 2) { return 5; } else { return chan + 4; } } static int wifi_utils_get_all_chans_in_range(uint8_t chan_start, uint8_t chan_end, struct wifi_band_channel *band_chan, uint8_t band_idx, uint8_t *chan_idx) { uint8_t i; bool start = false; bool end = false; uint8_t idx; if (!wifi_utils_validate_chan(band_idx, chan_start)) { NET_ERR("Invalid channel value %d in band %d", chan_start, band_idx); return -EINVAL; } if (!wifi_utils_validate_chan(band_idx, chan_end)) { NET_ERR("Invalid channel value %d in band %d", chan_end, band_idx); return -EINVAL; } if (chan_end < chan_start) { NET_ERR("Channel range end (%d) cannot be less than start (%d)", chan_end, chan_start); return -EINVAL; } switch (band_idx) { case WIFI_FREQ_BAND_2_4_GHZ: idx = *chan_idx; for (i = chan_start+1; i <= chan_end; i++) { band_chan[idx].band = band_idx; band_chan[idx].channel = i; idx++; } *chan_idx = idx; break; case WIFI_FREQ_BAND_5_GHZ: idx = *chan_idx; for (i = 0; i < ARRAY_SIZE(valid_5g_chans_20mhz); i++) { if (valid_5g_chans_20mhz[i] == chan_start) { start = true; continue; } if (valid_5g_chans_20mhz[i] == chan_end) { end = true; } if (start) { band_chan[idx].band = band_idx; band_chan[idx].channel = valid_5g_chans_20mhz[i]; idx++; } if (end) { *chan_idx = idx; break; } } break; case WIFI_FREQ_BAND_6_GHZ: idx = *chan_idx; i = wifi_utils_get_next_chan_6g(chan_start); while (i <= chan_end) { band_chan[idx].band = band_idx; band_chan[idx].channel = i; idx++; i = wifi_utils_get_next_chan_6g(i); } *chan_idx = idx; break; default: NET_ERR("Unknown band value: %d", band_idx); return -EINVAL; } return 0; } static int wifi_utils_validate_chan_str(char *chan_str) { uint8_t i; if ((!chan_str) || (!strlen(chan_str))) { NET_ERR("Null or empty channel string\n"); return -EINVAL; } for (i = 0; i < strlen(chan_str); i++) { if (!isdigit((int)chan_str[i])) { NET_ERR("Invalid character in channel string %c\n", chan_str[i]); return -EINVAL; } } return 0; } int wifi_utils_parse_scan_bands(char *scan_bands_str, uint8_t *band_map) { char parse_str[WIFI_MGMT_BAND_STR_SIZE_MAX + 1]; char *band_str = NULL; char *ctx = NULL; enum wifi_frequency_bands band = WIFI_FREQ_BAND_UNKNOWN; int len; if (!scan_bands_str) { return -EINVAL; } len = strlen(scan_bands_str); if (len > WIFI_MGMT_BAND_STR_SIZE_MAX) { NET_ERR("Band string (%s) size (%d) exceeds maximum allowed value (%d)", scan_bands_str, len, WIFI_MGMT_BAND_STR_SIZE_MAX); return -EINVAL; } strncpy(parse_str, scan_bands_str, sizeof(parse_str) - 1); parse_str[sizeof(parse_str) - 1] = '\0'; band_str = strtok_r(parse_str, ",", &ctx); while (band_str) { band = wifi_utils_map_band_str_to_idx(band_str); if (band == WIFI_FREQ_BAND_UNKNOWN) { NET_ERR("Invalid band value: %s", band_str); return -EINVAL; } *band_map |= (1 << band); band_str = strtok_r(NULL, ",", &ctx); } return 0; } int wifi_utils_parse_scan_ssids(char *scan_ssids_str, const char *ssids[], uint8_t num_ssids) { int len; if (!scan_ssids_str) { return -EINVAL; } len = strlen(scan_ssids_str); if (len > WIFI_SSID_MAX_LEN) { NET_ERR("SSID string (%s) size (%d) exceeds maximum allowed value (%d)", scan_ssids_str, len, WIFI_SSID_MAX_LEN); return -EINVAL; } for (int i = 0; i < num_ssids; i++) { if (ssids[i] != NULL) { continue; } ssids[i] = scan_ssids_str; return 0; } NET_WARN("Exceeded maximum allowed SSIDs (%d)", num_ssids); return 0; } int wifi_utils_parse_scan_chan(char *scan_chan_str, struct wifi_band_channel *band_chan, uint8_t max_channels) { char band_str[WIFI_UTILS_MAX_BAND_STR_LEN] = {0}; char chan_str[WIFI_UTILS_MAX_CHAN_STR_LEN] = {0}; enum wifi_frequency_bands band = WIFI_FREQ_BAND_UNKNOWN; uint16_t band_str_start_idx = 0; uint16_t chan_str_start_idx = 0; uint8_t chan_idx = 0; uint8_t chan_start = 0; uint8_t chan_val = 0; uint16_t i = 0; bool valid_band = false; bool valid_chan = false; while (scan_chan_str[i] != '\0') { if (scan_chan_str[i] != ':') { i++; continue; } if (((i - band_str_start_idx) <= 0) || ((i - band_str_start_idx) > WIFI_UTILS_MAX_BAND_STR_LEN)) { NET_ERR("Invalid band value %s", &scan_chan_str[band_str_start_idx]); return -EINVAL; } strncpy(band_str, &scan_chan_str[band_str_start_idx], (i - band_str_start_idx)); band = wifi_utils_map_band_str_to_idx(band_str); if (band == WIFI_FREQ_BAND_UNKNOWN) { NET_ERR("Unsupported band value: %s", band_str); return -EINVAL; } i++; chan_str_start_idx = i; valid_band = true; while (1) { if ((scan_chan_str[i] != ',') && (scan_chan_str[i] != '_') && (scan_chan_str[i] != '-') && (scan_chan_str[i] != '\0')) { i++; continue; } if ((i - chan_str_start_idx) > WIFI_UTILS_MAX_CHAN_STR_LEN) { NET_ERR("Invalid chan value %s", &scan_chan_str[chan_str_start_idx]); return -EINVAL; } strncpy(chan_str, &scan_chan_str[chan_str_start_idx], (i - chan_str_start_idx)); if (wifi_utils_validate_chan_str(chan_str)) { NET_ERR("Channel string validation failed"); return -EINVAL; } chan_val = atoi(chan_str); memset(chan_str, 0, sizeof(chan_str)); if (chan_start) { if (wifi_utils_get_all_chans_in_range(chan_start, chan_val, band_chan, band, &chan_idx)) { NET_ERR("Channel range invalid"); return -EINVAL; } if (chan_idx > max_channels) { NET_ERR("Too many channels specified (%d)", max_channels); return -EINVAL; } chan_start = 0; } else { if (!wifi_utils_validate_chan(band, chan_val)) { NET_ERR("Invalid channel %d", chan_val); return -EINVAL; } if (chan_idx == max_channels) { NET_ERR("Too many channels specified (%d)", max_channels); return -EINVAL; } band_chan[chan_idx].band = band; band_chan[chan_idx].channel = chan_val; chan_idx++; } valid_chan = true; if (scan_chan_str[i] == '_') { band_str_start_idx = ++i; break; } else if (scan_chan_str[i] == ',') { chan_str_start_idx = ++i; } else if (scan_chan_str[i] == '-') { chan_start = chan_val; chan_str_start_idx = ++i; } else if (scan_chan_str[i] == '\0') { break; } } } if (!valid_band) { NET_ERR("No valid band found"); return -EINVAL; } if (!valid_chan) { NET_ERR("No valid channel found"); return -EINVAL; } return 0; }