/* * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all * copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "qdf_mem.h" #include "qdf_module.h" #include "qdf_status.h" #include "qdf_str.h" #include "qdf_trace.h" #include "qdf_types.h" const char *qdf_opmode_str(const enum QDF_OPMODE opmode) { switch (opmode) { case QDF_STA_MODE: return "STA"; case QDF_SAP_MODE: return "SAP"; case QDF_P2P_CLIENT_MODE: return "P2P Client"; case QDF_P2P_GO_MODE: return "P2P GO"; case QDF_FTM_MODE: return "FTM"; case QDF_IBSS_MODE: return "IBSS"; case QDF_MONITOR_MODE: return "Monitor"; case QDF_P2P_DEVICE_MODE: return "P2P Device"; case QDF_OCB_MODE: return "OCB"; case QDF_EPPING_MODE: return "EPPing"; case QDF_QVIT_MODE: return "QVIT"; case QDF_NDI_MODE: return "NDI"; case QDF_WDS_MODE: return "WDS"; case QDF_BTAMP_MODE: return "BTAMP"; case QDF_AHDEMO_MODE: return "AHDEMO"; case QDF_TDLS_MODE: return "TDLS"; case QDF_NAN_DISC_MODE: return "NAN"; default: return "Invalid operating mode"; } } static QDF_STATUS qdf_consume_char(const char **str, char c) { if ((*str)[0] != c) return QDF_STATUS_E_FAILURE; (*str)++; return QDF_STATUS_SUCCESS; } static QDF_STATUS qdf_consume_dec(const char **str, uint8_t *out_digit) { uint8_t c = (*str)[0]; if (c >= '0' && c <= '9') *out_digit = c - '0'; else return QDF_STATUS_E_FAILURE; (*str)++; return QDF_STATUS_SUCCESS; } static QDF_STATUS qdf_consume_hex(const char **str, uint8_t *out_nibble) { uint8_t c = (*str)[0]; if (c >= '0' && c <= '9') *out_nibble = c - '0'; else if (c >= 'a' && c <= 'f') *out_nibble = c - 'a' + 10; else if (c >= 'A' && c <= 'F') *out_nibble = c - 'A' + 10; else return QDF_STATUS_E_FAILURE; (*str)++; return QDF_STATUS_SUCCESS; } static QDF_STATUS qdf_consume_octet_dec(const char **str, uint8_t *out_octet) { uint8_t len = 0; uint16_t octet = 0; int i; /* consume up to 3 decimal digits */ for (i = 0; i < 3; i++) { uint8_t digit; if (QDF_IS_STATUS_ERROR(qdf_consume_dec(str, &digit))) break; len++; octet = octet * 10 + digit; } /* require at least 1 digit */ if (!len) return QDF_STATUS_E_FAILURE; if (octet > 255) { (*str) -= len; return QDF_STATUS_E_FAILURE; } *out_octet = octet; return QDF_STATUS_SUCCESS; } static QDF_STATUS qdf_consume_hex_pair(const char **str, uint8_t *out_byte) { QDF_STATUS status; uint8_t hi, low; status = qdf_consume_hex(str, &hi); if (QDF_IS_STATUS_ERROR(status)) return status; status = qdf_consume_hex(str, &low); if (QDF_IS_STATUS_ERROR(status)) { (*str)--; return status; } *out_byte = hi << 4 | low; return QDF_STATUS_SUCCESS; } static QDF_STATUS qdf_consume_hextet(const char **str, uint16_t *out_hextet) { uint8_t len = 0; uint16_t hextet = 0; int i; /* consume up to 4 hex digits */ for (i = 0; i < 4; i++) { uint8_t digit; if (QDF_IS_STATUS_ERROR(qdf_consume_hex(str, &digit))) break; len++; hextet = (hextet << 4) + digit; } /* require at least 1 digit */ if (!len) return QDF_STATUS_E_FAILURE; /* no need to check for overflow */ *out_hextet = hextet; return QDF_STATUS_SUCCESS; } static QDF_STATUS qdf_consume_radix(const char **str, uint8_t *out_radix) { if ((*str)[0] == '0') { switch ((*str)[1]) { case 'b': *out_radix = 2; *str += 2; break; case 'o': *out_radix = 8; *str += 2; break; case 'x': *out_radix = 16; *str += 2; break; default: *out_radix = 10; break; } return QDF_STATUS_SUCCESS; } if (*str[0] >= '0' && *str[0] <= '9') { *out_radix = 10; return QDF_STATUS_SUCCESS; } return QDF_STATUS_E_FAILURE; } static QDF_STATUS __qdf_int_parse_lazy(const char **int_str, uint64_t *out_int, bool *out_negate) { QDF_STATUS status; bool negate = false; uint8_t radix; uint8_t digit; uint64_t value = 0; uint64_t next_value; const char *str = *int_str; str = qdf_str_left_trim(str); status = qdf_consume_char(&str, '-'); if (QDF_IS_STATUS_SUCCESS(status)) negate = true; else qdf_consume_char(&str, '+'); status = qdf_consume_radix(&str, &radix); if (QDF_IS_STATUS_ERROR(status)) return status; while (QDF_IS_STATUS_SUCCESS(qdf_consume_hex(&str, &digit))) { if (digit >= radix) return QDF_STATUS_E_FAILURE; next_value = value * radix + digit; if (next_value < value) return QDF_STATUS_E_RANGE; value = next_value; } *int_str = str; *out_negate = negate; *out_int = value; return QDF_STATUS_SUCCESS; } static QDF_STATUS qdf_int_parse(const char *int_str, uint64_t *out_int, bool *out_negate) { QDF_STATUS status; bool negate; uint64_t value; QDF_BUG(int_str); if (!int_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_int); if (!out_int) return QDF_STATUS_E_INVAL; status = __qdf_int_parse_lazy(&int_str, &value, &negate); if (QDF_IS_STATUS_ERROR(status)) return status; int_str = qdf_str_left_trim(int_str); if (int_str[0] != '\0') return QDF_STATUS_E_FAILURE; *out_negate = negate; *out_int = value; return QDF_STATUS_SUCCESS; } QDF_STATUS qdf_int32_parse(const char *int_str, int32_t *out_int) { QDF_STATUS status; int64_t value; status = qdf_int64_parse(int_str, &value); if (QDF_IS_STATUS_ERROR(status)) return status; if ((int32_t)value != value) return QDF_STATUS_E_RANGE; *out_int = value; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_int32_parse); QDF_STATUS qdf_uint32_parse(const char *int_str, uint32_t *out_int) { QDF_STATUS status; uint64_t value; status = qdf_uint64_parse(int_str, &value); if (QDF_IS_STATUS_ERROR(status)) return status; if ((uint32_t)value != value) return QDF_STATUS_E_RANGE; *out_int = value; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_uint32_parse); QDF_STATUS qdf_int64_parse(const char *int_str, int64_t *out_int) { QDF_STATUS status; bool negate = false; uint64_t value = 0; int64_t signed_value; status = qdf_int_parse(int_str, &value, &negate); if (QDF_IS_STATUS_ERROR(status)) return status; if (negate) { signed_value = -value; if (signed_value > 0) return QDF_STATUS_E_RANGE; } else { signed_value = value; if (signed_value < 0) return QDF_STATUS_E_RANGE; } *out_int = signed_value; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_int64_parse); QDF_STATUS qdf_uint64_parse(const char *int_str, uint64_t *out_int) { QDF_STATUS status; bool negate = false; uint64_t value = 0; status = qdf_int_parse(int_str, &value, &negate); if (QDF_IS_STATUS_ERROR(status)) return status; if (negate) return QDF_STATUS_E_RANGE; *out_int = value; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_uint64_parse); QDF_STATUS qdf_bool_parse(const char *bool_str, bool *out_bool) { bool value; QDF_BUG(bool_str); if (!bool_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_bool); if (!out_bool) return QDF_STATUS_E_INVAL; bool_str = qdf_str_left_trim(bool_str); switch (bool_str[0]) { case '1': case 'y': case 'Y': value = true; break; case '0': case 'n': case 'N': value = false; break; default: return QDF_STATUS_E_FAILURE; } bool_str++; bool_str = qdf_str_left_trim(bool_str); if (bool_str[0] != '\0') return QDF_STATUS_E_FAILURE; *out_bool = value; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_bool_parse); QDF_STATUS qdf_mac_parse(const char *mac_str, struct qdf_mac_addr *out_addr) { QDF_STATUS status; struct qdf_mac_addr addr; bool colons; int i; QDF_BUG(mac_str); if (!mac_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_addr); if (!out_addr) return QDF_STATUS_E_INVAL; mac_str = qdf_str_left_trim(mac_str); /* parse leading hex pair */ status = qdf_consume_hex_pair(&mac_str, &addr.bytes[0]); if (QDF_IS_STATUS_ERROR(status)) return status; /* dynamically detect colons */ colons = mac_str[0] == ':'; for (i = 1; i < QDF_MAC_ADDR_SIZE; i++) { /* ensure colon separator if previously detected */ if (colons) { status = qdf_consume_char(&mac_str, ':'); if (QDF_IS_STATUS_ERROR(status)) return status; } /* parse next hex pair */ status = qdf_consume_hex_pair(&mac_str, &addr.bytes[i]); if (QDF_IS_STATUS_ERROR(status)) return status; } mac_str = qdf_str_left_trim(mac_str); if (mac_str[0] != '\0') return QDF_STATUS_E_FAILURE; *out_addr = addr; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_mac_parse); QDF_STATUS qdf_ipv4_parse(const char *ipv4_str, struct qdf_ipv4_addr *out_addr) { QDF_STATUS status; struct qdf_ipv4_addr addr; int i; QDF_BUG(ipv4_str); if (!ipv4_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_addr); if (!out_addr) return QDF_STATUS_E_INVAL; ipv4_str = qdf_str_left_trim(ipv4_str); /* parse leading octet */ status = qdf_consume_octet_dec(&ipv4_str, &addr.bytes[0]); if (QDF_IS_STATUS_ERROR(status)) return status; for (i = 1; i < QDF_IPV4_ADDR_SIZE; i++) { /* ensure dot separator */ status = qdf_consume_char(&ipv4_str, '.'); if (QDF_IS_STATUS_ERROR(status)) return status; /* parse next octet */ status = qdf_consume_octet_dec(&ipv4_str, &addr.bytes[i]); if (QDF_IS_STATUS_ERROR(status)) return status; } ipv4_str = qdf_str_left_trim(ipv4_str); if (ipv4_str[0] != '\0') return QDF_STATUS_E_FAILURE; *out_addr = addr; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_ipv4_parse); static inline void qdf_ipv6_apply_zero_comp(struct qdf_ipv6_addr *addr, uint8_t hextets, uint8_t zero_comp_index) { /* Given the following hypothetical ipv6 address: * |---------------------------------------| * | 01 | ab | cd | ef | | | | | * |---------------------------------------| * ^--- zero_comp_index (2) * from -----^ * to ---------------------------^ * | hextets (4) | * | zero comp size | * | to move | * * We need to apply the zero compression such that we get: * |---------------------------------------| * | 01 | ab | 00 | 00 | 00 | 00 | cd | ef | * |---------------------------------------| * | zero comp | * | moved | */ size_t zero_comp_size = (QDF_IPV6_ADDR_HEXTET_COUNT - hextets) * 2; size_t bytes_to_move = (hextets - zero_comp_index) * 2; uint8_t *from = &addr->bytes[zero_comp_index * 2]; uint8_t *to = from + zero_comp_size; if (bytes_to_move) qdf_mem_move(to, from, bytes_to_move); qdf_mem_zero(from, to - from); } QDF_STATUS qdf_ipv6_parse(const char *ipv6_str, struct qdf_ipv6_addr *out_addr) { QDF_STATUS status; struct qdf_ipv6_addr addr; int8_t zero_comp = -1; uint8_t hextets_found = 0; QDF_BUG(ipv6_str); if (!ipv6_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_addr); if (!out_addr) return QDF_STATUS_E_INVAL; ipv6_str = qdf_str_left_trim(ipv6_str); /* check for leading zero-compression ("::") */ status = qdf_consume_char(&ipv6_str, ':'); if (QDF_IS_STATUS_SUCCESS(status)) { status = qdf_consume_char(&ipv6_str, ':'); if (QDF_IS_STATUS_SUCCESS(status)) zero_comp = 0; else return QDF_STATUS_E_FAILURE; } while (hextets_found < QDF_IPV6_ADDR_HEXTET_COUNT) { uint16_t hextet; /* parse hextet */ status = qdf_consume_hextet(&ipv6_str, &hextet); if (QDF_IS_STATUS_ERROR(status)) { /* we must end with hextet or zero compression */ if (hextets_found != zero_comp) return QDF_STATUS_E_FAILURE; break; } addr.bytes[hextets_found * 2] = hextet >> 8; addr.bytes[hextets_found * 2 + 1] = hextet; hextets_found++; /* parse ':' char */ status = qdf_consume_char(&ipv6_str, ':'); if (QDF_IS_STATUS_ERROR(status)) break; /* check for zero compression ("::") */ status = qdf_consume_char(&ipv6_str, ':'); if (QDF_IS_STATUS_SUCCESS(status)) { /* only one zero compression is allowed */ if (zero_comp >= 0) return QDF_STATUS_E_FAILURE; zero_comp = hextets_found; } } ipv6_str = qdf_str_left_trim(ipv6_str); if (ipv6_str[0] != '\0') return QDF_STATUS_E_FAILURE; /* we must have max hextets or a zero compression, but not both */ if (hextets_found < QDF_IPV6_ADDR_HEXTET_COUNT) { if (zero_comp < 0) return QDF_STATUS_E_FAILURE; qdf_ipv6_apply_zero_comp(&addr, hextets_found, zero_comp); } else if (zero_comp > -1) { return QDF_STATUS_E_FAILURE; } *out_addr = addr; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_ipv6_parse); QDF_STATUS qdf_uint32_array_parse(const char *in_str, uint32_t *out_array, qdf_size_t array_size, qdf_size_t *out_size) { QDF_STATUS status; bool negate; qdf_size_t size = 0; uint64_t value; QDF_BUG(in_str); if (!in_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_array); if (!out_array) return QDF_STATUS_E_INVAL; QDF_BUG(out_size); if (!out_size) return QDF_STATUS_E_INVAL; while (size < array_size) { status = __qdf_int_parse_lazy(&in_str, &value, &negate); if (QDF_IS_STATUS_ERROR(status)) return status; if ((uint32_t)value != value || negate) return QDF_STATUS_E_RANGE; in_str = qdf_str_left_trim(in_str); switch (in_str[0]) { case ',': out_array[size++] = value; in_str++; break; case '\0': out_array[size++] = value; *out_size = size; return QDF_STATUS_SUCCESS; default: return QDF_STATUS_E_FAILURE; } } return QDF_STATUS_E_FAILURE; } qdf_export_symbol(qdf_uint32_array_parse); QDF_STATUS qdf_uint16_array_parse(const char *in_str, uint16_t *out_array, qdf_size_t array_size, qdf_size_t *out_size) { QDF_STATUS status; bool negate; qdf_size_t size = 0; uint64_t value; QDF_BUG(in_str); if (!in_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_array); if (!out_array) return QDF_STATUS_E_INVAL; QDF_BUG(out_size); if (!out_size) return QDF_STATUS_E_INVAL; while (size < array_size) { status = __qdf_int_parse_lazy(&in_str, &value, &negate); if (QDF_IS_STATUS_ERROR(status)) return status; if ((uint16_t)value != value || negate) return QDF_STATUS_E_RANGE; in_str = qdf_str_left_trim(in_str); switch (in_str[0]) { case ',': out_array[size++] = value; in_str++; break; case '\0': out_array[size++] = value; *out_size = size; return QDF_STATUS_SUCCESS; default: return QDF_STATUS_E_FAILURE; } } return QDF_STATUS_E_FAILURE; } qdf_export_symbol(qdf_uint16_array_parse); QDF_STATUS qdf_uint8_array_parse(const char *in_str, uint8_t *out_array, qdf_size_t array_size, qdf_size_t *out_size) { QDF_STATUS status; bool negate; qdf_size_t size = 0; uint64_t value; QDF_BUG(in_str); if (!in_str) return QDF_STATUS_E_INVAL; QDF_BUG(out_array); if (!out_array) return QDF_STATUS_E_INVAL; QDF_BUG(out_size); if (!out_size) return QDF_STATUS_E_INVAL; while (size < array_size) { status = __qdf_int_parse_lazy(&in_str, &value, &negate); if (QDF_IS_STATUS_ERROR(status)) return status; if ((uint8_t)value != value || negate) return QDF_STATUS_E_RANGE; in_str = qdf_str_left_trim(in_str); switch (in_str[0]) { case ',': out_array[size++] = value; in_str++; break; case '\0': out_array[size++] = value; *out_size = size; return QDF_STATUS_SUCCESS; default: return QDF_STATUS_E_FAILURE; } } return QDF_STATUS_E_FAILURE; } qdf_export_symbol(qdf_uint8_array_parse);