Compare commits

..

10 Commits

Author SHA1 Message Date
Blaz Kristan
a7109fd572 Merge branch '0_15' into power-ap 2024-09-23 19:22:31 +02:00
Blaz Kristan
f3092627a4 Merge branch '0_15' into power-ap 2024-06-30 10:51:35 +02:00
Blaz Kristan
f5c82af200 Merge branch '0_15' into power-ap 2024-04-26 20:45:52 +02:00
Blaz Kristan
ad56414edf Merge branch '0_15' into power-ap 2024-04-09 16:38:53 +02:00
Blaz Kristan
75c048202e Merge branch 'main' into power-ap 2023-07-15 22:01:48 +02:00
Blaz Kristan
e67668693b Merge branch 'main' into power-ap 2023-03-28 09:29:39 +02:00
Blaz Kristan
93fe8c2818 Minor adjustment. 2022-12-10 19:58:34 +01:00
Blaz Kristan
e186b0568d Bugfix. 2022-12-10 01:44:16 +01:00
Blaz Kristan
10196ea4e5 remove comment 2022-12-03 21:22:48 +01:00
Blaz Kristan
f85a6f7c3c Force AP mode on power bounce
-  requires 3 power cycles
2022-12-03 12:35:48 +01:00
26 changed files with 692 additions and 1005 deletions

119
package-lock.json generated
View File

@@ -54,9 +54,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -67,10 +67,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"bin": {
"acorn": "bin/acorn"
},
@@ -185,9 +190,9 @@
}
},
"node_modules/aws4": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
@@ -228,11 +233,11 @@
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dependencies": {
"fill-range": "^7.1.1"
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
@@ -696,9 +701,9 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1276,6 +1281,17 @@
"node": ">=0.10.0"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -1359,9 +1375,9 @@
}
},
"node_modules/nodemon": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
"integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
"integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
@@ -1386,11 +1402,11 @@
}
},
"node_modules/nodemon/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "^2.1.3"
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
@@ -1402,9 +1418,9 @@
}
},
"node_modules/nodemon/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nodemon/node_modules/supports-color": {
"version": "5.5.0",
@@ -1417,6 +1433,20 @@
"node": ">=4"
}
},
"node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "*"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -1477,7 +1507,6 @@
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
@@ -1591,7 +1620,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
"deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
"engines": {
"node": ">=0.6.0",
"teleport": ">=0.2.0"
@@ -1798,9 +1826,12 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
@@ -1962,9 +1993,9 @@
}
},
"node_modules/terser": {
"version": "5.34.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.34.0.tgz",
"integrity": "sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ==",
"version": "5.29.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz",
"integrity": "sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -2011,9 +2042,12 @@
}
},
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
"dependencies": {
"nopt": "~1.0.10"
},
"bin": {
"nodetouch": "bin/nodetouch.js"
}
@@ -2031,9 +2065,9 @@
}
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
@@ -2195,6 +2229,11 @@
"node": ">=0.10.0"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yargs": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",

View File

@@ -26,6 +26,6 @@
"clean-css": "^5.3.3",
"html-minifier-terser": "^7.2.0",
"inliner": "^1.13.1",
"nodemon": "^3.1.7"
"nodemon": "^3.0.2"
}
}

View File

@@ -141,7 +141,6 @@ lib_deps =
makuna/NeoPixelBus @ 2.8.0
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1
https://github.com/Aircoookie/arduino-crypto.git
# for I2C interface
;Wire
# ESP-NOW library

View File

@@ -10,9 +10,6 @@
</p>
> [!CAUTION]
> This branch is actively used for research purposes. **Please do not push** to it.
# Welcome to my project WLED! ✨
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!

View File

@@ -4,17 +4,19 @@
#
# pip-compile
#
aiofiles==22.1.0
# via platformio
ajsonrpc==1.2.0
# via platformio
anyio==4.6.0
anyio==3.6.2
# via starlette
bottle==0.13.1
bottle==0.12.25
# via platformio
certifi==2024.8.30
certifi==2023.7.22
# via requests
charset-normalizer==3.3.2
charset-normalizer==3.1.0
# via requests
click==8.1.7
click==8.1.3
# via
# platformio
# uvicorn
@@ -26,33 +28,35 @@ h11==0.14.0
# via
# uvicorn
# wsproto
idna==3.10
idna==3.7
# via
# anyio
# requests
marshmallow==3.22.0
marshmallow==3.19.0
# via platformio
packaging==24.1
packaging==23.1
# via marshmallow
platformio==6.1.16
platformio==6.1.14
# via -r requirements.in
pyelftools==0.31
pyelftools==0.29
# via platformio
pyserial==3.5
# via platformio
requests==2.32.3
requests==2.32.0
# via platformio
semantic-version==2.10.0
# via platformio
sniffio==1.3.1
sniffio==1.3.0
# via anyio
starlette==0.39.1
starlette==0.23.1
# via platformio
tabulate==0.9.0
# via platformio
urllib3==2.2.3
typing-extensions==4.11.0
# via starlette
urllib3==1.26.19
# via requests
uvicorn==0.30.6
uvicorn==0.20.0
# via platformio
wsproto==1.2.0
# via platformio

View File

