esp32: separate factory setup
authorAndy Green <andy@warmcat.com>
Thu, 16 Mar 2017 02:46:31 +0000 (10:46 +0800)
committerAndy Green <andy@warmcat.com>
Fri, 31 Mar 2017 12:05:10 +0000 (20:05 +0800)
24 files changed:
CMakeLists.txt
Kconfig
README.esp32.md [new file with mode: 0644]
README.md
component.mk
cross-esp32.cmake
lib/client-handshake.c
lib/client.c
lib/context.c
lib/fops-zip.c
lib/libwebsockets.c
lib/libwebsockets.h
lib/lws-plat-esp32.c
lib/output.c
lib/private-libwebsockets.h
lib/romfs.c
lib/server.c
lib/ssl-client.c
lib/ssl-server.c
lib/ssl.c
plugins/protocol_esp32_lws_ota.c [new file with mode: 0644]
plugins/protocol_esp32_lws_reboot_to_factory.c [new file with mode: 0644]
plugins/protocol_esp32_lws_scan.c
scripts/esp32.mk [new file with mode: 0644]

index fc833a7..b33f5dc 100644 (file)
@@ -168,11 +168,9 @@ endif()
 if (LWS_WITH_ESP32)
  set(LWS_WITH_SHARED OFF)
  set(LWS_WITH_SSL ON)
- set(LWS_WITH_ZLIB OFF)
- # set(LWS_WITHOUT_CLIENT ON)
+  # set(LWS_WITHOUT_CLIENT ON)
  set(LWS_WITHOUT_TESTAPPS ON)
  set(LWS_WITHOUT_EXTENSIONS ON)
- set(LWS_WITHOUT_CLIENT ON)
  set(LWS_WITH_PLUGINS OFF)
  set(LWS_WITH_RANGES ON)
  # this implies no pthreads in the lib
@@ -180,7 +178,7 @@ if (LWS_WITH_ESP32)
  set(LWS_HAVE_MALLOC 1)
  set(LWS_HAVE_REALLOC 1)
  set(LWS_HAVE_GETIFADDRS 1)
- set(LWS_WITH_ZIP_FOPS 0)
+ set(LWS_WITH_ZIP_FOPS 1)
 endif()
 
 
diff --git a/Kconfig b/Kconfig
index 7d62343..ec36ac1 100644 (file)
--- a/Kconfig
+++ b/Kconfig
@@ -1,9 +1,24 @@
 menu "Libwebsockets"
 
-config LWS
-    bool "Enable Libwebsockets"
-    default n
-    help
-       Enable Libwebsockets Library
+config LWS_MODEL_NAME
+       string "Model name of device firmware is for"
+       default "lws"
+
+config LWS_OTA_SERVER_FQDN
+       string "Domain name of OTA update server, eg, warmcat.com"
+       default ""
+
+config LWS_OTA_SERVER_BASE_URL
+       string "Base URL on OTA update server, eg, /esp32-ota (model is added)"
+       default "/esp32-ota"
+
+config LWS_OTA_SERVER_UPLOAD_USER
+       string "User to scp to upload server with"
+       default "root"
+
+config LWS_OTA_SERVER_UPLOAD_PATH
+       string "Path served in upload server (eg, \"/var/www/libwebsockets.org\""
+       default "/var/www/libwebsockets.org"
 
 endmenu
+
diff --git a/README.esp32.md b/README.esp32.md
new file mode 100644 (file)
index 0000000..6f64891
--- /dev/null
@@ -0,0 +1,23 @@
+ESP32 Support
+=============
+
+Lws provides a "factory" application
+
+https://github.com/warmcat/lws-esp32-factory
+
+and a test application which implements the generic lws server test apps
+
+https://github.com/warmcat/lws-esp32-test-server-demos
+
+The behaviours of the generic factory are are quite rich, and cover uploading SSL certs through factory and user configuration, AP selection and passphrase entry, and managing a switch to allow the user to force entry to user setup mode at boot subsequently.
+
+The factory app comes with partitioning for a 1MB factory partition containing that app and data, and a single 2.9MB OTA partition containing the main app.
+
+The factory app is able to do OTA updates for both the factory and OTA partition slots; updating the factory slot first writes the new image to the OTA slot and copies it into place at the next boot, after which the user can reload the OTA slot.
+
+State|Image|AP SSID|Port|URL|Mode
+---|---|---|---|---|---
+Factory Reset or Uninitialized|Factory|AP: ESP_012345|80|http://192.168.4.1|factory.html - to set certificates and serial
+User configuration|Factory|AP: config-model-serial|443|https://192.168.4.1|index.html - user set up his AP information
+Operation|OTA|Station only|443|https://model-serial.local|OTA application
+
index 51a1fc6..1d7d108 100644 (file)
--- a/README.md
+++ b/README.md
@@ -5,10 +5,17 @@
 libwebsockets
 -------------
 
-| News |
-------
-| ESP32 is now supported in lws!  https://libwebsockets.org/lws-api-doc-master/html/md_README_8build.html |
-| v2.2 is out... see the changelog https://github.com/warmcat/libwebsockets/blob/v2.2-stable/changelog |
+News
+----
+
+ESP32 is now supported in lws!  Download the
+
+ - factory https://github.com/warmcat/lws-esp32-factory and
+ - test server app https://github.com/warmcat/lws-esp32-test-server-demos
+
+v2.2 is out... see the changelog https://github.com/warmcat/libwebsockets/blob/v2.2-stable/changelog
+
+
 
 This is the libwebsockets C library for lightweight websocket clients and
 servers.  For support, visit
index becad0f..d80bba9 100644 (file)
@@ -14,17 +14,19 @@ CROSS_PATH:= $(shell dirname $(CROSS_PATH1) )/..
 build:
        cd $(COMPONENT_BUILD_DIR) ; \
        echo "doing lws cmake" ; \
-       cmake $(COMPONENT_PATH)  -DLWS_C_FLAGS="$(CFLAGS)" \
+       cmake $(COMPONENT_PATH)  -DLWS_C_FLAGS="$(CFLAGS) -DNDEBUG=1 " \
+               -DIDF_PATH=$(IDF_PATH) \
                -DCROSS_PATH=$(CROSS_PATH) \
                -DCOMPONENT_PATH=$(COMPONENT_PATH) \
                -DBUILD_DIR_BASE=$(BUILD_DIR_BASE) \
                -DCMAKE_TOOLCHAIN_FILE=$(COMPONENT_PATH)/cross-esp32.cmake \
                -DCMAKE_BUILD_TYPE=RELEASE \
-               -DLWS_WITH_NO_LOGS=0 \
-               -DOPENSSL_INCLUDE_DIR=${COMPONENT_PATH}/../openssl/include \
+               -DOPENSSL_INCLUDE_DIR=${IDF_PATH}/components/openssl/include \
                -DOPENSSL_LIBRARIES=x \
+               -DZLIB_LIBRARY=$(BUILD_DIR_BASE)/zlib/libzlib.a \
+               -DZLIB_INCLUDE_DIR=$(COMPONENT_PATH)/../zlib \
                -DLWS_WITH_ESP32=1 ;\
-       make VERBOSE=1 && \
+       make && \
        cp ${COMPONENT_BUILD_DIR}/lib/libwebsockets.a ${COMPONENT_BUILD_DIR}/liblibwebsockets.a
 
 clean: myclean
index a0b3c1d..bea0b54 100644 (file)
@@ -12,7 +12,22 @@ set(CMAKE_SYSTEM_NAME Linux)
 # Name of C compiler.
 set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/xtensa-esp32-elf-gcc")
 
-SET(CMAKE_C_FLAGS "-nostdlib -Wall -Werror -I${BUILD_DIR_BASE}/include -I${COMPONENT_PATH}/../driver/include -I${COMPONENT_PATH}/../spi_flash/include -I${COMPONENT_PATH}/../nvs_flash/include -I${COMPONENT_PATH}/../tcpip_adapter/include -I${COMPONENT_PATH}/../lwip/include/lwip/posix -I${COMPONENT_PATH}/../lwip/include/lwip -I${COMPONENT_PATH}/../lwip/include/lwip/port -I${COMPONENT_PATH}/../esp32/include/ ${LWS_C_FLAGS} -I${COMPONENT_PATH}/../nvs_flash/test_nvs_host -I${COMPONENT_PATH}/../freertos/include -Os" CACHE STRING "" FORCE)
+SET(CMAKE_C_FLAGS "-nostdlib -Wall -Werror \
+       -I${BUILD_DIR_BASE}/include \
+       -I${IDF_PATH}/components/mdns/include \
+       -I${IDF_PATH}/components/driver/include \
+       -I${IDF_PATH}/components/spi_flash/include \
+       -I${IDF_PATH}/components/nvs_flash/include \
+       -I${IDF_PATH}/components/tcpip_adapter/include \
+       -I${IDF_PATH}/components/lwip/include/lwip/posix \
+       -I${IDF_PATH}/components/lwip/include/lwip \
+       -I${IDF_PATH}/components/lwip/include/lwip/port \
+       -I${IDF_PATH}/components/esp32/include/ \
+       -I${IDF_PATH}/components/bootloader_support/include/ \
+       -I${IDF_PATH}/components/app_update/include/ \
+       ${LWS_C_FLAGS} -Os \
+       -I${IDF_PATH}/components/nvs_flash/test_nvs_host \
+       -I${IDF_PATH}/components/freertos/include" CACHE STRING "" FORCE)
 
 # Where to look for the target environment. (More paths can be added here)
 set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")
index edfc311..c844053 100644 (file)
@@ -63,7 +63,7 @@ lws_client_connect_2(struct lws *wsi)
        /*
         * prepare the actual connection (to the proxy, if any)
         */
-       lwsl_notice("%s: address %s\n", __func__, ads);
+       lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads);
 
 #ifdef LWS_USE_IPV6
        if (LWS_IPV6_ENABLED(wsi->vhost)) {
index 5a29aa6..c337b11 100755 (executable)
@@ -320,7 +320,9 @@ client_http_body_sent:
 
 bail3:
                lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n");
-               wsi->vhost->protocols[0].callback(wsi,
+               if (cce)
+                       lwsl_info("reason: %s\n", cce);
+               wsi->protocol->callback(wsi,
                        LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
                        wsi->user_space, (void *)cce, cce ? strlen(cce) : 0);
                wsi->already_did_cce = 1;
@@ -369,6 +371,9 @@ lws_http_transaction_completed_client(struct lws *wsi)
                return 1;
        }
 
+       /* we don't support chained client connections yet */
+       return 1;
+
        /* otherwise set ourselves up ready to go again */
        wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED;
        wsi->mode = LWSCM_HTTP_CLIENT_ACCEPTED;
@@ -1022,19 +1027,21 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)
        const struct lws_extension *ext;
        int ext_count = 0;
 #endif
+       const char *pp = lws_hdr_simple_ptr(wsi,
+                               _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
 
        meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
        if (!meth) {
                meth = "GET";
                wsi->do_ws = 1;
-       } else
+       } else {
                wsi->do_ws = 0;
+       }
 
        if (!strcmp(meth, "RAW")) {
-               const char *pp = lws_hdr_simple_ptr(wsi,
-                                       _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
                lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
                lwsl_notice("client transition to raw\n");
+
                if (pp) {
                        const struct lws_protocols *pr;
 
@@ -1048,6 +1055,7 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)
 
                        lws_bind_protocol(wsi, pr);
                }
+
                if ((wsi->protocol->callback)(wsi,
                                LWS_CALLBACK_RAW_ADOPT,
                                wsi->user_space, NULL, 0))
index bab462c..435f48e 100644 (file)
@@ -309,12 +309,12 @@ static const struct lws_protocols protocols_dummy[] = {
                lws_callback_http_dummy,                /* callback */
                0,      /* per_session_data_size */
                0,                      /* max frame size / rx buffer */
-               0, NULL
+               0, NULL, 0
        },
        /*
         * the other protocols are provided by lws plugins
         */
-       { NULL, NULL, 0, 0, 0, NULL} /* terminator */
+       { NULL, NULL, 0, 0, 0, NULL, 0} /* terminator */
 };
 
 #ifdef LWS_PLAT_OPTEE
@@ -670,6 +670,8 @@ lws_create_context(struct lws_context_creation_info *info)
 
        context->time_up = time(NULL);
 
