feat(factory-cli): add Wi-Fi credential management commands
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include "control_plane_internal.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -29,7 +30,9 @@
|
||||
#define FACTORY_CLI_DEVICE_ID_MAX_LEN 64
|
||||
#define FACTORY_CLI_SERIAL_MAX_LEN 64
|
||||
#define FACTORY_CLI_JSON_MAX_LEN 640
|
||||
#define FACTORY_CLI_WIFI_JSON_MAX_LEN 3072
|
||||
#define FACTORY_CLI_READ_TIMEOUT_MS 100
|
||||
#define FACTORY_CLI_RESET_REPLY_DELAY_MS 100
|
||||
#ifndef CONFIG_TQ_FACTORY_CLI_TASK_STACK_BYTES
|
||||
#define CONFIG_TQ_FACTORY_CLI_TASK_STACK_BYTES (10U * 1024U)
|
||||
#endif
|
||||
@@ -184,23 +187,56 @@ static void trim_text(char *value)
|
||||
static size_t split_args(char *line, char *argv[], size_t max_args)
|
||||
{
|
||||
size_t argc = 0;
|
||||
char *cursor = line;
|
||||
while (*cursor != '\0' && argc < max_args) {
|
||||
while (*cursor != '\0' && isspace((unsigned char)(*cursor))) {
|
||||
cursor++;
|
||||
char *read_cursor = line;
|
||||
char *write_cursor = line;
|
||||
|
||||
while (*read_cursor != '\0') {
|
||||
while (*read_cursor != '\0' && isspace((unsigned char)(*read_cursor))) {
|
||||
read_cursor++;
|
||||
}
|
||||
if (*cursor == '\0') {
|
||||
if (*read_cursor == '\0') {
|
||||
break;
|
||||
}
|
||||
argv[argc++] = cursor;
|
||||
while (*cursor != '\0' && !isspace((unsigned char)(*cursor))) {
|
||||
cursor++;
|
||||
if (argc >= max_args) {
|
||||
return SIZE_MAX;
|
||||
}
|
||||
if (*cursor == '\0') {
|
||||
break;
|
||||
|
||||
argv[argc++] = write_cursor;
|
||||
char quote = '\0';
|
||||
while (*read_cursor != '\0') {
|
||||
char ch = *read_cursor;
|
||||
if (quote != '\0') {
|
||||
if (ch == quote) {
|
||||
quote = '\0';
|
||||
read_cursor++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (isspace((unsigned char)ch)) {
|
||||
break;
|
||||
}
|
||||
if (ch == '\'' || ch == '"') {
|
||||
quote = ch;
|
||||
read_cursor++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == '\\' && read_cursor[1] != '\0') {
|
||||
read_cursor++;
|
||||
ch = *read_cursor;
|
||||
}
|
||||
*write_cursor++ = ch;
|
||||
read_cursor++;
|
||||
}
|
||||
*cursor = '\0';
|
||||
cursor++;
|
||||
|
||||
if (quote != '\0') {
|
||||
return SIZE_MAX;
|
||||
}
|
||||
while (*read_cursor != '\0' && isspace((unsigned char)(*read_cursor))) {
|
||||
read_cursor++;
|
||||
}
|
||||
*write_cursor++ = '\0';
|
||||
}
|
||||
return argc;
|
||||
}
|
||||
@@ -226,6 +262,150 @@ static void cli_write_error(const char *message)
|
||||
cli_write_line(line);
|
||||
}
|
||||
|
||||
static bool json_append_text(char *out,
|
||||
size_t out_len,
|
||||
size_t *offset,
|
||||
const char *text)
|
||||
{
|
||||
if (out == NULL || offset == NULL || text == NULL || *offset >= out_len) {
|
||||
return false;
|
||||
}
|
||||
size_t text_len = strlen(text);
|
||||
if (text_len >= out_len - *offset) {
|
||||
return false;
|
||||
}
|
||||
memcpy(out + *offset, text, text_len);
|
||||
*offset += text_len;
|
||||
out[*offset] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool json_append_escaped_string(char *out,
|
||||
size_t out_len,
|
||||
size_t *offset,
|
||||
const char *value)
|
||||
{
|
||||
if (!json_append_text(out, out_len, offset, "\"")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const unsigned char *cursor = (const unsigned char *)value;
|
||||
*cursor != '\0';
|
||||
++cursor) {
|
||||
char escape[8] = {0};
|
||||
const char *encoded = escape;
|
||||
switch (*cursor) {
|
||||
case '"':
|
||||
encoded = "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
encoded = "\\\\";
|
||||
break;
|
||||
case '\b':
|
||||
encoded = "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
encoded = "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
encoded = "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
encoded = "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
encoded = "\\t";
|
||||
break;
|
||||
default:
|
||||
if (*cursor < 0x20U) {
|
||||
snprintf(escape, sizeof(escape), "\\u%04x", (unsigned)*cursor);
|
||||
} else {
|
||||
escape[0] = (char)*cursor;
|
||||
escape[1] = '\0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!json_append_text(out, out_len, offset, encoded)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return json_append_text(out, out_len, offset, "\"");
|
||||
}
|
||||
|
||||
static void handle_get_wifi(void)
|
||||
{
|
||||
domain_wifi_saved_networks_t networks = {0};
|
||||
esp_err_t err = domain_runtime_wifi_portal_get_saved_networks(&networks);
|
||||
if (err != ESP_OK) {
|
||||
cli_write_error("wifi_read_failed");
|
||||
return;
|
||||
}
|
||||
|
||||
char json[FACTORY_CLI_WIFI_JSON_MAX_LEN] = {0};
|
||||
size_t offset = 0;
|
||||
char prefix[48] = {0};
|
||||
snprintf(prefix, sizeof(prefix), "{\"count\":%u,\"networks\":[", (unsigned)networks.count);
|
||||
bool valid = json_append_text(json, sizeof(json), &offset, prefix);
|
||||
for (size_t i = 0; valid && i < networks.count; ++i) {
|
||||
char item_prefix[40] = {0};
|
||||
snprintf(item_prefix,
|
||||
sizeof(item_prefix),
|
||||
"%s{\"priority\":%u,\"ssid\":",
|
||||
i == 0U ? "" : ",",
|
||||
(unsigned)i);
|
||||
valid = json_append_text(json, sizeof(json), &offset, item_prefix) &&
|
||||
json_append_escaped_string(json,
|
||||
sizeof(json),
|
||||
&offset,
|
||||
networks.ssids[i]) &&
|
||||
json_append_text(json, sizeof(json), &offset, "}");
|
||||
}
|
||||
valid = valid && json_append_text(json, sizeof(json), &offset, "]}");
|
||||
if (!valid) {
|
||||
cli_write_error("wifi_response_too_large");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_write_line(json);
|
||||
cli_write_ok();
|
||||
}
|
||||
|
||||
static void handle_set_wifi(size_t argc, char *argv[])
|
||||
{
|
||||
if (argc < 2 || is_text_empty(argv[1])) {
|
||||
cli_write_error("missing_wifi_ssid");
|
||||
return;
|
||||
}
|
||||
if (argc > 3) {
|
||||
cli_write_error("invalid_wifi_arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *password = argc >= 3 ? argv[2] : "";
|
||||
esp_err_t err = domain_runtime_wifi_portal_save_credentials(argv[1], password);
|
||||
if (err != ESP_OK) {
|
||||
cli_write_error(err == ESP_ERR_INVALID_ARG ?
|
||||
"invalid_wifi_credentials" :
|
||||
"wifi_save_failed");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_write_ok();
|
||||
}
|
||||
|
||||
static void handle_reset_wifi(void)
|
||||
{
|
||||
esp_err_t err = domain_runtime_wifi_portal_clear_saved_credentials();
|
||||
if (err != ESP_OK) {
|
||||
cli_write_error("wifi_reset_failed");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_write_ok();
|
||||
vTaskDelay(pdMS_TO_TICKS(FACTORY_CLI_RESET_REPLY_DELAY_MS));
|
||||
domain_runtime_restart();
|
||||
}
|
||||
|
||||
static esp_err_t resolve_device_identity(char *device_id, size_t device_id_len)
|
||||
{
|
||||
char serial_number[FACTORY_CLI_SERIAL_MAX_LEN] = {0};
|
||||
@@ -406,6 +586,10 @@ static void handle_command_line(char *line)
|
||||
|
||||
char *argv[FACTORY_CLI_MAX_ARGC] = {0};
|
||||
size_t argc = split_args(line, argv, FACTORY_CLI_MAX_ARGC);
|
||||
if (argc == SIZE_MAX) {
|
||||
cli_write_error("invalid_command_syntax");
|
||||
return;
|
||||
}
|
||||
if (argc == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -443,6 +627,12 @@ static void handle_command_line(char *line)
|
||||
cli_write_ok();
|
||||
} else if (strcmp(cmd, "save_config") == 0) {
|
||||
handle_save_config();
|
||||
} else if (strcmp(cmd, "get_wifi") == 0) {
|
||||
handle_get_wifi();
|
||||
} else if (strcmp(cmd, "set_wifi") == 0) {
|
||||
handle_set_wifi(argc, argv);
|
||||
} else if (strcmp(cmd, "reset_wifi") == 0) {
|
||||
handle_reset_wifi();
|
||||
} else if (strcmp(cmd, "get_auth_material") == 0 ||
|
||||
strcmp(cmd, "auth_material") == 0 ||
|
||||
strcmp(cmd, "get_device_auth_material") == 0 ||
|
||||
|
||||
Reference in New Issue
Block a user