@@ -1,17 +0,0 @@
#!/bin/bash
# Pull all settings pages for comparison
HOST=$1
TGT_PATH=$2
CURL_ARGS="--compressed"
# Replicate one target many times
function replicate() {
for i in {0..10}
do
echo -n " http://${HOST}/settings.js?p=$i -o ${TGT_PATH}/$i.xml"
done
}
read -a TARGETS <<< $(replicate)
mkdir -p ${TGT_PATH}
curl ${CURL_ARGS} ${TARGETS[@]}

View File

@@ -0,0 +1,46 @@
#pragma once
#include "wled.h"
class PowerAPUsermod : public Usermod {
private:
unsigned long lastTime = 0;
String fname = F("/boot.dat");
public:
void setup() {
if (WLED_FS.exists(fname)) {
File fl = WLED_FS.open(fname,"r+");
if (!fl) DEBUG_PRINTLN(F("--- File read failed ---"));
char data = fl.read();
if (data == '0') {
DEBUG_PRINTLN(F("--- 2nd boot ---"));
fl.seek(0);
fl.write('1');
} else if (data == '1') {
DEBUG_PRINTLN(F("--- 3rd boot ---"));
apBehavior = AP_BEHAVIOR_ALWAYS;
WLED::instance().initAP(true);
}
fl.close();
} else {
DEBUG_PRINTLN(F("--- 1st boot ---"));
File fl = WLED_FS.open(fname,"w");
fl.write((uint8_t*)"0 ", 2); // write('0'); does not work somehow
fl.close();
}
}
void loop() {
if (millis() < 10000 && millis() - lastTime > 5000) {
lastTime = millis();
if (WLED_FS.exists(fname)) {
DEBUG_PRINTLN(F("--- Removing boot file ---"));
WLED_FS.remove(fname);
}
}
}
uint16_t getId() { return USERMOD_ID_UNSPECIFIED; }
};

View File

@@ -1290,7 +1290,7 @@ void WS2812FX::finalizeInit() {
// if we have less counts than pins and they do not align, use last known count to set current count
unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
// analog always has length 1
if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1;
if (Bus::isPWM(dataType)) count = 1;
prevLen += count;
BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer);
if (BusManager::add(defCfg) == -1) break;

View File

@@ -421,8 +421,6 @@
#define SEG_CAPABILITY_W 0x02
#define SEG_CAPABILITY_CCT 0x04
#define SESSION_ID_SIZE 16
// WLED Error modes
#define ERR_NONE 0 // All good :)
#define ERR_DENIED 1 // Permission denied
@@ -440,11 +438,6 @@
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
#define ERR_NONCE 40 // Invalid nonce
#define ERR_REPLAY 41 // Replay attack detected
#define ERR_HMAC 42 // HMAC verification failed
#define ERR_HMAC_MISS 43 // HMAC missing
#define ERR_HMAC_GEN 44 // HMAC handling error
// Timer mode types
#define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness

View File