+       context->simultaneous_ssl_restriction = info->simultaneous_ssl_restriction;
+
 #ifndef LWS_NO_DAEMONIZE
        if (pid_daemon) {
                context->started_with_parent = pid_daemon;
index f255055..58cce38 100644 (file)
@@ -178,7 +178,7 @@ int
 lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
 {
        lws_filepos_t amount;
-       uint8_t buf[64];
+       uint8_t buf[96];
        int i;
 
        if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0)
index 05b2724..b19e493 100755 (executable)
@@ -478,7 +478,9 @@ just_kill_connection:
         * for the POLLIN to show a zero-size rx before coming back and doing
         * the actual close.
         */
-       if (wsi->mode != LWSCM_RAW && wsi->state != LWSS_SHUTDOWN &&
+       if (wsi->mode != LWSCM_RAW &&
+           !(wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) &&
+           wsi->state != LWSS_SHUTDOWN &&
            wsi->state != LWSS_CLIENT_UNCONNECTED &&
            reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY &&
            !wsi->socket_is_permanently_unusable) {
@@ -503,7 +505,7 @@ just_kill_connection:
 
 // This causes problems with disconnection when the events are half closing connection
 // FD_READ | FD_CLOSE (33)
-#ifndef _WIN32_WCE
+#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP32)
                /* libuv: no event available to guarantee completion */
                if (!LWS_LIBUV_ENABLED(context)) {
 
@@ -511,6 +513,7 @@ just_kill_connection:
                        wsi->state = LWSS_SHUTDOWN;
                        lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH,
                                        context->timeout_secs);
+
                        return;
                }
 #endif
@@ -545,7 +548,6 @@ just_kill_connection:
        wsi->state = LWSS_DEAD_SOCKET;
 
        lws_free_set_NULL(wsi->rxflow_buffer);
-
        if (wsi->state_pre_close == LWSS_ESTABLISHED ||
            wsi->mode == LWSCM_WS_SERVING ||
            wsi->mode == LWSCM_WS_CLIENT) {
@@ -635,6 +637,7 @@ just_kill_connection:
                lwsl_warn("ext destroy wsi failed\n");
 
 async_close:
+
        wsi->socket_is_permanently_unusable = 1;
 
 #ifdef LWS_USE_LIBUV
index 7898b41..2a9fb3a 100644 (file)
@@ -528,14 +528,19 @@ static inline void uv_close(uv_handle_t *h, void *v)
 
 /* ESP32 helper declarations */
 
+#include <mdns.h>
+#include <esp_partition.h>
+
 #define LWS_PLUGIN_STATIC
+#define LWS_MAGIC_REBOOT_TYPE_ADS 0x50001ffc
+#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY 0xb00bcafe
+#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY 0xfaceb00b
+#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON 0xf0cedfac
 
-/* user code provides these */
+static inline uint32_t lws_esp32_get_reboot_type(void) { uint32_t *p = (uint32_t *)LWS_MAGIC_REBOOT_TYPE_ADS; return *p; }
 
-extern char lws_esp32_model[16];
+/* user code provides these */
 
-extern int
-lws_esp32_is_booting_in_ap_mode(void);
 extern void
 lws_esp32_identify_physical_device(void);
 
@@ -544,18 +549,49 @@ lws_esp32_identify_physical_device(void);
 extern void (*lws_cb_scan_done)(void *);
 extern void *lws_cb_scan_done_arg;
 
-extern char lws_esp32_serial[], lws_esp32_force_ap, lws_esp32_region;
+struct lws_esp32 {
+       char sta_ip[16];
+       char sta_mask[16];
+       char sta_gw[16];
+       char serial[16];
+       char opts[16];
+       char model[16];
+       char access_pw[16];
+       mdns_server_t *mdns;
+               char region;
+               char inet;
+};
+
+struct lws_esp32_image {
+       uint32_t romfs;
+       uint32_t romfs_len;
+       uint32_t json;
+       uint32_t json_len;
+};
+
+extern struct lws_esp32 lws_esp32;
 
 extern esp_err_t
 lws_esp32_event_passthru(void *ctx, system_event_t *event);
 extern void
 lws_esp32_wlan_config(void);
 extern void
-lws_esp32_wlan_start(void);
+lws_esp32_wlan_start_ap(void);
+extern void
+lws_esp32_wlan_start_station(void);
 struct lws_context_creation_info;
+extern void
+lws_esp32_set_creation_defaults(struct lws_context_creation_info *info);
 extern struct lws_context *
-lws_esp32_init(struct lws_context_creation_info *, unsigned int _romfs);
-
+lws_esp32_init(struct lws_context_creation_info *);
+extern int
+lws_esp32_wlan_nvs_get(int retry);
+extern void
+lws_esp32_restart_guided(uint32_t type);
+extern const esp_partition_t *
+lws_esp_ota_get_boot_partition(void);
+extern int
+lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i, char *json, int json_len);
 #else
 typedef int lws_sockfd_type;
 typedef int lws_filefd_type;
@@ -1387,8 +1423,8 @@ struct lws_protocols {
         * be able to consume it all without having to return to the event
         * loop.  That is supported in lws.
         *
-        * This also controls how much may be sent at once at the moment,
-        * although this is likely to change.
+        * If .tx_packet_size is 0, this also controls how much may be sent at once
+        * for backwards compatibility.
         */
        unsigned int id;
        /**< ignored by lws, but useful to contain user information bound
@@ -1399,6 +1435,15 @@ struct lws_protocols {
         * capability flags based on selected protocol version, etc. */
        void *user; /**< ignored by lws, but user code can pass a pointer
                        here it can later access from the protocol callback */
+       size_t tx_packet_size;
+       /**< 0 indicates restrict send() size to .rx_buffer_size for backwards-
+        * compatibility.
+        * If greater than zero, a single send() is restricted to this amount
+        * and any remainder is buffered by lws and sent afterwards also in
+        * these size chunks.  Since that is expensive, it's preferable
+        * to restrict one fragment you are trying to send to match this
+        * size.
+        */
 
        /* Add new things just above here ---^
         * This is part of the ABI, don't needlessly break compatibility */
@@ -1859,6 +1904,8 @@ struct lws_context_creation_info {
         * If NULL, lws provides just the platform file operations struct for
         * backwards compatibility.
         */
+       int simultaneous_ssl_restriction;
+       /**< CONTEXT: 0 (no limit) or limit of simultaneous SSL sessions possible.*/
 
        /* Add new things just above here ---^
         * This is part of the ABI, don't needlessly break compatibility
@@ -2335,7 +2382,6 @@ lws_client_connect_extended(struct lws_context *clients, const char *address,
 LWS_VISIBLE LWS_EXTERN int
 lws_init_vhost_client_ssl(const struct lws_context_creation_info *info,
                          struct lws_vhost *vhost);
-
 /**
  * lws_http_client_read() - consume waiting received http client data
  *
@@ -2999,7 +3045,7 @@ lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token,
                             const unsigned char *value, int length,
                             unsigned char **p, unsigned char *end);
 /**
- * lws_add_http_header_by_name() - append content-length helper
+ * lws_add_http_header_content_length() - append content-length helper
  *
  * \param wsi: the connection to check
  * \param content_length: the content length to use
@@ -4591,6 +4637,9 @@ LWS_VISIBLE LWS_EXTERN int
 _lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
                     uint8_t *buf, lws_filepos_t len);
 
+LWS_VISIBLE LWS_EXTERN int
+lws_alloc_vfs_file(struct lws_context *context, const char *filename, uint8_t **buf,
+                lws_filepos_t *amount);
 //@}
 
 /** \defgroup smtp
index 63315f2..c8b83f3 100644 (file)
@@ -1,6 +1,29 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
 #include "private-libwebsockets.h"
 #include "freertos/timers.h"
 #include <esp_attr.h>
+#include <esp_system.h>
+
 /*
  * included from libwebsockets.c for unix builds
  */
@@ -15,7 +38,20 @@ unsigned long long time_in_microseconds(void)
 LWS_VISIBLE int
 lws_get_random(struct lws_context *context, void *buf, int len)
 {
-       // !!!
+       while (len) {
+               uint32_t r = esp_random();
+               uint8_t *p = (uint8_t *)&r, *pb = buf;
+               int b = 4;
+
+               if (len < b)
+                       b = len;
+
+               len -= b;
+
+               while (b--)
+                       *pb++ = p[b];
+       }
+
        return 0;
 }
 
@@ -109,7 +145,7 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
        {
                fd_set readfds, writefds, errfds;
                struct timeval tv = { timeout_ms / 1000,
-                                     (timeout_ms % 1000) * 1000 };
+                                     (timeout_ms % 1000) * 1000 }, *ptv = &tv;
                int max_fd = 0;
                FD_ZERO(&readfds);
                FD_ZERO(&writefds);
@@ -126,7 +162,7 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
                        FD_SET(pt->fds[n].fd, &errfds);
                }
 
-               n = select(max_fd + 1, &readfds, &writefds, &errfds, &tv);
+               n = select(max_fd + 1, &readfds, &writefds, &errfds, ptv);
                for (n = 0; n < pt->fds_count; n++) {
                        if (FD_ISSET(pt->fds[n].fd, &readfds))
                                pt->fds[n].revents |= LWS_POLLIN;
@@ -237,15 +273,14 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
                        return 1;
 #endif
        }
-#if 0
+
        /* Disable Nagle */
        optval = 1;
 //     if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
 //             return 1;
-       tcp_proto = getprotobyname("TCP");
-       if (setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, &optval, optlen) < 0)
+       if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &optval, optlen) < 0)
                return 1;
-#endif
+
        /* We are nonblocking... */
        if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
                return 1;
@@ -533,72 +568,58 @@ char *ERR_error_string(unsigned long e, char *buf)
 /* helper functionality */
 
 #include "romfs.h"
-
-void (*lws_cb_scan_done)(void *);
-void *lws_cb_scan_done_arg;
-char lws_esp32_serial[16] = "unknown", lws_esp32_force_ap = 0,
-     lws_esp32_region = WIFI_COUNTRY_US; // default to safest option
+#include <esp_ota_ops.h>
+#include <tcpip_adapter.h>
+#include <esp_image_format.h>
+#include <esp_task_wdt.h>
+
+struct lws_esp32 lws_esp32 = {
+       .model = CONFIG_LWS_MODEL_NAME,
+       .serial = "unknown",
+       .region = WIFI_COUNTRY_US, // default to safest option
+};
 
 static romfs_t lws_esp32_romfs;
 
