From 058777ea60161c7d3c9d57002f3f86975a5d211c Mon Sep 17 00:00:00 2001 From: Jason Follas Date: Tue, 8 Nov 2016 15:58:33 -0500 Subject: [PATCH] Eus channelfix (#1583) Squashed commits included: Bug fixes and final implementation - Added Content-Length: 0 to all headers - Endpoint name checks not using trailing space so cache-busting techniques can be used (i.e., append a nonce to the URL) - Track when connecting so APList scan doesn't take place during (which changes the channel) - More debugging output added to assist in tracking down some issues Added /status.json endpoint for phone apps/XHR to get JSON response Station Status caching for wifi channel workaround + AJAX/CORS - During checkstation poll, cache the last station status - Shut down the station if status = 2,3,4 and channel is different than SoftAP - Add Access-Control-Allow-Origin: * to endpoint responses used by a service - Add a /setwifi GET endpoint for phone apps/XHR to use (same parameters as /update endpoint). Returns a JSON response containing chip id and status code. - Add handler for OPTIONS verb (needed for CORS support) Wi-Fi Channel Issue Workaround - Do a site survey upon startup, set SoftAP channel to the strongest rssi's channel - Compare successful station connect channel to SoftAP's. If different, then defer the Lua success callback to the end. Shut down Station and start the SoftAP back up with original channel. - After the 10 second shutdown timer fires, check to see if success callback was already called. If not, then call it while starting the Station back up. HTTP Response and DNS enhancements - If DNS's UDP buffer fills up, keep going as non-fatal. It's UDP and not guaranteed anyways. I've seen this occur when connecting a PC to the SoftAP and every open program tries to phone home at the same time, overwhelming the EUS DNS server. - Support for detecting/handling pre-gzipped `enduser_setup.html` (and `http_html_backup`) payload. Nice for keeping the size of the `state->http_payload_data` as small as possible (also makes minimization not as critical) - Corrected misuse of HTTP 401 response status (changed one occurrence to 400/Bad Request, and changed another to 405/Method Not Allowed) * Normalized formatting (tabs-to-spaces) * Added documentation * Corrected misuse of strlen for binary (gzip) data. * Added NULL check after malloc --- app/modules/enduser_setup.c | 600 ++++++++++++++++---------- app/modules/eus/enduser_setup.html | 315 ++++++++++++++ app/modules/eus/enduser_setup.html.gz | Bin 0 -> 2393 bytes app/modules/eus/http_html_backup.def | 203 +++++++++ app/modules/eus/prepare.sh | 7 + docs/en/modules/enduser-setup.md | 27 +- 6 files changed, 928 insertions(+), 224 deletions(-) create mode 100644 app/modules/eus/enduser_setup.html create mode 100644 app/modules/eus/enduser_setup.html.gz create mode 100644 app/modules/eus/http_html_backup.def create mode 100644 app/modules/eus/prepare.sh diff --git a/app/modules/enduser_setup.c b/app/modules/enduser_setup.c index 39a9975b..261d4183 100644 --- a/app/modules/enduser_setup.c +++ b/app/modules/enduser_setup.c @@ -31,6 +31,7 @@ * @author Robert Foss * * Additions & fixes: Johny Mattsson + * Jason Follas */ #include "module.h" @@ -50,6 +51,8 @@ #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #define LITLEN(strliteral) (sizeof (strliteral) -1) +#define STRINGIFY(x) #x +#define NUMLEN(x) (sizeof(STRINGIFY(x)) - 1) #define ENDUSER_SETUP_ERR_FATAL (1 << 0) #define ENDUSER_SETUP_ERR_NONFATAL (1 << 1) @@ -59,7 +62,7 @@ #define ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND 2 #define ENDUSER_SETUP_ERR_UNKOWN_ERROR 3 #define ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN 4 - +#define ENDUSER_SETUP_ERR_MAX_NUMBER 5 /** * DNS Response Packet: @@ -78,159 +81,19 @@ static const char dns_body[] = { 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04 }; static const char http_html_filename[] = "enduser_setup.html"; -static const char http_header_200[] = "HTTP/1.1 200 OK\r\nCache-control:no-cache\r\nContent-Type: text/html\r\n"; /* Note single \r\n here! */ -static const char http_header_204[] = "HTTP/1.1 204 No Content\r\n\r\n"; -static const char http_header_302[] = "HTTP/1.1 302 Moved\r\nLocation: /\r\n\r\n"; -static const char http_header_401[] = "HTTP/1.1 401 Bad request\r\n\r\n"; -static const char http_header_404[] = "HTTP/1.1 404 Not found\r\n\r\n"; -static const char http_header_500[] = "HTTP/1.1 500 Internal Error\r\n\r\n"; +static const char http_header_200[] = "HTTP/1.1 200 OK\r\nCache-control:no-cache\r\nConnection:close\r\nContent-Type:text/html\r\n"; /* Note single \r\n here! */ +static const char http_header_204[] = "HTTP/1.1 204 No Content\r\nContent-Length:0\r\nConnection:close\r\n\r\n"; +static const char http_header_302[] = "HTTP/1.1 302 Moved\r\nLocation: /\r\nContent-Length:0\r\nConnection:close\r\n\r\n"; +static const char http_header_400[] = "HTTP/1.1 400 Bad request\r\nContent-Length:0\r\nConnection:close\r\n\r\n"; +static const char http_header_404[] = "HTTP/1.1 404 Not found\r\nContent-Length:0\r\nConnection:close\r\n\r\n"; +static const char http_header_405[] = "HTTP/1.1 405 Method Not Allowed\r\nContent-Length:0\r\nConnection:close\r\n\r\n"; +static const char http_header_500[] = "HTTP/1.1 500 Internal Error\r\nContent-Length:0\r\nConnection:close\r\n\r\n"; -/* The below is the un-minified version of the http_html_backup[] string. - * Minified using https://kangax.github.io/html-minifier/ - * Note: using method="get" due to iOS not always sending body in same - * packet as the HTTP header, and us thus missing it in that case - */ -#if 0 - - - - - - - WiFi Login - - - -
-
-
- -

WiFi Login

-

Connect gadget to your WiFi network

- - - - -

Status: Updating...

-
-

-
-
- - - -#endif -static const char http_html_backup[] = -"WiFi Login

WiFi Login

Connect gadget to your WiFi network

Status: Updating...

"; +static const char http_header_content_len_fmt[] = "Content-length:%5d\r\n\r\n"; +static const char http_html_gzip_contentencoding[] = "Content-Encoding: gzip\r\n"; +// Externally defined: static const char http_html_backup[] = ... +#include "eus/http_html_backup.def" typedef struct scan_listener { @@ -250,9 +113,15 @@ typedef struct int lua_err_cb_ref; int lua_dbg_cb_ref; scan_listener_t *scan_listeners; + uint8_t softAPchannel; + uint8_t success; + uint8_t callbackDone; + uint8_t lastStationStatus; + uint8_t connecting; } enduser_setup_state_t; static enduser_setup_state_t *state; + static bool manual = false; static task_handle_t do_station_cfg_handle; @@ -340,7 +209,7 @@ static void enduser_setup_check_station_start(void) ENDUSER_SETUP_DEBUG("enduser_setup_check_station_start"); os_timer_setfn(&(state->check_station_timer), enduser_setup_check_station, NULL); - os_timer_arm(&(state->check_station_timer), 1*1000, TRUE); + os_timer_arm(&(state->check_station_timer), 3*1000, TRUE); } @@ -377,13 +246,60 @@ static void enduser_setup_check_station(void *p) has_ip |= ((char *) &ip)[i]; } + uint8_t currChan = wifi_get_channel(); + if (has_ip == 0) { - return; + // No IP Address yet, so check the reported status + uint8_t curr_status = wifi_station_get_connect_status(); + char buf[20]; + c_sprintf(buf, "status=%d,chan=%d", curr_status, currChan); + ENDUSER_SETUP_DEBUG(buf); + + if (curr_status == 2 || curr_status == 3 || curr_status == 4) + { + state->connecting = 0; + + // If the status is an error status and the channel changed, then cache the + // status to state since the Station won't be able to report the same status + // after switching the channel back to the SoftAP's + if (currChan != state->softAPchannel) { + state->lastStationStatus = curr_status; + + ENDUSER_SETUP_DEBUG("Turning off Station due to different channel than AP"); + + wifi_station_disconnect(); + wifi_set_opmode(SOFTAP_MODE); + enduser_setup_ap_start(); + } + } + return; } + state->success = 1; + state->lastStationStatus = 5; // We have an IP Address, so the status is 5 (as of SDK 1.5.1) + state->connecting = 0; + +#if ENDUSER_SETUP_DEBUG_ENABLE + char debuginfo[100]; + c_sprintf(debuginfo, "AP_CHAN: %d, STA_CHAN: %d", state->softAPchannel, currChan); + ENDUSER_SETUP_DEBUG(debuginfo); +#endif + + if (currChan == state->softAPchannel) + { + enduser_setup_connected_callback(); + state->callbackDone = 1; + } + else + { + ENDUSER_SETUP_DEBUG("Turning off Station due to different channel than AP"); + wifi_station_disconnect(); + wifi_set_opmode(SOFTAP_MODE); + enduser_setup_ap_start(); + } + enduser_setup_check_station_stop(); - enduser_setup_connected_callback(); /* Trigger shutdown, but allow time for HTTP client to fetch last status. */ if (!manual) @@ -411,6 +327,8 @@ static void enduser_setup_check_station(void *p) /* Callback on timeout to hard-close a connection */ static err_t force_abort (void *arg, struct tcp_pcb *pcb) { + ENDUSER_SETUP_DEBUG("force_abort"); + (void)arg; tcp_poll (pcb, 0, 0); tcp_abort (pcb); @@ -420,6 +338,8 @@ static err_t force_abort (void *arg, struct tcp_pcb *pcb) /* Callback to detect a remote-close of a connection */ static err_t handle_remote_close (void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { + ENDUSER_SETUP_DEBUG("handle_remote_close"); + (void)arg; (void)err; if (p) /* server sent us data, just ACK and move on */ { @@ -438,6 +358,8 @@ static err_t handle_remote_close (void *arg, struct tcp_pcb *pcb, struct pbuf *p /* Set up a deferred close of a connection, as discussed above. */ static inline void deferred_close (struct tcp_pcb *pcb) { + ENDUSER_SETUP_DEBUG("deferred_close"); + tcp_poll (pcb, force_abort, 15); /* ~3sec from now */ tcp_recv (pcb, handle_remote_close); tcp_sent (pcb, 0); @@ -446,6 +368,8 @@ static inline void deferred_close (struct tcp_pcb *pcb) /* Convenience function to queue up a close-after-send. */ static err_t close_once_sent (void *arg, struct tcp_pcb *pcb, u16_t len) { + ENDUSER_SETUP_DEBUG("close_once_sent"); + (void)arg; (void)len; deferred_close (pcb); return ERR_OK; @@ -473,7 +397,6 @@ static int enduser_setup_srch_str(const char *str, const char *srch_str) } } - /** * Load HTTP Payload * @@ -495,14 +418,26 @@ static int enduser_setup_http_load_payload(void) err2 = vfs_lseek(f, 0, VFS_SEEK_SET); } - const char cl_hdr[] = "Content-length:%5d\r\n\r\n"; - const size_t cl_len = LITLEN(cl_hdr) + 3; /* room to expand %4d */ + char cl_hdr[30]; + size_t ce_len = 0; + + c_sprintf(cl_hdr, http_header_content_len_fmt, file_len); + size_t cl_len = c_strlen(cl_hdr); if (!f || err != VFS_RES_OK || err2 != VFS_RES_OK) { ENDUSER_SETUP_DEBUG("enduser_setup_http_load_payload unable to load file enduser_setup.html, loading backup HTML."); - int payload_len = LITLEN(http_header_200) + cl_len + LITLEN(http_html_backup); + c_sprintf(cl_hdr, http_header_content_len_fmt, sizeof(http_html_backup)); + cl_len = c_strlen(cl_hdr); + + if (http_html_backup[0] == 0x1f && http_html_backup[1] == 0x8b) + { + ce_len = c_strlen(http_html_gzip_contentencoding); + ENDUSER_SETUP_DEBUG("Content is gzipped"); + } + + int payload_len = LITLEN(http_header_200) + cl_len + ce_len + LITLEN(http_html_backup); state->http_payload_len = payload_len; state->http_payload_data = (char *) c_malloc(payload_len); if (state->http_payload_data == NULL) @@ -513,13 +448,25 @@ static int enduser_setup_http_load_payload(void) int offset = 0; c_memcpy(&(state->http_payload_data[offset]), &(http_header_200), LITLEN(http_header_200)); offset += LITLEN(http_header_200); - offset += c_sprintf(state->http_payload_data + offset, cl_hdr, LITLEN(http_html_backup)); - c_memcpy(&(state->http_payload_data[offset]), &(http_html_backup), LITLEN(http_html_backup)); - - return 1; + if (ce_len > 0) + offset += c_sprintf(state->http_payload_data + offset, http_html_gzip_contentencoding, ce_len); + c_memcpy(&(state->http_payload_data[offset]), &(cl_hdr), cl_len); + offset += cl_len; + c_memcpy(&(state->http_payload_data[offset]), &(http_html_backup), sizeof(http_html_backup)); + + return 1; } - int payload_len = LITLEN(http_header_200) + cl_len + file_len; + char magic[2]; + vfs_read(f, magic, 2); + + if (magic[0] == 0x1f && magic[1] == 0x8b) + { + ce_len = c_strlen(http_html_gzip_contentencoding); + ENDUSER_SETUP_DEBUG("Content is gzipped"); + } + + int payload_len = LITLEN(http_header_200) + cl_len + ce_len + file_len; state->http_payload_len = payload_len; state->http_payload_data = (char *) c_malloc(payload_len); if (state->http_payload_data == NULL) @@ -527,13 +474,19 @@ static int enduser_setup_http_load_payload(void) return 2; } + vfs_lseek(f, 0, VFS_SEEK_SET); + int offset = 0; + c_memcpy(&(state->http_payload_data[offset]), &(http_header_200), LITLEN(http_header_200)); offset += LITLEN(http_header_200); - offset += c_sprintf(state->http_payload_data + offset, cl_hdr, file_len); + if (ce_len > 0) + offset += c_sprintf(state->http_payload_data + offset, http_html_gzip_contentencoding, ce_len); + c_memcpy(&(state->http_payload_data[offset]), &(cl_hdr), cl_len); + offset += cl_len; vfs_read(f, &(state->http_payload_data[offset]), file_len); - vfs_close(f); - + vfs_close(f); + return 0; } @@ -605,6 +558,9 @@ static int enduser_setup_http_urldecode(char *dst, const char *src, int src_len, */ static void do_station_cfg (task_param_t param, uint8_t prio) { + ENDUSER_SETUP_DEBUG("do_station_cfg"); + + state->connecting = 1; struct station_config *cnf = (struct station_config *)param; (void)prio; @@ -635,6 +591,9 @@ static int enduser_setup_http_handle_credentials(char *data, unsigned short data { ENDUSER_SETUP_DEBUG("enduser_setup_http_handle_credentials"); + state->success = 0; + state->lastStationStatus = 0; + char *name_str = (char *) ((uint32_t)strstr(&(data[6]), "wifi_ssid=")); char *pwd_str = (char *) ((uint32_t)strstr(&(data[6]), "wifi_password=")); if (name_str == NULL || pwd_str == NULL) @@ -663,7 +622,7 @@ static int enduser_setup_http_handle_credentials(char *data, unsigned short data int err; err = enduser_setup_http_urldecode(cnf->ssid, name_str_start, name_str_len, sizeof(cnf->ssid)); err |= enduser_setup_http_urldecode(cnf->password, pwd_str_start, pwd_str_len, sizeof(cnf->password)); - if (err != 0) + if (err != 0 || c_strlen(cnf->ssid) == 0) { ENDUSER_SETUP_DEBUG("Unable to decode HTTP parameter to valid password or SSID"); return 1; @@ -694,7 +653,7 @@ static int enduser_setup_http_handle_credentials(char *data, unsigned short data static int enduser_setup_http_serve_header(struct tcp_pcb *http_client, const char *header, uint32_t header_len) { ENDUSER_SETUP_DEBUG("enduser_setup_http_serve_header"); - + err_t err = tcp_write (http_client, header, header_len, TCP_WRITE_FLAG_COPY); if (err != ERR_OK) { @@ -708,8 +667,9 @@ static int enduser_setup_http_serve_header(struct tcp_pcb *http_client, const ch static err_t streamout_sent (void *arg, struct tcp_pcb *pcb, u16_t len) { - (void)len; + ENDUSER_SETUP_DEBUG("streamout_sent"); + (void)len; unsigned offs = (unsigned)arg; if (!state || !state->http_payload_data) @@ -754,6 +714,7 @@ static err_t streamout_sent (void *arg, struct tcp_pcb *pcb, u16_t len) static int enduser_setup_http_serve_html(struct tcp_pcb *http_client) { ENDUSER_SETUP_DEBUG("enduser_setup_http_serve_html"); + if (state->http_payload_data == NULL) { enduser_setup_http_load_payload(); @@ -774,7 +735,7 @@ static int enduser_setup_http_serve_html(struct tcp_pcb *http_client) } -static void serve_status(struct tcp_pcb *conn) +static void enduser_setup_serve_status(struct tcp_pcb *conn) { ENDUSER_SETUP_DEBUG("enduser_setup_serve_status"); @@ -782,11 +743,12 @@ static void serve_status(struct tcp_pcb *conn) "HTTP/1.1 200 OK\r\n" "Cache-control:no-cache\r\n" "Connection:close\r\n" + "Access-Control-Allow-Origin: *\r\n" "Content-type:text/plain\r\n" "Content-length: %d\r\n" "\r\n" - "%s%s"; - const char *state[] = + "%s"; + const char *states[] = { "Idle.", "Connecting to \"%s\".", @@ -796,8 +758,8 @@ static void serve_status(struct tcp_pcb *conn) "Connected to \"%s\" (%s)." }; - const size_t num_states = sizeof(state)/sizeof(state[0]); - uint8_t curr_state = wifi_station_get_connect_status (); + const size_t num_states = sizeof(states)/sizeof(states[0]); + uint8_t curr_state = state->lastStationStatus > 0 ? state->lastStationStatus : wifi_station_get_connect_status (); if (curr_state < num_states) { switch (curr_state) @@ -807,22 +769,21 @@ static void serve_status(struct tcp_pcb *conn) case STATION_NO_AP_FOUND: case STATION_GOT_IP: { - const char *s = state[curr_state]; + const char *s = states[curr_state]; struct station_config config; wifi_station_get_config(&config); config.ssid[31] = '\0'; - struct ip_info ip_info; + struct ip_info ip_info; - wifi_get_ip_info(STATION_IF , &ip_info); - - char ip_addr[16]; - ip_addr[0] = '\0'; - if (curr_state == STATION_GOT_IP) - { - c_sprintf (ip_addr, "%d.%d.%d.%d", IP2STR(&ip_info.ip.addr)); - } + wifi_get_ip_info(STATION_IF , &ip_info); + char ip_addr[16]; + ip_addr[0] = '\0'; + if (curr_state == STATION_GOT_IP) + { + c_sprintf (ip_addr, "%d.%d.%d.%d", IP2STR(&ip_info.ip.addr)); + } int state_len = c_strlen(s); int ip_len = c_strlen(ip_addr); @@ -844,7 +805,7 @@ static void serve_status(struct tcp_pcb *conn) /* Handle non-formatted strings */ default: { - const char *s = state[curr_state]; + const char *s = states[curr_state]; int status_len = c_strlen(s); int buf_len = sizeof(fmt) + status_len + 10; //10 = (9+1), 1 byte is '\0' and 9 are reserved for length field char buf[buf_len]; @@ -862,12 +823,76 @@ static void serve_status(struct tcp_pcb *conn) } } +static void enduser_setup_serve_status_as_json (struct tcp_pcb *http_client) +{ + ENDUSER_SETUP_DEBUG("enduser_setup_serve_status_as_json"); + + // If the station is currently shut down because of wi-fi channel issue, use the cached status + uint8_t curr_status = state->lastStationStatus > 0 ? state->lastStationStatus : wifi_station_get_connect_status (); + + char json_payload[64]; + c_sprintf(json_payload, "{\"deviceid\":\"%06X\", \"status\":%d}", system_get_chip_id(), curr_status); + + const char fmt[] = + "HTTP/1.1 200 OK\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Content-Type: application/json\r\n" + "Content-Length: %d\r\n" + "\r\n" + "%s"; + + int len = c_strlen(json_payload); + char buf[c_strlen(fmt) + NUMLEN(len) + len - 4]; + len = c_sprintf (buf, fmt, len, json_payload); + enduser_setup_http_serve_header (http_client, buf, len); +} + + +static void enduser_setup_handle_OPTIONS (struct tcp_pcb *http_client, char *data, unsigned short data_len) +{ + ENDUSER_SETUP_DEBUG("enduser_setup_handle_OPTIONS"); + + const char json[] = + "HTTP/1.1 200 OK\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 0\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Methods: GET\r\n" + "Access-Control-Allow-Age: 300\r\n" + "\r\n"; + + const char others[] = + "HTTP/1.1 200 OK\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Content-Length: 0\r\n" + "\r\n"; + + int type = 0; + + if (c_strncmp(data, "GET ", 4) == 0) + { + if (c_strncmp(data + 4, "/aplist", 7) == 0 || c_strncmp(data + 4, "/setwifi?", 9) == 0 || c_strncmp(data + 4, "/status.json", 12) == 0) + { + enduser_setup_http_serve_header (http_client, json, c_strlen(json)); + return; + } + } + enduser_setup_http_serve_header (http_client, others, c_strlen(others)); + return; +} /* --- WiFi AP scanning support -------------------------------------------- */ static void free_scan_listeners (void) { + ENDUSER_SETUP_DEBUG("free_scan_listeners"); + if (!state || !state->scan_listeners) { return; @@ -886,6 +911,8 @@ static void free_scan_listeners (void) static void remove_scan_listener (scan_listener_t *l) { + ENDUSER_SETUP_DEBUG("remove_scan_listener"); + if (state) { scan_listener_t **sl = &state->scan_listeners; @@ -921,6 +948,8 @@ static char *escape_ssid (char *dst, const char *src) static void notify_scan_listeners (const char *payload, size_t sz) { + ENDUSER_SETUP_DEBUG("notify_scan_listeners"); + if (!state) { return; @@ -944,6 +973,8 @@ static void notify_scan_listeners (const char *payload, size_t sz) static void on_scan_done (void *arg, STATUS status) { + ENDUSER_SETUP_DEBUG("on_scan_done"); + if (!state || !state->scan_listeners) { return; @@ -961,13 +992,14 @@ static void on_scan_done (void *arg, STATUS status) "HTTP/1.1 200 OK\r\n" "Connection:close\r\n" "Cache-control:no-cache\r\n" + "Access-Control-Allow-Origin: *\r\n" "Content-type:application/json\r\n" "Content-length:%4d\r\n" "\r\n"; const size_t hdr_sz = sizeof (header_fmt) +1 -1; /* +expand %4d, -\0 */ /* To be able to safely escape a pathological SSID, we need 2*32 bytes */ - const size_t max_entry_sz = sizeof("{\"ssid\":\"\",\"rssi\":},") + 2*32 + 6; + const size_t max_entry_sz = 27 + 2*32 + 6; // {"ssid":"","rssi":,"chan":} const size_t alloc_sz = hdr_sz + num_nets * max_entry_sz + 3; char *http = os_zalloc (alloc_sz); if (!http) @@ -996,6 +1028,12 @@ static void on_scan_done (void *arg, STATUS status) p += sizeof (entry_mid) -1; p += c_sprintf (p, "%d", wn->rssi); + + const char entry_chan[] = ",\"chan\":"; + strcpy (p, entry_chan); + p += sizeof (entry_chan) -1; + + p += c_sprintf (p, "%d", wn->channel); *p++ = '}'; } @@ -1006,6 +1044,8 @@ static void on_scan_done (void *arg, STATUS status) http[hdr_sz] = '['; /* Rewrite the \0 with the correct start of body */ notify_scan_listeners (http, hdr_sz + body_sz); + ENDUSER_SETUP_DEBUG(http + hdr_sz); + c_free (http); return; } @@ -1048,6 +1088,9 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s pbuf_free (p); err_t ret = ERR_OK; + + ENDUSER_SETUP_DEBUG(data); + if (c_strncmp(data, "GET ", 4) == 0) { if (c_strncmp(data + 4, "/ ", 2) == 0) @@ -1061,38 +1104,52 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s goto free_out; /* streaming now in progress */ } } - else if (c_strncmp(data + 4, "/aplist ", 8) == 0) + else if (c_strncmp(data + 4, "/aplist", 7) == 0) { - scan_listener_t *l = os_malloc (sizeof (scan_listener_t)); - if (!l) + // Don't do an AP Scan while station is trying to connect to Wi-Fi + if (state->connecting == 0) { - ENDUSER_SETUP_ERROR("out of memory", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_NONFATAL); - } - - bool already = (state->scan_listeners != NULL); - - tcp_arg (http_client, l); - /* TODO: check if also need a tcp_err() cb, or if recv() is enough */ - l->conn = http_client; - l->next = state->scan_listeners; - state->scan_listeners = l; - - if (!already) - { - if (!wifi_station_scan(NULL, on_scan_done)) + scan_listener_t *l = os_malloc (sizeof (scan_listener_t)); + if (!l) { - enduser_setup_http_serve_header(http_client, http_header_500, LITLEN(http_header_500)); - deferred_close (l->conn); - l->conn = 0; - free_scan_listeners(); + ENDUSER_SETUP_ERROR("out of memory", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_NONFATAL); } + + bool already = (state->scan_listeners != NULL); + + tcp_arg (http_client, l); + /* TODO: check if also need a tcp_err() cb, or if recv() is enough */ + l->conn = http_client; + l->next = state->scan_listeners; + state->scan_listeners = l; + + if (!already) + { + if (!wifi_station_scan(NULL, on_scan_done)) + { + enduser_setup_http_serve_header(http_client, http_header_500, LITLEN(http_header_500)); + deferred_close (l->conn); + l->conn = 0; + free_scan_listeners(); + } + } + goto free_out; /* request queued */ + } + else + { + // Return No Content status to the caller + enduser_setup_http_serve_header(http_client, http_header_204, LITLEN(http_header_204)); } - goto free_out; /* request queued */ } - else if (c_strncmp(data + 4, "/status ", 8) == 0) + else if (c_strncmp(data + 4, "/status.json", 12) == 0) { - serve_status(http_client); + enduser_setup_serve_status_as_json(http_client); + } + else if (c_strncmp(data + 4, "/status", 7) == 0) + { + enduser_setup_serve_status(http_client); } + else if (c_strncmp(data + 4, "/update?", 8) == 0) { switch (enduser_setup_http_handle_credentials(data, data_len)) @@ -1101,14 +1158,29 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s enduser_setup_http_serve_header(http_client, http_header_302, LITLEN(http_header_302)); break; case 1: - enduser_setup_http_serve_header(http_client, http_header_401, LITLEN(http_header_401)); + enduser_setup_http_serve_header(http_client, http_header_400, LITLEN(http_header_400)); break; default: ENDUSER_SETUP_ERROR("http_recvcb failed. Failed to handle wifi credentials.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL); break; } } - else if (c_strncmp(data + 4, "/generate_204 ", 14) == 0) + else if (c_strncmp(data + 4, "/setwifi?", 9) == 0) + { + switch (enduser_setup_http_handle_credentials(data, data_len)) + { + case 0: + enduser_setup_serve_status_as_json(http_client); + break; + case 1: + enduser_setup_http_serve_header(http_client, http_header_400, LITLEN(http_header_400)); + break; + default: + ENDUSER_SETUP_ERROR("http_recvcb failed. Failed to handle wifi credentials.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL); + break; + } + } + else if (c_strncmp(data + 4, "/generate_204", 13) == 0) { /* Convince Android devices that they have internet access to avoid pesky dialogues. */ enduser_setup_http_serve_header(http_client, http_header_204, LITLEN(http_header_204)); @@ -1116,13 +1188,16 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s else { ENDUSER_SETUP_DEBUG("serving 404"); - ENDUSER_SETUP_DEBUG(data + 4); enduser_setup_http_serve_header(http_client, http_header_404, LITLEN(http_header_404)); } } - else /* not GET */ + else if (c_strncmp(data, "OPTIONS ", 8) == 0) { - enduser_setup_http_serve_header(http_client, http_header_401, LITLEN(http_header_401)); + enduser_setup_handle_OPTIONS(http_client, data, data_len); + } + else /* not GET or OPTIONS */ + { + enduser_setup_http_serve_header(http_client, http_header_405, LITLEN(http_header_405)); } deferred_close (http_client); @@ -1135,6 +1210,7 @@ free_out: static err_t enduser_setup_http_connectcb(void *arg, struct tcp_pcb *pcb, err_t err) { ENDUSER_SETUP_DEBUG("enduser_setup_http_connectcb"); + if (!state) { ENDUSER_SETUP_DEBUG("connect callback but no state?!"); @@ -1208,7 +1284,7 @@ static void enduser_setup_http_stop(void) static void enduser_setup_ap_stop(void) { - ENDUSER_SETUP_DEBUG("enduser_setup_station_stop"); + ENDUSER_SETUP_DEBUG("enduser_setup_ap_stop"); wifi_set_opmode(~SOFTAP_MODE & wifi_get_opmode()); } @@ -1234,15 +1310,47 @@ static void enduser_setup_ap_start(void) cnf.ssid[ssid_name_len] = '_'; c_sprintf(cnf.ssid + ssid_name_len + 1, "%02X%02X%02X", mac[3], mac[4], mac[5]); cnf.ssid_len = ssid_name_len + 7; - cnf.channel = 1; + cnf.channel = state == NULL? 1 : state->softAPchannel; cnf.authmode = AUTH_OPEN; cnf.ssid_hidden = 0; cnf.max_connection = 5; cnf.beacon_interval = 100; wifi_set_opmode(STATIONAP_MODE); wifi_softap_set_config(&cnf); + +#if ENDUSER_SETUP_DEBUG_ENABLE + char debuginfo[100]; + c_sprintf(debuginfo, "SSID: %s, CHAN: %d", cnf.ssid, cnf.channel); + ENDUSER_SETUP_DEBUG(debuginfo); +#endif } +static void on_initial_scan_done (void *arg, STATUS status) +{ + ENDUSER_SETUP_DEBUG("on_initial_scan_done"); + + if (!state) + { + return; + } + + int8_t rssi = -100; + + if (status == OK) + { + for (struct bss_info *wn = arg; wn; wn = wn->next.stqe_next) + { + if (wn->rssi > rssi) + { + state->softAPchannel = wn->channel; + rssi = wn->rssi; + } + } + } + + enduser_setup_ap_start(); + enduser_setup_check_station_start(); +} static void enduser_setup_dns_recv_callback(void *arg, char *recv_data, unsigned short recv_len) { @@ -1255,6 +1363,31 @@ static void enduser_setup_dns_recv_callback(void *arg, char *recv_data, unsigned uint32_t dns_reply_static_len = (uint32_t) sizeof(dns_header) + (uint32_t) sizeof(dns_body) + 2 + 4; // dns_id=2bytes, ip=4bytes uint32_t dns_reply_len = dns_reply_static_len + qname_len; +#if ENDUSER_SETUP_DEBUG_ENABLE + char *qname = c_malloc(qname_len + 12); + if (qname != NULL) + { + c_sprintf(qname, "DNS QUERY = %s", &(recv_data[12])); + + uint32_t p; + int i, j; + + for(i=12;i<(int)strlen(qname);i++) + { + p=qname[i]; + for(j=0;j<(int)p;j++) + { + qname[i]=qname[i+1]; + i=i+1; + } + qname[i]='.'; + } + qname[i-1]='\0'; + ENDUSER_SETUP_DEBUG(qname); + c_free(qname); + } +#endif + uint8_t if_mode = wifi_get_opmode(); if ((if_mode & SOFTAP_MODE) == 0) { @@ -1305,6 +1438,14 @@ static void enduser_setup_dns_recv_callback(void *arg, char *recv_data, unsigned { ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Can't execute transmission.", ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND, ENDUSER_SETUP_ERR_FATAL); } + else if (err == ESPCONN_MAXNUM) + { + ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Buffer full. Discarding...", ENDUSER_SETUP_ERR_MAX_NUMBER, ENDUSER_SETUP_ERR_NONFATAL); + } + else if (err == ESPCONN_IF) + { + ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Send UDP data failed", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL); + } else if (err != 0) { ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. espconn_send failed", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL); @@ -1408,6 +1549,7 @@ static void enduser_setup_dns_stop(void) static int enduser_setup_init(lua_State *L) { + // Note: Normal to not see this debug message on first invocation because debug callback is set below ENDUSER_SETUP_DEBUG("enduser_setup_init"); if (state != NULL) @@ -1452,12 +1594,19 @@ static int enduser_setup_init(lua_State *L) { lua_pushvalue (L, 3); state->lua_dbg_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + ENDUSER_SETUP_DEBUG("enduser_setup_init: Debug callback has been defined"); } else { state->lua_dbg_cb_ref = LUA_NOREF; } + state->softAPchannel = 1; + state->success = 0; + state->callbackDone = 0; + state->lastStationStatus = 0; + state->connecting = 0; + return 0; } @@ -1475,6 +1624,7 @@ static int enduser_setup_manual(lua_State *L) static int enduser_setup_start(lua_State *L) { + // Note: The debug callback is set in enduser_setup_init. It's normal to not see this debug message on first invocation. ENDUSER_SETUP_DEBUG("enduser_setup_start"); if (!do_station_cfg_handle) @@ -1487,10 +1637,14 @@ static int enduser_setup_start(lua_State *L) goto failed; } - enduser_setup_check_station_start(); if (!manual) { - enduser_setup_ap_start(); + ENDUSER_SETUP_DEBUG("Performing AP Scan to identify likely AP's channel"); + wifi_station_scan(NULL, on_initial_scan_done); + } + else + { + enduser_setup_check_station_start(); } if(enduser_setup_dns_start()) @@ -1529,6 +1683,12 @@ static int enduser_setup_stop(lua_State* L) { enduser_setup_ap_stop(); } + if (state->success && !state->callbackDone) + { + wifi_set_opmode(STATION_MODE | wifi_get_opmode()); + wifi_station_connect(); + enduser_setup_connected_callback(); + } enduser_setup_dns_stop(); enduser_setup_http_stop(); enduser_setup_free(); diff --git a/app/modules/eus/enduser_setup.html b/app/modules/eus/enduser_setup.html new file mode 100644 index 00000000..b1e0de09 --- /dev/null +++ b/app/modules/eus/enduser_setup.html @@ -0,0 +1,315 @@ + + + + + + WiFi Login + + + + +
+
+
+
+

Connect device to your Wi-Fi

+ + + + + +
+
+

Success!

+
+

Your device has successfully connected to the Wi-Fi network.

+
+
+
+

Trying...

+ +
+
+

Updating Status...

+
+ + + + \ No newline at end of file diff --git a/app/modules/eus/enduser_setup.html.gz b/app/modules/eus/enduser_setup.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..3abbaa13a7d8a321c7fa1825d827d2576d28d278 GIT binary patch literal 2393 zcmV-f38wZRiwFP!000021C?3*a@#f%|GPJH@30>CLb;)2eb{Nn64lA&k|sAv+Ns^? zbuyU_M1m$_3g7{tWnJobKP&-ShCqk4T2}!+6&zyJIS3cW6ufB)OU zed7})nR)tVv8I{;Cu^ES*Nko|A$0_);F@xsL>rdrBFX4AOX;v##K1VyjFdx_l9DEq z(Ku=ZX{JlM{EfY4@J?JZzBsl!c&7BWq@cGIO*FmH$Ei}d|IK&s1;4&o6KOckU_5V@ z70EKjFWdDk{s;fi{1_irBHKFFg0jm(pTT52{)gqxjq$^rtXa7|14XzRDk@oS4^jP) zl+Wi$tuP&vid3FYeCnqP_$%(J~(8a{5t%PEl2_uE9lqhRWoqSQB7rsL{n?zwgH9Jjn6ky;DiHpZ)3+rZRD32z=1Eos`CJKWIK z6*Hb!6(y4Jl%4?>oVKRPjLM-T8LL$g^_<Q)hFVIqx zKbFB^nx^+94BLcFoM@LO2en>jQc((N?43@hJ6sy(XGY^%aG@%aQr~Y3o^P|C#cm0H zgoaM9Hd!_Vl5*Xr#G%G1vHUL1LBa#D%&OMkS}J6$1lwJ!jH#+5TN9m9q*p;4I&Wu2 zQz41HTn?k2`G}3vPH4;~tCQ33!(4SZdzOo|R`I=!LR?b`UphfOhN)4CA&Ko8-QMZ* zYSiprvJf29`s#z?*|TSzShFzp$a(0xINW`oz zT03rrE@a=I+K71`%&Kl!w;ot4f&$(YgYJ9m{0NP9sqwZRpPW$sVJ1aoxOm*G@~Qit z+3(fW)G2VFb?EDs5wrnGOQP8|eV|gH{U!|?SH8U(f9fh#cjfW*ld5E@rwN7H!zQUk z6||zDP4(1CuhDu1DY&koA5x0V9q9i(!=7ZO7ayb}+TuxNlO^ zh-~7%d~$d*Jzb@96NHNFh-|Y#G6yr93*EEPWRD%S^VUAw&T9CSV%cuaYp|XQDGgg9 z^4zXT#WX2RSxNAIK~<8J7NX3kOrjOn)-q7%Kh{%_mky$ou*W&1C!^;UaFW~Ta5gUh;RTYhyrE# z^13XykXiuI46&mN3N~5}B_qU8n4B)l?#PPi@;b=4gleDdzlqBubvk5qtq@OpzQik=AR<_2|DfmD_Viu%n#A?c5hhRz~KxgXplmfYDJ5 zq;i|~K14@*OiGLDMOmUXq*OEpNz9)zRCjbcJ5?bzgFJ?5`*7x^Ra$Z}Ji7K7+mSxkLag8#si2%f@w zqKlCvJQM4INpAGxtBWWGPB-T|hH0NMX)VK;*`!8?`NT-dorvIY`nAIu9`-1=Mh;XA zSztjk*0s@!hi zXxsLlFWJZ*uq+5pd(qQ|tRf7pdyoo4{juBb=xtWgD1PA3jS03WKS=eOu##plgpCv? z@NHA$UN+-Fy|wXA7C)OD{&hb#wd-?Sx6`V5;y51_Vt*;x?mgc24lZ*r1ysH%^Nz)7A$&-c|4%}SU)4-6{>tU^cQAzowE?^1c z2P*>_<99aX?5R0{VzK&6QyukbL57~T>9LHIkb1BK8i`@$>j)>l)krGEUt2rDEO#;4Yx>T+uNIq_wOJ<6&-wJg)=ur;7Sgg0bnzNnlW5bm-V) zjDAmeO-gvhwcJLJ%8;`@>@`7LD3NJl${+^k4n^kfI(^;159hJq`m|RQTCOFgfE!I= zVR-aAqkYZ)+UufiEcgohKnL0|!?1Bv!M*Vc5=BLz|xgkRFW@nC=M^MCAc zo1U86K&c%>tx{*6-3lmIS5uFy)3k6@_SEB|`^|PdIt? http_html_backup.def + diff --git a/docs/en/modules/enduser-setup.md b/docs/en/modules/enduser-setup.md index 704d1dfc..ade57594 100644 --- a/docs/en/modules/enduser-setup.md +++ b/docs/en/modules/enduser-setup.md @@ -7,10 +7,29 @@ This module provides a simple way of configuring ESP8266 chips without using a s ![enduser setup config dialog](../../img/enduser-setup.jpg "enduser setup config dialog") -After running [`enduser_setup.start()`](#enduser_setupstart) a portal like the above can be accessed through a wireless network called SetupGadget_XXXXXX. The portal is used to submit the credentials for the WiFi of the enduser. -After an IP address has been successfully obtained this module will stop as if [`enduser_setup.stop()`](#enduser_setupstop) had been called. +After running [`enduser_setup.start()`](#enduser_setupstart), a wireless network named "SetupGadget_XXXXXX" will start. Connect to that SSID and then navigate to the root +of any website (e.g., `http://example.com/` will work, but do not use `.local` domains because it will fail on iOS). A web page similar to the picture above will load, allowing the +end user to provide their Wi-Fi information. + +After an IP address has been successfully obtained, then this module will stop as if [`enduser_setup.stop()`](#enduser_setupstop) had been called. There is a 10-second delay before +teardown to allow connected clients to obtain a last status message while the SoftAP is still active. + +Alternative HTML can be served by placing a file called `enduser_setup.html` on the filesystem. Everything needed by the web page must be included in this one file. This file will be kept +in RAM, so keep it as small as possible. The file can be gzip'd ahead of time to reduce the size (i.e., using `gzip -n` or `zopfli`), and when served, the End User Setup module will add +the appropriate `Content-Encoding` header to the response. *Note: Even if gzipped, the file still needs to be named `enduser_setup.html`.* + +The following HTTP endpoints exist: + +|Endpoint|Description| +|--------|-----------| +|/|Returns HTML for the web page. Will return the contents of `enduser_setup.html` if it exists on the filesystem, otherwise will return a page embedded into the firmware image.| +|/aplist|Forces the ESP8266 to perform a site survey across all channels, reporting access points that it can find. Return payload is a JSON array: `[{"ssid":"foobar","rssi":-36,"chan":3}]`| +|/generate_204|Returns a HTTP 204 status (expected by certain Android clients during Wi-Fi connectivity checks)| +|/status|Returns plaintext status description, used by the web page| +|/status.json|Returns a JSON payload containing the ESP8266's chip id in hexadecimal format and the status code: 0=Idle, 1=Connecting, 2=Wrong Password, 3=Network not Found, 4=Failed, 5=Success| +|/setwifi|Endpoint intended for services to use for setting the wifi credentials. Identical to `/update` except returns the same payload as `/status.json` instead of redirecting to `/`.| +|/update|Form submission target. Example: `http://example.com/update?wifi_ssid=foobar&wifi_password=CorrectHorseBatteryStaple`. Must be a GET request. Will redirect to `/` when complete. | -Alternative HTML can be served by placing a file called `enduser_setup.html` in the filesystem. This file will be kept in RAM, so keep it as small as possible. ## enduser_setup.manual() @@ -53,7 +72,7 @@ Starts the captive portal. #### Parameters - `onConnected()` callback will be fired when an IP-address has been obtained, just before the enduser_setup module will terminate itself - `onError()` callback will be fired if an error is encountered. `err_num` is a number describing the error, and `string` contains a description of the error. - - `onDebug()` callback is disabled by default. It is intended to be used to find internal issues in the module. `string` contains a description of what is going on. + - `onDebug()` callback is disabled by default (controlled by `#define ENDUSER_SETUP_DEBUG_ENABLE` in `enduser_setup.c`). It is intended to be used to find internal issues in the module. `string` contains a description of what is going on. #### Returns `nil`