@@ -1,320 +0,0 @@
#include <Crypto.h>
#include "wled.h"
#define HMAC_KEY_SIZE 32
#define MAX_SESSION_IDS 8
void printByteArray(const byte* arr, size_t len) {
for (size_t i = 0; i < len; i++) {
Serial.print(arr[i], HEX);
}
Serial.println();
}
struct Nonce {
byte sessionId[SESSION_ID_SIZE];
uint32_t counter;
};
Nonce knownSessions[MAX_SESSION_IDS] = {};
void moveToFirst(uint32_t i) {
if (i >= MAX_SESSION_IDS) return;
Nonce tmp = knownSessions[i];
for (int j = i; j > 0; j--) {
knownSessions[j] = knownSessions[j - 1];
}
knownSessions[0] = tmp;
}
uint8_t verifyNonce(const byte* sid, uint32_t counter) {
Serial.println(F("check sid"));
printByteArray(sid, SESSION_ID_SIZE);
uint32_t sum = 0;
for (size_t i = 0; i < SESSION_ID_SIZE; i++) {
sum += sid[i];
}
if (sum == 0) { // all-zero session ID is invalid as it is used for uninitialized entries
return ERR_NONCE;
}
for (int i = 0; i < MAX_SESSION_IDS; i++) {
if (memcmp(knownSessions[i].sessionId, sid, SESSION_ID_SIZE) == 0) {
Serial.print(F("Session ID matches e"));
Serial.println(i);
if (counter <= knownSessions[i].counter) {
Serial.println(F("Retransmission detected!"));
return ERR_REPLAY;
}
knownSessions[i].counter = counter;
// nonce good, move this entry to the first position of knownSessions
moveToFirst(i);
return ERR_NONE;
}
}
Serial.println(F("Unknown session ID!"));
return ERR_NONCE;
}
void addSessionId(byte* sid) {
RNG::fill(sid, SESSION_ID_SIZE);
// first, try to find a completely unused slot
for (int i = 0; i < MAX_SESSION_IDS; i++) {
// this is not perfect, but it is extremely unlikely that the first 32 bit of a random session ID are all zeroes
if ((uint32_t)(knownSessions[i].sessionId) == 0 && knownSessions[i].counter == 0) {
memcpy(knownSessions[i].sessionId, sid, SESSION_ID_SIZE);
moveToFirst(i);
return;
}
}
// next, find oldest slot that has counter 0 (not used before)
// but leave the two most recent slots alone
for (int i = MAX_SESSION_IDS - 1; i > 1; i--) {
if (knownSessions[i].counter == 0) {
memcpy(knownSessions[i].sessionId, sid, SESSION_ID_SIZE);
moveToFirst(i);
return;
}
}
// if all else fails, overwrite the oldest slot
memcpy(knownSessions[MAX_SESSION_IDS - 1].sessionId, sid, SESSION_ID_SIZE);
moveToFirst(MAX_SESSION_IDS - 1);
}
void hexStringToByteArray(const char* hexString, unsigned char* byteArray, size_t byteArraySize) {
size_t lenStr = strlen(hexString);
if (lenStr < 2 * byteArraySize) byteArraySize = lenStr / 2;
for (size_t i = 0; i < byteArraySize; i++) {
char c[3] = {hexString[2 * i], hexString[2 * i + 1], '\0'}; // Get two characters
byteArray[i] = (unsigned char)strtoul(c, NULL, 16); // Convert to byte
}
}
// requires hexString to be at least 2 * byteLen + 1 characters long
char* byteArrayToHexString(char* hexString, const byte* byteArray, size_t byteLen) {
for (size_t i = 0; i < byteLen; ++i) {
// Convert each byte to a two-character hex string
sprintf(&hexString[i * 2], "%02x", byteArray[i]);
}
// Null-terminate the string
hexString[byteLen * 2] = '\0';
return hexString;
}
void hmacSign(const byte* message, size_t msgLen, const char* pskHex, byte* signature) {
size_t len = strlen(pskHex) / 2; // This will drop the last character if the string has an odd length
if (len > HMAC_KEY_SIZE) {
Serial.println(F("PSK too long!"));
return;
}
unsigned char pskByteArray[len];
hexStringToByteArray(pskHex, pskByteArray, len);
SHA256HMAC hmac(pskByteArray, len);
hmac.doUpdate(message, msgLen);
hmac.doFinal(signature);
}
bool hmacVerify(const byte* message, size_t msgLen, const char* pskHex, const byte* signature) {
byte sigCalculated[SHA256HMAC_SIZE];
hmacSign(message, msgLen, pskHex, sigCalculated);
//Serial.print(F("Calculated: "));
//printByteArray(sigCalculated, SHA256HMAC_SIZE);
if (memcmp(sigCalculated, signature, SHA256HMAC_SIZE) != 0) {
Serial.println(F("HMAC verification failed!"));
Serial.print(F("Expected: "));
printByteArray(signature, SHA256HMAC_SIZE);
return false;
}
Serial.println(F("HMAC verification successful!"));
return true;
}
#define WLED_HMAC_TEST_PW "guessihadthekeyafterall"
#define WLED_HMAC_TEST_PSK "a6f8488da62c5888d7f640276676e78da8639faf0495110b43e226b35ac37a4c"
uint8_t verifyHmacFromJsonString0Term(byte* jsonStr, size_t len) {
// Zero-terminate the JSON string (replace the last character, usually '}', with a null terminator temporarily)
byte lastChar = jsonStr[len-1];
jsonStr[len-1] = '\0';
uint8_t result = verifyHmacFromJsonStr((const char*)jsonStr, len);
jsonStr[len-1] = lastChar;
return result;
}
uint8_t verifyHmacFromJsonStr(const char* jsonStr, uint32_t maxLen) {
// Extract the signature from the JSON string
size_t jsonLen = strlen(jsonStr);
Serial.print(F("Length: "));
Serial.println(jsonLen);
if (jsonLen > maxLen) { // memory safety
Serial.print(F("JSON string too long!"));
Serial.print(F(", max: "));
Serial.println(maxLen);
return ERR_HMAC_GEN;
}
Serial.print(F("Received JSON: "));
Serial.println(jsonStr);
const char* macPos = strstr(jsonStr, "\"mac\":\"");
if (macPos == nullptr) {
Serial.println(F("No MAC found in JSON."));
return ERR_HMAC_MISS;
}
StaticJsonDocument<128> macDoc;
DeserializationError error = deserializeJson(macDoc, macPos +6);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
return ERR_HMAC_GEN;
}
const char* mac = macDoc.as<const char*>();
if (mac == nullptr) {
Serial.println(F("Failed MAC JSON."));
return ERR_HMAC_GEN;
}
Serial.print(F("Received MAC: "));
Serial.println(mac);
// extract the message object from the JSON string
char* msgPos = strstr(jsonStr, "\"msg\":");
char* objStart = strchr(msgPos + 6, '{');
if (objStart == nullptr) {
Serial.println(F("Couldn't find msg object start."));
return ERR_HMAC_GEN;
}
size_t maxObjLen = jsonLen - (objStart - jsonStr);
Serial.print(F("Max object length: ")); Serial.println(maxObjLen);
int32_t objDepth = 0;
char* objEnd = nullptr;
for (size_t i = 0; i < maxObjLen; i++) {
Serial.write(objStart[i]);
if (objStart[i] == '{') objDepth++;
if (objStart[i] == '}') objDepth--;
if (objDepth == 0) {
Serial.print(F("Found msg object end: "));
Serial.println(i);
objEnd = objStart + i;
break;
}
}
if (objEnd == nullptr) {
Serial.println(F("Couldn't find msg object end."));
return ERR_HMAC_GEN;
}
// get nonce (note: the nonce implementation uses "nc" for the key instead of "n" to avoid conflicts with segment names)
const char* noncePos = strstr(objStart, "\"nc\":");
if (noncePos == nullptr || noncePos > objEnd) {
// note that it is critical to check that the nonce is within the "msg" object and thus authenticated
Serial.println(F("No nonce found in msg."));
return ERR_HMAC_GEN;
}
// Convert the MAC from hex string to byte array
size_t len = strlen(mac) / 2; // This will drop the last character if the string has an odd length
if (len != SHA256HMAC_SIZE) {
Serial.println(F("Received MAC not expected size!"));
return ERR_HMAC_GEN;
}
unsigned char macByteArray[len];
hexStringToByteArray(mac, macByteArray, len);
// Calculate the HMAC of the message object
if (!hmacVerify((const byte*)objStart, objEnd - objStart + 1, WLED_HMAC_TEST_PSK, macByteArray)) {
return ERR_HMAC;
}
// Nonce verification (Replay attack prevention)
{
StaticJsonDocument<128> nonceDoc;
DeserializationError error = deserializeJson(nonceDoc, noncePos +5);
if (error) {
Serial.print(F("deser nc failed: "));
Serial.println(error.c_str());
return ERR_HMAC_GEN;
}
JsonObject nonceObj = nonceDoc.as<JsonObject>();
if (nonceObj.isNull()) {
Serial.println(F("Failed nonce JSON."));
return ERR_HMAC_GEN;
}
const char* sessionId = nonceObj["sid"];
if (sessionId == nullptr) {
Serial.println(F("No session ID found in nonce."));
return ERR_HMAC_GEN;
}
uint32_t counter = nonceObj["c"] | 0;
if (counter == 0) {
Serial.println(F("No counter found in nonce."));
return ERR_HMAC_GEN;
}
if (counter > UINT32_MAX - 100) {
Serial.println(F("Counter too large."));
return ERR_NONCE;
}
byte sidBytes[SESSION_ID_SIZE] = {};
hexStringToByteArray(sessionId, sidBytes, SESSION_ID_SIZE);
uint8_t nonceResult = verifyNonce(sidBytes, counter);
return nonceResult ? nonceResult : ERR_NONE;
}
}
bool hmacTest() {
Serial.println(F("Testing HMAC..."));
unsigned long start = millis();
const char message[] = "Hello, World!";
const char psk[] = "d0c0ffeedeadbeef";
byte mac[SHA256HMAC_SIZE];
hmacSign((const byte*)message, strlen(message), psk, mac);
Serial.print(F("Took "));
Serial.print(millis() - start);
Serial.println(F("ms to sign message."));
Serial.print(F("MAC: "));
printByteArray(mac, SHA256HMAC_SIZE);
start = millis();
bool result = hmacVerify((const byte*)message, strlen(message), psk, mac);
Serial.print(F("Took "));
Serial.print(millis() - start);
Serial.println(F("ms to verify MAC."));
return result;
}
void printDuration(unsigned long start) {
unsigned long end = millis();
Serial.print(F("Took "));
Serial.print(end - start);
Serial.println(F(" ms."));
yield();
}
#define HMAC_BENCH_ITERATIONS 100
void hmacBenchmark(const char* message) {
Serial.print(F("Starting HMAC benchmark with message length:"));
Serial.println(strlen(message));
Serial.println(F("100 iterations signing message."));
unsigned long start = millis();
byte mac[SHA256HMAC_SIZE];
for (int i = 0; i < HMAC_BENCH_ITERATIONS; i++) {
hmacSign((const byte*)message, strlen(message), WLED_HMAC_TEST_PSK, mac);
}
printDuration(start);
Serial.println(F("100 iterations verifying message."));
start = millis();
for (int i = 0; i < HMAC_BENCH_ITERATIONS; i++) {
hmacVerify((const byte*)message, strlen(message), WLED_HMAC_TEST_PSK, mac);
}
printDuration(start);
}

