| /* |
| * 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 <string.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <stdarg.h> |
| #include <errno.h> |
| #include <assert.h> |
| #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; |
| } |
| |
| 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_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_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; |
| } |