blob: 2abe831be2879af8ce225f03e3e79d8f213d3aec [file] [log] [blame]
/*
* Copyright (c) 2018-2019 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";
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;
uint64_t value;
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;
uint64_t value;
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_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);