View File

@@ -214,10 +214,6 @@ function loadSkinCSS(cId)
}
}
var useSRA = false;
var sraWindow = null;
var sraOrigin = '';
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
@@ -247,13 +243,6 @@ function onLoad()
var sett = localStorage.getItem('wledUiCfg');
if (sett) cfg = mergeDeep(cfg, JSON.parse(sett));
if (window.opener) {
// can't get opener origin due to cross-origin browser policy
//var openerOrigin = window.opener.location.origin;
//console.log("WLED-UI opener origin: " + openerOrigin);
window.opener.postMessage('{"wled-ui":"onload"}', '*'); //openerOrigin);
}
tooltip();
resetPUtil();
initFilters();
@@ -312,28 +301,6 @@ function onLoad()
});
}
function handleWindowMessageEvent(event) {
console.log(`Received message: ${event.data}`);
console.log(`origin: ${event.origin}`);
try {
var json = JSON.parse(event.data)
} catch (e) {
console.log(`Error parsing JSON: ${e}`);
return;
}
if (json['wled-rc'] === 'ready') {
useSRA = true;
sraWindow = event.source;
sraOrigin = event.origin;
} else if (json['wled-rc'] === 'hmac') {
console.log(`Received HMAC: ${json['mac']}`);
// Pass the message containing the HMAC to the ESP
requestJson(json);
}
}
onmessage = (event) => { handleWindowMessageEvent(event) };
function updateTablinks(tabI)
{
var tablinks = gEBCN("tablinks");
@@ -713,12 +680,6 @@ function parseInfo(i) {
} else {
gId("filter2D").classList.remove('hide');
}
if (useSRA && i.sid) {
if (sraWindow) {
sraWindow.postMessage(JSON.stringify({"wled-ui":"sid","sid":i.sid}), sraOrigin);
}
}
// if (i.noaudio) {
// gId("filterVol").classList.add("hide");
// gId("filterFreq").classList.add("hide");
@@ -1443,23 +1404,6 @@ function makeWS() {
if (isInfo) populateInfo(i);
} else
i = lastinfo;
if (json.error) {
if (json.error == 42) {
showToast('HMAC verification failed! Please make sure you used the right password!', true);
return;
} else if (json.error == 43) {
showToast("This light's control is password protected. Please access it through rc.wled.me", true);
return;
} else if (json.error == 41) {
showToast('Replayed message detected!', true);
return;
} else if (json.error == 40) {
showToast('Invalid nonce', true);
return;
}
showToast(json.error, true);
return;
}
var s = json.state ? json.state : json;
displayRover(i, s);
readState(s);
@@ -1759,12 +1703,6 @@ function requestJson(command=null)
if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes
};
if (command && useSRA && !command['mac']) { // secure remote access integration, need to get HMAC from rc.wled.me
// if we already have a command including a MAC, we are good to go
sraWindow.postMessage(JSON.stringify({"wled-ui":"hmac-req", "msg":command}), sraOrigin);
return; // TODO need a sort of pending indicator
}
if (useWs) {
ws.send(req?req:'{"v":true}');
return;

View File

@@ -67,7 +67,7 @@
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
//console.info("Peek uses top WS");
ws.send('{"lv":true}');
ws.send("{'lv':true}");
} else {
//console.info("Peek WS opening");
let l = window.location;
@@ -80,7 +80,7 @@
ws = new WebSocket(url+"/ws");
ws.onopen = function () {
//console.info("Peek WS open");
ws.send('{"lv":true}');
ws.send("{'lv':true}");
}
}
ws.binaryType = "arraybuffer";

