1 /*
2 * libwebsockets - small server side websockets and web server implementation
3 *
4 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25 #include <string.h>
26 #include <nvs.h>
27 #include <esp_ota_ops.h>
28
29 typedef enum {
30 SCAN_STATE_NONE,
31 SCAN_STATE_INITIAL,
32 SCAN_STATE_INITIAL_MANIFEST,
33 SCAN_STATE_KNOWN,
34 SCAN_STATE_LIST,
35 SCAN_STATE_FINAL
36 } scan_state;
37
38 struct store_json {
39 const char *j;
40 const char *nvs;
41 };
42
43 struct per_session_data__esplws_scan {
44 struct per_session_data__esplws_scan *next;
45 scan_state scan_state;
46 struct timeval last_send;
47
48 struct lws_spa *spa;
49 char filename[32];
50 char result[LWS_PRE + 512];
51 unsigned char buffer[4096];
52 int result_len;
53 int filename_length;
54 long file_length;
55 nvs_handle nvh;
56
57 char ap_record;
58 unsigned char subsequent:1;
59 unsigned char changed_partway:1;
60 };
61
62 #define max_aps 12
63
64 struct per_vhost_data__esplws_scan {
65 wifi_ap_record_t ap_records[10];
66 TimerHandle_t timer, reboot_timer;
67 struct per_session_data__esplws_scan *live_pss_list;
68 struct lws_context *context;
69 struct lws_vhost *vhost;
70 const struct lws_protocols *protocol;
71 struct lws_wifi_scan *known_aps_list;
72
73 const esp_partition_t *part;
74 esp_ota_handle_t otahandle;
75 long file_length;
76 long content_length;
77
78 int cert_remaining_days;
79
80 struct lws *cwsi;
81 char json[2048];
82 int json_len;
83
84 int acme_state;
85 char acme_msg[256];
86
87 uint16_t count_ap_records;
88 char count_live_pss;
89 unsigned char scan_ongoing:1;
90 unsigned char completed_any_scan:1;
91 unsigned char reboot:1;
92 unsigned char changed_settings:1;
93 unsigned char checked_updates:1;
94 unsigned char autonomous_update:1;
95 unsigned char autonomous_update_sampled:1;
96 };
97
98 static const struct store_json store_json[] = {
99 { "\"ssid0\":\"", "0ssid" },
100 { ",\"pw0\":\"", "0password" },
101 { "\"ssid1\":\"", "1ssid" },
102 { ",\"pw1\":\"", "1password" },
103 { "\"ssid2\":\"", "2ssid" },
104 { ",\"pw2\":\"", "2password" },
105 { "\"ssid3\":\"", "3ssid" },
106 { ",\"pw3\":\"", "3password" },
107 { ",\"access_pw\":\"", "access_pw" },
108 { "{\"group\":\"", "group" },
109 { "{\"role\":\"", "role" },
110 { ",\"region\":\"", "region" },
111 };
112
113 static wifi_scan_config_t scan_config = {
114 .ssid = 0,
115 .bssid = 0,
116 .channel = 0,
117 .show_hidden = true
118 };
119
120 const esp_partition_t *
121 ota_choose_part(void);
122
123 static const char * const param_names[] = {
124 "text",
125 "pub",
126 "pri",
127 "serial",
128 "opts",
129 "group",
130 "role",
131 "updsettings",
132 };
133
134 enum enum_param_names {
135 EPN_TEXT,
136 EPN_PUB,
137 EPN_PRI,
138 EPN_SERIAL,
139 EPN_OPTS,
140 EPN_GROUP,
141 EPN_ROLE,
142 EPN_UPDSETTINGS,
143 };
144
145
146 static void
147 scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v);
148
149 static int
esplws_simple_arg(char * dest,int len,const char * in,const char * match)150 esplws_simple_arg(char *dest, int len, const char *in, const char *match)
151 {
152 const char *p = strstr(in, match);
153 int n = 0;
154
155 if (!p)
156 return 1;
157
158 p += strlen(match);
159 while (*p && *p != '\"' && n < len - 1)
160 dest[n++] = *p++;
161 dest[n] = '\0';
162
163 return 0;
164 }
165
166 static void
scan_start(struct per_vhost_data__esplws_scan * vhd)167 scan_start(struct per_vhost_data__esplws_scan *vhd)
168 {
169 int n;
170
171 if (vhd->reboot)
172 esp_restart();
173
174 if (vhd->scan_ongoing)
175 return;
176
177 if (lws_esp32.acme)
178 return;
179
180 if (lws_esp32.upload)
181 return;
182
183 vhd->scan_ongoing = 1;
184 lws_esp32.scan_consumer = scan_finished;
185 lws_esp32.scan_consumer_arg = vhd;
186 n = esp_wifi_scan_start(&scan_config, false);
187 if (n != ESP_OK)
188 lwsl_err("scan start failed %d\n", n);
189 }
190
191 static int scan_defer;
192
timer_cb(TimerHandle_t t)193 static void timer_cb(TimerHandle_t t)
194 {
195 struct per_vhost_data__esplws_scan *vhd = pvTimerGetTimerID(t);
196
197 // if (!lws_esp32.inet && ((scan_defer++) & 1))
198 /*
199 * AP mode + scan does not work well on ESP32... if we didn't connect to an AP
200 * ourselves, just scan once at boot. Then leave us on the AP channel.
201 *
202 * Do the callback for everyone to keep the heartbeat alive.
203 */
204 if (!lws_esp32.inet && scan_defer++) {
205 lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
206
207 return;
208 }
209
210 scan_start(vhd);
211 }
212
reboot_timer_cb(TimerHandle_t t)213 static void reboot_timer_cb(TimerHandle_t t)
214 {
215 esp_restart();
216 }
217
218 static int
client_connection(struct per_vhost_data__esplws_scan * vhd,const char * file)219 client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
220 {
221 #if defined(CONFIG_LWS_IS_FACTORY_APPLICATION) && defined(CONFIG_LWS_OTA_SERVER_BASE_URL) && \
222 defined(CONFIG_LWS_OTA_SERVER_FQDN)
223 static struct lws_client_connect_info i;
224 char path[256];
225
226 memset(&i, 0, sizeof i);
227
228 snprintf(path, sizeof(path) - 1, CONFIG_LWS_OTA_SERVER_BASE_URL "/" CONFIG_LWS_MODEL_NAME "/%s", file);
229
230 lwsl_notice("Fetching %s\n", path);
231
232 i.port = 443;
233 i.context = vhd->context;
234 i.address = CONFIG_LWS_OTA_SERVER_FQDN;
235 i.ssl_connection = 1;
236 i.host = i.address;
237 i.origin = i.host;
238 i.vhost = vhd->vhost;
239 i.method = "GET";
240 i.path = path;
241 i.protocol = "esplws-scan";
242 i.pwsi = &vhd->cwsi;
243
244 vhd->cwsi = lws_client_connect_via_info(&i);
245 if (!vhd->cwsi) {
246 lwsl_notice("NULL return\n");
247 return 1; /* fail */
248 }
249 #endif
250 return 0; /* ongoing */
251 }
252
253 static int
lws_wifi_scan_rssi(struct lws_wifi_scan * p)254 lws_wifi_scan_rssi(struct lws_wifi_scan *p)
255 {
256 if (!p->count)
257 return -127;
258
259 return p->rssi / p->count;
260 }
261
262 /*
263 * Insert new lws_wifi_scan into linkedlist in rssi-sorted order, trimming the
264 * list if needed to keep it at or below max_aps entries.
265 */
266
267 static int
lws_wifi_scan_insert_trim(struct lws_wifi_scan ** list,struct lws_wifi_scan * ns)268 lws_wifi_scan_insert_trim(struct lws_wifi_scan **list, struct lws_wifi_scan *ns)
269 {
270 int count = 0, ins = 1, worst;
271 struct lws_wifi_scan *newlist, **pworst, *pp1;
272
273 lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
274 /* try to find existing match */
275 if (!strcmp((*pp)->ssid, ns->ssid) &&
276 !memcmp((*pp)->bssid, ns->bssid, 6)) {
277 if ((*pp)->count > 127) {
278 (*pp)->count /= 2;
279 (*pp)->rssi /= 2;
280 }
281 (*pp)->rssi += ns->rssi;
282 (*pp)->count++;
283 ins = 0;
284 break;
285 }
286 } lws_end_foreach_llp(pp, next);
287
288 if (ins) {
289 lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
290 /* trim any excess guys */
291 if (count++ >= max_aps - 1) {
292 pp1 = *pp;
293 *pp = (*pp)->next;
294 free(pp1);
295 continue; /* stay where we are */
296 }
297 } lws_end_foreach_llp(pp, next);
298
299 /* we are inserting... so alloc a copy of him */
300 pp1 = malloc(sizeof(*pp1));
301 if (!pp1)
302 return -1;
303
304 memcpy(pp1, ns, sizeof(*pp1));
305 pp1->next = *list;
306 *list = pp1;
307 }
308
309 /* sort the list ... worst first, but added at the newlist head */
310
311 newlist = NULL;
312
313 /* while anybody left on the old list */
314 while (*list) {
315 worst = 0;
316 pworst = NULL;
317
318 /* who is the worst guy still left on the old list? */
319 lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
320 if (lws_wifi_scan_rssi(*pp) <= worst) {
321 worst = lws_wifi_scan_rssi(*pp);
322 pworst = pp;
323 }
324 } lws_end_foreach_llp(pp, next);
325
326 if (pworst) {
327 /* move the worst to the head of the new list */
328 pp1 = *pworst;
329 *pworst = (*pworst)->next;
330 pp1->next = newlist;
331 newlist = pp1;
332 }
333 }
334
335 *list = newlist;
336
337 return 0;
338 }
339
340 static void
scan_finished(uint16_t count,wifi_ap_record_t * recs,void * v)341 scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v)
342 {
343 struct per_vhost_data__esplws_scan *vhd = v;
344 struct per_session_data__esplws_scan *p = vhd->live_pss_list;
345 struct lws_wifi_scan lws;
346 wifi_ap_record_t *r;
347 int m;
348
349 lwsl_notice("%s: count %d\n", __func__, count);
350
351 vhd->scan_ongoing = 0;
352
353 if (count < LWS_ARRAY_SIZE(vhd->ap_records))
354 vhd->count_ap_records = count;
355 else
356 vhd->count_ap_records = LWS_ARRAY_SIZE(vhd->ap_records);
357
358 memcpy(vhd->ap_records, recs, vhd->count_ap_records * sizeof(*recs));
359
360 while (p) {
361 if (p->scan_state != SCAN_STATE_INITIAL &&
362 p->scan_state != SCAN_STATE_NONE)
363 p->changed_partway = 1;
364 else
365 p->scan_state = SCAN_STATE_INITIAL;
366 p = p->next;
367 }
368
369 /* convert to generic, cumulative scan results */
370
371 for (m = 0; m < vhd->count_ap_records; m++) {
372
373 r = &vhd->ap_records[m];
374
375 lws.authmode = r->authmode;
376 lws.channel = r->primary;
377 lws.rssi = r->rssi;
378 lws.count = 1;
379 memcpy(&lws.bssid, r->bssid, 6);
380 lws_strncpy(lws.ssid, (const char *)r->ssid, sizeof(lws.ssid));
381
382 lws_wifi_scan_insert_trim(&vhd->known_aps_list, &lws);
383 }
384
385 lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
386
387 if (lws_esp32.inet && !vhd->cwsi && !vhd->checked_updates)
388 client_connection(vhd, "manifest.json");
389
390 if (vhd->changed_settings) {
391 lws_esp32_wlan_nvs_get(1);
392 vhd->changed_settings = 0;
393 } else
394 esp_wifi_connect();
395 }
396
397 static const char *ssl_names[] = { "ap-cert.pem", "ap-key.pem" };
398
399 static int
file_upload_cb(void * data,const char * name,const char * filename,char * buf,int len,enum lws_spa_fileupload_states state)400 file_upload_cb(void *data, const char *name, const char *filename,
401 char *buf, int len, enum lws_spa_fileupload_states state)
402 {
403 struct per_session_data__esplws_scan *pss =
404 (struct per_session_data__esplws_scan *)data;
405 int n;
406
407 switch (state) {
408 case LWS_UFS_OPEN:
409 if (lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
410 return -1;
411
412 lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
413 lws_strncpy(pss->filename, filename, sizeof(pss->filename));
414 if (!strcmp(name, "pub") || !strcmp(name, "pri")) {
415 if (nvs_open("lws-station", NVS_READWRITE, &pss->nvh))
416 return 1;
417 } else
418 return 1;
419 pss->file_length = 0;
420 break;
421
422 case LWS_UFS_FINAL_CONTENT:
423 case LWS_UFS_CONTENT:
424 if (len) {
425 /* if the file length is too big, drop it */
426 if (pss->file_length + len > sizeof(pss->buffer))
427 return 1;
428
429 memcpy(pss->buffer + pss->file_length, buf, len);
430 }
431 pss->file_length += len;
432
433 if (state == LWS_UFS_CONTENT)
434 break;
435
436 lwsl_notice("LWS_UFS_FINAL_CONTENT\n");
437 n = 0;
438 if (!strcmp(name, "pri"))
439 n = 1;
440 lwsl_notice("writing %s\n", ssl_names[n]);
441 n = nvs_set_blob(pss->nvh, ssl_names[n], pss->buffer, pss->file_length);
442 if (n == ESP_OK)
443 nvs_commit(pss->nvh);
444 nvs_close(pss->nvh);
445 if (n != ESP_OK)
446 return 1;
447 break;
448 }
449
450 return 0;
451 }
452
453 static int
callback_esplws_scan(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)454 callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
455 void *user, void *in, size_t len)
456 {
457 struct per_session_data__esplws_scan *pss =
458 (struct per_session_data__esplws_scan *)user;
459 struct per_vhost_data__esplws_scan *vhd =
460 (struct per_vhost_data__esplws_scan *)
461 lws_protocol_vh_priv_get(lws_get_vhost(wsi),
462 lws_get_protocol(wsi));
463 unsigned char *start = pss->buffer + LWS_PRE - 1, *p = start,
464 *end = pss->buffer + sizeof(pss->buffer) - 1;
465 union lws_tls_cert_info_results ir;
466 struct lws_wifi_scan *lwscan;
467 char subject[64];
468 int n, m;
469 nvs_handle nvh;
470 size_t s;
471
472
473 switch (reason) {
474
475 case LWS_CALLBACK_PROTOCOL_INIT:
476 vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
477 lws_get_protocol(wsi),
478 sizeof(struct per_vhost_data__esplws_scan));
479 vhd->context = lws_get_context(wsi);
480 vhd->protocol = lws_get_protocol(wsi);
481 vhd->vhost = lws_get_vhost(wsi);
482 vhd->timer = xTimerCreate("x", pdMS_TO_TICKS(10000), 1, vhd,
483 (TimerCallbackFunction_t)timer_cb);
484 vhd->scan_ongoing = 0;
485 strcpy(vhd->json, " { }");
486 // scan_start(vhd);
487 break;
488
489 case LWS_CALLBACK_PROTOCOL_DESTROY:
490 if (!vhd)
491 break;
492 xTimerStop(vhd->timer, 0);
493 xTimerDelete(vhd->timer, 0);
494 break;
495
496 case LWS_CALLBACK_ESTABLISHED:
497 lwsl_notice("%s: ESTABLISHED\n", __func__);
498 if (!vhd->live_pss_list) {
499 // scan_start(vhd);
500 xTimerStart(vhd->timer, 0);
501 }
502 vhd->count_live_pss++;
503 pss->next = vhd->live_pss_list;
504 vhd->live_pss_list = pss;
505 /* if we have scan results, update them. Otherwise wait */
506 // if (vhd->count_ap_records) {
507 pss->scan_state = SCAN_STATE_INITIAL;
508 lws_callback_on_writable(wsi);
509 // }
510 break;
511
512 case LWS_CALLBACK_SERVER_WRITEABLE:
513 if (vhd->autonomous_update_sampled) {
514 p += snprintf((char *)p, end - p,
515 " {\n \"auton\":\"1\",\n \"pos\": \"%ld\",\n"
516 " \"len\":\"%ld\"\n}\n",
517 vhd->file_length,
518 vhd->content_length);
519
520 n = LWS_WRITE_TEXT;
521 goto issue;
522 }
523
524 switch (pss->scan_state) {
525 struct timeval t;
526 uint8_t mac[6];
527 struct lws_esp32_image i;
528 char img_factory[384], img_ota[384], group[16], role[16];
529 int grt;
530
531 case SCAN_STATE_NONE:
532
533 /* fallthru */
534
535 case SCAN_STATE_INITIAL:
536
537 gettimeofday(&t, NULL);
538 // if (t.tv_sec - pss->last_send.tv_sec < 10)
539 // return 0;
540
541 pss->last_send = t;
542
543 if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
544 lwsl_err("unable to open nvs\n");
545 return -1;
546 }
547 n = 0;
548 if (nvs_get_blob(nvh, "ap-cert.pem", NULL, &s) == ESP_OK)
549 n = 1;
550 if (nvs_get_blob(nvh, "ap-key.pem", NULL, &s) == ESP_OK)
551 n |= 2;
552 s = sizeof(group) - 1;
553 group[0] = '\0';
554 role[0] = '\0';
555 nvs_get_str(nvh, "group", group, &s);
556 nvs_get_str(nvh, "role", role, &s);
557
558 nvs_close(nvh);
559
560 ir.ns.name[0] = '\0';
561 subject[0] = '\0';
562
563 if (t.tv_sec > 1464083026 &&
564 !lws_tls_vhost_cert_info(vhd->vhost,
565 LWS_TLS_CERT_INFO_VALIDITY_TO, &ir, 0)) {
566 vhd->cert_remaining_days =
567 (ir.time - t.tv_sec) / (24 * 3600);
568 ir.ns.name[0] = '\0';
569 lws_tls_vhost_cert_info(vhd->vhost,
570 LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
571 sizeof(ir.ns.name));
572 lws_strncpy(subject, ir.ns.name, sizeof(subject));
573
574 ir.ns.name[0] = '\0';
575 lws_tls_vhost_cert_info(vhd->vhost,
576 LWS_TLS_CERT_INFO_ISSUER_NAME, &ir,
577 sizeof(ir.ns.name));
578 }
579
580 /*
581 * this value in the JSON is just
582 * used for UI indication. Each conditional feature confirms
583 * it itself before it allows itself to be used.
584 */
585
586 grt = lws_esp32_get_reboot_type();
587
588 esp_efuse_mac_get_default(mac);
589 strcpy(img_factory, " { \"date\": \"Empty\" }");
590 strcpy(img_ota, " { \"date\": \"Empty\" }");
591
592 // if (grt != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
593 lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
594 ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL), &i,
595 img_factory, sizeof(img_factory) - 1);
596 img_factory[sizeof(img_factory) - 1] = '\0';
597 if (img_factory[0] == 0xff || strlen(img_factory) < 8)
598 strcpy(img_factory, " { \"date\": \"Empty\" }");
599
600 lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
601 ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL), &i,
602 img_ota, sizeof(img_ota) - 1);
603 img_ota[sizeof(img_ota) - 1] = '\0';
604 if (img_ota[0] == 0xff || strlen(img_ota) < 8)
605 strcpy(img_ota, " { \"date\": \"Empty\" }");
606 // }
607
608 p += snprintf((char *)p, end - p,
609 "{ \"model\":\"%s\",\n"
610 " \"forced_button\":\"%d\",\n"
611 " \"serial\":\"%s\",\n"
612 " \"opts\":\"%s\",\n"
613 " \"host\":\"%s-%s\",\n"
614 " \"region\":\"%d\",\n"
615 " \"ssl_pub\":\"%d\",\n"
616 " \"ssl_pri\":\"%d\",\n"
617 " \"mac\":\"%02X%02X%02X%02X%02X%02X\",\n"
618 " \"ssid\":\"%s\",\n"
619 " \"conn_ip\":\"%s\",\n"
620 " \"conn_mask\":\"%s\",\n"
621 " \"conn_gw\":\"%s\",\n"
622 " \"certdays\":\"%d\",\n"
623 " \"unixtime\":\"%llu\",\n"
624 " \"certissuer\":\"%s\",\n"
625 " \"certsubject\":\"%s\",\n"
626 " \"le_dns\":\"%s\",\n"
627 " \"le_email\":\"%s\",\n"
628 " \"acme_state\":\"%d\",\n"
629 " \"acme_msg\":\"%s\",\n"
630 " \"button\":\"%d\",\n"
631 " \"group\":\"%s\",\n"
632 " \"role\":\"%s\",\n",
633 lws_esp32.model,
634 grt == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON,
635 lws_esp32.serial,
636 lws_esp32.opts,
637 lws_esp32.model, lws_esp32.serial,
638 lws_esp32.region,
639 n & 1, (n >> 1) & 1,
640 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] | 1,
641 lws_esp32.active_ssid,
642 lws_esp32.sta_ip,
643 lws_esp32.sta_mask,
644 lws_esp32.sta_gw,
645 vhd->cert_remaining_days,
646 (unsigned long long)t.tv_sec,
647 ir.ns.name, subject,
648 lws_esp32.le_dns,
649 lws_esp32.le_email,
650 vhd->acme_state,
651 vhd->acme_msg,
652 ((volatile struct lws_esp32 *)(&lws_esp32))->button_is_down,
653 group, role);
654 p += snprintf((char *)p, end - p,
655 " \"img_factory\": %s,\n"
656 " \"img_ota\": %s,\n",
657 img_factory,
658 img_ota
659 );
660
661
662 n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;
663 pss->scan_state = SCAN_STATE_INITIAL_MANIFEST;
664 pss->ap_record = 0;
665 pss->subsequent = 0;
666 break;
667
668 case SCAN_STATE_INITIAL_MANIFEST:
669 p += snprintf((char *)p, end - p,
670 " \"latest\": %s,\n"
671 " \"inet\":\"%d\",\n",
672 vhd->json,
673 lws_esp32.inet
674 );
675
676 p += snprintf((char *)p, end - p,
677 " \"known\":[\n");
678
679 n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
680 pss->scan_state = SCAN_STATE_KNOWN;
681 break;
682
683 case SCAN_STATE_KNOWN:
684 if (nvs_open("lws-station", NVS_READONLY, &nvh)) {
685 lwsl_notice("unable to open nvh\n");
686 return -1;
687 }
688
689 for (m = 0; m < 4; m++) {
690 char name[10], ssid[65];
691 unsigned int pp = 0, use = 0;
692
693 if (m)
694 *p++ = ',';
695
696 s = sizeof(ssid) - 1;
697 ssid[0] = '\0';
698 lws_snprintf(name, sizeof(name) - 1, "%dssid", m);
699 nvs_get_str(nvh, name, ssid, &s);
700 lws_snprintf(name, sizeof(name) - 1, "%dpassword", m);
701 s = 10;
702 nvs_get_str(nvh, name, NULL, &s);
703 pp = !!s;
704 lws_snprintf(name, sizeof(name) - 1, "%duse", m);
705 nvs_get_u32(nvh, name, &use);
706
707 p += snprintf((char *)p, end - p,
708 "{\"ssid\":\"%s\",\n"
709 " \"pp\":\"%u\",\n"
710 "\"use\":\"%u\"}\n",
711 ssid, pp, use);
712 }
713 nvs_close(nvh);
714 pss->ap_record = 0;
715
716 p += snprintf((char *)p, end - p,
717 "], \"aps\":[\n");
718
719 n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
720 pss->scan_state = SCAN_STATE_LIST;
721 break;
722
723 case SCAN_STATE_LIST:
724 lwscan = vhd->known_aps_list;
725
726 n = pss->ap_record;
727 while (lwscan && n--)
728 lwscan = lwscan->next;
729
730 for (m = 0; m < 6; m++) {
731 n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
732 if (!lwscan)
733 goto scan_state_final;
734
735 if (pss->subsequent)
736 *p++ = ',';
737 pss->subsequent = 1;
738 pss->ap_record++;
739
740 p += snprintf((char *)p, end - p,
741 "{\"ssid\":\"%s\",\n"
742 "\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\",\n"
743 "\"rssi\":\"%d\",\n"
744 "\"chan\":\"%d\",\n"
745 "\"auth\":\"%d\"}\n",
746 lwscan->ssid,
747 lwscan->bssid[0], lwscan->bssid[1], lwscan->bssid[2],
748 lwscan->bssid[3], lwscan->bssid[4], lwscan->bssid[5],
749 lws_wifi_scan_rssi(lwscan),
750 lwscan->channel, lwscan->authmode);
751
752 lwscan = lwscan->next;
753 if (!lwscan)
754 pss->scan_state = SCAN_STATE_FINAL;
755 }
756 break;
757
758 case SCAN_STATE_FINAL:
759 scan_state_final:
760 n = LWS_WRITE_CONTINUATION;
761 p += sprintf((char *)p, "]\n}\n");
762 if (pss->changed_partway) {
763 pss->changed_partway = 0;
764 pss->subsequent = 0;
765 pss->scan_state = SCAN_STATE_INITIAL;
766 } else {
767 pss->scan_state = SCAN_STATE_NONE;
768 vhd->autonomous_update_sampled = vhd->autonomous_update;
769 }
770 break;
771 default:
772 return 0;
773 }
774 issue:
775 m = lws_write(wsi, (unsigned char *)start, p - start, n);
776 if (m < 0) {
777 lwsl_err("ERROR %d writing to di socket\n", m);
778 return -1;
779 }
780
781 if (pss->scan_state != SCAN_STATE_NONE)
782 lws_callback_on_writable(wsi);
783
784 break;
785
786 case LWS_CALLBACK_VHOST_CERT_UPDATE:
787 lwsl_notice("LWS_CALLBACK_VHOST_CERT_UPDATE: %d\n", (int)len);
788 vhd->acme_state = (int)len;
789 if (in) {
790 lws_strncpy(vhd->acme_msg, in, sizeof(vhd->acme_msg));
791 lwsl_notice("acme_msg: %s\n", (char *)in);
792 }
793 lws_callback_on_writable_all_protocol_vhost(vhd->vhost, vhd->protocol);
794 break;
795
796 case LWS_CALLBACK_RECEIVE:
797 {
798 const char *sect = "\"app\": {", *b;
799 nvs_handle nvh;
800 char p[64], use[6];
801 int n, si = -1;
802
803 if (strstr((const char *)in, "identify")) {
804 lws_esp32_identify_physical_device();
805 break;
806 }
807
808 if (vhd->json_len && strstr((const char *)in, "update-factory")) {
809 sect = "\"factory\": {";
810 goto auton;
811 }
812 if (vhd->json_len && strstr((const char *)in, "update-ota"))
813 goto auton;
814
815 if (strstr((const char *)in, "\"reset\""))
816 goto sched_reset;
817
818 if (!strncmp((const char *)in, "{\"job\":\"start-le\"", 17))
819 goto start_le;
820
821
822 if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
823 lwsl_err("Unable to open nvs\n");
824 break;
825 }
826
827 if (!esplws_simple_arg(p, sizeof(p), in, ",\"slot\":\""))
828 si = atoi(p);
829
830 lwsl_notice("si %d\n", si);
831
832 for (n = 0; n < LWS_ARRAY_SIZE(store_json); n++) {
833 if (esplws_simple_arg(p, sizeof(p), in, store_json[n].j))
834 continue;
835
836 /* only change access password if he has physical access to device */
837 if (n == 8 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
838 continue;
839
840 if (lws_nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) {
841 lwsl_err("Unable to store %s in nvm\n", store_json[n].nvs);
842 goto bail_nvs;
843 }
844
845 if (si != -1 && n < 8) {
846 if (!(n & 1)) {
847 lws_strncpy(lws_esp32.ssid[(n >> 1) & 3], p,
848 sizeof(lws_esp32.ssid[0]));
849 lws_snprintf(use, sizeof(use) - 1, "%duse", si);
850 lwsl_notice("resetting %s to 0\n", use);
851 nvs_set_u32(nvh, use, 0);
852
853 } else
854 lws_strncpy(lws_esp32.password[(n >> 1) & 3], p,
855 sizeof(lws_esp32.password[0]));
856 }
857
858 }
859
860 nvs_commit(nvh);
861 nvs_close(nvh);
862
863 if (strstr((const char *)in, "\"factory-reset\"")) {
864 if (lws_esp32_get_reboot_type() ==
865 LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
866
867 lwsl_notice("Doing factory reset\n");
868 ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
869 n = nvs_erase_all(nvh);
870 if (n)
871 lwsl_notice("erase_all failed %d\n", n);
872 nvs_commit(nvh);
873 nvs_close(nvh);
874
875 goto sched_reset;
876 } else
877 lwsl_notice("failed on factory button boot\n");
878 }
879
880 if (vhd->scan_ongoing)
881 vhd->changed_settings = 1;
882 else
883 lws_esp32_wlan_nvs_get(1);
884
885 lwsl_notice("set Join AP info\n");
886 break;
887
888 bail_nvs:
889 nvs_close(nvh);
890
891 return 1;
892
893 sched_reset:
894 vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
895 (TimerCallbackFunction_t)reboot_timer_cb);
896 xTimerStart(vhd->reboot_timer, 0);
897
898 return 1;
899
900 auton:
901 lwsl_notice("Autonomous upload\n");
902 b = strstr(vhd->json, sect);
903 if (!b) {
904 lwsl_notice("Can't find %s in JSON\n", sect);
905 return 1;
906 }
907 b = strstr(b, "\"file\": \"");
908 if (!b) {
909 lwsl_notice("Can't find \"file\": JSON\n");
910 return 1;
911 }
912 vhd->autonomous_update = 1;
913 if (pss->scan_state == SCAN_STATE_NONE)
914 vhd->autonomous_update_sampled = 1;
915 b += 9;
916 n = 0;
917 while ((*b != '\"') && n < sizeof(p) - 1)
918 p[n++] = *b++;
919
920 p[n] = '\0';
921
922 vhd->part = ota_choose_part();
923 if (!vhd->part)
924 return 1;
925
926 if (client_connection(vhd, p))
927 vhd->autonomous_update = 0;
928
929 break;
930
931 start_le:
932 lws_esp32.acme = 1; /* hold off scanning */
933 puts(in);
934 /*
935 * {"job":"start-le","cn":"home.warmcat.com",
936 * "email":"andy@warmcat.com", "staging":"true"}
937 */
938
939 if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
940 lwsl_err("Unable to open nvs\n");
941 break;
942 }
943
944 n = 0;
945 b = strstr(in, ",\"cn\":\"");
946 if (b) {
947 b += 7;
948 while (*b && *b != '\"' && n < sizeof(lws_esp32.le_dns) - 1)
949 lws_esp32.le_dns[n++] = *b++;
950 }
951 lws_esp32.le_dns[n] = '\0';
952
953 lws_nvs_set_str(nvh, "acme-cn", lws_esp32.le_dns);
954 n = 0;
955 b = strstr(in, ",\"email\":\"");
956 if (b) {
957 b += 10;
958 while (*b && *b != '\"' && n < sizeof(lws_esp32.le_email) - 1)
959 lws_esp32.le_email[n++] = *b++;
960 }
961 lws_esp32.le_email[n] = '\0';
962 lws_nvs_set_str(nvh, "acme-email", lws_esp32.le_email);
963 nvs_commit(nvh);
964
965 nvs_close(nvh);
966
967 n = 1;
968 b = strstr(in, ",\"staging\":\"");
969 if (b)
970 lwsl_notice("staging: %s\n", b);
971 if (b && b[12] == 'f')
972 n = 0;
973
974 lwsl_notice("cn: %s, email: %s, staging: %d\n", lws_esp32.le_dns, lws_esp32.le_email, n);
975
976 {
977 struct lws_acme_cert_aging_args caa;
978
979 memset(&caa, 0, sizeof(caa));
980 caa.vh = vhd->vhost;
981
982 caa.element_overrides[LWS_TLS_REQ_ELEMENT_COMMON_NAME] = lws_esp32.le_dns;
983 caa.element_overrides[LWS_TLS_REQ_ELEMENT_EMAIL] = lws_esp32.le_email;
984
985 if (n)
986 caa.element_overrides[LWS_TLS_SET_DIR_URL] =
987 "https://acme-staging.api.letsencrypt.org/directory"; /* staging */
988 else
989 caa.element_overrides[LWS_TLS_SET_DIR_URL] =
990 "https://acme-v01.api.letsencrypt.org/directory"; /* real */
991
992 lws_callback_vhost_protocols_vhost(vhd->vhost,
993 LWS_CALLBACK_VHOST_CERT_AGING,
994 (void *)&caa, 0);
995 }
996
997 break;
998
999 }
1000
1001 case LWS_CALLBACK_CLOSED:
1002 {
1003 struct per_session_data__esplws_scan **p = &vhd->live_pss_list;
1004
1005 while (*p) {
1006 if ((*p) == pss) {
1007 *p = pss->next;
1008 continue;
1009 }
1010
1011 p = &((*p)->next);
1012 }
1013
1014 vhd->count_live_pss--;
1015 }
1016 if (!vhd->live_pss_list)
1017 xTimerStop(vhd->timer, 0);
1018 break;
1019
1020 /* "factory" POST handling */
1021
1022 case LWS_CALLBACK_HTTP_BODY:
1023 /* create the POST argument parser if not already existing */
1024 if (!pss->spa) {
1025 pss->spa = lws_spa_create(wsi, param_names,
1026 LWS_ARRAY_SIZE(param_names), 1024,
1027 file_upload_cb, pss);
1028 if (!pss->spa)
1029 return -1;
1030
1031 pss->filename[0] = '\0';
1032 pss->file_length = 0;
1033 }
1034 //puts((const char *)in);
1035 /* let it parse the POST data */
1036 if (lws_spa_process(pss->spa, in, len))
1037 return -1;
1038 break;
1039
1040 case LWS_CALLBACK_HTTP_BODY_COMPLETION:
1041 lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (scan)\n");
1042 /* call to inform no more payload data coming */
1043 lws_spa_finalize(pss->spa);
1044
1045 for (n = 0; n < LWS_ARRAY_SIZE(param_names); n++)
1046 if (lws_spa_get_string(pss->spa, n))
1047 lwsl_notice(" Param %s: %s\n", param_names[n],
1048 lws_spa_get_string(pss->spa, n));
1049 else
1050 lwsl_notice(" Param %s: (none)\n",
1051 param_names[n]);
1052
1053 if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
1054 lwsl_err("Unable to open nvs\n");
1055 break;
1056 }
1057
1058 if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
1059
1060 if (lws_spa_get_string(pss->spa, EPN_SERIAL)) {
1061 if (lws_nvs_set_str(nvh, "serial", lws_spa_get_string(pss->spa, EPN_SERIAL)) != ESP_OK) {
1062 lwsl_err("Unable to store serial in nvm\n");
1063 goto bail_nvs;
1064 }
1065
1066 nvs_commit(nvh);
1067 }
1068
1069 if (lws_spa_get_string(pss->spa, EPN_OPTS)) {
1070 if (lws_nvs_set_str(nvh, "opts", lws_spa_get_string(pss->spa, EPN_OPTS)) != ESP_OK) {
1071 lwsl_err("Unable to store options in nvm\n");
1072 goto bail_nvs;
1073 }
1074
1075 nvs_commit(nvh);
1076 }
1077 }
1078
1079 if (lws_spa_get_string(pss->spa, EPN_GROUP)) {
1080 if (lws_nvs_set_str(nvh, "group", lws_spa_get_string(pss->spa, EPN_GROUP)) != ESP_OK) {
1081 lwsl_err("Unable to store group in nvm\n");
1082 goto bail_nvs;
1083 }
1084
1085 nvs_commit(nvh);
1086 }
1087
1088 if (lws_spa_get_string(pss->spa, EPN_ROLE)) {
1089 if (lws_nvs_set_str(nvh, "role", lws_spa_get_string(pss->spa, EPN_ROLE)) != ESP_OK) {
1090 lwsl_err("Unable to store group in nvm\n");
1091 goto bail_nvs;
1092 }
1093
1094 nvs_commit(nvh);
1095 }
1096
1097 nvs_close(nvh);
1098
1099 pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
1100 "<html>OK</html>");
1101
1102 if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
1103 goto bail;
1104
1105 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
1106 (unsigned char *)"text/html", 9, &p, end))
1107 goto bail;
1108 if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
1109 goto bail;
1110 if (lws_finalize_http_header(wsi, &p, end))
1111 goto bail;
1112
1113 n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
1114 goto bail;
1115
1116 case LWS_CALLBACK_HTTP_WRITEABLE:
1117 lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
1118 pss->result_len);
1119 if (!pss->result_len)
1120 break;
1121 n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
1122 pss->result_len, LWS_WRITE_HTTP);
1123 if (n < 0)
1124 return 1;
1125
1126 vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(3000), 0, vhd,
1127 (TimerCallbackFunction_t)reboot_timer_cb);
1128 xTimerStart(vhd->reboot_timer, 0);
1129
1130 return 1; // hang up since we will reset
1131
1132 /* ----- client handling ----- */
1133
1134 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
1135 lwsl_notice("Client connection error %s\n", (char *)in);
1136 vhd->cwsi = NULL;
1137 break;
1138
1139 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
1140 if (!vhd->autonomous_update)
1141 break;
1142
1143 {
1144 char pp[20];
1145
1146 if (lws_hdr_copy(wsi, pp, sizeof(pp) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH) < 0)
1147 return -1;
1148
1149 vhd->content_length = atoi(pp);
1150 if (vhd->content_length <= 0 ||
1151 vhd->content_length > vhd->part->size)
1152 return -1;
1153
1154 if (esp_ota_begin(vhd->part, (long)-1, &vhd->otahandle) != ESP_OK) {
1155 lwsl_err("OTA: Failed to begin\n");
1156 return 1;
1157 }
1158
1159 vhd->file_length = 0;
1160 break;
1161 }
1162 break;
1163
1164 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
1165 //lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: %ld\n",
1166 // (long)len);
1167
1168 if (!vhd->autonomous_update) {
1169 if (sizeof(vhd->json) - vhd->json_len - 1 < len)
1170 len = sizeof(vhd->json) - vhd->json_len - 1;
1171 memcpy(vhd->json + vhd->json_len, in, len);
1172 vhd->json_len += len;
1173 vhd->json[vhd->json_len] = '\0';
1174 break;
1175 }
1176
1177 /* autonomous download */
1178
1179
1180 if (vhd->file_length + len > vhd->part->size) {
1181 lwsl_err("OTA: incoming file too large\n");
1182 goto abort_ota;
1183 }
1184
1185 lwsl_debug("writing 0x%lx... 0x%lx\n",
1186 vhd->part->address + vhd->file_length,
1187 vhd->part->address + vhd->file_length + len);
1188 if (esp_ota_write(vhd->otahandle, in, len) != ESP_OK) {
1189 lwsl_err("OTA: Failed to write\n");
1190 goto abort_ota;
1191 }
1192 vhd->file_length += len;
1193
1194 lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
1195 break;
1196
1197 abort_ota:
1198 esp_ota_end(vhd->otahandle);
1199 vhd->otahandle = 0;
1200 vhd->autonomous_update = 0;
1201
1202 return 1;
1203
1204 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
1205 {
1206 char *px = (char *)pss->buffer + LWS_PRE;
1207 int lenx = sizeof(pss->buffer) - LWS_PRE - 1;
1208
1209 //lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: %d\n", len);
1210
1211 if (lws_http_client_read(wsi, &px, &lenx) < 0)
1212 return -1;
1213 }
1214 break;
1215
1216 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
1217 lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
1218 vhd->cwsi = NULL;
1219 if (!vhd->autonomous_update) {
1220
1221 vhd->checked_updates = 1;
1222 puts(vhd->json);
1223 return -1;
1224 }
1225
1226 /* autonomous download */
1227
1228 lwsl_notice("auton complete\n");
1229
1230 if (esp_ota_end(vhd->otahandle) != ESP_OK) {
1231 lwsl_err("OTA: end failed\n");
1232 return 1;
1233 }
1234
1235 if (esp_ota_set_boot_partition(vhd->part) != ESP_OK) {
1236 lwsl_err("OTA: set boot part failed\n");
1237 return 1;
1238 }
1239 vhd->otahandle = 0;
1240 vhd->autonomous_update = 0;
1241
1242 vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
1243 (TimerCallbackFunction_t)reboot_timer_cb);
1244 xTimerStart(vhd->reboot_timer, 0);
1245 return -1;
1246
1247 case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
1248 lwsl_notice("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
1249 break;
1250
1251 case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
1252 /* called when our wsi user_space is going to be destroyed */
1253 if (pss->spa) {
1254 lws_spa_destroy(pss->spa);
1255 pss->spa = NULL;
1256 }
1257 break;
1258
1259 default:
1260 break;
1261 }
1262
1263 return 0;
1264
1265 bail:
1266 return 1;
1267 }
1268
1269 #define LWS_PLUGIN_PROTOCOL_ESPLWS_SCAN \
1270 { \
1271 "esplws-scan", \
1272 callback_esplws_scan, \
1273 sizeof(struct per_session_data__esplws_scan), \
1274 1024, 0, NULL, 900 \
1275 }
1276
1277