You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1277 lines
31 KiB

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <string.h>
#include <nvs.h>
#include <esp_ota_ops.h>
typedef enum {
SCAN_STATE_NONE,
SCAN_STATE_INITIAL,
SCAN_STATE_INITIAL_MANIFEST,
SCAN_STATE_KNOWN,
SCAN_STATE_LIST,
SCAN_STATE_FINAL
} scan_state;
struct store_json {
const char *j;
const char *nvs;
};
struct per_session_data__esplws_scan {
struct per_session_data__esplws_scan *next;
scan_state scan_state;
struct timeval last_send;
struct lws_spa *spa;
char filename[32];
char result[LWS_PRE + 512];
unsigned char buffer[4096];
int result_len;
int filename_length;
long file_length;
nvs_handle nvh;
char ap_record;
unsigned char subsequent:1;
unsigned char changed_partway:1;
};
#define max_aps 12
struct per_vhost_data__esplws_scan {
wifi_ap_record_t ap_records[10];
TimerHandle_t timer, reboot_timer;
struct per_session_data__esplws_scan *live_pss_list;
struct lws_context *context;
struct lws_vhost *vhost;
const struct lws_protocols *protocol;
struct lws_wifi_scan *known_aps_list;
const esp_partition_t *part;
esp_ota_handle_t otahandle;
long file_length;
long content_length;
int cert_remaining_days;
struct lws *cwsi;
char json[2048];
int json_len;
int acme_state;
char acme_msg[256];
uint16_t count_ap_records;
char count_live_pss;
unsigned char scan_ongoing:1;
unsigned char completed_any_scan:1;
unsigned char reboot:1;
unsigned char changed_settings:1;
unsigned char checked_updates:1;
unsigned char autonomous_update:1;
unsigned char autonomous_update_sampled:1;
};
static const struct store_json store_json[] = {
{ "\"ssid0\":\"", "0ssid" },
{ ",\"pw0\":\"", "0password" },
{ "\"ssid1\":\"", "1ssid" },
{ ",\"pw1\":\"", "1password" },
{ "\"ssid2\":\"", "2ssid" },
{ ",\"pw2\":\"", "2password" },
{ "\"ssid3\":\"", "3ssid" },
{ ",\"pw3\":\"", "3password" },
{ ",\"access_pw\":\"", "access_pw" },
{ "{\"group\":\"", "group" },
{ "{\"role\":\"", "role" },
{ ",\"region\":\"", "region" },
};
static wifi_scan_config_t scan_config = {
.ssid = 0,
.bssid = 0,
.channel = 0,
.show_hidden = true
};
const esp_partition_t *
ota_choose_part(void);
static const char * const param_names[] = {
"text",
"pub",
"pri",
"serial",
"opts",
"group",
"role",
"updsettings",
};
enum enum_param_names {
EPN_TEXT,
EPN_PUB,
EPN_PRI,
EPN_SERIAL,
EPN_OPTS,
EPN_GROUP,
EPN_ROLE,
EPN_UPDSETTINGS,
};
static void
scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v);
static int
esplws_simple_arg(char *dest, int len, const char *in, const char *match)
{
const char *p = strstr(in, match);
int n = 0;
if (!p)
return 1;
p += strlen(match);
while (*p && *p != '\"' && n < len - 1)
dest[n++] = *p++;
dest[n] = '\0';
return 0;
}
static void
scan_start(struct per_vhost_data__esplws_scan *vhd)
{
int n;
if (vhd->reboot)
esp_restart();
if (vhd->scan_ongoing)
return;
if (lws_esp32.acme)
return;
if (lws_esp32.upload)
return;
vhd->scan_ongoing = 1;
lws_esp32.scan_consumer = scan_finished;
lws_esp32.scan_consumer_arg = vhd;
n = esp_wifi_scan_start(&scan_config, false);
if (n != ESP_OK)
lwsl_err("scan start failed %d\n", n);
}
static int scan_defer;
static void timer_cb(TimerHandle_t t)
{
struct per_vhost_data__esplws_scan *vhd = pvTimerGetTimerID(t);
// if (!lws_esp32.inet && ((scan_defer++) & 1))
/*
* AP mode + scan does not work well on ESP32... if we didn't connect to an AP
* ourselves, just scan once at boot. Then leave us on the AP channel.
*
* Do the callback for everyone to keep the heartbeat alive.
*/
if (!lws_esp32.inet && scan_defer++) {
lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
return;
}
scan_start(vhd);
}
static void reboot_timer_cb(TimerHandle_t t)
{
esp_restart();
}
static int
client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
{
#if defined(CONFIG_LWS_IS_FACTORY_APPLICATION) && defined(CONFIG_LWS_OTA_SERVER_BASE_URL) && \
defined(CONFIG_LWS_OTA_SERVER_FQDN)
static struct lws_client_connect_info i;
char path[256];
memset(&i, 0, sizeof i);
snprintf(path, sizeof(path) - 1, CONFIG_LWS_OTA_SERVER_BASE_URL "/" CONFIG_LWS_MODEL_NAME "/%s", file);
lwsl_notice("Fetching %s\n", path);
i.port = 443;
i.context = vhd->context;
i.address = CONFIG_LWS_OTA_SERVER_FQDN;
i.ssl_connection = 1;
i.host = i.address;
i.origin = i.host;
i.vhost = vhd->vhost;
i.method = "GET";
i.path = path;
i.protocol = "esplws-scan";
i.pwsi = &vhd->cwsi;
vhd->cwsi = lws_client_connect_via_info(&i);
if (!vhd->cwsi) {
lwsl_notice("NULL return\n");
return 1; /* fail */
}
#endif
return 0; /* ongoing */
}
static int
lws_wifi_scan_rssi(struct lws_wifi_scan *p)
{
if (!p->count)
return -127;
return p->rssi / p->count;
}
/*
* Insert new lws_wifi_scan into linkedlist in rssi-sorted order, trimming the
* list if needed to keep it at or below max_aps entries.
*/
static int
lws_wifi_scan_insert_trim(struct lws_wifi_scan **list, struct lws_wifi_scan *ns)
{
int count = 0, ins = 1, worst;
struct lws_wifi_scan *newlist, **pworst, *pp1;
lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
/* try to find existing match */
if (!strcmp((*pp)->ssid, ns->ssid) &&
!memcmp((*pp)->bssid, ns->bssid, 6)) {
if ((*pp)->count > 127) {
(*pp)->count /= 2;
(*pp)->rssi /= 2;
}
(*pp)->rssi += ns->rssi;
(*pp)->count++;
ins = 0;
break;
}
} lws_end_foreach_llp(pp, next);
if (ins) {
lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
/* trim any excess guys */
if (count++ >= max_aps - 1) {
pp1 = *pp;
*pp = (*pp)->next;
free(pp1);
continue; /* stay where we are */
}
} lws_end_foreach_llp(pp, next);
/* we are inserting... so alloc a copy of him */
pp1 = malloc(sizeof(*pp1));
if (!pp1)
return -1;
memcpy(pp1, ns, sizeof(*pp1));
pp1->next = *list;
*list = pp1;
}
/* sort the list ... worst first, but added at the newlist head */
newlist = NULL;
/* while anybody left on the old list */
while (*list) {
worst = 0;
pworst = NULL;
/* who is the worst guy still left on the old list? */
lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
if (lws_wifi_scan_rssi(*pp) <= worst) {
worst = lws_wifi_scan_rssi(*pp);
pworst = pp;
}
} lws_end_foreach_llp(pp, next);
if (pworst) {
/* move the worst to the head of the new list */
pp1 = *pworst;
*pworst = (*pworst)->next;
pp1->next = newlist;
newlist = pp1;
}
}
*list = newlist;
return 0;
}
static void
scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v)
{
struct per_vhost_data__esplws_scan *vhd = v;
struct per_session_data__esplws_scan *p = vhd->live_pss_list;
struct lws_wifi_scan lws;
wifi_ap_record_t *r;
int m;
lwsl_notice("%s: count %d\n", __func__, count);
vhd->scan_ongoing = 0;
if (count < LWS_ARRAY_SIZE(vhd->ap_records))
vhd->count_ap_records = count;
else
vhd->count_ap_records = LWS_ARRAY_SIZE(vhd->ap_records);
memcpy(vhd->ap_records, recs, vhd->count_ap_records * sizeof(*recs));
while (p) {
if (p->scan_state != SCAN_STATE_INITIAL &&
p->scan_state != SCAN_STATE_NONE)
p->changed_partway = 1;
else
p->scan_state = SCAN_STATE_INITIAL;
p = p->next;
}
/* convert to generic, cumulative scan results */
for (m = 0; m < vhd->count_ap_records; m++) {
r = &vhd->ap_records[m];
lws.authmode = r->authmode;
lws.channel = r->primary;
lws.rssi = r->rssi;
lws.count = 1;
memcpy(&lws.bssid, r->bssid, 6);
lws_strncpy(lws.ssid, (const char *)r->ssid, sizeof(lws.ssid));
lws_wifi_scan_insert_trim(&vhd->known_aps_list, &lws);
}
lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
if (lws_esp32.inet && !vhd->cwsi && !vhd->checked_updates)
client_connection(vhd, "manifest.json");
if (vhd->changed_settings) {
lws_esp32_wlan_nvs_get(1);
vhd->changed_settings = 0;
} else
esp_wifi_connect();
}
static const char *ssl_names[] = { "ap-cert.pem", "ap-key.pem" };
static int
file_upload_cb(void *data, const char *name, const char *filename,
char *buf, int len, enum lws_spa_fileupload_states state)
{
struct per_session_data__esplws_scan *pss =
(struct per_session_data__esplws_scan *)data;
int n;
switch (state) {
case LWS_UFS_OPEN:
if (lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
return -1;
lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
lws_strncpy(pss->filename, filename, sizeof(pss->filename));
if (!strcmp(name, "pub") || !strcmp(name, "pri")) {
if (nvs_open("lws-station", NVS_READWRITE, &pss->nvh))
return 1;
} else
return 1;
pss->file_length = 0;
break;
case LWS_UFS_FINAL_CONTENT:
case LWS_UFS_CONTENT:
if (len) {
/* if the file length is too big, drop it */
if (pss->file_length + len > sizeof(pss->buffer))
return 1;
memcpy(pss->buffer + pss->file_length, buf, len);
}
pss->file_length += len;
if (state == LWS_UFS_CONTENT)
break;
lwsl_notice("LWS_UFS_FINAL_CONTENT\n");
n = 0;
if (!strcmp(name, "pri"))
n = 1;
lwsl_notice("writing %s\n", ssl_names[n]);
n = nvs_set_blob(pss->nvh, ssl_names[n], pss->buffer, pss->file_length);
if (n == ESP_OK)
nvs_commit(pss->nvh);
nvs_close(pss->nvh);
if (n != ESP_OK)
return 1;
break;
}
return 0;
}
static int
callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct per_session_data__esplws_scan *pss =
(struct per_session_data__esplws_scan *)user;
struct per_vhost_data__esplws_scan *vhd =
(struct per_vhost_data__esplws_scan *)
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
lws_get_protocol(wsi));
unsigned char *start = pss->buffer + LWS_PRE - 1, *p = start,
*end = pss->buffer + sizeof(pss->buffer) - 1;
union lws_tls_cert_info_results ir;
struct lws_wifi_scan *lwscan;
char subject[64];
int n, m;
nvs_handle nvh;
size_t s;
switch (reason) {
case LWS_CALLBACK_PROTOCOL_INIT:
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
lws_get_protocol(wsi),
sizeof(struct per_vhost_data__esplws_scan));
vhd->context = lws_get_context(wsi);
vhd->protocol = lws_get_protocol(wsi);
vhd->vhost = lws_get_vhost(wsi);
vhd->timer = xTimerCreate("x", pdMS_TO_TICKS(10000), 1, vhd,
(TimerCallbackFunction_t)timer_cb);
vhd->scan_ongoing = 0;
strcpy(vhd->json, " { }");
// scan_start(vhd);
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
if (!vhd)
break;
xTimerStop(vhd->timer, 0);
xTimerDelete(vhd->timer, 0);
break;
case LWS_CALLBACK_ESTABLISHED:
lwsl_notice("%s: ESTABLISHED\n", __func__);
if (!vhd->live_pss_list) {
// scan_start(vhd);
xTimerStart(vhd->timer, 0);
}
vhd->count_live_pss++;
pss->next = vhd->live_pss_list;
vhd->live_pss_list = pss;
/* if we have scan results, update them. Otherwise wait */
// if (vhd->count_ap_records) {
pss->scan_state = SCAN_STATE_INITIAL;
lws_callback_on_writable(wsi);
// }
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
if (vhd->autonomous_update_sampled) {
p += snprintf((char *)p, end - p,
" {\n \"auton\":\"1\",\n \"pos\": \"%ld\",\n"
" \"len\":\"%ld\"\n}\n",
vhd->file_length,
vhd->content_length);
n = LWS_WRITE_TEXT;
goto issue;
}
switch (pss->scan_state) {
struct timeval t;
uint8_t mac[6];
struct lws_esp32_image i;
char img_factory[384], img_ota[384], group[16], role[16];
int grt;
case SCAN_STATE_NONE:
/* fallthru */
case SCAN_STATE_INITIAL:
gettimeofday(&t, NULL);
// if (t.tv_sec - pss->last_send.tv_sec < 10)
// return 0;
pss->last_send = t;
if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
lwsl_err("unable to open nvs\n");
return -1;
}
n = 0;
if (nvs_get_blob(nvh, "ap-cert.pem", NULL, &s) == ESP_OK)
n = 1;
if (nvs_get_blob(nvh, "ap-key.pem", NULL, &s) == ESP_OK)
n |= 2;
s = sizeof(group) - 1;
group[0] = '\0';
role[0] = '\0';
nvs_get_str(nvh, "group", group, &s);
nvs_get_str(nvh, "role", role, &s);
nvs_close(nvh);
ir.ns.name[0] = '\0';
subject[0] = '\0';
if (t.tv_sec > 1464083026 &&
!lws_tls_vhost_cert_info(vhd->vhost,
LWS_TLS_CERT_INFO_VALIDITY_TO, &ir, 0)) {
vhd->cert_remaining_days =
(ir.time - t.tv_sec) / (24 * 3600);
ir.ns.name[0] = '\0';
lws_tls_vhost_cert_info(vhd->vhost,
LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
sizeof(ir.ns.name));
lws_strncpy(subject, ir.ns.name, sizeof(subject));
ir.ns.name[0] = '\0';
lws_tls_vhost_cert_info(vhd->vhost,
LWS_TLS_CERT_INFO_ISSUER_NAME, &ir,
sizeof(ir.ns.name));
}
/*
* this value in the JSON is just
* used for UI indication. Each conditional feature confirms
* it itself before it allows itself to be used.
*/
grt = lws_esp32_get_reboot_type();
esp_efuse_mac_get_default(mac);
strcpy(img_factory, " { \"date\": \"Empty\" }");
strcpy(img_ota, " { \"date\": \"Empty\" }");
// if (grt != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL), &i,
img_factory, sizeof(img_factory) - 1);
img_factory[sizeof(img_factory) - 1] = '\0';
if (img_factory[0] == 0xff || strlen(img_factory) < 8)
strcpy(img_factory, " { \"date\": \"Empty\" }");
lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL), &i,
img_ota, sizeof(img_ota) - 1);
img_ota[sizeof(img_ota) - 1] = '\0';
if (img_ota[0] == 0xff || strlen(img_ota) < 8)
strcpy(img_ota, " { \"date\": \"Empty\" }");
// }
p += snprintf((char *)p, end - p,
"{ \"model\":\"%s\",\n"
" \"forced_button\":\"%d\",\n"
" \"serial\":\"%s\",\n"
" \"opts\":\"%s\",\n"
" \"host\":\"%s-%s\",\n"
" \"region\":\"%d\",\n"
" \"ssl_pub\":\"%d\",\n"
" \"ssl_pri\":\"%d\",\n"
" \"mac\":\"%02X%02X%02X%02X%02X%02X\",\n"
" \"ssid\":\"%s\",\n"
" \"conn_ip\":\"%s\",\n"
" \"conn_mask\":\"%s\",\n"
" \"conn_gw\":\"%s\",\n"
" \"certdays\":\"%d\",\n"
" \"unixtime\":\"%llu\",\n"
" \"certissuer\":\"%s\",\n"
" \"certsubject\":\"%s\",\n"
" \"le_dns\":\"%s\",\n"
" \"le_email\":\"%s\",\n"
" \"acme_state\":\"%d\",\n"
" \"acme_msg\":\"%s\",\n"
" \"button\":\"%d\",\n"
" \"group\":\"%s\",\n"
" \"role\":\"%s\",\n",
lws_esp32.model,
grt == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON,
lws_esp32.serial,
lws_esp32.opts,
lws_esp32.model, lws_esp32.serial,
lws_esp32.region,
n & 1, (n >> 1) & 1,
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] | 1,
lws_esp32.active_ssid,
lws_esp32.sta_ip,
lws_esp32.sta_mask,
lws_esp32.sta_gw,
vhd->cert_remaining_days,
(unsigned long long)t.tv_sec,
ir.ns.name, subject,
lws_esp32.le_dns,
lws_esp32.le_email,
vhd->acme_state,
vhd->acme_msg,
((volatile struct lws_esp32 *)(&lws_esp32))->button_is_down,
group, role);
p += snprintf((char *)p, end - p,
" \"img_factory\": %s,\n"
" \"img_ota\": %s,\n",
img_factory,
img_ota
);
n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;
pss->scan_state = SCAN_STATE_INITIAL_MANIFEST;
pss->ap_record = 0;
pss->subsequent = 0;
break;
case SCAN_STATE_INITIAL_MANIFEST:
p += snprintf((char *)p, end - p,
" \"latest\": %s,\n"
" \"inet\":\"%d\",\n",
vhd->json,
lws_esp32.inet
);
p += snprintf((char *)p, end - p,
" \"known\":[\n");
n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
pss->scan_state = SCAN_STATE_KNOWN;
break;
case SCAN_STATE_KNOWN:
if (nvs_open("lws-station", NVS_READONLY, &nvh)) {
lwsl_notice("unable to open nvh\n");
return -1;
}
for (m = 0; m < 4; m++) {
char name[10], ssid[65];
unsigned int pp = 0, use = 0;
if (m)
*p++ = ',';
s = sizeof(ssid) - 1;
ssid[0] = '\0';
lws_snprintf(name, sizeof(name) - 1, "%dssid", m);
nvs_get_str(nvh, name, ssid, &s);
lws_snprintf(name, sizeof(name) - 1, "%dpassword", m);
s = 10;
nvs_get_str(nvh, name, NULL, &s);
pp = !!s;
lws_snprintf(name, sizeof(name) - 1, "%duse", m);
nvs_get_u32(nvh, name, &use);
p += snprintf((char *)p, end - p,
"{\"ssid\":\"%s\",\n"
" \"pp\":\"%u\",\n"
"\"use\":\"%u\"}\n",
ssid, pp, use);
}
nvs_close(nvh);
pss->ap_record = 0;
p += snprintf((char *)p, end - p,
"], \"aps\":[\n");
n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
pss->scan_state = SCAN_STATE_LIST;
break;
case SCAN_STATE_LIST:
lwscan = vhd->known_aps_list;
n = pss->ap_record;
while (lwscan && n--)
lwscan = lwscan->next;
for (m = 0; m < 6; m++) {
n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
if (!lwscan)
goto scan_state_final;
if (pss->subsequent)
*p++ = ',';
pss->subsequent = 1;
pss->ap_record++;
p += snprintf((char *)p, end - p,
"{\"ssid\":\"%s\",\n"
"\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\",\n"
"\"rssi\":\"%d\",\n"
"\"chan\":\"%d\",\n"
"\"auth\":\"%d\"}\n",
lwscan->ssid,
lwscan->bssid[0], lwscan->bssid[1], lwscan->bssid[2],
lwscan->bssid[3], lwscan->bssid[4], lwscan->bssid[5],
lws_wifi_scan_rssi(lwscan),
lwscan->channel, lwscan->authmode);
lwscan = lwscan->next;
if (!lwscan)
pss->scan_state = SCAN_STATE_FINAL;
}
break;
case SCAN_STATE_FINAL:
scan_state_final:
n = LWS_WRITE_CONTINUATION;
p += sprintf((char *)p, "]\n}\n");
if (pss->changed_partway) {
pss->changed_partway = 0;
pss->subsequent = 0;
pss->scan_state = SCAN_STATE_INITIAL;
} else {
pss->scan_state = SCAN_STATE_NONE;
vhd->autonomous_update_sampled = vhd->autonomous_update;
}
break;
default:
return 0;
}
issue:
m = lws_write(wsi, (unsigned char *)start, p - start, n);
if (m < 0) {
lwsl_err("ERROR %d writing to di socket\n", m);
return -1;
}
if (pss->scan_state != SCAN_STATE_NONE)
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_VHOST_CERT_UPDATE:
lwsl_notice("LWS_CALLBACK_VHOST_CERT_UPDATE: %d\n", (int)len);
vhd->acme_state = (int)len;
if (in) {
lws_strncpy(vhd->acme_msg, in, sizeof(vhd->acme_msg));
lwsl_notice("acme_msg: %s\n", (char *)in);
}
lws_callback_on_writable_all_protocol_vhost(vhd->vhost, vhd->protocol);
break;
case LWS_CALLBACK_RECEIVE:
{
const char *sect = "\"app\": {", *b;
nvs_handle nvh;
char p[64], use[6];
int n, si = -1;
if (strstr((const char *)in, "identify")) {
lws_esp32_identify_physical_device();
break;
}
if (vhd->json_len && strstr((const char *)in, "update-factory")) {
sect = "\"factory\": {";
goto auton;
}
if (vhd->json_len && strstr((const char *)in, "update-ota"))
goto auton;
if (strstr((const char *)in, "\"reset\""))
goto sched_reset;
if (!strncmp((const char *)in, "{\"job\":\"start-le\"", 17))
goto start_le;
if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
lwsl_err("Unable to open nvs\n");
break;
}
if (!esplws_simple_arg(p, sizeof(p), in, ",\"slot\":\""))
si = atoi(p);
lwsl_notice("si %d\n", si);
for (n = 0; n < LWS_ARRAY_SIZE(store_json); n++) {
if (esplws_simple_arg(p, sizeof(p), in, store_json[n].j))
continue;
/* only change access password if he has physical access to device */
if (n == 8 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
continue;
if (lws_nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) {
lwsl_err("Unable to store %s in nvm\n", store_json[n].nvs);
goto bail_nvs;
}
if (si != -1 && n < 8) {
if (!(n & 1)) {
lws_strncpy(lws_esp32.ssid[(n >> 1) & 3], p,
sizeof(lws_esp32.ssid[0]));
lws_snprintf(use, sizeof(use) - 1, "%duse", si);
lwsl_notice("resetting %s to 0\n", use);
nvs_set_u32(nvh, use, 0);
} else
lws_strncpy(lws_esp32.password[(n >> 1) & 3], p,
sizeof(lws_esp32.password[0]));
}
}
nvs_commit(nvh);
nvs_close(nvh);
if (strstr((const char *)in, "\"factory-reset\"")) {
if (lws_esp32_get_reboot_type() ==
LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
lwsl_notice("Doing factory reset\n");
ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
n = nvs_erase_all(nvh);
if (n)
lwsl_notice("erase_all failed %d\n", n);
nvs_commit(nvh);
nvs_close(nvh);
goto sched_reset;
} else
lwsl_notice("failed on factory button boot\n");
}
if (vhd->scan_ongoing)
vhd->changed_settings = 1;
else
lws_esp32_wlan_nvs_get(1);
lwsl_notice("set Join AP info\n");
break;
bail_nvs:
nvs_close(nvh);
return 1;
sched_reset:
vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
(TimerCallbackFunction_t)reboot_timer_cb);
xTimerStart(vhd->reboot_timer, 0);
return 1;
auton:
lwsl_notice("Autonomous upload\n");
b = strstr(vhd->json, sect);
if (!b) {
lwsl_notice("Can't find %s in JSON\n", sect);
return 1;
}
b = strstr(b, "\"file\": \"");
if (!b) {
lwsl_notice("Can't find \"file\": JSON\n");
return 1;
}
vhd->autonomous_update = 1;
if (pss->scan_state == SCAN_STATE_NONE)
vhd->autonomous_update_sampled = 1;
b += 9;
n = 0;
while ((*b != '\"') && n < sizeof(p) - 1)
p[n++] = *b++;
p[n] = '\0';
vhd->part = ota_choose_part();
if (!vhd->part)
return 1;
if (client_connection(vhd, p))
vhd->autonomous_update = 0;
break;
start_le:
lws_esp32.acme = 1; /* hold off scanning */
puts(in);
/*
* {"job":"start-le","cn":"home.warmcat.com",
* "email":"andy@warmcat.com", "staging":"true"}
*/
if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
lwsl_err("Unable to open nvs\n");
break;
}
n = 0;
b = strstr(in, ",\"cn\":\"");
if (b) {
b += 7;
while (*b && *b != '\"' && n < sizeof(lws_esp32.le_dns) - 1)
lws_esp32.le_dns[n++] = *b++;
}
lws_esp32.le_dns[n] = '\0';
lws_nvs_set_str(nvh, "acme-cn", lws_esp32.le_dns);
n = 0;
b = strstr(in, ",\"email\":\"");
if (b) {
b += 10;
while (*b && *b != '\"' && n < sizeof(lws_esp32.le_email) - 1)
lws_esp32.le_email[n++] = *b++;
}
lws_esp32.le_email[n] = '\0';
lws_nvs_set_str(nvh, "acme-email", lws_esp32.le_email);
nvs_commit(nvh);
nvs_close(nvh);
n = 1;
b = strstr(in, ",\"staging\":\"");
if (b)
lwsl_notice("staging: %s\n", b);
if (b && b[12] == 'f')
n = 0;
lwsl_notice("cn: %s, email: %s, staging: %d\n", lws_esp32.le_dns, lws_esp32.le_email, n);
{
struct lws_acme_cert_aging_args caa;
memset(&caa, 0, sizeof(caa));
caa.vh = vhd->vhost;
caa.element_overrides[LWS_TLS_REQ_ELEMENT_COMMON_NAME] = lws_esp32.le_dns;
caa.element_overrides[LWS_TLS_REQ_ELEMENT_EMAIL] = lws_esp32.le_email;
if (n)
caa.element_overrides[LWS_TLS_SET_DIR_URL] =
"https://acme-staging.api.letsencrypt.org/directory"; /* staging */
else
caa.element_overrides[LWS_TLS_SET_DIR_URL] =
"https://acme-v01.api.letsencrypt.org/directory"; /* real */
lws_callback_vhost_protocols_vhost(vhd->vhost,
LWS_CALLBACK_VHOST_CERT_AGING,
(void *)&caa, 0);
}
break;
}
case LWS_CALLBACK_CLOSED:
{
struct per_session_data__esplws_scan **p = &vhd->live_pss_list;
while (*p) {
if ((*p) == pss) {
*p = pss->next;
continue;
}
p = &((*p)->next);
}
vhd->count_live_pss--;
}
if (!vhd->live_pss_list)
xTimerStop(vhd->timer, 0);
break;
/* "factory" POST handling */
case LWS_CALLBACK_HTTP_BODY:
/* create the POST argument parser if not already existing */
if (!pss->spa) {
pss->spa = lws_spa_create(wsi, param_names,
LWS_ARRAY_SIZE(param_names), 1024,
file_upload_cb, pss);
if (!pss->spa)
return -1;
pss->filename[0] = '\0';
pss->file_length = 0;
}
//puts((const char *)in);
/* let it parse the POST data */
if (lws_spa_process(pss->spa, in, len))
return -1;
break;
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (scan)\n");
/* call to inform no more payload data coming */
lws_spa_finalize(pss->spa);
for (n = 0; n < LWS_ARRAY_SIZE(param_names); n++)
if (lws_spa_get_string(pss->spa, n))
lwsl_notice(" Param %s: %s\n", param_names[n],
lws_spa_get_string(pss->spa, n));
else
lwsl_notice(" Param %s: (none)\n",
param_names[n]);
if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
lwsl_err("Unable to open nvs\n");
break;
}
if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
if (lws_spa_get_string(pss->spa, EPN_SERIAL)) {
if (lws_nvs_set_str(nvh, "serial", lws_spa_get_string(pss->spa, EPN_SERIAL)) != ESP_OK) {
lwsl_err("Unable to store serial in nvm\n");
goto bail_nvs;
}
nvs_commit(nvh);
}
if (lws_spa_get_string(pss->spa, EPN_OPTS)) {
if (lws_nvs_set_str(nvh, "opts", lws_spa_get_string(pss->spa, EPN_OPTS)) != ESP_OK) {
lwsl_err("Unable to store options in nvm\n");
goto bail_nvs;
}
nvs_commit(nvh);
}
}
if (lws_spa_get_string(pss->spa, EPN_GROUP)) {
if (lws_nvs_set_str(nvh, "group", lws_spa_get_string(pss->spa, EPN_GROUP)) != ESP_OK) {
lwsl_err("Unable to store group in nvm\n");
goto bail_nvs;
}
nvs_commit(nvh);
}
if (lws_spa_get_string(pss->spa, EPN_ROLE)) {
if (lws_nvs_set_str(nvh, "role", lws_spa_get_string(pss->spa, EPN_ROLE)) != ESP_OK) {
lwsl_err("Unable to store group in nvm\n");
goto bail_nvs;
}
nvs_commit(nvh);
}
nvs_close(nvh);
pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
"<html>OK</html>");
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
goto bail;
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
(unsigned char *)"text/html", 9, &p, end))
goto bail;
if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
goto bail;
if (lws_finalize_http_header(wsi, &p, end))
goto bail;
n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
goto bail;
case LWS_CALLBACK_HTTP_WRITEABLE:
lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
pss->result_len);
if (!pss->result_len)
break;
n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
pss->result_len, LWS_WRITE_HTTP);
if (n < 0)
return 1;
vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(3000), 0, vhd,
(TimerCallbackFunction_t)reboot_timer_cb);
xTimerStart(vhd->reboot_timer, 0);
return 1; // hang up since we will reset
/* ----- client handling ----- */
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_notice("Client connection error %s\n", (char *)in);
vhd->cwsi = NULL;
break;
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
if (!vhd->autonomous_update)
break;
{
char pp[20];
if (lws_hdr_copy(wsi, pp, sizeof(pp) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH) < 0)
return -1;
vhd->content_length = atoi(pp);
if (vhd->content_length <= 0 ||
vhd->content_length > vhd->part->size)
return -1;
if (esp_ota_begin(vhd->part, (long)-1, &vhd->otahandle) != ESP_OK) {
lwsl_err("OTA: Failed to begin\n");
return 1;
}
vhd->file_length = 0;
break;
}
break;
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
//lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: %ld\n",
// (long)len);
if (!vhd->autonomous_update) {
if (sizeof(vhd->json) - vhd->json_len - 1 < len)
len = sizeof(vhd->json) - vhd->json_len - 1;
memcpy(vhd->json + vhd->json_len, in, len);
vhd->json_len += len;
vhd->json[vhd->json_len] = '\0';
break;
}
/* autonomous download */
if (vhd->file_length + len > vhd->part->size) {
lwsl_err("OTA: incoming file too large\n");
goto abort_ota;
}
lwsl_debug("writing 0x%lx... 0x%lx\n",
vhd->part->address + vhd->file_length,
vhd->part->address + vhd->file_length + len);
if (esp_ota_write(vhd->otahandle, in, len) != ESP_OK) {
lwsl_err("OTA: Failed to write\n");
goto abort_ota;
}
vhd->file_length += len;
lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
break;
abort_ota:
esp_ota_end(vhd->otahandle);
vhd->otahandle = 0;
vhd->autonomous_update = 0;
return 1;
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
{
char *px = (char *)pss->buffer + LWS_PRE;
int lenx = sizeof(pss->buffer) - LWS_PRE - 1;
//lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: %d\n", len);
if (lws_http_client_read(wsi, &px, &lenx) < 0)
return -1;
}
break;
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
vhd->cwsi = NULL;
if (!vhd->autonomous_update) {
vhd->checked_updates = 1;
puts(vhd->json);
return -1;
}
/* autonomous download */
lwsl_notice("auton complete\n");
if (esp_ota_end(vhd->otahandle) != ESP_OK) {
lwsl_err("OTA: end failed\n");
return 1;
}
if (esp_ota_set_boot_partition(vhd->part) != ESP_OK) {
lwsl_err("OTA: set boot part failed\n");
return 1;
}
vhd->otahandle = 0;
vhd->autonomous_update = 0;
vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
(TimerCallbackFunction_t)reboot_timer_cb);
xTimerStart(vhd->reboot_timer, 0);
return -1;
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
lwsl_notice("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
break;
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
/* called when our wsi user_space is going to be destroyed */
if (pss->spa) {
lws_spa_destroy(pss->spa);
pss->spa = NULL;
}
break;
default:
break;
}
return 0;
bail:
return 1;
}
#define LWS_PLUGIN_PROTOCOL_ESPLWS_SCAN \
{ \
"esplws-scan", \
callback_esplws_scan, \
sizeof(struct per_session_data__esplws_scan), \
1024, 0, NULL, 900 \
}