View File

@@ -31,7 +31,7 @@
ws = top.window.ws;
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send('{"lv":true}');
ws.send("{'lv':true}");
} else {
let l = window.location;
let pathn = l.pathname;
@@ -42,7 +42,7 @@
}
ws = new WebSocket(url+"/ws");
ws.onopen = ()=>{
ws.send('{"lv":true}');
ws.send("{'lv':true}");
}
}
ws.binaryType = "arraybuffer";

View File

@@ -95,16 +95,6 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb);
//crypto.cpp
void addSessionId(byte* sid);
char* byteArrayToHexString(char* hexString, const byte* byteArray, size_t byteLen);
void hmacSign(const byte* message, size_t msgLen, const char* pskHex, byte* signature);
bool hmacVerify(const byte* message, size_t msgLen, const char* pskHex, const byte* signature);
uint8_t verifyHmacFromJsonStr(const char* jsonStr, uint32_t maxLen);
uint8_t verifyHmacFromJsonString0Term(byte* jsonStr, size_t len);
bool hmacTest();
void hmacBenchmark(const char* message);
//dmx.cpp
void initDMX();
void handleDMX();
@@ -312,7 +302,7 @@ class Usermod {
virtual bool handleButton(uint8_t b) { return false; } // button overrides are possible here
virtual bool getUMData(um_data_t **data) { if (data) *data = nullptr; return false; }; // usermod data exchange [see examples for audio effects]
virtual void connected() {} // called when WiFi is (re)connected
virtual void appendConfigData(Print& settingsScript); // helper function called from usermod settings page to add metadata for entry fields
virtual void appendConfigData() {} // helper function called from usermod settings page to add metadata for entry fields
virtual void addToJsonState(JsonObject& obj) {} // add JSON objects for WLED state
virtual void addToJsonInfo(JsonObject& obj) {} // add JSON objects for UI Info page
virtual void readFromJsonState(JsonObject& obj) {} // process JSON messages received from web server
@@ -324,16 +314,6 @@ class Usermod {
virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update
virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
// API shims
private:
static Print* oappend_shim;
// old form of appendConfigData; called by default appendConfigData(Print&) with oappend_shim set up
// private so it is not accidentally invoked except via Usermod::appendConfigData(Print&)
virtual void appendConfigData() {}
protected:
// Shim for oappend(), which used to exist in utils.cpp
template<typename T> static inline void oappend(const T& t) { oappend_shim->print(t); };
};
class UsermodManager {
@@ -348,7 +328,7 @@ class UsermodManager {
static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods
static void setup();
static void connected();
static void appendConfigData(Print&);
static void appendConfigData();
static void addToJsonState(JsonObject& obj);
static void addToJsonInfo(JsonObject& obj);
static void readFromJsonState(JsonObject& obj);
@@ -382,11 +362,10 @@ void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255);
bool getBoolVal(JsonVariant elem, bool dflt);
bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255);
size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, const char* val);
size_t printSetFormIndex(Print& settingsScript, const char* key, int index);
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val);
bool oappend(const char* txt); // append new c string to temp buffer efficiently
bool oappendi(int i); // append new number to temp buffer efficiently
void sappend(char stype, const char* key, int val);
void sappends(char stype, const char* key, char* val);
void prepareHostname(char* hostname);
bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255);
@@ -462,10 +441,10 @@ void serveSettingsJS(AsyncWebServerRequest* request);
//ws.cpp
void handleWs();
void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
void sendDataWs(AsyncWebSocketClient * client = nullptr, bool initialConnection = false);
void sendDataWs(AsyncWebSocketClient * client = nullptr);
//xml.cpp
void XML_response(Print& dest);
void getSettingsJS(byte subPage, Print& dest);
void XML_response(AsyncWebServerRequest *request, char* dest = nullptr);
void getSettingsJS(byte subPage, char* dest);
#endif

