140 lines
5.3 KiB
C++
140 lines
5.3 KiB
C++
#include <atomic>
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <string>
|
|
using String = std::string;
|
|
constexpr int HTTP_GET=0, HTTP_POST=1, U_FLASH=0, U_SPIFFS=100;
|
|
constexpr unsigned UPDATE_SIZE_UNKNOWN=~0U;
|
|
constexpr int pdPASS=1;
|
|
#define pdMS_TO_TICKS(value) (value)
|
|
const char ELEGANT_HTML[] = "page";
|
|
const unsigned ELEGANT_HTML_SIZE=sizeof(ELEGANT_HTML);
|
|
int Serial=0;
|
|
bool in_network_callback=false;
|
|
struct Esp {
|
|
int restarts=0;
|
|
void restart() { assert(!in_network_callback); ++restarts; }
|
|
} ESP;
|
|
void yield() { assert(!in_network_callback); }
|
|
void delay(unsigned) { assert(!in_network_callback); }
|
|
void vTaskDelay(unsigned) { assert(!in_network_callback); }
|
|
void vTaskDelete(void*) {}
|
|
using Task = void (*)(void*);
|
|
Task pending_task=nullptr;
|
|
bool allow_task=true;
|
|
int xTaskCreate(Task task, const char*, unsigned, void*, unsigned, void*) {
|
|
if (!allow_task) return 0;
|
|
assert(!pending_task); pending_task=task; return pdPASS;
|
|
}
|
|
@REBOOT_STATE@
|
|
struct Parameter { String text="valid"; String value() { return text; } };
|
|
struct Response {
|
|
int code;
|
|
void addHeader(const char*, const char*) {}
|
|
};
|
|
struct Request {
|
|
void* _tempObject=nullptr;
|
|
bool md5_present=true, authorized=true, sent=false;
|
|
int code=0;
|
|
std::function<void()> disconnected;
|
|
Parameter md5;
|
|
~Request() { free(_tempObject); }
|
|
bool authenticate(const char*, const char*) { return authorized; }
|
|
void requestAuthentication() { code=401; sent=true; }
|
|
bool hasParam(const char*, bool) { return md5_present; }
|
|
Parameter* getParam(const char*, bool) { return &md5; }
|
|
Response* beginResponse(int code, const char*, const char*) { return new Response{code}; }
|
|
Response* beginResponse_P(int code, const char*, const char*, unsigned) { return new Response{code}; }
|
|
void send(Response* response) { code=response->code; sent=true; delete response; }
|
|
void send(int status, const char*, const char*) { code=status; sent=true; }
|
|
void send(int status, const char*, const String&) { code=status; sent=true; }
|
|
bool isSent() const { return sent; }
|
|
void onDisconnect(std::function<void()> fn) { disconnected=fn; }
|
|
};
|
|
using AsyncWebServerRequest=Request;
|
|
static AsyncWebServerRequest* ota_upload_owner=nullptr;
|
|
using AsyncWebServerResponse=Response;
|
|
struct AsyncWebServer {
|
|
std::function<void(Request*)> completion;
|
|
std::function<void(Request*,String,size_t,uint8_t*,size_t,bool)> upload;
|
|
template<class F> void on(const char*, int, F) {}
|
|
template<class F,class U> void on(const char*, int method, F callback, U chunks) {
|
|
assert(method==HTTP_POST); completion=callback; upload=chunks;
|
|
}
|
|
};
|
|
struct UpdateFake {
|
|
bool error=false, fail_end=false;
|
|
int aborts=0;
|
|
bool hasError() const { return error; }
|
|
bool setMD5(const char* md5) { return String(md5)=="valid"; }
|
|
bool begin(unsigned, int) { error=false; return true; }
|
|
size_t write(uint8_t*, size_t size) { return size; }
|
|
bool end(bool) { error=fail_end; return !error; }
|
|
void abort() { error=true; ++aborts; }
|
|
void printError(int) {}
|
|
} Update;
|
|
struct AsyncElegantOtaClass {
|
|
AsyncWebServer* _server=nullptr;
|
|
bool _authRequired=false;
|
|
String _username, _password, _id="fixture";
|
|
void begin(AsyncWebServer*, const char*, const char*);
|
|
void restart();
|
|
bool setEnabled(bool);
|
|
};
|
|
@BEGIN@
|
|
@RESTART@
|
|
|
|
int main() {
|
|
AsyncWebServer server;
|
|
AsyncElegantOtaClass ota;
|
|
ota.begin(&server,"","");
|
|
auto complete=[&](Request& request) {
|
|
in_network_callback=true; server.completion(&request); in_network_callback=false;
|
|
};
|
|
auto upload=[&](Request& request) {
|
|
uint8_t data[8]={};
|
|
in_network_callback=true;
|
|
server.upload(&request,"firmware",0,data,sizeof(data),true);
|
|
in_network_callback=false;
|
|
};
|
|
// A POST without an actual completed upload cannot report OK or reboot.
|
|
Request empty;
|
|
complete(empty);
|
|
assert(empty.code>=400 && !pending_task && ESP.restarts==0);
|
|
Request invalid;
|
|
invalid.md5_present=false;
|
|
upload(invalid); complete(invalid);
|
|
assert(invalid.code==400 && !pending_task && ESP.restarts==0);
|
|
Request corrupt;
|
|
Update.fail_end=true;
|
|
upload(corrupt); complete(corrupt);
|
|
assert(corrupt.code==400 && !pending_task && ESP.restarts==0);
|
|
in_network_callback=true; corrupt.disconnected(); in_network_callback=false;
|
|
assert(Update.aborts==1 && ota_upload_owner==nullptr);
|
|
Update.fail_end=false;
|
|
Request partial;
|
|
uint8_t chunk[4]={};
|
|
server.upload(&partial,"firmware",0,chunk,sizeof(chunk),false);
|
|
assert(!ota.setEnabled(false)); // An admitted upload keeps its listener alive.
|
|
Request concurrent;
|
|
upload(concurrent); complete(concurrent);
|
|
assert(concurrent.code==409 && ota_upload_owner==&partial);
|
|
in_network_callback=true; partial.disconnected(); in_network_callback=false;
|
|
assert(Update.aborts==2 && ota_upload_owner==nullptr && ESP.restarts==0);
|
|
assert(ota.setEnabled(false));
|
|
Request stopped;
|
|
upload(stopped); complete(stopped);
|
|
assert(stopped.code==503 && !ota_upload_owner && !pending_task);
|
|
assert(ota.setEnabled(true));
|
|
Request valid;
|
|
upload(valid); complete(valid);
|
|
assert(valid.code==200 && valid.disconnected && !pending_task && ESP.restarts==0);
|
|
// The response drains before disconnect; its callback only schedules work.
|
|
in_network_callback=true; valid.disconnected(); in_network_callback=false;
|
|
assert(pending_task && ESP.restarts==0);
|
|
auto task=pending_task; pending_task=nullptr; task(nullptr);
|
|
assert(ESP.restarts==1);
|
|
}
|