-/*
- * configuration related to the AP setup website
- *
- * The 'esplws-scan' protocol drives the configuration
- * site, and updates the scan results in realtime over
- * a websocket link.
- */
-
-#include "../plugins/protocol_esp32_lws_scan.c"
-
-static const struct lws_protocols protocols_ap[] = {
-       {
-               "http-only",
-               lws_callback_http_dummy,
-               0,      /* per_session_data_size */
-               900, 0, NULL
-       },
-       LWS_PLUGIN_PROTOCOL_ESPLWS_SCAN,
-       { NULL, NULL, 0, 0, 0, NULL } /* terminator */
+struct esp32_file {
+       const struct inode *i;
 };
 
-static const struct lws_protocol_vhost_options ap_pvo = {
-       NULL,
-       NULL,
-       "esplws-scan",
-       ""
-};
+static void render_ip(char *dest, int len, uint8_t *ip)
+{
+       snprintf(dest, len, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
+}
 
-static const struct lws_http_mount mount_ap = {
-        .mountpoint            = "/",
-        .origin                        = "/ap",
-        .def                   = "index.html",
-        .origin_protocol       = LWSMPRO_FILE,
-        .mountpoint_len                = 1,
-};
+void lws_esp32_restart_guided(uint32_t type)
+{
+        uint32_t *p_force_factory_magic = (uint32_t *)LWS_MAGIC_REBOOT_TYPE_ADS;
 
-struct esp32_file {
-       const struct inode *i;
-};
+       lwsl_notice("%s: %x\n", __func__, type);
+        *p_force_factory_magic = type;
+
+       esp_restart();
+}
 
 esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
 {
        switch(event->event_id) {
-       case SYSTEM_EVENT_SCAN_DONE:
-               if (lws_cb_scan_done)
-                       lws_cb_scan_done(lws_cb_scan_done_arg);
-               break;
        case SYSTEM_EVENT_STA_START:
                esp_wifi_connect();
                break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
-               /* This is a workaround as ESP32 WiFi libs don't currently
-                  auto-reassociate. */
+               lws_esp32.inet = 0;
                esp_wifi_connect();
                break;
+       case SYSTEM_EVENT_STA_GOT_IP:
+               lws_esp32.inet = 1;
+               render_ip(lws_esp32.sta_ip, sizeof(lws_esp32.sta_ip) - 1, (uint8_t *)&event->event_info.got_ip.ip_info.ip);
+               render_ip(lws_esp32.sta_mask, sizeof(lws_esp32.sta_mask) - 1, (uint8_t *)&event->event_info.got_ip.ip_info.netmask);
+               render_ip(lws_esp32.sta_gw, sizeof(lws_esp32.sta_gw) - 1, (uint8_t *)&event->event_info.got_ip.ip_info.gw);
+               break;
        default:
                break;
        }
+
        return ESP_OK;
 }
 
@@ -614,7 +635,6 @@ esp32_lws_fops_open(const struct lws_plat_file_ops *fops, const char *filename,
 
        if (!f)
                return NULL;
-
        f->i = romfs_get_info(lws_esp32_romfs, filename, &len);
        if (!f->i)
                goto bail;
@@ -664,13 +684,13 @@ esp32_lws_fops_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount, uint8_t *buf,
                    lws_filepos_t len)
 {
        struct esp32_file *f = fop_fd->filesystem_priv;
-
+#if 0
        if ((long)buf & 3) {
                lwsl_err("misaligned buf\n");
 
                return -1;
        }
-
+#endif
        if (fop_fd->pos >= fop_fd->len)
                return 0;
 
@@ -686,88 +706,288 @@ esp32_lws_fops_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount, uint8_t *buf,
 }
 
 static const struct lws_plat_file_ops fops = {
+       .next = &fops_zip,
        .LWS_FOP_OPEN = esp32_lws_fops_open,
        .LWS_FOP_CLOSE = esp32_lws_fops_close,
        .LWS_FOP_READ = esp32_lws_fops_read,
        .LWS_FOP_SEEK_CUR = esp32_lws_fops_seek_cur,
 };
 
-static wifi_config_t sta_config = {
-               .sta = {
-                       .bssid_set = false
-               }
-       }, ap_config = {
-               .ap = {
-                   .channel = 6,
-                   .authmode = WIFI_AUTH_OPEN,
-                   .max_connection = 1,
-               }
-       };
+static wifi_config_t config = {
+       .ap = {
+           .channel = 6,
+           .authmode = WIFI_AUTH_OPEN,
+           .max_connection = 1,
+       } }, sta_config = {
+       .sta = {
+               .bssid_set = 0,
+       } };
 
-void
-lws_esp32_wlan_config(void)
+int
+lws_esp32_wlan_nvs_get(int retry)
 {
        nvs_handle nvh;
-       char r[2];
+       char r[2], lws_esp32_force_ap = 0;
        size_t s;
+       uint8_t mac[6];
+
+       esp_efuse_read_mac(mac);
+       mac[5] |= 1; /* match the AP MAC */
+       snprintf(lws_esp32.serial, sizeof(lws_esp32.serial) - 1, "%02X%02X%02X", mac[3], mac[4], mac[5]);
 
        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
 
-       s = sizeof(sta_config.sta.ssid) - 1;
+       s = sizeof(config.sta.ssid) - 1;
        if (nvs_get_str(nvh, "ssid", (char *)sta_config.sta.ssid, &s) != ESP_OK)
                lws_esp32_force_ap = 1;
-       s = sizeof(sta_config.sta.password) - 1;
+       s = sizeof(config.sta.password) - 1;
        if (nvs_get_str(nvh, "password", (char *)sta_config.sta.password, &s) != ESP_OK)
                lws_esp32_force_ap = 1;
-       s = sizeof(lws_esp32_serial) - 1;
-       if (nvs_get_str(nvh, "serial", lws_esp32_serial, &s) != ESP_OK)
+       s = sizeof(lws_esp32.serial) - 1;
+       if (nvs_get_str(nvh, "serial", lws_esp32.serial, &s) != ESP_OK)
                lws_esp32_force_ap = 1;
        else
-               snprintf((char *)ap_config.ap.ssid, sizeof(ap_config.ap.ssid) - 1,
-                        "config-%s-%s", lws_esp32_model, lws_esp32_serial);
+               snprintf((char *)config.ap.ssid, sizeof(config.ap.ssid) - 1,
+                        "config-%s-%s", lws_esp32.model, lws_esp32.serial);
+       s = sizeof(lws_esp32.opts) - 1;
+       if (nvs_get_str(nvh, "opts", lws_esp32.opts, &s) != ESP_OK)
+               lws_esp32_force_ap = 1;
+
        s = sizeof(r);
        if (nvs_get_str(nvh, "region", r, &s) != ESP_OK)
                lws_esp32_force_ap = 1;
        else
-               lws_esp32_region = atoi(r);
+               lws_esp32.region = atoi(r);
+       lws_esp32.access_pw[0] = '\0';
+       nvs_get_str(nvh, "access_pw", lws_esp32.access_pw, &s);
 
        nvs_close(nvh);
 
+       if (retry && sta_config.sta.ssid[0]) {
+               tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
+               ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+               ESP_ERROR_CHECK( esp_wifi_connect());
+
+               ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_STA, &lws_esp32.mdns));
+               mdns_set_hostname(lws_esp32.mdns,  (const char *)&config.ap.ssid[7]);
+               mdns_set_instance(lws_esp32.mdns, "instance");
+       }
+
+       return lws_esp32_force_ap;
+}
+
+void
+lws_esp32_wlan_config(void)
+{
+       lws_esp32_wlan_nvs_get(0);
        tcpip_adapter_init();
 }
 
 void
-lws_esp32_wlan_start(void)
+lws_esp32_wlan_start_ap(void)
 {
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
 
        ESP_ERROR_CHECK( esp_wifi_init(&cfg));
        ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM));
-       esp_wifi_set_country(lws_esp32_region);
+       esp_wifi_set_country(lws_esp32.region);
+
+       ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_APSTA) );
+       ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &config) );
+       ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+       ESP_ERROR_CHECK( esp_wifi_start());
 
-       if (!lws_esp32_is_booting_in_ap_mode() && !lws_esp32_force_ap) {
-               ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA));
+       if (sta_config.sta.ssid[0]) {
+               tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
+               esp_wifi_set_auto_connect(1);
+               ESP_ERROR_CHECK( esp_wifi_connect());
                ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config));
-       } else {
-               ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_APSTA) );
-               ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &ap_config) );
+               ESP_ERROR_CHECK( esp_wifi_connect());
        }
+}
+
+void
+lws_esp32_wlan_start_station(void)
+{
+       wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+
+       ESP_ERROR_CHECK( esp_wifi_init(&cfg));
+       ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM));
+       esp_wifi_set_country(lws_esp32.region);
+
+       ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA));
+       ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config));
 
        ESP_ERROR_CHECK( esp_wifi_start());
 
-       tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&ap_config.ap.ssid[7]);
+       tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
+       esp_wifi_set_auto_connect(1);
+       ESP_ERROR_CHECK( esp_wifi_connect());
 
-       if (!lws_esp32_is_booting_in_ap_mode() && !lws_esp32_force_ap)
-               ESP_ERROR_CHECK( esp_wifi_connect());
+       ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_STA, &lws_esp32.mdns));
+       mdns_set_hostname(lws_esp32.mdns,  (const char *)&config.ap.ssid[7]);
+       mdns_set_instance(lws_esp32.mdns, "instance");
+}
+
+const esp_partition_t *
+lws_esp_ota_get_boot_partition(void)
+{
+       const esp_partition_t *part = esp_ota_get_boot_partition(), *factory_part, *ota;
+       esp_image_header_t eih, ota_eih;
+       uint32_t *p_force_factory_magic = (uint32_t *)LWS_MAGIC_REBOOT_TYPE_ADS;
+
+       /* confirm what we are told is the boot part is sane */
+       spi_flash_read(part->address , &eih, sizeof(eih));
+       factory_part = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
+                       ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
+       ota = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
+                       ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
+       spi_flash_read(ota->address , &ota_eih, sizeof(ota_eih));
+
+       if (eih.spi_mode == 0xff ||
+           *p_force_factory_magic == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY ||
+           *p_force_factory_magic == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON
+          ) {
+               /*
+                * we believed we were going to boot OTA, but we fell
+                * back to FACTORY in the bootloader when we saw it
+                * had been erased.  esp_ota_get_boot_partition() still
+                * says the OTA partition then even if we are in the
+                * factory partition right now.
+                */
+               part = factory_part;
+       } else
+               if (LWS_IS_FACTORY_APPLICATION == 1 &&
+                   ota_eih.spi_mode != 0xff &&
+                   part->address != factory_part->address) {
+                       uint8_t buf[4096];
+                       uint32_t n;
+                       /*
+                        * we are a FACTORY image running in an OTA slot...
+                        * it means we were just written and need to copy
+                        * ourselves into the FACTORY slot.
+                        */
+                       lwsl_notice("Copying FACTORY update into place 0x%x len 0x%x\n",
+                                   factory_part->address, factory_part->size);
+                       esp_task_wdt_feed();
+                       if (spi_flash_erase_range(factory_part->address, factory_part->size) != ESP_OK) {
+                               lwsl_err("spi: Failed to erase\n");
+                               goto retry;
+                       }
+
+                       for (n = 0; n < factory_part->size; n += sizeof(buf)) {
+                               esp_task_wdt_feed();
+                               spi_flash_read(part->address + n , buf, sizeof(buf));
+                               if (spi_flash_write(factory_part->address + n, buf, sizeof(buf)) != ESP_OK) {
+                                       lwsl_err("spi: Failed to write\n");
+                                       goto retry;
+                               }
+                       }
+
+                       /* destroy our OTA image header */
+                       spi_flash_erase_range(ota->address, 4096);
+
+                       /*
+                        * with no viable OTA image, we will come back up in factory
+                        * where the user can reload the OTA image
+                        */
+                       lwsl_notice("  FACTORY copy successful, rebooting\n");
+retry:
+                       esp_restart();
+               }
+
+       return part;
+}
+
+
+void
+lws_esp32_set_creation_defaults(struct lws_context_creation_info *info)
+{
+       const esp_partition_t *part;
+
+       memset(info, 0, sizeof(*info));
+
+       lws_set_log_level(63, lwsl_emit_syslog);
+
+       part = lws_esp_ota_get_boot_partition();
+       (void)part;
+
+       info->port = 443;
+       info->fd_limit_per_thread = 30;
+       info->max_http_header_pool = 2;
+       info->max_http_header_data = 1024;
+       info->pt_serv_buf_size = 4096;
+       info->keepalive_timeout = 5;
+       info->simultaneous_ssl_restriction = 2;
+       info->options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+                      LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+
+       info->ssl_cert_filepath = "ssl-pub.der";
+       info->ssl_private_key_filepath = "ssl-pri.der";
+}
+
+int
+lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i,
+                        char *json, int json_len)
+{
+       esp_image_segment_header_t eis;
+       esp_image_header_t eih;
+       uint32_t hdr;
+
+       spi_flash_read(part->address , &eih, sizeof(eih));
+       hdr = part->address + sizeof(eih);
+
+       if (eih.magic != ESP_IMAGE_HEADER_MAGIC)
+               return 1;
+
+       eis.data_len = 0;
+       while (eih.segment_count-- && eis.data_len != 0xffffffff) {
+               spi_flash_read(hdr, &eis, sizeof(eis));
+               hdr += sizeof(eis) + eis.data_len;
+       }
+       hdr += (~hdr & 15) + 1;
+
+       i->romfs = hdr + 4;
+       spi_flash_read(hdr, &i->romfs_len, sizeof(i->romfs_len));
+       i->json = i->romfs + i->romfs_len + 4;
+       spi_flash_read(i->json - 4, &i->json_len, sizeof(i->json_len));
+
+       if (i->json_len < json_len - 1)
+               json_len = i->json_len;
+       spi_flash_read(i->json, json, json_len);
+       json[json_len] = '\0';
+
+       return 0;
 }
 
 struct lws_context *