View File

@@ -433,7 +433,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
}
#endif
if(WLED_FS.exists(path) || WLED_FS.exists(path + ".gz")) {
request->send(request->beginResponse(WLED_FS, path, {}, request->hasArg(F("download")), {}));
request->send(WLED_FS, path, String(), request->hasArg(F("download")));
return true;
}
return false;

View File

@@ -124,32 +124,6 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
payloadStr = nullptr;
}
// Print adapter for flat buffers
namespace {
class bufferPrint : public Print {
char* _buf;
size_t _size, _offset;
public:
bufferPrint(char* buf, size_t size) : _buf(buf), _size(size), _offset(0) {};
size_t write(const uint8_t *buffer, size_t size) {
size = std::min(size, _size - _offset);
memcpy(_buf + _offset, buffer, size);
_offset += size;
return size;
}
size_t write(uint8_t c) {
return this->write(&c, 1);
}
char* data() const { return _buf; }
size_t size() const { return _offset; }
size_t capacity() const { return _size; }
};
}; // anonymous namespace
void publishMqtt()
{
@@ -174,13 +148,11 @@ void publishMqtt()
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
// TODO: use a DynamicBufferList. Requires a list-read-capable MQTT client API.
DynamicBuffer buf(1024);
bufferPrint pbuf(buf.data(), buf.size());
XML_response(pbuf);
char apires[1024]; // allocating 1024 bytes from stack can be risky
XML_response(nullptr, apires);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263)
mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263)
#endif
}

View File

@@ -1191,11 +1191,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
// internal call, does not send XML response
pos = req.indexOf(F("IN"));
if (pos < 1) {
auto response = request->beginResponseStream("text/xml");
XML_response(*response);
request->send(response);
}
if (pos < 1) XML_response(request);
return true;
}

View File

@@ -974,8 +974,10 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
DEBUG_PRINTLN();
#endif
#ifndef WLED_DISABLE_ESPNOW
// usermods hook can override processing
if (UsermodManager::onEspNowMessage(address, data, len)) return;
#endif
// handle WiZ Mote data
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {

View File

@@ -8,7 +8,7 @@ void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++
void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); }
void UsermodManager::loop() { for (unsigned i = 0; i < numMods; i++) ums[i]->loop(); }
void UsermodManager::handleOverlayDraw() { for (unsigned i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); }
void UsermodManager::appendConfigData(Print& dest) { for (unsigned i = 0; i < numMods; i++) ums[i]->appendConfigData(dest); }
void UsermodManager::appendConfigData() { for (unsigned i = 0; i < numMods; i++) ums[i]->appendConfigData(); }
bool UsermodManager::handleButton(uint8_t b) {
bool overrideIO = false;
for (unsigned i = 0; i < numMods; i++) {
@@ -71,13 +71,3 @@ bool UsermodManager::add(Usermod* um)
Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr};
byte UsermodManager::numMods = 0;
/* Usermod v2 interface shim for oappend */
Print* Usermod::oappend_shim = nullptr;
void Usermod::appendConfigData(Print& settingsScript) {
assert(!oappend_shim);
oappend_shim = &settingsScript;
this->appendConfigData();
oappend_shim = nullptr;
}

View File

@@ -198,6 +198,10 @@
#include "../usermods/pwm_outputs/usermod_pwm_outputs.h"
#endif
#ifdef USERMOD_POWER_AP
#include "../usermods/powerap/powerap.h"
#endif
#ifdef USERMOD_HTTP_PULL_LIGHT_CONTROL
#include "../usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.h"
#endif
@@ -419,6 +423,10 @@ void registerUsermods()
UsermodManager::add(new ShtUsermod());
#endif
#ifdef USERMOD_POWER_AP
usermods.add(new PowerAPUsermod());
#endif
#ifdef USERMOD_ANIMARTRIX
UsermodManager::add(new AnimartrixUsermod("Animartrix", false));
#endif

View File

@@ -87,28 +87,88 @@ bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv
return true;
}
static size_t printSetFormInput(Print& settingsScript, const char* key, const char* selector, int value) {
return settingsScript.printf_P(PSTR("d.Sf.%s.%s=%d;"), key, selector, value);
//append a numeric setting to string buffer
void sappend(char stype, const char* key, int val)
{
char ds[] = "d.Sf.";
switch(stype)
{
case 'c': //checkbox
oappend(ds);
oappend(key);
oappend(".checked=");
oappendi(val);
oappend(";");
break;
case 'v': //numeric
oappend(ds);
oappend(key);
oappend(".value=");
oappendi(val);
oappend(";");
break;
case 'i': //selectedIndex
oappend(ds);
oappend(key);
oappend(SET_F(".selectedIndex="));
oappendi(val);
oappend(";");
break;
}
}
size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val) {
return printSetFormInput(settingsScript, key, PSTR("checked"), val);
}
size_t printSetFormValue(Print& settingsScript, const char* key, int val) {
return printSetFormInput(settingsScript, key, PSTR("value"), val);
}
size_t printSetFormIndex(Print& settingsScript, const char* key, int index) {
return printSetFormInput(settingsScript, key, PSTR("selectedIndex"), index);
//append a string setting to buffer
void sappends(char stype, const char* key, char* val)
{
switch(stype)
{
case 's': {//string (we can interpret val as char*)
String buf = val;
//convert "%" to "%%" to make EspAsyncWebServer happy
//buf.replace("%","%%");
oappend("d.Sf.");
oappend(key);
oappend(".value=\"");
oappend(buf.c_str());
oappend("\";");
break;}
case 'm': //message
oappend(SET_F("d.getElementsByClassName"));
oappend(key);
oappend(SET_F(".innerHTML=\""));
oappend(val);
oappend("\";");
break;
}
}
size_t printSetFormValue(Print& settingsScript, const char* key, const char* val) {
return settingsScript.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val);
bool oappendi(int i)
{
char s[12]; // 32bit signed number can have 10 digits plus - sign
sprintf(s, "%d", i);
return oappend(s);
}
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val) {
return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val);
}
bool oappend(const char* txt)
{
unsigned len = strlen(txt);
if ((obuf == nullptr) || (olen + len >= SETTINGS_STACK_BUF_SIZE)) { // sanity checks
#ifdef WLED_DEBUG
DEBUG_PRINT(F("oappend() buffer overflow. Cannot append "));
DEBUG_PRINT(len); DEBUG_PRINT(F(" bytes \t\""));
DEBUG_PRINT(txt); DEBUG_PRINTLN(F("\""));
#endif
return false; // buffer full
}
strcpy(obuf + olen, txt);
olen += len;
return true;
}
void prepareHostname(char* hostname)

