• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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