-lws_esp32_init(struct lws_context_creation_info *info, unsigned int _romfs)
+lws_esp32_init(struct lws_context_creation_info *info)
 {
-       size_t romfs_size;
+       const esp_partition_t *part = lws_esp_ota_get_boot_partition();
        struct lws_context *context;
+       struct lws_esp32_image i;
+       struct lws_vhost *vhost;
+       nvs_handle nvh;
+       char buf[512];
+       size_t s;
+       int n;
+
+       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+       n = 0;
+       s = 1;
+       if (nvs_get_blob(nvh, "ssl-pub.der", NULL, &s) == ESP_OK)
+               n = 1;
+       s = 1;
+       if (nvs_get_blob(nvh, "ssl-pri.der", NULL, &s) == ESP_OK)
+               n |= 2;
+       nvs_close(nvh);
 
-       lws_set_log_level(65535, lwsl_emit_syslog);
+       if (n != 3) {
+               /* we are not configured for SSL yet... fall back to port 80 / http */
+               info->port = 80;
+               info->options &= ~LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               lwsl_notice("No SSL certs... using port 80\n");
+       }
 
        context = lws_create_context(info);
        if (context == NULL) {
@@ -775,31 +995,29 @@ lws_esp32_init(struct lws_context_creation_info *info, unsigned int _romfs)
                return NULL;
        }
 
-       lws_esp32_romfs = (romfs_t)(void *)_romfs;
-       romfs_size = romfs_mount_check(lws_esp32_romfs);
-       if (!romfs_size) {
-               lwsl_err("Failed to mount ROMFS\n");
+       lws_esp32_get_image_info(part, &i, buf, sizeof(buf) - 1);
+       
+       lws_esp32_romfs = (romfs_t)i.romfs;
+       if (!romfs_mount_check(lws_esp32_romfs)) {
+               lwsl_err("Failed to mount ROMFS at %p 0x%x\n", lws_esp32_romfs, i.romfs);
                return NULL;
        }
 
-       lwsl_notice("ROMFS length %uKiB\n", romfs_size >> 10);
+       lwsl_notice("ROMFS length %uKiB\n", i.romfs_len >> 10);
+
+       puts(buf);
 
        /* set the lws vfs to use our romfs */
 
        lws_set_fops(context, &fops);
 
-       if (lws_esp32_is_booting_in_ap_mode() || lws_esp32_force_ap) {
-               info->vhost_name = "ap";
-               info->protocols = protocols_ap;
-               info->mounts = &mount_ap;
-               info->pvo = &ap_pvo;
-       }
-
-       if (!lws_create_vhost(context, info))
+       vhost = lws_create_vhost(context, info);
+       if (!vhost)
                lwsl_err("Failed to create vhost\n");
+       else
+               lws_init_vhost_client_ssl(info, vhost); 
 
        lws_protocol_init(context);
 
        return context;
 }
-
index 0e5fcfd..461f314 100644 (file)
@@ -137,9 +137,13 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
                lwsl_warn("** error invalid sock but expected to send\n");
 
        /* limit sending */
-       n = wsi->protocol->rx_buffer_size;
-       if (!n)
-               n = context->pt_serv_buf_size;
+       if (wsi->protocol->tx_packet_size)
+               n = wsi->protocol->tx_packet_size;
+       else {
+               n = wsi->protocol->rx_buffer_size;
+               if (!n)
+                       n = context->pt_serv_buf_size;
+       }
        n += LWS_PRE + 4;
        if (n > len)
                n = len;
@@ -618,6 +622,14 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 #endif
 
                poss = context->pt_serv_buf_size - n;
+
+               /*
+                * if there is a hint about how much we will do well to send at one time,
+                * restrict ourselves to only trying to send that.
+                */
+               if (wsi->protocol->tx_packet_size && poss > wsi->protocol->tx_packet_size)
+                       poss = wsi->protocol->tx_packet_size;
+
 #if defined(LWS_WITH_RANGES)
                if (wsi->u.http.range.count_ranges) {
                        if (wsi->u.http.range.count_ranges > 1)
index edf3f70..64cfcf6 100644 (file)
@@ -905,6 +905,8 @@ struct lws_context {
        unsigned int timeout_secs;
        unsigned int pt_serv_buf_size;
        int max_http_header_data;
+       int simultaneous_ssl_restriction;
+       int simultaneous_ssl;
 
        unsigned int deprecated:1;
        unsigned int being_destroyed:1;
@@ -1860,7 +1862,6 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info,
 #endif
 LWS_EXTERN void
 lws_ssl_destroy(struct lws_vhost *vhost);
-
 /* HTTP2-related */
 
 #ifdef LWS_USE_HTTP2
index f1f5093..1f6d2f2 100644 (file)
@@ -37,7 +37,7 @@
 #include "romfs.h"
 #include "esp_spi_flash.h"
 
-#define RFS_STRING_MAX 64
+#define RFS_STRING_MAX 96
 
 static u32_be_t cache[(RFS_STRING_MAX + 32) / 4];
 static romfs_inode_t ci = (romfs_inode_t)cache;
@@ -118,7 +118,7 @@ dir_link(romfs_t romfs, romfs_inode_t i)
 static romfs_inode_t
 romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
 {
-       romfs_inode_t level, i = start;
+       romfs_inode_t level, i = start, i_in;
        const char *p, *n, *cp;
        uint32_t next_be;
 
@@ -128,6 +128,7 @@ romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
        while (i != (romfs_inode_t)romfs) {
                p = path;
                n = ((const char *)i) + sizeof(*i);
+               i_in = i;
 
                set_cache(i, sizeof(*i));
                next_be = ci->next;
@@ -135,7 +136,7 @@ romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
                cp = (const char *)cache;
                set_cache((romfs_inode_t)n, RFS_STRING_MAX);
 
-               while (*p && *p != '/' && *cp && *p == *cp) {
+               while (*p && *p != '/' && *cp && *p == *cp && (p - path) < RFS_STRING_MAX) {
                        p++;
                        n++;
                        cp++;
@@ -158,6 +159,9 @@ romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
                        return i;
                }
 
+               if (!*p && *cp == '/')
+                       return NULL;
+
                if (*p == '/' && !*cp) {
                        set_cache(i, sizeof(*i));
                        switch (ntohl(ci->next) & 7) {
@@ -191,6 +195,8 @@ romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
 
                i = (romfs_inode_t)((const uint8_t *)romfs +
                                    (ntohl(ci->next) & ~15));
+               if (i == i_in)
+                       return NULL;
        }
 
        return NULL;
index 0eaf964..f8c7da3 100644 (file)
@@ -1744,6 +1744,19 @@ lws_http_transaction_completed(struct lws *wsi)
                if (!wsi->more_rx_waiting) {
                        wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;
                        lws_header_table_detach(wsi, 1);
+#ifdef LWS_OPENSSL_SUPPORT
+                       /*
+                        * additionally... if we are hogging an SSL instance
+                        * with no pending pipelined headers (or ah now), and
+                        * SSL is scarce, drop this connection without waiting
+                        */
+
+                       if (wsi->vhost->use_ssl &&
+                           wsi->context->simultaneous_ssl == wsi->context->simultaneous_ssl_restriction) {
+                               lwsl_info("%s: simultaneous_ssl_restriction and nothing pipelined\n", __func__);
+                               return 1;
+                       }
+#endif
                } else
                        lws_header_table_reset(wsi, 1);
        }
@@ -2232,6 +2245,21 @@ try_pollout:
                        if (!(pollfd->revents & LWS_POLLIN) || !(pollfd->events & LWS_POLLIN))
                                break;
 
+#ifdef LWS_OPENSSL_SUPPORT
+                       /*
+                        * can we really accept it, with regards to SSL limit?
+                        * another vhost may also have had POLLIN on his listener this
+                        * round and used it up already
+                        */
+
+                       if (wsi->vhost->use_ssl &&
+                           context->simultaneous_ssl == context->simultaneous_ssl_restriction)
+                               /* no... ignore it, he won't come again until we are
+                                * below the simultaneous_ssl_restriction limit and
+                                * POLLIN is enabled on him again
+                                */
+                               break;
+#endif
                        /* listen socket got an unencrypted connection... */
 
                        clilen = sizeof(cli_addr);
@@ -3112,6 +3140,32 @@ lws_spa_destroy(struct lws_spa *spa)
 {
        int n = 0;
 
+       lwsl_notice("%s: destroy spa %p\n", __func__, spa);
+
+       if (spa->s)
+               lws_urldecode_s_destroy(spa->s);
+
+       lwsl_debug("%s %p %p %p %p\n", __func__,
+                       spa->param_length,
+                       spa->params,
+                       spa->storage,
+                       spa
+                       );
+
+       lws_free(spa->param_length);
+       lws_free(spa->params);
+       lws_free(spa->storage);
+       lws_free(spa);
+
+       return n;
+}
+
+#if 0
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_destroy(struct lws_spa *spa)
+{
+       int n = 0;
+
        lwsl_info("%s: destroy spa %p\n", __func__, spa);
 
        if (spa->s)
@@ -3126,7 +3180,7 @@ lws_spa_destroy(struct lws_spa *spa)
 
        return n;
 }
-
+#endif
 LWS_VISIBLE LWS_EXTERN int
 lws_chunked_html_process(struct lws_process_html_args *args,
                         struct lws_process_html_state *s)
index c89d3c8..53be68f 100644 (file)
@@ -29,12 +29,18 @@ lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info
 
 extern int lws_ssl_get_error(struct lws *wsi, int n);
 
-#if defined(USE_WOLFSSL) || defined(LWS_WITH_ESP32)
+#if defined(USE_WOLFSSL)
 #else
 
 static int
 OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
 {
+#if defined(LWS_WITH_ESP32)
+//     long gvr = ssl_pm_get_verify_result(
+       lwsl_notice("%s\n", __func__);
+
+       return 0;
+#else
        SSL *ssl;
        int n;
        struct lws *wsi;
@@ -87,6 +93,7 @@ OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
        }
        /* convert callback return code from 0 = OK to verify callback return value 1 = OK */
        return !n;
+#endif
 }
 #endif
 
@@ -119,7 +126,7 @@ lws_ssl_client_bio_create(struct lws *wsi)
        if (!wsi->ssl) {
                lwsl_err("SSL_new failed: %s\n",
                         ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
-               lws_decode_ssl_error();
+               lws_ssl_elaborate_error();
                return -1;
        }
 
@@ -162,10 +169,17 @@ lws_ssl_client_bio_create(struct lws *wsi)
 #endif
 #endif
 #else
+#if defined(LWS_WITH_ESP32)
+// esp-idf openssl shim does not seem ready for this
+//     SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback);
+       SSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, OpenSSL_client_verify_callback);
+
+#else
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
        SSL_set_tlsext_host_name(wsi->ssl, hostname);
 #endif
 #endif
+#endif
 
 #ifdef USE_WOLFSSL
        /*
@@ -184,8 +198,12 @@ lws_ssl_client_bio_create(struct lws *wsi)
 #endif
 #endif /* USE_WOLFSSL */
 
+#if !defined(LWS_WITH_ESP32)
        wsi->client_bio = BIO_new_socket(wsi->desc.sockfd, BIO_NOCLOSE);
        SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
+#else
+       SSL_set_fd(wsi->ssl, wsi->desc.sockfd);
+#endif
 
 #ifdef USE_WOLFSSL
 #ifdef USE_OLD_CYASSL
@@ -194,15 +212,26 @@ lws_ssl_client_bio_create(struct lws *wsi)
        wolfSSL_set_using_nonblock(wsi->ssl, 1);
 #endif
 #else
+#if !defined(LWS_WITH_ESP32)
        BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */
 #endif
+#endif
 
+#if !defined(LWS_WITH_ESP32)
        SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index,
                        wsi);
+#endif
 
        return 0;
 }
 
+#if defined(LWS_WITH_ESP32)
+int ERR_get_error(void)
+{
+       return 0;
+}
+#endif
+
 int
 lws_ssl_client_connect1(struct lws *wsi)
 {
@@ -343,6 +372,19 @@ lws_ssl_client_connect2(struct lws *wsi)
                }
        }
 
+#if defined(LWS_WITH_ESP32)
+       {
+               X509 *peer = SSL_get_peer_certificate(wsi->ssl);
+
+               if (!peer) {
+                       lwsl_notice("peer did not provide cert\n");
+
+                       return -1;
+               }
+               lwsl_notice("peer provided cert\n");
+       }
+#endif
+
 #ifndef USE_WOLFSSL
        /*
         * See comment above about wolfSSL certificate
@@ -353,6 +395,8 @@ lws_ssl_client_connect2(struct lws *wsi)
        lws_latency(context, wsi,
                "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0);
 
+       lwsl_notice("get_verify says %d\n", n);
+
        if (n != X509_V_OK) {
                if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
                     n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
@@ -385,10 +429,12 @@ int lws_context_init_client_ssl(struct lws_context_creation_info *info,
        SSL_METHOD *method;
        struct lws wsi;
        unsigned long error;
+#if !defined(LWS_WITH_ESP32)
        const char *cipher_list = info->ssl_cipher_list;
        const char *ca_filepath = info->ssl_ca_filepath;
-       const char *cert_filepath = info->ssl_cert_filepath;
        const char *private_key_filepath = info->ssl_private_key_filepath;
+       const char *cert_filepath = info->ssl_cert_filepath;
+
        int n;
 
        /*
@@ -403,6 +449,7 @@ int lws_context_init_client_ssl(struct lws_context_creation_info *info,
                cert_filepath = info->client_ssl_cert_filepath;
        if (info->client_ssl_private_key_filepath)
                private_key_filepath = info->client_ssl_private_key_filepath;
+#endif
 
        if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
                return 0;
@@ -421,7 +468,7 @@ int lws_context_init_client_ssl(struct lws_context_creation_info *info,
 
        /* basic openssl init already happened in context init */
 
-       method = (SSL_METHOD *)SSLv23_client_method();
+       method = (SSL_METHOD *)TLSv1_2_client_method();
        if (!method) {
                error = ERR_get_error();
                lwsl_err("problem creating ssl method %lu: %s\n",
@@ -442,8 +489,11 @@ int lws_context_init_client_ssl(struct lws_context_creation_info *info,
 #ifdef SSL_OP_NO_COMPRESSION
        SSL_CTX_set_options(vhost->ssl_client_ctx, SSL_OP_NO_COMPRESSION);
 #endif
+
+#if !defined(LWS_WITH_ESP32)
        SSL_CTX_set_options(vhost->ssl_client_ctx,
                            SSL_OP_CIPHER_SERVER_PREFERENCE);
+
        if (cipher_list)
                SSL_CTX_set_cipher_list(vhost->ssl_client_ctx, cipher_list);
 
@@ -474,11 +524,12 @@ int lws_context_init_client_ssl(struct lws_context_creation_info *info,
                }
                else
                        lwsl_info("loaded ssl_ca_filepath\n");
-
+#endif
        /*
         * callback allowing user code to load extra verification certs
         * helping the client to verify server identity
         */
+#if !defined(LWS_WITH_ESP32)
 
        /* support for client-side certificate authentication */
        if (cert_filepath) {
@@ -493,7 +544,6 @@ int lws_context_init_client_ssl(struct lws_context_creation_info *info,
                }
                lwsl_notice("Loaded client cert %s\n", cert_filepath);
        }
-
        if (private_key_filepath) {
                lwsl_notice("%s: doing private key filepath\n", __func__);
                lws_ssl_bind_passphrase(vhost->ssl_client_ctx, info);
@@ -514,7 +564,7 @@ int lws_context_init_client_ssl(struct lws_context_creation_info *info,
                        return 1;
                }
        }
-
+#endif
        /*
         * give him a fake wsi with context set, so he can use
         * lws_get_context() in the callback
index 6406011..039c7bf 100644 (file)
@@ -130,8 +130,10 @@ lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info,
 
        lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve);
 #else
+#if !defined(LWS_WITH_ESP32)
        lwsl_notice(" OpenSSL doesn't support ECDH\n");
 #endif
+#endif
        return 0;
 }
 
index a884942..d161e5f 100644 (file)
--- a/lib/ssl.c
+++ b/lib/ssl.c
@@ -21,8 +21,7 @@
 
 #include "private-libwebsockets.h"
 
-#if defined(LWS_WITH_ESP32)
-int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
+int lws_alloc_vfs_file(struct lws_context *context, const char *filename, uint8_t **buf,
                lws_filepos_t *amount)
 {
        lws_filepos_t len;
@@ -49,6 +48,35 @@ bail:
 
        return ret;
 }
+
+#if defined(LWS_WITH_ESP32)
+int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
+              lws_filepos_t *amount)
+{
+       nvs_handle nvh;
+       size_t s;
+       int n = 0;
+
+       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+       if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) {
+               n = 1;
+               goto bail;
+       }
+       *buf = malloc(s);
+       if (!*buf) {
+               n = 2;
+               goto bail;
+       }
+       if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK)
+               n = 1;
+
+       *amount = s;
+
+bail:
+       nvs_close(nvh);
+
+       return n;
+}
 #endif
 
 int openssl_websocket_private_data_index,
@@ -225,20 +253,6 @@ lws_ssl_destroy(struct lws_vhost *vhost)
 }
 
 LWS_VISIBLE void
-lws_decode_ssl_error(void)
-{
-#if defined(LWS_WITH_ESP32)
-#else
-       char buf[256];
-       u_long err;
-       while ((err = ERR_get_error()) != 0) {
-               ERR_error_string_n(err, buf, sizeof(buf));
-               lwsl_err("*** %lu %s\n", err, buf);
-       }
-#endif
-}
-
-LWS_VISIBLE void
 lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
 {
        struct lws_context *context = wsi->context;
@@ -291,37 +305,38 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len)
        lwsl_debug("%p: SSL_read says %d\n", wsi, n);
        /* manpage: returning 0 means connection shut down */
        if (!n) {
-  n = lws_ssl_get_error(wsi, n);
-lwsl_debug("%p: ssl err %d errno %d\n", wsi, n, errno);
-  if (n == SSL_ERROR_ZERO_RETURN)
-   return LWS_SSL_CAPABLE_ERROR;
+               n = lws_ssl_get_error(wsi, n);
+               lwsl_debug("%p: ssl err %d errno %d\n", wsi, n, errno);
+               if (n == SSL_ERROR_ZERO_RETURN)
+                       return LWS_SSL_CAPABLE_ERROR;
 
-  if (n == SSL_ERROR_SYSCALL) {
+               if (n == SSL_ERROR_SYSCALL) {
 #if !defined(LWS_WITH_ESP32)
-   int err = ERR_get_error();
-   if (err == 0
-     && (ssl_read_errno == EPIPE
-      || ssl_read_errno == ECONNABORTED
-      || ssl_read_errno == 0))
-               return LWS_SSL_CAPABLE_ERROR;
+                       int err = ERR_get_error();
+                       if (err == 0 && (ssl_read_errno == EPIPE ||
+                                        ssl_read_errno == ECONNABORTED ||
+                                        ssl_read_errno == 0))
+                               return LWS_SSL_CAPABLE_ERROR;
 #endif
-  }
+               }
 
                lwsl_err("%s failed: %s\n",__func__,
                         ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
-               lws_decode_ssl_error();
+               lws_ssl_elaborate_error();
 
                return LWS_SSL_CAPABLE_ERROR;
        }
 
        if (n < 0) {
                n = lws_ssl_get_error(wsi, n);
-               if (n ==  SSL_ERROR_WANT_READ || n ==  SSL_ERROR_WANT_WRITE)
+               if (n ==  SSL_ERROR_WANT_READ || n ==  SSL_ERROR_WANT_WRITE) {
+                       lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
                        return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
 
                lwsl_err("%s failed2: %s\n",__func__,
                                 ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
-                       lws_decode_ssl_error();
+                       lws_ssl_elaborate_error();
 
                return LWS_SSL_CAPABLE_ERROR;
        }
@@ -381,7 +396,9 @@ LWS_VISIBLE int
 lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len)
 {
        int n;
- int ssl_read_errno = 0;
+#if !defined(LWS_WITH_ESP32)
+               int ssl_read_errno = 0;
+#endif
 
        if (!wsi->ssl)
                return lws_ssl_capable_write_no_ssl(wsi, buf, len);
@@ -415,11 +432,29 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len)
 
  lwsl_err("%s failed: %s\n",__func__,
    ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
lws_decode_ssl_error();
      lws_ssl_elaborate_error();
 
        return LWS_SSL_CAPABLE_ERROR;
 }
 
+static int
+lws_gate_accepts(struct lws_context *context, int on)
+{
+       struct lws_vhost *v = context->vhost_list;
+
+       lwsl_info("gating accepts %d\n", on);
+
+       while (v) {
+               if (v->use_ssl &&  v->lserv_wsi) /* gate ability to accept incoming connections */
+                       if (lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on, (LWS_POLLIN) * on))
+                               lwsl_err("Unable to set accept POLLIN %d\n", on);
+
+               v = v->vhost_next;
+       }
+
+       return 0;
+}
+
 LWS_VISIBLE int
 lws_ssl_close(struct lws *wsi)
 {
@@ -434,6 +469,10 @@ lws_ssl_close(struct lws *wsi)
        SSL_free(wsi->ssl);
        wsi->ssl = NULL;
 
+       if (wsi->context->simultaneous_ssl-- == wsi->context->simultaneous_ssl_restriction)
+               /* we made space and can do an accept */
+               lws_gate_accepts(wsi->context, 1);
+
        return 1; /* handled */
 }
 
@@ -460,18 +499,25 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd)
                        lwsl_err("%s: leaking ssl\n", __func__);
                if (accept_fd == LWS_SOCK_INVALID)
                        assert(0);
-
+               if (context->simultaneous_ssl >= context->simultaneous_ssl_restriction) {
+                       lwsl_notice("unable to deal with SSL connection\n");
+                       return 1;
+               }
                errno = 0;
                wsi->ssl = SSL_new(wsi->vhost->ssl_ctx);
                if (wsi->ssl == NULL) {
-                       lwsl_err("SSL_new failed: %s (errno %d)\n",
-                                ERR_error_string(lws_ssl_get_error(wsi, 0), NULL), errno);
+                       lwsl_err("SSL_new failed: %d (errno %d)\n",
+                                lws_ssl_get_error(wsi, 0), errno);
 
-                       lws_decode_ssl_error();
+                       lws_ssl_elaborate_error();
                        if (accept_fd != LWS_SOCK_INVALID)
                                compatible_close(accept_fd);
                        goto fail;
                }
+               if (++context->simultaneous_ssl == context->simultaneous_ssl_restriction)
+                       /* that was the last allowed SSL connection */
+                       lws_gate_accepts(context, 0);
+
 #if !defined(LWS_WITH_ESP32)
                SSL_set_ex_data(wsi->ssl,
                        openssl_websocket_private_data_index, wsi);
diff --git a/plugins/protocol_esp32_lws_ota.c b/plugins/protocol_esp32_lws_ota.c
new file mode 100644 (file)
index 0000000..0a775f4
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * ESP32 OTA update protocol handler
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA 
+ *
+ */
+#include <string.h>
+#include <esp_partition.h>
+#include <esp_ota_ops.h>
+#include <nvs.h>
+
+struct per_session_data__esplws_ota {
+       struct lws_spa *spa;
+       char filename[32];
+       char result[LWS_PRE + 512];
+       int result_len;
+       int filename_length;
+       esp_ota_handle_t otahandle;
+       const esp_partition_t *part;
+       long file_length;
+       nvs_handle nvh;
+       TimerHandle_t reboot_timer;
+};
+
+struct per_vhost_data__esplws_ota {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+};
+
+static const char * const ota_param_names[] = {
+       "upload",
+};
+
+enum enum_ota_param_names {
+       EPN_UPLOAD,
+};
+
+static void ota_reboot_timer_cb(TimerHandle_t t)
+{
+       esp_restart();
+}
+
+const esp_partition_t *
+ota_choose_part(void)
+{
+       const esp_partition_t *bootpart, *part;
+       esp_partition_iterator_t i;
+
+       bootpart = lws_esp_ota_get_boot_partition();
+       i = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL);
+       while (i) {
+               part = esp_partition_get(i);
+
+               /* cannot update ourselves */
+               if (part == bootpart)
+                       goto next;
+
+               if (part->subtype < ESP_PARTITION_SUBTYPE_APP_OTA_MIN ||
+                   part->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MIN +
+                                    ESP_PARTITION_SUBTYPE_APP_OTA_MAX)
+                       goto next;
+
+               break;
+
+next:
+               i = esp_partition_next(i);
+       }
+
+       if (!i) {
+               lwsl_err("Can't find good OTA part\n");
+               return NULL;
+       }
+       lwsl_notice("Directing OTA to part type %d/%d start 0x%x\n",
+                       part->type, part->subtype,
+                       (uint32_t)part->address);
+
+       return part;
+}
+
+static int
+ota_file_upload_cb(void *data, const char *name, const char *filename,
+              char *buf, int len, enum lws_spa_fileupload_states state)
+{
+       struct per_session_data__esplws_ota *pss =
+                       (struct per_session_data__esplws_ota *)data;
+
+       switch (state) {
+       case LWS_UFS_OPEN:
+               lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
+               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               if (strcmp(name, "ota"))
+                       return 1;
+
+               pss->part = ota_choose_part();
+               if (!pss->part)
+                       return 1;
+
+               if (esp_ota_begin(pss->part, (long)-1, &pss->otahandle) != ESP_OK) {
+                       lwsl_err("OTA: Failed to begin\n");
+                       return 1;
+               }
+
+               pss->file_length = 0;
+               break;
+
+       case LWS_UFS_FINAL_CONTENT:
+       case LWS_UFS_CONTENT:
+               if (pss->file_length + len > pss->part->size) {
+                       lwsl_err("OTA: incoming file too large\n");
+                       return 1;
+               }
+
+               lwsl_debug("writing 0x%lx... 0x%lx\n",
+                          pss->part->address + pss->file_length,
+                          pss->part->address + pss->file_length + len);
+               if (esp_ota_write(pss->otahandle, buf, len) != ESP_OK) {
+                       lwsl_err("OTA: Failed to write\n");
+                       return 1;
+               }
+               pss->file_length += len;
+
+               if (state == LWS_UFS_CONTENT)
+                       break;
+
+               lwsl_notice("LWS_UFS_FINAL_CONTENT\n");
+               if (esp_ota_end(pss->otahandle) != ESP_OK) {
+                       lwsl_err("OTA: end failed\n");
+                       return 1;
+               }
+
+               if (esp_ota_set_boot_partition(pss->part) != ESP_OK) {
+                       lwsl_err("OTA: set boot part failed\n");
+                       return 1;
+               }
+
+               pss->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, NULL,
+                                                ota_reboot_timer_cb);
+               xTimerStart(pss->reboot_timer, 0);
+               break;
+       }
+
+       return 0;
+}
+
+static int
+callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason,
+                   void *user, void *in, size_t len)
+{
+       struct per_session_data__esplws_ota *pss =
+                       (struct per_session_data__esplws_ota *)user;
+       struct per_vhost_data__esplws_ota *vhd =
+                       (struct per_vhost_data__esplws_ota *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       unsigned char buf[LWS_PRE + 384], *start = buf + LWS_PRE - 1, *p = start,
+            *end = buf + sizeof(buf) - 1;
+       int n;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__esplws_ota));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               if (!vhd)
+                       break;
+               break;
+
+       /* OTA POST handling */
+
+       case LWS_CALLBACK_HTTP_BODY:
+               /* create the POST argument parser if not already existing */
+               //lwsl_notice("LWS_CALLBACK_HTTP_BODY (ota) %d %d %p\n", (int)pss->file_length, (int)len, pss->spa);
+               if (!pss->spa) {
+                       pss->spa = lws_spa_create(wsi, ota_param_names,
+                                       ARRAY_SIZE(ota_param_names), 4096,
+                                       ota_file_upload_cb, pss);
+                       if (!pss->spa)
+                               return -1;
+
+                       pss->filename[0] = '\0';
+                       pss->file_length = 0;
+               }
+
+               /* let it parse the POST data */
+               if (lws_spa_process(pss->spa, in, len))
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+               lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (ota)\n");
+               /* call to inform no more payload data coming */
+               lws_spa_finalize(pss->spa);
+
+               pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
+                       "Rebooting after OTA update");
+
+               if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
+                       goto bail;
+
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                               (unsigned char *)"text/html", 9, &p, end))
+                       goto bail;
+               if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
+                       goto bail;
+               if (lws_finalize_http_header(wsi, &p, end))
+                       goto bail;
+
+               n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
+               if (n < 0)
+                       goto bail;
+
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
+                          pss->result_len);
+               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
+                             pss->result_len, LWS_WRITE_HTTP);
+               if (n < 0)
+                       return 1;
+
+               if (lws_http_transaction_completed(wsi))
+                       return 1;
+
+               /* stop further service so we don't serve the probe GET to see if we rebooted */
+               while (1);
+
+               break;
+
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+
+bail:
+       return 1;
+}
+
+#define LWS_PLUGIN_PROTOCOL_ESPLWS_OTA \
+       { \
+               "esplws-ota", \
+               callback_esplws_ota, \
+               sizeof(struct per_session_data__esplws_ota), \
+               4096, 0, NULL, 900 \
+       }
+
diff --git a/plugins/protocol_esp32_lws_reboot_to_factory.c b/plugins/protocol_esp32_lws_reboot_to_factory.c
new file mode 100644 (file)
index 0000000..058c741
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Example ESP32 app code using Libwebsockets
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * This is intended to be mounted somewhere in your ESP32 user app... if the
+ * client touched the mount, the plugin hangs up and reboots into the
+ * factory mode one second later.
+ *
+ * The factory mode will reassociate with the same IP with the same MAC
+ * shortly afterwards and be accessible by the same IP / mDNS name.
+ */
+#include <string.h>
+#include <esp_partition.h>
+#include <esp_ota_ops.h>
+#include <nvs.h>
+
+static int
+callback_esplws_rtf(struct lws *wsi, enum lws_callback_reasons reason,
+                   void *user, void *in, size_t len)
+{
+       switch (reason) {
+
+       case LWS_CALLBACK_HTTP:
+               
+               lws_esp32_restart_guided(LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY);
+               return 1;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_ESPLWS_RTF \
+       { \
+               "esplws-rtf", \
+               callback_esplws_rtf, \
+               0, \
+               10, 0, NULL, 0 \
+       }
+
index 54e79f1..1ef407b 100644 (file)
@@ -1,29 +1,32 @@
 /*
- * Example ESP32 app code using Libwebsockets
+ * ESP32 Scan / Factory protocol handler
  *
  * Copyright (C) 2017 Andy Green <andy@warmcat.com>
  *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
  *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
  *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary. So unlike the library itself, they are licensed
- * Public Domain.
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA*
  *
  */
 #include <string.h>
 #include <nvs.h>
+#include <esp_ota_ops.h>
 
 typedef enum {
        SCAN_STATE_NONE,
        SCAN_STATE_INITIAL,
+       SCAN_STATE_INITIAL_MANIFEST,
        SCAN_STATE_LIST,
        SCAN_STATE_FINAL
 } scan_state;
@@ -36,29 +39,53 @@ struct store_json {
 struct per_session_data__esplws_scan {
        struct per_session_data__esplws_scan *next;
        scan_state scan_state;
+
+       struct lws_spa *spa;
+       char filename[32];
+       char result[LWS_PRE + 512];
+       unsigned char buffer[4096];
+       int result_len;
+       int filename_length;
+       long file_length;
+       nvs_handle nvh;
+
        char ap_record;
        unsigned char subsequent:1;
        unsigned char changed_partway:1;
 };
 
 struct per_vhost_data__esplws_scan {
-       wifi_ap_record_t ap_records[20];
-       TimerHandle_t timer;
+       wifi_ap_record_t ap_records[10];
+       TimerHandle_t timer, reboot_timer;
        struct per_session_data__esplws_scan *live_pss_list;
        struct lws_context *context;
        struct lws_vhost *vhost;
        const struct lws_protocols *protocol;
+
+       const esp_partition_t *part;
+       esp_ota_handle_t otahandle;
+       long file_length;
+       long content_length;
+
+       struct lws *cwsi;
+       char json[1024];
+       int json_len;
+
        uint16_t count_ap_records;
        char count_live_pss;
        unsigned char scan_ongoing:1;
        unsigned char completed_any_scan:1;
        unsigned char reboot:1;
+       unsigned char changed_settings:1;
+       unsigned char checked_updates:1;
+       unsigned char autonomous_update:1;
+       unsigned char autonomous_update_sampled:1;
 };
 
 static const struct store_json store_json[] = {
        { "ssid\":\"", "ssid" },
        { ",\"pw\":\"", "password" },
-       { ",\"serial\":\"", "serial" },
+       { ",\"access_pw\":\"", "access_pw" },
        { ",\"region\":\"", "region" },
 };
 
@@ -72,6 +99,25 @@ static wifi_scan_config_t scan_config = {
 extern void (*lws_cb_scan_done)(void *);
 extern void *lws_cb_scan_done_arg;
 
+const esp_partition_t *
+ota_choose_part(void);
+
+static const char * const param_names[] = {
+       "text",
+       "pub",
+       "pri",
+       "serial",
+       "opts",
+};
+
+enum enum_param_names {
+       EPN_TEXT,
+       EPN_PUB,
+       EPN_PRI,
+       EPN_SERIAL,
+       EPN_OPTS,
+};
+
 
 static void
 scan_finished(void *v);
@@ -82,10 +128,8 @@ esplws_simple_arg(char *dest, int len, const char *in, const char *match)
        const char *p = strstr(in, match);
        int n = 0;
 
-       if (!p) {
-               lwsl_err("No match %s\n", match);
+       if (!p)
                return 1;
-       }
 
        p += strlen(match);
        while (*p && *p != '\"' && n < len - 1)
@@ -121,6 +165,44 @@ static void timer_cb(TimerHandle_t t)
        scan_start(vhd);
 }
 
+static void reboot_timer_cb(TimerHandle_t t)
+{
+       esp_restart();
+}
+
+static int
+client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
+{
+       static struct lws_client_connect_info i;
+       char path[256];
+
+       memset(&i, 0, sizeof i);
+
+       snprintf(path, sizeof(path) - 1, CONFIG_LWS_OTA_SERVER_BASE_URL "/" CONFIG_LWS_MODEL_NAME "/%s", file);
+
+       lwsl_notice("Fetching %s\n", path);
+
+       i.port = 443;
+       i.context = vhd->context;
+       i.address = CONFIG_LWS_OTA_SERVER_FQDN;
+       i.ssl_connection = 1;
+       i.host = i.address;
+       i.origin = i.host;
+       i.vhost = vhd->vhost;
+       i.method = "GET";
+       i.path = path;
+       i.protocol = "esplws-scan";
+       i.pwsi = &vhd->cwsi;
+
+       vhd->cwsi = lws_client_connect_via_info(&i);
+       if (!vhd->cwsi) {
+               lwsl_notice("NULL return\n");
+               return 1; /* fail */
+       }
+
+       return 0; /* ongoing */
+}
+
 static void
 scan_finished(void *v)
 {
@@ -144,6 +226,70 @@ scan_finished(void *v)
        }
 
        lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
+
+       if (lws_esp32.inet && !vhd->cwsi && !vhd->checked_updates)
+               client_connection(vhd, "manifest.json");
+
+       if (vhd->changed_settings) {
+               lws_esp32_wlan_nvs_get(1);
+               vhd->changed_settings = 0;
+       } else
+               esp_wifi_connect();
+}
+
+static const char *ssl_names[] = { "ssl-pub.der", "ssl-pri.der" };
+
+static int
+file_upload_cb(void *data, const char *name, const char *filename,
+              char *buf, int len, enum lws_spa_fileupload_states state)
+{
+       struct per_session_data__esplws_scan *pss =
+                       (struct per_session_data__esplws_scan *)data;
+       int n;
+
+       switch (state) {
+       case LWS_UFS_OPEN:
+               if (lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
+                       return -1;
+
+               lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
+               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               if (!strcmp(name, "pub") || !strcmp(name, "pri")) {
+                       if (nvs_open("lws-station", NVS_READWRITE, &pss->nvh))
+                               return 1;
+               } else
+                       return 1;
+               pss->file_length = 0;
+               break;
+
+       case LWS_UFS_FINAL_CONTENT:
+       case LWS_UFS_CONTENT:
+               if (len) {
+                       /* if the file length is too big, drop it */
+                       if (pss->file_length + len > sizeof(pss->buffer))
+                               return 1;
+
+                       memcpy(pss->buffer + pss->file_length, buf, len);
+               }
+               pss->file_length += len;
+
+               if (state == LWS_UFS_CONTENT)
+                       break;
+
+               lwsl_notice("LWS_UFS_FINAL_CONTENT\n");
+               n = 0;
+               if (!strcmp(name, "pri"))
+                       n = 1;
+               n = nvs_set_blob(pss->nvh, ssl_names[n], pss->buffer, pss->file_length);
+               if (n == ESP_OK)
+                       nvs_commit(pss->nvh);
+               nvs_close(pss->nvh);
+               if (n != ESP_OK)
+                       return 1;
+               break;
+       }
+
+       return 0;
 }
 
 static int
@@ -156,10 +302,13 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                        (struct per_vhost_data__esplws_scan *)
                        lws_protocol_vh_priv_get(lws_get_vhost(wsi),
                                        lws_get_protocol(wsi));
-       char buf[LWS_PRE + 384], /*ip[24],*/ *start = buf + LWS_PRE - 1, *p = start,
-            *end = buf + sizeof(buf) - 1;
+       unsigned char *start = pss->buffer + LWS_PRE - 1, *p = start,
+                     *end = pss->buffer + sizeof(pss->buffer) - 1;
        wifi_ap_record_t *r;
        int n, m;
+       nvs_handle nvh;
+       size_t s;
+
 
        switch (reason) {
 
@@ -174,6 +323,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                          (TimerCallbackFunction_t)timer_cb);
                xTimerStart(vhd->timer, 0);
                vhd->scan_ongoing = 0;
+               strcpy(vhd->json, " { }");
                scan_start(vhd);
                break;
 
@@ -196,61 +346,144 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                break;
 
        case LWS_CALLBACK_SERVER_WRITEABLE:
+
+               if (vhd->autonomous_update_sampled) {
+                       p += snprintf((char *)p, end - p,
+                                     " {\n \"auton\":\"1\",\n \"pos\": \"%ld\",\n"
+                                     " \"len\":\"%ld\"\n}\n",
+                                       vhd->file_length,
+                                       vhd->content_length);
+
+                       n = LWS_WRITE_TEXT;
+                       goto issue;
+               }
+
                switch (pss->scan_state) {
+                       char ssid[32];
+                       uint8_t mac[6];
+                       struct lws_esp32_image i;
+                       char img_factory[512], img_ota[512];
+
                case SCAN_STATE_INITIAL:
-                       n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;;
-                       p += snprintf(p, end - p,
-                                     "{ \"model\":\"%s\","
-                                     " \"serial\":\"%s\","
-                                     " \"host\":\"%s-%s\","
-                                     " \"region\":\"%d\","
-                                     " \"aps\":[",
-                                     lws_esp32_model,
-                                     lws_esp32_serial,
-                                     lws_esp32_model, lws_esp32_serial,
-                                     lws_esp32_region);
-                       pss->scan_state = SCAN_STATE_LIST;
+
+                       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+                       n = 0;
+                       if (nvs_get_blob(nvh, "ssl-pub.der", NULL, &s) == ESP_OK)
+                               n = 1;
+                       if (nvs_get_blob(nvh, "ssl-pri.der", NULL, &s) == ESP_OK)
+                               n |= 2;
+                       s = sizeof(ssid) - 1;
+                       ssid[0] = '\0';
+                       nvs_get_str(nvh, "ssid", ssid, &s);
+
+                       nvs_close(nvh);
+
+                       esp_efuse_read_mac(mac);
+                       strcpy(img_factory, " { \"date\": \"Empty\" }");
+                       strcpy(img_ota, " { \"date\": \"Empty\" }");
+
+                       lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
+                               ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL), &i,
+                               img_factory, sizeof(img_factory));
+                       lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP,
+                               ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL), &i,
+                               img_ota, sizeof(img_ota));
+
+                       p += snprintf((char *)p, end - p,
+                                     "{ \"model\":\"%s\",\n"
+                                     " \"forced_button\":\"%d\",\n"
+                                     " \"serial\":\"%s\",\n"
+                                     " \"opts\":\"%s\",\n"
+                                     " \"host\":\"%s-%s\",\n"
+                                     " \"region\":\"%d\",\n"
+                                     " \"ssl_pub\":\"%d\",\n"
+                                     " \"ssl_pri\":\"%d\",\n"
+                                     " \"mac\":\"%02X%02X%02X%02X%02X%02X\",\n"
+                                     " \"ssid\":\"%s\",\n"
+                                     " \"conn_ip\":\"%s\",\n"
+                                     " \"conn_mask\":\"%s\",\n"
+                                     " \"conn_gw\":\"%s\",\n"
+                                     " \"img_factory\": %s,\n"
+                                     " \"img_ota\": %s,\n",
+                                     lws_esp32.model,
+                                     lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON, 
+                                     lws_esp32.serial,
+                                     lws_esp32.opts,
+                                     lws_esp32.model, lws_esp32.serial,
+                                     lws_esp32.region,
+                                     n & 1, (n >> 1) & 1,
+                                     mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] | 1,
+                                     ssid,
+                                     lws_esp32.sta_ip,
+                                     lws_esp32.sta_mask,
+                                     lws_esp32.sta_gw,
+                                       img_factory,
+                                       img_ota
+                                     );
+
+                       n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;
+                       pss->scan_state = SCAN_STATE_INITIAL_MANIFEST;
                        pss->ap_record = 0;
                        pss->subsequent = 0;
                        break;