View File

@@ -359,13 +359,6 @@ void WLED::setup()
#if !defined(WLED_DEBUG) && defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DEBUG_HOST) && ARDUINO_USB_CDC_ON_BOOT
Serial.setDebugOutput(false); // switch off kernel messages when using USBCDC
#endif
{
//hmacTest();
//const char testMsg[] = "WLED HMAC test!!";
//hmacBenchmark(testMsg);
//const char longMsg[] = "LoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIps";
//hmacBenchmark(longMsg);
}
DEBUG_PRINTLN();
DEBUG_PRINTF_P(PSTR("---WLED %s %u INIT---\n"), versionString, VERSION);
DEBUG_PRINTLN();

View File

@@ -839,6 +839,10 @@ WLED_GLOBAL time_t sunrise _INIT(0);
WLED_GLOBAL time_t sunset _INIT(0);
WLED_GLOBAL Toki toki _INIT(Toki());
// Temp buffer
WLED_GLOBAL char* obuf;
WLED_GLOBAL uint16_t olen _INIT(0);
// General filesystem
WLED_GLOBAL size_t fsBytesUsed _INIT(0);
WLED_GLOBAL size_t fsBytesTotal _INIT(0);

View File

@@ -287,50 +287,18 @@ void initServer()
bool verboseResponse = false;
bool isConfig = false;
Serial.println("JSON request");
Serial.println((const char*)request->_tempObject);
if (!verifyHmacFromJsonString0Term((byte*)request->_tempObject, request->contentLength())) {
//releaseJSONBufferLock();
serveJsonError(request, 401, ERR_DENIED);
return;
}
if (!requestJSONBufferLock(14)) {
serveJsonError(request, 503, ERR_NOBUF);
return;
}
DeserializationError error = deserializeJson(*pDoc, (uint8_t*)(request->_tempObject));
// // if enabled, calculate HMAC and verify it
// Serial.println(F("HMAC verification"));
// Serial.write((const char*)request->_tempObject, request->contentLength());
// // actually we need to verify the HMAC of the nested "msg" object
// if (strlen((const char*)request->_tempObject) > request->contentLength()) {
// Serial.println(F("HMAC verification failed: content is not null-terminated"));
// releaseJSONBufferLock();
// serveJsonError(request, 400, ERR_JSON);
// return;
// }
// // find the "msg" object in JSON
// char * msgPtr = strstr((const char*)request->_tempObject, "\"msg\":");
// if (msgPtr == NULL) {
// Serial.println(F("HMAC verification failed: no \"msg\" object found"));
// releaseJSONBufferLock();
// serveJsonError(request, 400, ERR_JSON);
// return;
// }
// char * objStart = strchr(msgPtr, '{');
JsonObject root = pDoc->as<JsonObject>();
if (error || root.isNull()) {
releaseJSONBufferLock();
serveJsonError(request, 400, ERR_JSON);
return;
}
// old 4-digit pin logic for settings authentication (no transport encryption)
if (root.containsKey("pin")) checkSettingsPIN(root["pin"].as<const char*>());
const String& url = request->url();
@@ -343,11 +311,7 @@ void initServer()
DEBUG_PRINTLN();
#endif
*/
if (root.containsKey("msg")) {
verboseResponse = deserializeState(root["msg"]);
} else {
verboseResponse = deserializeState(root);
}
verboseResponse = deserializeState(root);
} else {
if (!correctPIN && strlen(settingsPIN)>0) {
releaseJSONBufferLock();
@@ -556,23 +520,27 @@ void serveSettingsJS(AsyncWebServerRequest* request)
handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length);
return;
}
char buf[SETTINGS_STACK_BUF_SIZE+37];
buf[0] = 0;
byte subPage = request->arg(F("p")).toInt();
if (subPage > 10) {
request->send_P(501, FPSTR(CONTENT_TYPE_JAVASCRIPT), PSTR("alert('Settings for this request are not implemented.');"));
strcpy_P(buf, PSTR("alert('Settings for this request are not implemented.');"));
request->send(501, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf);
return;
}
if (subPage > 0 && !correctPIN && strlen(settingsPIN)>0) {
request->send_P(401, FPSTR(CONTENT_TYPE_JAVASCRIPT), PSTR("alert('PIN incorrect.');"));
strcpy_P(buf, PSTR("alert('PIN incorrect.');"));
request->send(401, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf);
return;
}
strcat_P(buf,PSTR("function GetV(){var d=document;"));
getSettingsJS(subPage, buf+strlen(buf)); // this may overflow by 35bytes!!!
strcat_P(buf,PSTR("}"));
AsyncResponseStream *response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JAVASCRIPT));
AsyncWebServerResponse *response;
response = request->beginResponse(200, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf);
response->addHeader(F("Cache-Control"), F("no-store"));
response->addHeader(F("Expires"), F("0"));
response->print(F("function GetV(){var d=document;"));
getSettingsJS(subPage, *response);
response->print(F("}"));
request->send(response);
}

