1/*
2 * libwebsockets - esp32 wifi -> lws_netdev_wifi
3 *
4 * Copyright (C) 2010 - 2020 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 * These are the esp platform wifi-specific netdev pieces.  Nothing else should
26 * know any esp-specific apis.
27 *
28 * Operations happen via the generic lws_detdev instantiation for the platform
29 * wifi device, which point in here for operations.  We also set up native OS
30 * event hooks per device for wifi and IP stack events, and post them as lws_smd
31 * NETWORK events on the if in the "platform private" namespace.  We then
32 * service the events in the lws event loop thread context, which may again
33 * generate lws_smd NETWORK events in the public namespace depending on what
34 * happened.
35 *
36 * Scan requests go through a sul to make sure we don't get "piling on" from
37 * scheduled, timed scans.  Scan results go through the lws_smd "washing" and
38 * are actually parsed in lws thread context, where they are converted to lws
39 * netdev scan results and processed by generic code.
40 */
41
42#include "private-lib-core.h"
43
44#include "esp_system.h"
45#include "esp_spi_flash.h"
46#include "esp_wifi.h"
47#include <nvs_flash.h>
48#include <esp_netif.h>
49
50/*
51 * lws_netdev_instance_t:
52 *   lws_netdev_instance_wifi_t:
53 *     lws_netdev_instance_wifi_esp32_t
54 */
55
56typedef struct lws_netdev_instance_wifi_esp32 {
57	lws_netdev_instance_wifi_t		wnd;
58	esp_event_handler_instance_t		instance_any_id;
59	esp_event_handler_instance_t		instance_got_ip;
60	wifi_config_t				sta_config;
61} lws_netdev_instance_wifi_esp32_t;
62
63/*
64static wifi_config_t config = {
65	.ap = {
66	    .channel = 6,
67	    .authmode = WIFI_AUTH_OPEN,
68	    .max_connection = 1,
69	} };
70	*/
71
72/*
73 * Platform-specific connect / associate
74 */
75
76int
77lws_netdev_wifi_connect_plat(lws_netdev_instance_t *nd, const char *ssid,
78			     const char *passphrase, uint8_t *bssid)
79{
80	lws_netdev_instance_wifi_esp32_t *wnde32 =
81					(lws_netdev_instance_wifi_esp32_t *)nd;
82
83	wnde32->wnd.inst.ops->up(&wnde32->wnd.inst);
84
85	wnde32->wnd.flags |= LNDIW_MODE_STA;
86	esp_wifi_set_mode(WIFI_MODE_STA);
87
88#if 0
89	/* we will do our own dhcp */
90	tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
91#endif
92
93	lws_strncpy((char *)wnde32->sta_config.sta.ssid, ssid,
94		    sizeof(wnde32->sta_config.sta.ssid));
95	lws_strncpy((char *)wnde32->sta_config.sta.password, passphrase,
96		    sizeof(wnde32->sta_config.sta.password));
97
98	esp_wifi_set_config(WIFI_IF_STA, &wnde32->sta_config);
99	esp_wifi_connect();
100
101	return 0;
102}
103
104/*
105 * This is called from the SMD / lws thread context, after we heard there were
106 * scan results on this netdev
107 */
108
109static void
110lws_esp32_scan_update(lws_netdev_instance_wifi_t *wnd)
111{
112//	lws_netdevs_t *netdevs = lws_netdevs_from_ndi(&wnd->inst);
113	wifi_ap_record_t ap_records[LWS_WIFI_MAX_SCAN_TRACK], *ar;
114	uint32_t now = lws_now_secs();
115	uint16_t count_ap_records;
116	int n;
117
118	count_ap_records = LWS_ARRAY_SIZE(ap_records);
119	if (esp_wifi_scan_get_ap_records(&count_ap_records, ap_records)) {
120		lwsl_err("%s: failed\n", __func__);
121		return;
122	}
123
124	if (!count_ap_records)
125		return;
126
127	if (wnd->state != LWSNDVWIFI_STATE_SCAN)
128		return;
129
130	/*
131	 * ... let's collect the OS-specific scan results, and convert then to
132	 * lws_netdev sorted by rssi.  If we already have it in the scan list,
133	 * keep it and keep a little ringbuffer of its rssi along with an
134	 * averaging.  If it's new, add it into the linked-list sorted by rssi.
135	 */
136
137	ar = &ap_records[0];
138	for (n = 0; n < count_ap_records; n++) {
139		lws_wifi_sta_t *w;
140		int m;
141
142		m = strlen((const char *)ar->ssid);
143		if (!m)
144			goto next;
145
146		/*
147		 * We know this guy from before?
148		 */
149
150		w = lws_netdev_wifi_scan_find(wnd, (const char *)ar->ssid,
151						ar->bssid);
152		if (!w) {
153			w = lws_zalloc(sizeof(*w) + m + 1, __func__);
154			if (!w)
155				goto next;
156
157			w->ssid = (char *)&w[1];
158			memcpy(w->ssid, ar->ssid, m + 1);
159			w->ssid_len = m;
160
161			memcpy(w->bssid, ar->bssid, 6);
162
163			lws_dll2_add_sorted(&w->list, &wnd->scan,
164					    lws_netdev_wifi_rssi_sort_compare);
165		}
166
167		if (w->rssi_count == LWS_ARRAY_SIZE(w->rssi))
168			w->rssi_avg -= w->rssi[w->rssi_next];
169		else
170			w->rssi_count++;
171		w->rssi[w->rssi_next] = ar->rssi;
172		w->rssi_avg += w->rssi[w->rssi_next++];
173		w->rssi_next = w->rssi_next & (LWS_ARRAY_SIZE(w->rssi) - 1);
174
175		w->ch = ar->primary;
176		w->authmode = ar->authmode;
177		w->last_seen = now;
178
179next:
180		ar++;
181	}
182
183	/*
184	 * We can do the rest of it using the generic scan list and credentials
185	 */
186
187	lws_netdev_wifi_scan_select(wnd);
188}
189
190static wifi_scan_config_t scan_config = {
191        .ssid = 0,
192        .bssid = 0,
193        .channel = 0,
194        .show_hidden = true
195};
196
197void
198lws_netdev_wifi_scan_plat(lws_netdev_instance_t *nd)
199{
200	lws_netdev_instance_wifi_t *wnd = (lws_netdev_instance_wifi_t *)nd;
201
202	if (esp_wifi_scan_start(&scan_config, false))
203		lwsl_err("%s: %s scan failed\n", __func__, wnd->inst.name);
204}
205
206/*
207 * Platform-private interface events turn up here after going through SMD and
208 * passed down by matching network interface name via generic lws_netdev.  All
209 * that messing around gets us from an OS-specific thread with an event to back
210 * here in lws event loop thread context, with the same event bound to a the
211 * netdev it belongs to.
212 */
213
214int
215lws_netdev_wifi_event_plat(struct lws_netdev_instance *nd, lws_usec_t timestamp,
216			   void *buf, size_t len)
217{
218	lws_netdev_instance_wifi_t *wnd = (lws_netdev_instance_wifi_t *)nd;
219	struct lws_context *ctx = netdev_instance_to_ctx(&wnd->inst);
220	size_t al;
221
222	/*
223	 * netdev-private sync messages?
224	 */
225
226	if (!lws_json_simple_strcmp(buf, len, "\"type\":", "priv")) {
227		const char *ev = lws_json_simple_find(buf, len, "\"ev\":", &al);
228
229		if (!ev)
230			return 0;
231
232		lwsl_notice("%s: smd priv ev %.*s\n", __func__, (int)al, ev);
233
234		switch (atoi(ev)) {
235		case WIFI_EVENT_STA_START:
236			wnd->state = LWSNDVWIFI_STATE_INITIAL;
237			if (!lws_netdev_wifi_redo_last(wnd))
238				break;
239
240			/*
241			 * if the "try last successful" one fails, start the
242			 * scan by falling through
243			 */
244
245		case WIFI_EVENT_STA_DISCONNECTED:
246			lws_smd_msg_printf(ctx, LWSSMDCL_NETWORK,
247					   "{\"type\":\"linkdown\","
248					   "\"if\":\"%s\"}", wnd->inst.name);
249			wnd->state = LWSNDVWIFI_STATE_SCAN;
250			/*
251			 * We do it via the sul so we don't get timed scans
252			 * on top of each other
253			 */
254			lws_sul_schedule(ctx, 0, &wnd->sul_scan,
255					 lws_netdev_wifi_scan, 1);
256			break;
257
258		case WIFI_EVENT_STA_CONNECTED:
259			lws_smd_msg_printf(ctx, LWSSMDCL_NETWORK,
260					   "{\"type\":\"linkup\","
261					   "\"if\":\"%s\"}", wnd->inst.name);
262			break;
263
264		case WIFI_EVENT_SCAN_DONE:
265			lws_esp32_scan_update(wnd);
266			break;
267		default:
268			return 0;
269		}
270
271		return 0;
272	}
273
274	return 0;
275}
276
277/*
278 * This is coming from a thread context unrelated to lws... the first order is
279 * to turn these into lws_smd events synchronized on lws thread, since we want
280 * to change correspsonding lws netdev object states without locking.
281 */
282
283static void
284_event_handler_wifi(void *arg, esp_event_base_t event_base, int32_t event_id,
285		   void *event_data)
286{
287	lws_netdev_instance_wifi_t *wnd = (lws_netdev_instance_wifi_t *)arg;
288	struct lws_context *ctx = netdev_instance_to_ctx(&wnd->inst);
289
290	switch (event_id) {
291	case WIFI_EVENT_STA_START:
292	case WIFI_EVENT_STA_DISCONNECTED:
293	case WIFI_EVENT_SCAN_DONE:
294	case WIFI_EVENT_STA_CONNECTED:
295		/*
296		 * These are events in the platform's private namespace,
297		 * interpreted only by the lws_smd handler above, ** in the lws
298		 * event thread context **.  The point of this is to requeue the
299		 * event in the lws thread context like a bottom-half.
300		 *
301		 * To save on registrations, the context's NETWORK smd
302		 * participant passes messages to lws_netdev, who passes ones
303		 * that have if matching the netdev name to that netdev's
304		 * (*event) handler.
305		 *
306		 * The other handler may emit generic network state SMD events
307		 * for other things to consume.
308		 */
309
310		lws_smd_msg_printf(ctx, LWSSMDCL_NETWORK,
311				   "{\"type\":\"priv\",\"if\":\"%s\",\"ev\":%d}",
312				   wnd->inst.name, (int)event_id);
313		break;
314	default:
315		return;
316	}
317}
318
319#if 0
320static int
321espip_to_sa46(lws_sockaddr46 *sa46, esp_ip_addr_t *eip)
322{
323	memset(sa46, 0, sizeof(sa46));
324
325	switch (eip->type) {
326	case ESP_IPADDR_TYPE_V4:
327		sa46->sa4.sin_family = AF_INET;
328		memcpy(sa46->sa4.sin_addr, &eip->u_addr.ip4.addr, );
329		return;
330	case ESP_IPADDR_TYPE_V6:
331	}
332}
333#endif
334
335/*
336 * This is coming from a thread context unrelated to lws
337 */
338
339static void
340_event_handler_ip(void *arg, esp_event_base_t event_base, int32_t event_id,
341	      void *event_data)
342{
343	lws_netdev_instance_wifi_t *wnd = (lws_netdev_instance_wifi_t *)arg;
344	lws_netdevs_t *netdevs = lws_netdevs_from_ndi(&wnd->inst);
345	struct lws_context *ctx = lws_context_from_netdevs(netdevs);
346
347	if (event_id == IP_EVENT_STA_GOT_IP) {
348		ip_event_got_ip_t *e = (ip_event_got_ip_t *)event_data;
349		char ip[16];
350#if 0
351		tcpip_adapter_dns_info_t e32ip;
352
353		/*
354		 * Since atm we get this via DHCP, presumably we can get ahold
355		 * of related info set by the router
356		 */
357
358		if (tcpip_adapter_get_dns_info(TCPIP_ADAPTER_IF_STA,
359					   TCPIP_ADAPTER_DNS_MAIN,
360					   /* also _BACKUP, _FALLBACK */
361					   &e32ip)) {
362			lwsl_err("%s: there's no dns server set\n", __func__);
363			e32ip.ip.u_addr.ipv4 = 0x08080808;
364			e32ip.ip.type = ESP_IPADDR_TYPE_V4;
365		}
366
367		netdevs->sa46_dns_resolver.
368#endif
369
370		lws_write_numeric_address((void *)&e->ip_info.ip, 4, ip,
371				sizeof(ip));
372		lws_smd_msg_printf(ctx, LWSSMDCL_NETWORK,
373				   "{\"type\":\"ipacq\",\"if\":\"%s\","
374				   "\"ipv4\":\"%s\"}", wnd->inst.name, ip);
375	}
376}
377
378/*
379 * This is the platform (esp-idf) init for any kind of networking to be
380 * available at all
381 */
382int
383lws_netdev_plat_init(void)
384{
385        nvs_flash_init();
386	esp_netif_init();
387	ESP_ERROR_CHECK(esp_event_loop_create_default());
388
389	return 0;
390}
391
392/*
393 * This is the platform (esp-idf) init for any wifi to be available at all
394 */
395int
396lws_netdev_plat_wifi_init(void)
397{
398	wifi_init_config_t wic = WIFI_INIT_CONFIG_DEFAULT();
399	int n;
400
401	esp_netif_create_default_wifi_sta();
402
403	n = esp_wifi_init(&wic);
404	if (n) {
405		lwsl_err("%s: wifi init fail: %d\n", __func__, n);
406		return 1;
407	}
408
409	return 0;
410}
411
412
413struct lws_netdev_instance *
414lws_netdev_wifi_create_plat(struct lws_context *ctx,
415			    const lws_netdev_ops_t *ops,
416			    const char *name, void *platinfo)
417{
418	lws_netdev_instance_wifi_esp32_t *wnde32 = lws_zalloc(
419						sizeof(*wnde32), __func__);
420
421	if (!wnde32)
422		return NULL;
423
424	wnde32->wnd.inst.type = LWSNDTYP_WIFI;
425	lws_netdev_instance_create(&wnde32->wnd.inst, ctx, ops, name, platinfo);
426
427	return &wnde32->wnd.inst;
428}
429
430int
431lws_netdev_wifi_configure_plat(struct lws_netdev_instance *nd,
432			       lws_netdev_config_t *config)
433{
434	return 0;
435}
436
437int
438lws_netdev_wifi_up_plat(struct lws_netdev_instance *nd)
439{
440	lws_netdev_instance_wifi_esp32_t *wnde32 =
441					(lws_netdev_instance_wifi_esp32_t *)nd;
442	struct lws_context *ctx = netdev_instance_to_ctx(&wnde32->wnd.inst);
443
444	if (wnde32->wnd.flags & LNDIW_UP)
445		return 0;
446
447	ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
448			  IP_EVENT_STA_GOT_IP, &_event_handler_ip, nd,
449			  &wnde32->instance_got_ip));
450
451	ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
452			  ESP_EVENT_ANY_ID, &_event_handler_wifi, nd,
453			  &wnde32->instance_any_id));
454
455	esp_wifi_start();
456	wnde32->wnd.flags |= LNDIW_UP;
457
458	lws_smd_msg_printf(ctx, LWSSMDCL_NETWORK,
459			   "{\"type\":\"up\",\"if\":\"%s\"}",
460			   wnde32->wnd.inst.name);
461
462	return 0;
463}
464
465int
466lws_netdev_wifi_down_plat(struct lws_netdev_instance *nd)
467{
468	lws_netdev_instance_wifi_esp32_t *wnde32 =
469					(lws_netdev_instance_wifi_esp32_t *)nd;
470	struct lws_context *ctx = netdev_instance_to_ctx(&wnde32->wnd.inst);
471
472	if (!(wnde32->wnd.flags & LNDIW_UP))
473		return 0;
474
475	lws_smd_msg_printf(ctx, LWSSMDCL_NETWORK,
476			   "{\"type\":\"down\",\"if\":\"%s\"}",
477			   wnde32->wnd.inst.name);
478
479	esp_wifi_stop();
480
481	esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
482						&wnde32->instance_got_ip);
483	esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
484						&wnde32->instance_any_id);
485
486	wnde32->wnd.flags &= ~LNDIW_UP;
487
488	return 0;
489}
490
491void
492lws_netdev_wifi_destroy_plat(struct lws_netdev_instance **pnd)
493{
494	lws_free(*pnd);
495	*pnd = NULL;
496}
497