-               case SCAN_STATE_LIST:
+
+               case SCAN_STATE_INITIAL_MANIFEST:
+                       p += snprintf((char *)p, end - p,
+                                     " \"latest\": %s,\n"
+                                     " \"inet\":\"%d\",\n",
+                                       vhd->json,
+                                     lws_esp32.inet
+                                     );
+
+                       p += snprintf((char *)p, end - p,
+                                      " \"aps\":[\n");
+
                        n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
-                       if (pss->ap_record >= vhd->count_ap_records)
-                               goto scan_state_final;
-
-                       if (pss->subsequent)
-                               *p++ = ',';
-                       pss->subsequent = 1;
-
-                       r = &vhd->ap_records[(int)pss->ap_record++];
-                       p += snprintf(p, end - p,
-                                     "{\"ssid\":\"%s\","
-                                      "\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\","
-                                      "\"rssi\":\"%d\","
-                                      "\"chan\":\"%d\","
-                                      "\"auth\":\"%d\"}",
-                                       r->ssid,
-                                       r->bssid[0], r->bssid[1], r->bssid[2],
-                                       r->bssid[3], r->bssid[4], r->bssid[5],
-                                       r->rssi, r->primary, r->authmode);
-                       if (pss->ap_record >= vhd->count_ap_records)
-                               pss->scan_state = SCAN_STATE_FINAL;
+                       pss->scan_state = SCAN_STATE_LIST;
+                       break;
+
+               case SCAN_STATE_LIST:
+                       for (m = 0; m < 6; m++) {
+                               n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
+                               if (pss->ap_record >= vhd->count_ap_records)
+                                       goto scan_state_final;
+
+                               if (pss->subsequent)
+                                       *p++ = ',';
+                               pss->subsequent = 1;
+
+                               r = &vhd->ap_records[(int)pss->ap_record++];
+                               p += snprintf((char *)p, end - p,
+                                             "{\"ssid\":\"%s\",\n"
+                                              "\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\",\n"
+                                              "\"rssi\":\"%d\",\n"
+                                              "\"chan\":\"%d\",\n"
+                                              "\"auth\":\"%d\"}\n",
+                                               r->ssid,
+                                               r->bssid[0], r->bssid[1], r->bssid[2],
+                                               r->bssid[3], r->bssid[4], r->bssid[5],
+                                               r->rssi, r->primary, r->authmode);
+                               if (pss->ap_record >= vhd->count_ap_records)
+                                       pss->scan_state = SCAN_STATE_FINAL;
+                       }
                        break;
 
                case SCAN_STATE_FINAL:
 scan_state_final:
                        n = LWS_WRITE_CONTINUATION;
