/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include "console/console.h" #include "host/ble_hs.h" #include "host/ble_uuid.h" #include "host/ble_eddystone.h" #include "cmd.h" #include "btshell.h" #define CMD_MAX_ARGS 16 static char *cmd_args[CMD_MAX_ARGS][2]; static int cmd_num_args; int parse_arg_find_idx(const char *key) { int i; for (i = 0; i < cmd_num_args; i++) { if (strcmp(cmd_args[i][0], key) == 0) { return i; } } return -1; } char * parse_arg_peek(const char *key) { int i; for (i = 0; i < cmd_num_args; i++) { if (strcmp(cmd_args[i][0], key) == 0) { return cmd_args[i][1]; } } return NULL; } char * parse_arg_extract(const char *key) { int i; for (i = 0; i < cmd_num_args; i++) { if (strcmp(cmd_args[i][0], key) == 0) { /* Erase parameter. */ cmd_args[i][0][0] = '\0'; return cmd_args[i][1]; } } return NULL; } /** * Determines which number base to use when parsing the specified numeric * string. This just avoids base '0' so that numbers don't get interpreted as * octal. */ static int parse_arg_long_base(char *sval) { if (sval[0] == '0' && sval[1] == 'x') { return 0; } else { return 10; } } long parse_long_bounds(char *sval, long min, long max, int *out_status) { char *endptr; long lval; lval = strtol(sval, &endptr, parse_arg_long_base(sval)); if (sval[0] != '\0' && *endptr == '\0' && lval >= min && lval <= max) { *out_status = 0; return lval; } *out_status = EINVAL; return 0; } long parse_arg_long_bounds_peek(char *name, long min, long max, int *out_status) { char *sval; sval = parse_arg_peek(name); if (sval == NULL) { *out_status = ENOENT; return 0; } return parse_long_bounds(sval, min, max, out_status); } long parse_arg_long_bounds(char *name, long min, long max, int *out_status) { char *sval; sval = parse_arg_extract(name); if (sval == NULL) { *out_status = ENOENT; return 0; } return parse_long_bounds(sval, min, max, out_status); } long parse_arg_long_bounds_dflt(char *name, long min, long max, long dflt, int *out_status) { long val; int rc; val = parse_arg_long_bounds(name, min, max, &rc); if (rc == ENOENT) { rc = 0; val = dflt; } *out_status = rc; return val; } uint64_t parse_arg_uint64_bounds(char *name, uint64_t min, uint64_t max, int *out_status) { char *endptr; char *sval; uint64_t lval; sval = parse_arg_extract(name); if (sval == NULL) { *out_status = ENOENT; return 0; } lval = strtoull(sval, &endptr, parse_arg_long_base(sval)); if (sval[0] != '\0' && *endptr == '\0' && lval >= min && lval <= max) { *out_status = 0; return lval; } *out_status = EINVAL; return 0; } long parse_arg_long(char *name, int *out_status) { return parse_arg_long_bounds(name, LONG_MIN, LONG_MAX, out_status); } uint8_t parse_arg_bool(char *name, int *out_status) { return parse_arg_long_bounds(name, 0, 1, out_status); } uint8_t parse_arg_bool_dflt(char *name, uint8_t dflt, int *out_status) { return parse_arg_long_bounds_dflt(name, 0, 1, dflt, out_status); } uint8_t parse_arg_uint8(char *name, int *out_status) { return parse_arg_long_bounds(name, 0, UINT8_MAX, out_status); } uint16_t parse_arg_uint16(char *name, int *out_status) { return parse_arg_long_bounds(name, 0, UINT16_MAX, out_status); } uint16_t parse_arg_uint16_peek(char *name, int *out_status) { return parse_arg_long_bounds_peek(name, 0, UINT16_MAX, out_status); } uint32_t parse_arg_uint32(char *name, int *out_status) { return parse_arg_uint64_bounds(name, 0, UINT32_MAX, out_status); } uint64_t parse_arg_uint64(char *name, int *out_status) { return parse_arg_uint64_bounds(name, 0, UINT64_MAX, out_status); } uint8_t parse_arg_uint8_dflt(char *name, uint8_t dflt, int *out_status) { uint8_t val; int rc; val = parse_arg_uint8(name, &rc); if (rc == ENOENT) { val = dflt; rc = 0; } *out_status = rc; return val; } uint16_t parse_arg_uint16_dflt(char *name, uint16_t dflt, int *out_status) { uint16_t val; int rc; val = parse_arg_uint16(name, &rc); if (rc == ENOENT) { val = dflt; rc = 0; } *out_status = rc; return val; } uint32_t parse_arg_uint32_dflt(char *name, uint32_t dflt, int *out_status) { uint32_t val; int rc; val = parse_arg_uint32(name, &rc); if (rc == ENOENT) { val = dflt; rc = 0; } *out_status = rc; return val; } static uint32_t parse_time_unit_mult(const char *str) { if (!strcasecmp(str, "us")) { return 1; } else if (!strcasecmp(str, "ms")) { return 1000; } else if (!strcasecmp(str, "s")) { return 1000000; } return 0; } static uint32_t parse_time_us(const char *str, int *out_status) { uint32_t val = 0; uint32_t val_div = 1; uint32_t val_mult = 1; uint32_t val_us; while (isdigit(*str)) { val *= 10; val += *str - '0'; str++; } if (*str == '.') { str++; while (isdigit(*str)) { val *= 10; val += *str - '0'; val_div *= 10; str++; } } val_mult = parse_time_unit_mult(str); if (val_mult == 0) { *out_status = EINVAL; return 0; } if (val_mult > val_div) { val_us = val * (val_mult / val_div); } else { val_us = val * (val_div / val_mult); } *out_status = 0; return val_us; } uint32_t parse_arg_time_dflt(char *name, int step_us, uint32_t dflt, int *out_status) { const char *arg; uint32_t val; int rc; arg = parse_arg_peek(name); if (!arg) { *out_status = 0; return dflt; } val = parse_time_us(arg, &rc); if (rc) { val = parse_arg_uint32(name, &rc); if (rc == ENOENT) { *out_status = 0; return dflt; } } else { val /= step_us; parse_arg_extract(name); } *out_status = rc; return val; } const struct kv_pair * parse_kv_find(const struct kv_pair *kvs, char *name) { const struct kv_pair *kv; int i; for (i = 0; kvs[i].key != NULL; i++) { kv = kvs + i; if (strcmp(name, kv->key) == 0) { return kv; } } return NULL; } int parse_arg_kv(char *name, const struct kv_pair *kvs, int *out_status) { const struct kv_pair *kv; char *sval; sval = parse_arg_extract(name); if (sval == NULL) { *out_status = ENOENT; return -1; } kv = parse_kv_find(kvs, sval); if (kv == NULL) { *out_status = EINVAL; return -1; } *out_status = 0; return kv->val; } int parse_arg_kv_dflt(char *name, const struct kv_pair *kvs, int def_val, int *out_status) { int val; int rc; val = parse_arg_kv(name, kvs, &rc); if (rc == ENOENT) { rc = 0; val = def_val; } *out_status = rc; return val; } static int parse_arg_byte_stream_delim(char *sval, char *delims, int max_len, uint8_t *dst, int *out_len) { unsigned long ul; char *endptr; char *token; int i; i = 0; for (token = strtok(sval, delims); token != NULL; token = strtok(NULL, delims)) { if (i >= max_len) { return EINVAL; } ul = strtoul(token, &endptr, 16); if (sval[0] == '\0' || *endptr != '\0' || ul > UINT8_MAX) { return -1; } dst[i] = ul; i++; } *out_len = i; return 0; } int parse_arg_byte_stream(char *name, int max_len, uint8_t *dst, int *out_len) { char *sval; sval = parse_arg_extract(name); if (sval == NULL) { return ENOENT; } return parse_arg_byte_stream_delim(sval, ":-", max_len, dst, out_len); } int parse_arg_uint8_list_with_separator(char *name, char *separator, int max_len, uint8_t *dst, int *out_len) { char *sval; sval = parse_arg_extract(name); if (sval == NULL) { return ENOENT; } return parse_arg_byte_stream_delim(sval, separator, max_len, dst, out_len); } int parse_arg_byte_stream_exact_length(char *name, uint8_t *dst, int len) { int actual_len; int rc; rc = parse_arg_byte_stream(name, len, dst, &actual_len); if (rc != 0) { return rc; } if (actual_len != len) { return EINVAL; } return 0; } static void parse_reverse_bytes(uint8_t *bytes, int len) { uint8_t tmp; int i; for (i = 0; i < len / 2; i++) { tmp = bytes[i]; bytes[i] = bytes[len - i - 1]; bytes[len - i - 1] = tmp; } } int parse_arg_mac(char *name, uint8_t *dst) { int rc; rc = parse_arg_byte_stream_exact_length(name, dst, 6); if (rc != 0) { return rc; } parse_reverse_bytes(dst, 6); return 0; } int parse_arg_addr(char *name, ble_addr_t *addr) { char *arg; size_t len; uint8_t addr_type; bool addr_type_found; int rc; arg = parse_arg_peek(name); if (!arg) { return ENOENT; } len = strlen(arg); if (len < 2) { return EINVAL; } addr_type_found = false; if ((arg[len - 2] == ':') || (arg[len - 2] == '-')) { if (tolower(arg[len - 1]) == 'p') { addr_type = BLE_ADDR_PUBLIC; addr_type_found = true; } else if (tolower(arg[len - 1]) == 'r') { addr_type = BLE_ADDR_RANDOM; addr_type_found = true; } if (addr_type_found) { arg[len - 2] = '\0'; } } rc = parse_arg_mac(name, addr->val); if (rc != 0) { return rc; } if (addr_type_found) { addr->type = addr_type; } else { rc = EAGAIN; } return rc; } int parse_arg_uuid(char *str, ble_uuid_any_t *uuid) { uint16_t uuid16; uint8_t val[16]; int len; int rc; uuid16 = parse_arg_uint16_peek(str, &rc); switch (rc) { case ENOENT: parse_arg_extract(str); return ENOENT; case 0: len = 2; val[0] = uuid16; val[1] = uuid16 >> 8; parse_arg_extract(str); break; default: len = 16; rc = parse_arg_byte_stream_exact_length(str, val, 16); if (rc != 0) { return EINVAL; } parse_reverse_bytes(val, 16); break; } rc = ble_uuid_init_from_buf(uuid, val, len); if (rc != 0) { return EINVAL; } else { return 0; } } int parse_arg_all(int argc, char **argv) { char *key; char *val; int i; cmd_num_args = 0; for (i = 0; i < argc; i++) { key = strtok(argv[i], "="); val = strtok(NULL, "="); if (key != NULL && val != NULL) { if (strlen(key) == 0) { console_printf("Error: invalid argument: %s\n", argv[i]); return -1; } if (cmd_num_args >= CMD_MAX_ARGS) { console_printf("Error: too many arguments"); return -1; } cmd_args[cmd_num_args][0] = key; cmd_args[cmd_num_args][1] = val; cmd_num_args++; } } return 0; } int parse_eddystone_url(char *full_url, uint8_t *out_scheme, char *out_body, uint8_t *out_body_len, uint8_t *out_suffix) { static const struct { char *s; uint8_t scheme; } schemes[] = { { "http://www.", BLE_EDDYSTONE_URL_SCHEME_HTTP_WWW }, { "https://www.", BLE_EDDYSTONE_URL_SCHEME_HTTPS_WWW }, { "http://", BLE_EDDYSTONE_URL_SCHEME_HTTP }, { "https://", BLE_EDDYSTONE_URL_SCHEME_HTTPS }, }; static const struct { char *s; uint8_t code; } suffixes[] = { { ".com/", BLE_EDDYSTONE_URL_SUFFIX_COM_SLASH }, { ".org/", BLE_EDDYSTONE_URL_SUFFIX_ORG_SLASH }, { ".edu/", BLE_EDDYSTONE_URL_SUFFIX_EDU_SLASH }, { ".net/", BLE_EDDYSTONE_URL_SUFFIX_NET_SLASH }, { ".info/", BLE_EDDYSTONE_URL_SUFFIX_INFO_SLASH }, { ".biz/", BLE_EDDYSTONE_URL_SUFFIX_BIZ_SLASH }, { ".gov/", BLE_EDDYSTONE_URL_SUFFIX_GOV_SLASH }, { ".com", BLE_EDDYSTONE_URL_SUFFIX_COM }, { ".org", BLE_EDDYSTONE_URL_SUFFIX_ORG }, { ".edu", BLE_EDDYSTONE_URL_SUFFIX_EDU }, { ".net", BLE_EDDYSTONE_URL_SUFFIX_NET }, { ".info", BLE_EDDYSTONE_URL_SUFFIX_INFO }, { ".biz", BLE_EDDYSTONE_URL_SUFFIX_BIZ }, { ".gov", BLE_EDDYSTONE_URL_SUFFIX_GOV }, }; char *prefix; char *suffix; int full_url_len; int prefix_len; int suffix_len; int suffix_idx; int rc; int i; full_url_len = strlen(full_url); rc = BLE_HS_EINVAL; for (i = 0; i < sizeof schemes / sizeof schemes[0]; i++) { prefix = schemes[i].s; prefix_len = strlen(schemes[i].s); if (full_url_len >= prefix_len && memcmp(full_url, prefix, prefix_len) == 0) { *out_scheme = i; rc = 0; break; } } if (rc != 0) { return rc; } rc = BLE_HS_EINVAL; for (i = 0; i < sizeof suffixes / sizeof suffixes[0]; i++) { suffix = suffixes[i].s; suffix_len = strlen(suffixes[i].s); suffix_idx = full_url_len - suffix_len; if (suffix_idx >= prefix_len && memcmp(full_url + suffix_idx, suffix, suffix_len) == 0) { *out_suffix = i; rc = 0; break; } } if (rc != 0) { *out_suffix = BLE_EDDYSTONE_URL_SUFFIX_NONE; *out_body_len = full_url_len - prefix_len; } else { *out_body_len = full_url_len - prefix_len - suffix_len; } memcpy(out_body, full_url + prefix_len, *out_body_len); return 0; }