View File

@@ -11,28 +11,12 @@ unsigned long wsLastLiveTime = 0;
#define WS_LIVE_INTERVAL 40
void sendWsError(AsyncWebSocketClient * client, uint8_t error)
{
if (!ws.count()) return;
char errorStr[16];
strcpy_P(errorStr, PSTR("{\"error\":"));
strcpy(errorStr + 9, itoa(error, errorStr + 9, 10));
strcat(errorStr + 10, "}");
if (client) {
client->text(errorStr); // ERR_NOBUF
} else {
ws.textAll(errorStr); // ERR_NOBUF
}
}
void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)
{
if(type == WS_EVT_CONNECT){
//client connected
DEBUG_PRINTLN(F("WS client connected."));
sendDataWs(client, true);
sendDataWs(client);
} else if(type == WS_EVT_DISCONNECT){
//client disconnected
if (client->id() == wsLiveClientId) wsLiveClientId = 0;
@@ -52,48 +36,27 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
bool verboseResponse = false;
Serial.print(F("WS message: "));
Serial.write(data, len);
Serial.println();
if (len < 11 && memcmp(data, "{\"v\":true}", 10) == 0) {
// if the received value is just "{"v":true}", send only to this client
verboseResponse = true;
Serial.println(F("Simple state query."));
} else if (len < 13 && memcmp(data, "{\"lv\":", 6) == 0) {
wsLiveClientId = data[6] == 't' ? client->id() : 0;
} else {
// check HMAC, must do before parsing JSON as that modifies "data" to store strings
uint8_t hmacVerificationResult = verifyHmacFromJsonString0Term(data, len);
if (hmacVerificationResult != ERR_NONE) {
sendWsError(client, hmacVerificationResult);
return;
}
if (!requestJSONBufferLock(11)) {
sendWsError(client, 3); // ERR_NOBUF
return;
}
Serial.print(F("deser input: "));
Serial.write(data, len);
Serial.println();
DeserializationError error = deserializeJson(*pDoc, data, len);
JsonObject root = pDoc->as<JsonObject>();
if (error || root.isNull()) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
//Serial.println(F("WS JSON parse F!"));
sendWsError(client, 9); // ERR_JSON
releaseJSONBufferLock();
return;
}
verboseResponse = deserializeState(root["msg"]);
releaseJSONBufferLock();
if (!requestJSONBufferLock(11)) {
client->text(F("{\"error\":3}")); // ERR_NOBUF
return;
}
DeserializationError error = deserializeJson(*pDoc, data, len);
JsonObject root = pDoc->as<JsonObject>();
if (error || root.isNull()) {
releaseJSONBufferLock();
return;
}
if (root["v"] && root.size() == 1) {
//if the received value is just "{"v":true}", send only to this client
verboseResponse = true;
} else if (root.containsKey("lv")) {
wsLiveClientId = root["lv"] ? client->id() : 0;
} else {
verboseResponse = deserializeState(root);
}
releaseJSONBufferLock();
if (!interfaceUpdateCallMode) { // individual client response only needed if no WS broadcast soon
if (verboseResponse) {
sendDataWs(client);
@@ -119,7 +82,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if((info->index + len) == info->len){
if(info->final){
if(info->message_opcode == WS_TEXT) {
sendWsError(client, 9); // ERR_JSON we do not handle split packets right now
client->text(F("{\"error\":9}")); // ERR_JSON we do not handle split packets right now
}
}
}
@@ -138,7 +101,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
}
void sendDataWs(AsyncWebSocketClient * client, bool initialConnection)
void sendDataWs(AsyncWebSocketClient * client)
{
if (!ws.count()) return;
@@ -157,16 +120,6 @@ void sendDataWs(AsyncWebSocketClient * client, bool initialConnection)
JsonObject info = pDoc->createNestedObject("info");
serializeInfo(info);
if (initialConnection) {
char sid[SESSION_ID_SIZE*2+1] = {};
byte sidBytes[SESSION_ID_SIZE] = {};
addSessionId(sidBytes);
byteArrayToHexString(sid, sidBytes, SESSION_ID_SIZE);
Serial.print(F("New session ID: "));
Serial.println(sid);
info["sid"] = sid;
}
size_t len = measureJson(*pDoc);
DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len);

File diff suppressed because it is too large Load Diff