-                       p += sprintf(p, "]}");
+                       p += sprintf((char *)p, "]\n}\n");
                        if (pss->changed_partway) {
                                pss->subsequent = 0;
                                pss->scan_state = SCAN_STATE_INITIAL;
-                       } else
+                       } else {
                                pss->scan_state = SCAN_STATE_NONE;
+                               vhd->autonomous_update_sampled = vhd->autonomous_update;
+                       }
                        break;
                default:
                        return 0;
                }
-
+issue:
                m = lws_write(wsi, (unsigned char *)start, p - start, n);
                if (m < 0) {
                        lwsl_err("ERROR %d writing to di socket\n", m);
@@ -264,6 +497,7 @@ scan_state_final:
 
        case LWS_CALLBACK_RECEIVE:
                {
+                       const char *sect = "\"app\": {", *b;
                        nvs_handle nvh;
                        char p[64];
                        int n;
@@ -273,14 +507,30 @@ scan_state_final:
                                break;
                        }
 
+                       if (vhd->json_len && strstr((const char *)in, "update-factory")) {
+                               sect = "\"factory\": {";
+                               goto auton;
+                       }
+                       if (vhd->json_len && strstr((const char *)in, "update-ota"))
+                               goto auton;
+
+                       if (strstr((const char *)in, "reset"))
+                               goto sched_reset;
+
                        if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
                                lwsl_err("Unable to open nvs\n");
                                break;
                        }
 
                        for (n = 0; n < ARRAY_SIZE(store_json); n++) {
-                               if (esplws_simple_arg(p, sizeof(p),  in, store_json[n].j))
-                                       goto bail_nvs;
+                               if (esplws_simple_arg(p, sizeof(p), in, store_json[n].j))
+                                       continue;
+
+                               /* only change access password if he has physical access to device */
+                               if (n == 2 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
+                                       continue;
+
+                               lwsl_notice("%s '%s\n", store_json[n].nvs, p);
 
                                if (nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) {
                                        lwsl_err("Unable to store %s in nvm\n", store_json[n].nvs);
@@ -291,13 +541,69 @@ scan_state_final:
                        nvs_commit(nvh);
                        nvs_close(nvh);
 
-                       vhd->reboot = 1;
+                       if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
+
+                               if (strstr((const char *)in, "factory-reset")) {
+                                       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+                                       nvs_erase_all(nvh);
+                                       nvs_commit(nvh);
+                                       nvs_close(nvh);
+
+                                       goto sched_reset;
+                               }
+
+                       }
+
+                       if (vhd->scan_ongoing)
+                               vhd->changed_settings = 1;
+                       else
+                               lws_esp32_wlan_nvs_get(1);
+
+                       lwsl_notice("set Join AP info\n");
                        break;
 
 bail_nvs:
                        nvs_close(nvh);
 
                        return 1;
+
+sched_reset:
+                       vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
+                                               (TimerCallbackFunction_t)reboot_timer_cb);
+                       xTimerStart(vhd->reboot_timer, 0);
+
+                       return 1;
+
+auton:
+                       lwsl_notice("Autonomous upload\n");
+                       b = strstr(vhd->json, sect);
+                       if (!b) {
+                               lwsl_notice("Can't find %s in JSON\n", sect);
+                               return 1;
+                       }
+                       b = strstr(b, "\"file\": \"");
+                       if (!b) {
+                               lwsl_notice("Can't find \"file\": JSON\n");
+                               return 1;
+                       }
+                       vhd->autonomous_update = 1;
+                       if (pss->scan_state == SCAN_STATE_NONE)
+                               vhd->autonomous_update_sampled = 1;
+                       b += 9;
+                       n = 0;
+                       while ((*b != '\"') && n < sizeof(p) - 1)
+                               p[n++] = *b++;
+
+                       p[n] = '\0';
+
+                       vhd->part = ota_choose_part();
+                       if (!vhd->part)
+                               return 1;
+
+                       if (client_connection(vhd, p))
+                               vhd->autonomous_update = 0;
+
+                       break;
                }
 
        case LWS_CALLBACK_CLOSED:
