From 8b9fabe9e4c452f8c721fac3efc1538913f4daf8 Mon Sep 17 00:00:00 2001
From: wellenvogel <andreas@wellenvogel.de>
Date: Tue, 14 Dec 2021 13:14:36 +0100
Subject: [PATCH] intermediate: prepare ota upload

---
 lib/update/GwUpdate.cpp     | 130 ++++++++++++++++++++++++++++++++++++
 lib/update/GwUpdate.h       |  14 ++++
 lib/webserver/GwWebServer.h |   1 +
 src/main.cpp                |   4 +-
 4 files changed, 148 insertions(+), 1 deletion(-)
 create mode 100644 lib/update/GwUpdate.cpp
 create mode 100644 lib/update/GwUpdate.h

diff --git a/lib/update/GwUpdate.cpp b/lib/update/GwUpdate.cpp
new file mode 100644
index 0000000..4933222
--- /dev/null
+++ b/lib/update/GwUpdate.cpp
@@ -0,0 +1,130 @@
+#include "GwUpdate.h"
+#include <Update.h>
+
+static String jsonError(String e){
+    e.replace('"',' ');
+    return "{\"status\":\""+e+"\"}";
+}
+class UpdateParam{
+    public:
+        String error;
+        size_t uplodaded=0;
+        bool hasError(){
+            return ! error.isEmpty();
+        }
+
+};
+bool GwUpdate::delayedRestart(){
+  return xTaskCreate([](void *p){
+    GwLog *logRef=(GwLog *)p;
+    logRef->logDebug(GwLog::LOG,"delayed reset started");
+    delay(800);
+    ESP.restart();
+    vTaskDelete(NULL);
+  },"reset",2000,logger,0,NULL) == pdPASS;
+}
+GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker checker)
+{
+    this->checker=checker;
+    this->logger = log;
+    this->server = webserver->getServer();
+    server->on("/update", HTTP_POST, [&](AsyncWebServerRequest *request) {
+                // the request handler is triggered after the upload has finished... 
+                // create the response, add header, and send response
+                String result="{\"status\":\"OK\"}";
+                bool updateOk=true;
+                if ( request->_tempObject == NULL){
+                    //no data
+                    LOG_DEBUG(GwLog::ERROR,"no data in update request");
+                    result=jsonError("no data uploaded");
+                    updateOk=false;
+                }
+                else{
+                    UpdateParam *param=(UpdateParam *)(request->_tempObject);
+                    updateOk=!param->hasError();
+                    LOG_DEBUG(GwLog::LOG,"update finished status=%s %s (%d)",
+                        (updateOk?"ok":"error"),
+                        param->error.c_str(),
+                        param->uplodaded
+                        );
+                    if (!updateOk){
+                        result=jsonError(String("Update failed: ")+param->error);
+                    }
+                }
+                AsyncWebServerResponse *response = request->beginResponse(200, "application/json",result);
+                response->addHeader("Connection", "close");
+                request->send(response);
+                updateRunning=false;
+                if (updateOk) delayedRestart();
+            }, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
+                //Upload handler chunks in data
+                LOG_DEBUG(GwLog::DEBUG,"update chunk f=%s,idx=%d,len=%d,final=%d",
+                    filename.c_str(),(int)index,(int)len,(int)final
+                );
+                UpdateParam *param=(UpdateParam *)request->_tempObject;
+                if (!index) {
+                    if (param == NULL) {
+                        param=new UpdateParam();
+                        request->_tempObject=param;
+                    }
+                    if (updateRunning){
+                        param->error="another update is running";
+                    }
+                    else{
+                        updateRunning=true;
+                    }
+                    if (!param->hasError())
+                    {
+                        if (!request->hasParam("_hash", true))
+                        {
+                            LOG_DEBUG(GwLog::ERROR, "missing _hash in update");
+                            param->error = "missing _hash";
+                        }
+                        else
+                        {
+                            if (!checker(request->getParam("_hash")->value()))
+                            {
+                                LOG_DEBUG(GwLog::ERROR, "invalid _hash in update");
+                                param->error = "invalid password";
+                            }
+                        }
+                    }
+                    if (! param->hasError()){
+                        int cmd=U_FLASH;
+                        if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) { // Start with max available size
+                            LOG_DEBUG(GwLog::ERROR,"unable to start update %s",Update.errorString());
+                            param->error=Update.errorString();
+                        }
+                    }
+                }
+                if (param && !param->hasError())
+                {
+                    // Write chunked data to the free sketch space
+                    if (len)
+                    {
+                        size_t wr = Update.write(data, len);
+                        if (wr != len)
+                        {
+                            LOG_DEBUG(GwLog::ERROR, "invalid write, expected %d got %d", (int)len, (int)wr);
+                            param->error="unable to write";
+                        }
+                        else{
+                            param->uplodaded+=wr;
+                        }
+                    }
+
+                    if (final && ! param->hasError())
+                    { // if the final flag is set then this is the last frame of data
+                        if (!Update.end(true))
+                        { //true to set the size to the current progress
+                            LOG_DEBUG(GwLog::ERROR, "unable to end update %s", Update.errorString());
+                            param->error=String("unable to end update:") + String(Update.errorString());
+                        }
+                    }
+                    else
+                    {
+                        return;
+                    }
+                }
+            });
+}
\ No newline at end of file
diff --git a/lib/update/GwUpdate.h b/lib/update/GwUpdate.h
new file mode 100644
index 0000000..0b1e3d8
--- /dev/null
+++ b/lib/update/GwUpdate.h
@@ -0,0 +1,14 @@
+#pragma once
+#include "GwWebServer.h"
+class GwUpdate{
+    public:
+        typedef bool (*PasswordChecker)(String hash);
+    private:
+        AsyncWebServer *server;
+        GwLog *logger;
+        PasswordChecker checker;
+        bool updateRunning=false;
+    public:
+        bool delayedRestart();
+        GwUpdate(GwLog *log,GwWebServer *webserver, PasswordChecker checker);
+};
\ No newline at end of file
diff --git a/lib/webserver/GwWebServer.h b/lib/webserver/GwWebServer.h
index 0afd0aa..35b5cba 100644
--- a/lib/webserver/GwWebServer.h
+++ b/lib/webserver/GwWebServer.h
@@ -15,5 +15,6 @@ class GwWebServer{
         void begin();
         bool registerMainHandler(const char *url,RequestCreator creator);
         void handleAsyncWebRequest(AsyncWebServerRequest *request, GwRequestMessage *msg);
+        AsyncWebServer * getServer(){return server;}
 };
 #endif
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index d2d41c7..6136415 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -74,7 +74,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
 #include "GwSynchronized.h"
 #include "GwUserCode.h"
 #include "GwStatistics.h"
-
+#include "GwUpdate.h"
 
 //NMEA message channels
 #define N2K_CHANNEL_ID 0
@@ -169,6 +169,8 @@ bool checkPass(String hash){
   return false;
 }
 
+GwUpdate updater(&logger,&webserver,checkPass);
+
 void updateNMEACounter(int id,const char *msg,bool incoming,bool fail=false){
   //we rely on the msg being long enough
   char key[6];