esp32: reapply pending count just for esp32
[platform/upstream/libwebsockets.git] / plugins / protocol_esp32_lws_scan.c
1 /*
2  * ESP32 Scan / Factory protocol handler
3  *
4  * Copyright (C) 2017 Andy Green <andy@warmcat.com>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation:
9  *  version 2.1 of the License.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA  02110-1301  USA*
20  *
21  */
22 #include <string.h>
23 #include <nvs.h>
24 #include <esp_ota_ops.h>
25
26 typedef enum {
27         SCAN_STATE_NONE,
28         SCAN_STATE_INITIAL,
29         SCAN_STATE_INITIAL_MANIFEST,
30         SCAN_STATE_LIST,
31         SCAN_STATE_FINAL
32 } scan_state;
33
34 struct store_json {
35         const char *j;
36         const char *nvs;
37 };
38
39 struct per_session_data__esplws_scan {
40         struct per_session_data__esplws_scan *next;
41         scan_state scan_state;
42         struct timeval last_send;
43
44         struct lws_spa *spa;
45         char filename[32];
46         char result[LWS_PRE + 512];
47         unsigned char buffer[4096];
48         int result_len;
49         int filename_length;
50         long file_length;
51         nvs_handle nvh;
52
53         char ap_record;
54         unsigned char subsequent:1;
55         unsigned char changed_partway:1;
56 };
57
58 struct per_vhost_data__esplws_scan {
59         wifi_ap_record_t ap_records[10];
60         TimerHandle_t timer, reboot_timer;
61         struct per_session_data__esplws_scan *live_pss_list;
62         struct lws_context *context;
63         struct lws_vhost *vhost;
64         const struct lws_protocols *protocol;
65
66         const esp_partition_t *part;
67         esp_ota_handle_t otahandle;
68         long file_length;
69         long content_length;
70
71         struct lws *cwsi;
72         char json[1024];
73         int json_len;
74
75         uint16_t count_ap_records;
76         char count_live_pss;
77         unsigned char scan_ongoing:1;
78         unsigned char completed_any_scan:1;
79         unsigned char reboot:1;
80         unsigned char changed_settings:1;
81         unsigned char checked_updates:1;
82         unsigned char autonomous_update:1;
83         unsigned char autonomous_update_sampled:1;
84 };
85
86 static const struct store_json store_json[] = {
87         { "ssid\":\"", "ssid" },
88         { ",\"pw\":\"", "password" },
89         { ",\"access_pw\":\"", "access_pw" },
90         { ",\"region\":\"", "region" },
91 };
92
93 static wifi_scan_config_t scan_config = {
94         .ssid = 0,
95         .bssid = 0,
96         .channel = 0,
97         .show_hidden = true
98 };
99
100 extern void (*lws_cb_scan_done)(void *);
101 extern void *lws_cb_scan_done_arg;
102
103 const esp_partition_t *
104 ota_choose_part(void);
105
106 static const char * const param_names[] = {
107         "text",
108         "pub",
109         "pri",
110         "serial",
111         "opts",
112 };
113
114 enum enum_param_names {
115         EPN_TEXT,
116         EPN_PUB,
117         EPN_PRI,
118         EPN_SERIAL,
119         EPN_OPTS,
120 };
121
122
123 static void
124 scan_finished(void *v);
125
126 static int
127 esplws_simple_arg(char *dest, int len, const char *in, const char *match)
128 {
129        const char *p = strstr(in, match);
130        int n = 0;
131
132        if (!p)
133                return 1;
134
135        p += strlen(match);
136        while (*p && *p != '\"' && n < len - 1)
137                dest[n++] = *p++;
138        dest[n] = '\0';
139
140        return 0;
141 }
142
143 static void
144 scan_start(struct per_vhost_data__esplws_scan *vhd)
145 {
146         int n;
147
148         if (vhd->reboot)
149                 esp_restart();
150
151         if (vhd->scan_ongoing)
152                 return;
153
154         vhd->scan_ongoing = 1;
155         lws_cb_scan_done = scan_finished;
156         lws_cb_scan_done_arg = vhd;
157         n = esp_wifi_scan_start(&scan_config, false);
158         if (n != ESP_OK)
159                 lwsl_err("scan start failed %d\n", n);
160 }
161
162 static void timer_cb(TimerHandle_t t)
163 {
164         struct per_vhost_data__esplws_scan *vhd = pvTimerGetTimerID(t);
165
166         scan_start(vhd);
167 }
168
169 static void reboot_timer_cb(TimerHandle_t t)
170 {
171         esp_restart();
172 }
173
174 static int
175 client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
176 {
177 #if CONFIG_LWS_IS_FACTORY_APPLICATION == 'y' && defined(CONFIG_LWS_OTA_SERVER_BASE_URL) && \
178     defined(CONFIG_LWS_OTA_SERVER_FQDN)
179         static struct lws_client_connect_info i;
180         char path[256];
181
182         memset(&i, 0, sizeof i);
183
184         snprintf(path, sizeof(path) - 1, CONFIG_LWS_OTA_SERVER_BASE_URL "/" CONFIG_LWS_MODEL_NAME "/%s", file);
185
186         lwsl_notice("Fetching %s\n", path);
187
188         i.port = 443;
189         i.context = vhd->context;
190         i.address = CONFIG_LWS_OTA_SERVER_FQDN;
191         i.ssl_connection = 1;
192         i.host = i.address;
193         i.origin = i.host;
194         i.vhost = vhd->vhost;
195         i.method = "GET";
196         i.path = path;
197         i.protocol = "esplws-scan";
198         i.pwsi = &vhd->cwsi;
199
200         vhd->cwsi = lws_client_connect_via_info(&i);
201         if (!vhd->cwsi) {
202                 lwsl_notice("NULL return\n");
203                 return 1; /* fail */
204         }
205 #endif
206         return 0; /* ongoing */
207 }
208
209 static void
210 scan_finished(void *v)
211 {
212         struct per_vhost_data__esplws_scan *vhd = v;
213         struct per_session_data__esplws_scan *p = vhd->live_pss_list;
214
215         vhd->scan_ongoing = 0;
216
217         vhd->count_ap_records = ARRAY_SIZE(vhd->ap_records);
218         if (esp_wifi_scan_get_ap_records(&vhd->count_ap_records, vhd->ap_records) != ESP_OK) {
219                 lwsl_err("%s: failed\n", __func__);
220                 return;
221         }
222         
223         while (p) {
224                 if (p->scan_state != SCAN_STATE_INITIAL && p->scan_state != SCAN_STATE_NONE)
225                         p->changed_partway = 1;
226                 else
227                         p->scan_state = SCAN_STATE_INITIAL;
228                 p = p->next;
229         }
230
231         lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
232
233         if (lws_esp32.inet && !vhd->cwsi && !vhd->checked_updates)
234                 client_connection(vhd, "manifest.json");
235
236         if (vhd->changed_settings) {
237                 lws_esp32_wlan_nvs_get(1);
238                 vhd->changed_settings = 0;
239         } else
240                esp_wifi_connect();
241 }
242
243 static const char *ssl_names[] = { "ssl-pub.der", "ssl-pri.der" };
244
245 static int
246 file_upload_cb(void *data, const char *name, const char *filename,
247                char *buf, int len, enum lws_spa_fileupload_states state)
248 {
249         struct per_session_data__esplws_scan *pss =
250                         (struct per_session_data__esplws_scan *)data;
251         int n;
252
253         switch (state) {
254         case LWS_UFS_OPEN:
255                 if (lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
256                         return -1;
257
258                 lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
259                 strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
260                 if (!strcmp(name, "pub") || !strcmp(name, "pri")) {
261                         if (nvs_open("lws-station", NVS_READWRITE, &pss->nvh))
262                                 return 1;
263                 } else
264                         return 1;
265                 pss->file_length = 0;
266                 break;
267
268         case LWS_UFS_FINAL_CONTENT:
269         case LWS_UFS_CONTENT:
270                 if (len) {
271                         /* if the file length is too big, drop it */
272                         if (pss->file_length + len > sizeof(pss->buffer))
273                                 return 1;
274
275                         memcpy(pss->buffer + pss->file_length, buf, len);
276                 }
277                 pss->file_length += len;
278
279                 if (state == LWS_UFS_CONTENT)
280                         break;
281
282                 lwsl_notice("LWS_UFS_FINAL_CONTENT\n");
283                 n = 0;
284                 if (!strcmp(name, "pri"))
285                         n = 1;
286                 n = nvs_set_blob(pss->nvh, ssl_names[n], pss->buffer, pss->file_length);
287                 if (n == ESP_OK)
288                         nvs_commit(pss->nvh);
289                 nvs_close(pss->nvh);
290                 if (n != ESP_OK)
291                         return 1;
292                 break;
293         }
294
295         return 0;
296 }
297
298 static int
299 callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
300                     void *user, void *in, size_t len)
301 {
302         struct per_session_data__esplws_scan *pss =
303                         (struct per_session_data__esplws_scan *)user;
304         struct per_vhost_data__esplws_scan *vhd =
305                         (struct per_vhost_data__esplws_scan *)
306                         lws_protocol_vh_priv_get(lws_get_vhost(wsi),
307                                         lws_get_protocol(wsi));
308         unsigned char *start = pss->buffer + LWS_PRE - 1, *p = start,
309                       *end = pss->buffer + sizeof(pss->buffer) - 1;
310         wifi_ap_record_t *r;
311         int n, m;
312         nvs_handle nvh;
313         size_t s;
314
315
316         switch (reason) {
317
318         case LWS_CALLBACK_PROTOCOL_INIT:
319                 vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
320                                 lws_get_protocol(wsi),
321                                 sizeof(struct per_vhost_data__esplws_scan));
322                 vhd->context = lws_get_context(wsi);
323                 vhd->protocol = lws_get_protocol(wsi);
324                 vhd->vhost = lws_get_vhost(wsi);
325                 vhd->timer = xTimerCreate("x", pdMS_TO_TICKS(10000), 1, vhd,
326                           (TimerCallbackFunction_t)timer_cb);
327                 xTimerStart(vhd->timer, 0);
328                 vhd->scan_ongoing = 0;
329                 strcpy(vhd->json, " { }");
330                 scan_start(vhd);
331                 break;
332
333         case LWS_CALLBACK_PROTOCOL_DESTROY:
334                 if (!vhd)
335                         break;
336                 xTimerStop(vhd->timer, 0);
337                 xTimerDelete(vhd->timer, 0);
338                 break;
339
340         case LWS_CALLBACK_ESTABLISHED:
341                 vhd->count_live_pss++;
342                 pss->next = vhd->live_pss_list;
343                 vhd->live_pss_list = pss;
344                 /* if we have scan results, update them.  Otherwise wait */
345                 if (vhd->count_ap_records) {
346                         pss->scan_state = SCAN_STATE_INITIAL;
347                         lws_callback_on_writable(wsi);
348                 }
349                 break;
350
351         case LWS_CALLBACK_SERVER_WRITEABLE:
352
353                 if (vhd->autonomous_update_sampled) {
354                         p += snprintf((char *)p, end - p,
355                                       " {\n \"auton\":\"1\",\n \"pos\": \"%ld\",\n"
356                                       " \"len\":\"%ld\"\n}\n",
357                                         vhd->file_length,
358                                         vhd->content_length);
359
360                         n = LWS_WRITE_TEXT;
361                         goto issue;
362                 }
363
364                 switch (pss->scan_state) {
365                         struct timeval t;
366                         char ssid[32];
367                         uint8_t mac[6];
368                         struct lws_esp32_image i;
369                         char img_factory[512], img_ota[512];
370                         int grt;
371
372                 case SCAN_STATE_INITIAL:
373
374                         gettimeofday(&t, NULL);
375                         if (t.tv_sec - pss->last_send.tv_sec < 10)
376                                 return 0;
377
378                         pss->last_send = t;
379
380                         ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
381                         n = 0;
382                         if (nvs_get_blob(nvh, "ssl-pub.der", NULL, &s) == ESP_OK)
383                                 n = 1;
384                         if (nvs_get_blob(nvh, "ssl-pri.der", NULL, &s) == ESP_OK)
385                                 n |= 2;
386                         s = sizeof(ssid) - 1;
387                         ssid[0] = '\0';
388                         nvs_get_str(nvh, "ssid", ssid, &s);
389
390                         nvs_close(nvh);
391
392                         /*
393                          * this value in the JSON is just
394                          * used for UI indication.  Each conditional feature confirms
395                          * it itself before it allows itself to be used.
396                          */
397
398                         grt = lws_esp32_get_reboot_type();
399
400                         esp_efuse_read_mac(mac);
401                         strcpy(img_factory, " { \"date\": \"Empty\" }");
402                         strcpy(img_ota, " { \"date\": \"Empty\" }");
403
404                         lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
405                                 ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL), &i,
406                                 img_factory, sizeof(img_factory));
407                         lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
408                                 ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL), &i,
409                                 img_ota, sizeof(img_ota));
410
411                         p += snprintf((char *)p, end - p,
412                                       "{ \"model\":\"%s\",\n"
413                                       " \"forced_button\":\"%d\",\n"
414                                       " \"serial\":\"%s\",\n"
415                                       " \"opts\":\"%s\",\n"
416                                       " \"host\":\"%s-%s\",\n"
417                                       " \"region\":\"%d\",\n"
418                                       " \"ssl_pub\":\"%d\",\n"
419                                       " \"ssl_pri\":\"%d\",\n"
420                                       " \"mac\":\"%02X%02X%02X%02X%02X%02X\",\n"
421                                       " \"ssid\":\"%s\",\n"
422                                       " \"conn_ip\":\"%s\",\n"
423                                       " \"conn_mask\":\"%s\",\n"
424                                       " \"conn_gw\":\"%s\",\n"
425                                       " \"img_factory\": %s,\n"
426                                       " \"img_ota\": %s,\n",
427                                       lws_esp32.model,
428                                       grt == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON, 
429                                       lws_esp32.serial,
430                                       lws_esp32.opts,
431                                       lws_esp32.model, lws_esp32.serial,
432                                       lws_esp32.region,
433                                       n & 1, (n >> 1) & 1,
434                                       mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] | 1,
435                                       ssid,
436                                       lws_esp32.sta_ip,
437                                       lws_esp32.sta_mask,
438                                       lws_esp32.sta_gw,
439                                         img_factory,
440                                         img_ota
441                                       );
442
443                         n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;
444                         pss->scan_state = SCAN_STATE_INITIAL_MANIFEST;
445                         pss->ap_record = 0;
446                         pss->subsequent = 0;
447                         break;
448
449                 case SCAN_STATE_INITIAL_MANIFEST:
450                         p += snprintf((char *)p, end - p,
451                                       " \"latest\": %s,\n"
452                                       " \"inet\":\"%d\",\n",
453                                         vhd->json,
454                                       lws_esp32.inet
455                                       );
456
457                         p += snprintf((char *)p, end - p,
458                                       " \"aps\":[\n");
459
460                         n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
461                         pss->scan_state = SCAN_STATE_LIST;
462                         break;
463
464                 case SCAN_STATE_LIST:
465                         for (m = 0; m < 6; m++) {
466                                 n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
467                                 if (pss->ap_record >= vhd->count_ap_records)
468                                         goto scan_state_final;
469
470                                 if (pss->subsequent)
471                                         *p++ = ',';
472                                 pss->subsequent = 1;
473
474                                 r = &vhd->ap_records[(int)pss->ap_record++];
475                                 p += snprintf((char *)p, end - p,
476                                               "{\"ssid\":\"%s\",\n"
477                                                "\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\",\n"
478                                                "\"rssi\":\"%d\",\n"
479                                                "\"chan\":\"%d\",\n"
480                                                "\"auth\":\"%d\"}\n",
481                                                 r->ssid,
482                                                 r->bssid[0], r->bssid[1], r->bssid[2],
483                                                 r->bssid[3], r->bssid[4], r->bssid[5],
484                                                 r->rssi, r->primary, r->authmode);
485                                 if (pss->ap_record >= vhd->count_ap_records)
486                                         pss->scan_state = SCAN_STATE_FINAL;
487                         }
488                         break;
489
490                 case SCAN_STATE_FINAL:
491 scan_state_final:
492                         n = LWS_WRITE_CONTINUATION;
493                         p += sprintf((char *)p, "]\n}\n");
494                         if (pss->changed_partway) {
495                                 pss->subsequent = 0;
496                                 pss->scan_state = SCAN_STATE_INITIAL;
497                         } else {
498                                 pss->scan_state = SCAN_STATE_NONE;
499                                 vhd->autonomous_update_sampled = vhd->autonomous_update;
500                         }
501                         break;
502                 default:
503                         return 0;
504                 }
505 issue:
506 //              lwsl_notice("issue: %d (%d)\n", p - start, n);
507                 m = lws_write(wsi, (unsigned char *)start, p - start, n);
508                 if (m < 0) {
509                         lwsl_err("ERROR %d writing to di socket\n", m);
510                         return -1;
511                 }
512
513                 if (pss->scan_state != SCAN_STATE_NONE)
514                         lws_callback_on_writable(wsi);
515
516                 break;
517
518         case LWS_CALLBACK_RECEIVE:
519                 {
520                         const char *sect = "\"app\": {", *b;
521                         nvs_handle nvh;
522                         char p[64];
523                         int n;
524
525                         if (strstr((const char *)in, "identify")) {
526                                 lws_esp32_identify_physical_device();
527                                 break;
528                         }
529
530                         if (vhd->json_len && strstr((const char *)in, "update-factory")) {
531                                 sect = "\"factory\": {";
532                                 goto auton;
533                         }
534                         if (vhd->json_len && strstr((const char *)in, "update-ota"))
535                                 goto auton;
536
537                         if (strstr((const char *)in, "reset"))
538                                 goto sched_reset;
539
540                         if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
541                                 lwsl_err("Unable to open nvs\n");
542                                 break;
543                         }
544
545                         for (n = 0; n < ARRAY_SIZE(store_json); n++) {
546                                 if (esplws_simple_arg(p, sizeof(p), in, store_json[n].j))
547                                         continue;
548
549                                 /* only change access password if he has physical access to device */
550                                 if (n == 2 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
551                                         continue;
552
553                                 lwsl_notice("%s '%s\n", store_json[n].nvs, p);
554
555                                 if (nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) {
556                                         lwsl_err("Unable to store %s in nvm\n", store_json[n].nvs);
557                                         goto bail_nvs;
558                                 }
559                         }
560
561                         nvs_commit(nvh);
562                         nvs_close(nvh);
563
564                         if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
565
566                                 if (strstr((const char *)in, "factory-reset")) {
567                                         ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
568                                         nvs_erase_all(nvh);
569                                         nvs_commit(nvh);
570                                         nvs_close(nvh);
571
572                                         goto sched_reset;
573                                 }
574
575                         }
576
577                         if (vhd->scan_ongoing)
578                                 vhd->changed_settings = 1;
579                         else
580                                 lws_esp32_wlan_nvs_get(1);
581
582                         lwsl_notice("set Join AP info\n");
583                         break;
584
585 bail_nvs:
586                         nvs_close(nvh);
587
588                         return 1;
589
590 sched_reset:
591                         vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
592                                                 (TimerCallbackFunction_t)reboot_timer_cb);
593                         xTimerStart(vhd->reboot_timer, 0);
594
595                         return 1;
596
597 auton:
598                         lwsl_notice("Autonomous upload\n");
599                         b = strstr(vhd->json, sect);
600                         if (!b) {
601                                 lwsl_notice("Can't find %s in JSON\n", sect);
602                                 return 1;
603                         }
604                         b = strstr(b, "\"file\": \"");
605                         if (!b) {
606                                 lwsl_notice("Can't find \"file\": JSON\n");
607                                 return 1;
608                         }
609                         vhd->autonomous_update = 1;
610                         if (pss->scan_state == SCAN_STATE_NONE)
611                                 vhd->autonomous_update_sampled = 1;
612                         b += 9;
613                         n = 0;
614                         while ((*b != '\"') && n < sizeof(p) - 1)
615                                 p[n++] = *b++;
616
617                         p[n] = '\0';
618
619                         vhd->part = ota_choose_part();
620                         if (!vhd->part)
621                                 return 1;
622
623                         if (client_connection(vhd, p))
624                                 vhd->autonomous_update = 0;
625
626                         break;
627                 }
628
629         case LWS_CALLBACK_CLOSED:
630                 {
631                         struct per_session_data__esplws_scan **p = &vhd->live_pss_list;
632
633                         while (*p) {
634                                 if ((*p) == pss) {
635                                         *p = pss->next;
636                                         continue;
637                                 }
638
639                                 p = &((*p)->next);
640                         }
641
642                         vhd->count_live_pss--;
643                 }
644                 break;
645
646         /* "factory" POST handling */
647
648         case LWS_CALLBACK_HTTP_BODY:
649                 /* create the POST argument parser if not already existing */
650                 lwsl_notice("LWS_CALLBACK_HTTP_BODY (scan)\n");
651                 if (!pss->spa) {
652                         pss->spa = lws_spa_create(wsi, param_names,
653                                         ARRAY_SIZE(param_names), 1024,
654                                         file_upload_cb, pss);
655                         if (!pss->spa)
656                                 return -1;
657
658                         pss->filename[0] = '\0';
659                         pss->file_length = 0;
660                 }
661
662                 /* let it parse the POST data */
663                 if (lws_spa_process(pss->spa, in, len))
664                         return -1;
665                 break;
666
667         case LWS_CALLBACK_HTTP_BODY_COMPLETION:
668                 lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (scan)\n");
669                 /* call to inform no more payload data coming */
670                 lws_spa_finalize(pss->spa);
671
672                 if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
673                         lwsl_err("Unable to open nvs\n");
674                         break;
675                 }
676
677                 if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
678
679                         if (lws_spa_get_string(pss->spa, EPN_SERIAL)) {
680                                 if (nvs_set_str(nvh, "serial", lws_spa_get_string(pss->spa, EPN_SERIAL)) != ESP_OK) {
681                                         lwsl_err("Unable to store serial in nvm\n");
682                                         goto bail_nvs;
683                                 }
684                 
685                                 nvs_commit(nvh);
686                         }
687
688                         if (lws_spa_get_string(pss->spa, EPN_OPTS)) {
689                                 if (nvs_set_str(nvh, "opts", lws_spa_get_string(pss->spa, EPN_OPTS)) != ESP_OK) {
690                                         lwsl_err("Unable to store options in nvm\n");
691                                         goto bail_nvs;
692                                 }
693                 
694                                 nvs_commit(nvh);
695                         }
696                 }
697                 nvs_close(nvh);
698
699                 pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
700                                 "<html>Rebooting after storing certs...<br>connect to AP '<b>config-%s-%s</b>' and continue here: "
701                                 "<a href=\"https://192.168.4.1\">https://192.168.4.1</a></html>",
702                                 lws_esp32.model, lws_spa_get_string(pss->spa, EPN_SERIAL));
703
704                 if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
705                         goto bail;
706
707                 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
708                                 (unsigned char *)"text/html", 9, &p, end))
709                         goto bail;
710                 if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
711                         goto bail;
712                 if (lws_finalize_http_header(wsi, &p, end))
713                         goto bail;
714
715                 n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
716                 if (n < 0)
717                         goto bail;
718
719                 lws_callback_on_writable(wsi);
720                 break;
721
722         case LWS_CALLBACK_HTTP_WRITEABLE:
723                 lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
724                            pss->result_len);
725                 n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
726                               pss->result_len, LWS_WRITE_HTTP);
727                 if (n < 0)
728                         return 1;
729
730                 vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(3000), 0, vhd,
731                           (TimerCallbackFunction_t)reboot_timer_cb);
732                 xTimerStart(vhd->reboot_timer, 0);
733
734                 return 1; // hang up since we will reset
735
736         /* ----- client handling ----- */
737
738         case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
739                 lwsl_notice("Client connection error %s\n", (char *)in);
740                 vhd->cwsi = NULL;
741                 break;
742
743         case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
744                 if (!vhd->autonomous_update)
745                         break;
746
747                 {
748                         char pp[20];
749
750                         if (lws_hdr_copy(wsi, pp, sizeof(pp) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH) < 0)
751                                 return -1;
752         
753                         vhd->content_length = atoi(pp);
754                         if (vhd->content_length <= 0 ||
755                             vhd->content_length > vhd->part->size)
756                                 return -1;
757
758                         if (esp_ota_begin(vhd->part, (long)-1, &vhd->otahandle) != ESP_OK) {
759                                 lwsl_err("OTA: Failed to begin\n");
760                                 return 1;
761                         }
762
763                         vhd->file_length = 0;
764                         break;
765                 }
766                 break;
767
768         case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
769                 //lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: %ld\n",
770                 //          (long)len);
771
772                 if (!vhd->autonomous_update) {
773                         if (sizeof(vhd->json) - vhd->json_len - 1 < len)
774                                 len = sizeof(vhd->json) - vhd->json_len - 1;
775                         memcpy(vhd->json + vhd->json_len, in, len);
776                         vhd->json_len += len;
777                         vhd->json[vhd->json_len] = '\0';
778                         break;
779                 }
780
781                 /* autonomous download */
782
783
784                 if (vhd->file_length + len > vhd->part->size) {
785                         lwsl_err("OTA: incoming file too large\n");
786                         goto abort_ota;
787                 }
788
789                 lwsl_debug("writing 0x%lx... 0x%lx\n",
790                            vhd->part->address + vhd->file_length,
791                            vhd->part->address + vhd->file_length + len);
792                 if (esp_ota_write(vhd->otahandle, in, len) != ESP_OK) {
793                         lwsl_err("OTA: Failed to write\n");
794                         goto abort_ota;
795                 }
796                 vhd->file_length += len;
797
798                 lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
799                 break;
800
801 abort_ota:
802                 esp_ota_end(vhd->otahandle);
803                 vhd->otahandle = 0;
804                 vhd->autonomous_update = 0;
805
806                 return 1;
807
808         case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
809                 {
810                         char *px = (char *)pss->buffer + LWS_PRE;
811                         int lenx = sizeof(pss->buffer) - LWS_PRE - 1;
812
813                         //lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: %d\n", len);
814
815                         if (lws_http_client_read(wsi, &px, &lenx) < 0)
816                                 return -1;
817                 }
818                 break;
819
820         case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
821                 lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
822                 vhd->cwsi = NULL;
823                 if (!vhd->autonomous_update) {
824
825                         vhd->checked_updates = 1;
826                         puts(vhd->json);
827                         return -1;
828                 }
829
830                 /* autonomous download */
831
832                 lwsl_notice("auton complete\n");
833
834                 if (esp_ota_end(vhd->otahandle) != ESP_OK) {
835                         lwsl_err("OTA: end failed\n");
836                         return 1;
837                 }
838
839                 if (esp_ota_set_boot_partition(vhd->part) != ESP_OK) {
840                         lwsl_err("OTA: set boot part failed\n");
841                         return 1;
842                 }
843                 vhd->otahandle = 0;
844                 vhd->autonomous_update = 0;
845
846                 vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
847                           (TimerCallbackFunction_t)reboot_timer_cb);
848                         xTimerStart(vhd->reboot_timer, 0);
849                 return -1;
850
851         case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
852                 lwsl_notice("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
853                 break;
854
855         case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
856                 /* called when our wsi user_space is going to be destroyed */
857                 if (pss->spa) {
858                         lws_spa_destroy(pss->spa);
859                         pss->spa = NULL;
860                 }
861                 break;
862
863         default:
864                 break;
865         }
866
867         return 0;
868
869 bail:
870         return 1;
871 }
872
873 #define LWS_PLUGIN_PROTOCOL_ESPLWS_SCAN \
874         { \
875                 "esplws-scan", \
876                 callback_esplws_scan, \
877                 sizeof(struct per_session_data__esplws_scan), \
878                 1024, 0, NULL, 900 \
879         }
880