@@ -316,11 +622,232 @@ bail_nvs:
                        vhd->count_live_pss--;
                }
                break;
+
+       /* "factory" POST handling */
+
+       case LWS_CALLBACK_HTTP_BODY:
+               /* create the POST argument parser if not already existing */
+               lwsl_notice("LWS_CALLBACK_HTTP_BODY (scan)\n");
+               if (!pss->spa) {
+                       pss->spa = lws_spa_create(wsi, param_names,
+                                       ARRAY_SIZE(param_names), 1024,
+                                       file_upload_cb, pss);
+                       if (!pss->spa)
+                               return -1;
+
+                       pss->filename[0] = '\0';
+                       pss->file_length = 0;
+               }
+
+               /* let it parse the POST data */
+               if (lws_spa_process(pss->spa, in, len))
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+               lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (scan)\n");
+               /* call to inform no more payload data coming */
+               lws_spa_finalize(pss->spa);
+
+               if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
+                       lwsl_err("Unable to open nvs\n");
+                       break;
+               }
+
+               if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
+
+                       if (lws_spa_get_string(pss->spa, EPN_SERIAL)) {
+                               if (nvs_set_str(nvh, "serial", lws_spa_get_string(pss->spa, EPN_SERIAL)) != ESP_OK) {
+                                       lwsl_err("Unable to store serial in nvm\n");
+                                       goto bail_nvs;
+                               }
+               
+                               nvs_commit(nvh);
+                       }
+
+                       if (lws_spa_get_string(pss->spa, EPN_OPTS)) {
+                               if (nvs_set_str(nvh, "opts", lws_spa_get_string(pss->spa, EPN_OPTS)) != ESP_OK) {
+                                       lwsl_err("Unable to store options in nvm\n");
+                                       goto bail_nvs;
+                               }
+               
+                               nvs_commit(nvh);
+                       }
+               }
+               nvs_close(nvh);
+
+               pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
+                               "<html>Rebooting after storing certs...<br>connect to AP '<b>config-%s-%s</b>' and continue here: "
+                               "<a href=\"https://192.168.4.1\">https://192.168.4.1</a></html>",
+                               lws_esp32.model, lws_spa_get_string(pss->spa, EPN_SERIAL));
+
+               if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
+                       goto bail;
+
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                               (unsigned char *)"text/html", 9, &p, end))
+                       goto bail;
+               if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
+                       goto bail;
+               if (lws_finalize_http_header(wsi, &p, end))
+                       goto bail;
+
+               n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
+               if (n < 0)
+                       goto bail;
+
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
+                          pss->result_len);
+               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
+                             pss->result_len, LWS_WRITE_HTTP);
+               if (n < 0)
+                       return 1;
+
+               vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(3000), 0, vhd,
+                         (TimerCallbackFunction_t)reboot_timer_cb);
+               xTimerStart(vhd->reboot_timer, 0);
+
+               return 1; // hang up since we will reset
+
+       /* ----- client handling ----- */
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_notice("Client connection error %s\n", (char *)in);
+               vhd->cwsi = NULL;
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               if (!vhd->autonomous_update)
+                       break;
+
+               {
+                       char pp[20];
+
+                       if (lws_hdr_copy(wsi, pp, sizeof(pp) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH) < 0)
+                               return -1;
+       
+                       vhd->content_length = atoi(pp);
+                       if (vhd->content_length <= 0 ||
+                           vhd->content_length > vhd->part->size)
+                               return -1;
+
+                       if (esp_ota_begin(vhd->part, (long)-1, &vhd->otahandle) != ESP_OK) {
+                               lwsl_err("OTA: Failed to begin\n");
+                               return 1;
+                       }
+
+                       vhd->file_length = 0;
+                       break;
+               }
+               break;
+
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               //lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: %ld\n",
+               //          (long)len);
+
+               if (!vhd->autonomous_update) {
+                       if (sizeof(vhd->json) - vhd->json_len - 1 < len)
+                               len = sizeof(vhd->json) - vhd->json_len - 1;
+                       memcpy(vhd->json + vhd->json_len, in, len);
+                       vhd->json_len += len;
+                       vhd->json[vhd->json_len] = '\0';
+                       break;
+               }
+
+               /* autonomous download */
+
+
+               if (vhd->file_length + len > vhd->part->size) {
+                       lwsl_err("OTA: incoming file too large\n");
+                       goto abort_ota;
+               }
+
+               lwsl_debug("writing 0x%lx... 0x%lx\n",
+                          vhd->part->address + vhd->file_length,
+                          vhd->part->address + vhd->file_length + len);
+               if (esp_ota_write(vhd->otahandle, in, len) != ESP_OK) {
+                       lwsl_err("OTA: Failed to write\n");
+                       goto abort_ota;
+               }
+               vhd->file_length += len;
+
+               lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
+               break;
+
+abort_ota:
+               esp_ota_end(vhd->otahandle);
+               vhd->otahandle = 0;
+               vhd->autonomous_update = 0;
+
+               return 1;
+
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               {
+                       char *px = (char *)pss->buffer + LWS_PRE;
+                       int lenx = sizeof(pss->buffer) - LWS_PRE - 1;
+
+                       //lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: %d\n", len);
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               break;
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+               vhd->cwsi = NULL;
+               if (!vhd->autonomous_update) {
+
+                       vhd->checked_updates = 1;
+                       puts(vhd->json);
+                       return -1;
+               }
+
+               /* autonomous download */
+
+               lwsl_notice("auton complete\n");
+
+               if (esp_ota_end(vhd->otahandle) != ESP_OK) {
+                       lwsl_err("OTA: end failed\n");
+                       return 1;
+               }
+
+               if (esp_ota_set_boot_partition(vhd->part) != ESP_OK) {
+                       lwsl_err("OTA: set boot part failed\n");
+                       return 1;
+               }
+               vhd->otahandle = 0;
+               vhd->autonomous_update = 0;
+
+               vhd->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, vhd,
+                         (TimerCallbackFunction_t)reboot_timer_cb);
+                       xTimerStart(vhd->reboot_timer, 0);
+               return -1;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               lwsl_notice("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
+               break;
+
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               break;
+
        default:
                break;
        }
 
        return 0;
+
+bail:
+       return 1;
 }
 
 #define LWS_PLUGIN_PROTOCOL_ESPLWS_SCAN \
@@ -328,6 +855,6 @@ bail_nvs:
                "esplws-scan", \
                callback_esplws_scan, \
                sizeof(struct per_session_data__esplws_scan), \
-               512, 0, NULL \
+               1024, 0, NULL, 900 \
        }
 
diff --git a/scripts/esp32.mk b/scripts/esp32.mk
new file mode 100644 (file)
index 0000000..03a79cc
--- /dev/null
@@ -0,0 +1,105 @@
+#
+# LWS-style images are composed like this
+#
+# [ OTA or Factory standard xpressif image ]
+# [ 32-b LE len ] [ ROMFS ]
+# [ 32-b LE len ] [ Image information JSON ]
+#
+
+jbi=$(COMPONENT_PATH)/../build/json-buildinfo
+
+.PHONY: romfs.img
+pack.img:
+       DIRNAME=$$(basename $$(pwd) | tr -d '\n') ;\
+       cp $(COMPONENT_PATH)/../build/$(PROJECT_NAME).bin $(COMPONENT_PATH)/../build/$$DIRNAME.bin ; \
+       genromfs -f $(COMPONENT_PATH)/../build/romfs.img -d $(COMPONENT_PATH)/../romfs-files ; \
+        RLEN=$$(stat -c %s $(COMPONENT_PATH)/../build/romfs.img) ;\
+        LEN=$$(stat -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
+        printf "             Original length: 0x%06x (%8d)\n" $$LEN $$LEN ; \
+        printf %02x $$(( $$RLEN % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+        printf %02x $$(( ( $$RLEN / 256 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+        printf %02x $$(( ( $$RLEN / 65536 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+        printf %02x $$(( ( $$RLEN / 16777216 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+        cat $(COMPONENT_PATH)/../build/romfs.img >>$(COMPONENT_PATH)/../build/$$DIRNAME.bin ; \
+        LEN=$$(stat -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
+       UNIXTIME=$$(date +%s | tr -d '\n') ; \
+       echo -n -e "{\r\n \"schema\": \"lws1\",\r\n \"model\": \"$(CONFIG_LWS_MODEL_NAME)\",\r\n \"builder\": \"" > $(jbi) ;\
+       hostname | tr -d '\n' >> $(jbi) ;\
+       echo -n -e "\",\r\n \"app\": \"" >> $(jbi) ;\
+       echo -n $$DIRNAME >> $(jbi) ;\
+       echo -n -e "\",\r\n \"user\": \"" >> $(jbi) ;\
+       whoami | tr -d '\n' >>$(jbi) ;\
+       echo -n -e  "\",\r\n \"git\": \"" >> $(jbi) ;\
+       git describe --dirty --always | tr -d '\n' >> $(jbi) ;\
+       echo -n -e  "\",\r\n \"date\": \"" >> $(jbi) ;\
+       date | tr -d '\n' >> $(jbi) ;\
+       echo -n -e "\",\r\n \"unixtime\": \"" >> $(jbi) ;\
+       echo -n $$UNIXTIME >> $(jbi) ;\
+       echo -n -e "\",\r\n \"file\": \""$$DIRNAME-$$UNIXTIME.bin >> $(jbi) ;\
+       echo -n -e "\",\r\n \"factory\": \"$(LWS_IS_FACTORY_APPLICATION)" >> $(jbi) ;\
+       echo -n -e "\"\r\n}"  >> $(jbi) ;\
+       JLEN=$$(stat -c %s $(jbi)) ;\
+       printf %02x $$(( $$JLEN % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+       printf %02x $$(( ( $$JLEN / 256 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+       printf %02x $$(( ( $$JLEN / 65536 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+       printf %02x $$(( ( $$JLEN / 16777216 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+       cat $(jbi) >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+        LEN=$$(stat -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
+       cp $(COMPONENT_PATH)/../build/$$DIRNAME.bin $(COMPONENT_PATH)/../build/$$DIRNAME-$$UNIXTIME.bin ;\
+       printf "    After ROMFS + Build info: 0x%06x (%8d)\n" $$LEN $$LEN
+
+.PHONY: manifest
+manifest:
+ifeq ($F,)
+       echo "Usage make F=<factory app dir> A=<app dir> manifest"
+       exit 1
+endif
+ifeq ($A,)
+       echo "Usage make F=<factory app dir> A=<app dir> manifest"
+       exit 1
+endif
+       echo -n -e "{\r\n\"app\": " > build/manifest.json
+       cat $(A)/build/json-buildinfo >> build/manifest.json
+       echo -n -e ", \"factory\": " >> build/manifest.json
+       cat $(F)/build/json-buildinfo >> build/manifest.json
+       echo -n -e "\r\n}\r\n" >> build/manifest.json
+
+all: pack.img
+
+flash_ota:
+       DIRNAME=$$(basename $$(pwd) | tr -d '\n') ;\
+       $(IDF_PATH)/components/esptool_py/esptool/esptool.py \
+               --chip esp32 \
+               --port $(CONFIG_ESPTOOLPY_PORT) \
+               --baud $(CONFIG_ESPTOOLPY_BAUD) \
+               write_flash 0x110000 $(COMPONENT_PATH)/../build/$$DIRNAME.bin
+
+erase_ota:
+       $(IDF_PATH)/components/esptool_py/esptool/esptool.py \
+               --chip esp32 \
+               --port $(CONFIG_ESPTOOLPY_PORT) \
+               --baud $(CONFIG_ESPTOOLPY_BAUD) \
+               erase_region 0x110000 0x2f0000
+
+
+export A
+export F
+.PHONY: upload
+upload: manifest
+ifeq ($F,)
+       echo "Usage make F=<factory app dir> A=<app dir> manifest"
+       exit 1
+endif
+ifeq ($A,)
+       echo "Usage make F=<factory app dir> A=<app dir> manifest"
+       exit 1
+endif
+       UPL=$(CONFIG_LWS_OTA_SERVER_UPLOAD_USER)@$(CONFIG_LWS_OTA_SERVER_FQDN):$(CONFIG_LWS_OTA_SERVER_UPLOAD_PATH)/$(CONFIG_LWS_OTA_SERVER_BASE_URL)/$(CONFIG_LWS_MODEL_NAME)/ ;\
+       AFILE=$(A)/build/$$(cat $$A/build/json-buildinfo | grep -- \"file\"\: |cut -d' ' -f3 |cut -d'"' -f2) ;\
+       echo "  Uploading $$AFILE to " $$UPL ;\
+       scp $$AFILE $$UPL ;\
+       FFILE=$(F)/build/$$(cat $$F/build/json-buildinfo | grep -- \"file\"\: |cut -d' ' -f3 |cut -d'"' -f2) ;\
+       echo "  Uploading $$FFILE" ;\
+       scp  $$FFILE $$UPL ;\
+       echo "  Uploading manifest" ;\
+       scp build/manifest.json $$UPL