Skip to content

Commit addff5c

Browse files
fhirschmannclaude
andcommitted
feat: guard the per-path EQ-rule store against the NVS 4 KB limit
Per-path EQ rules are serialized into a single NVS string under "eqRules". NVS caps a string value at 4000 bytes, so once enough rules were added the putString() silently failed (returned 0) and the rule was lost without any feedback. Reject an over-full save up front with HTTP 413 (body {"error":"eqRulesFull","used":N,"max":N}) while leaving the previously stored rules untouched, and surface a failed NVS write as 500. The Files tab now uses fetch() for setEqRule so it can show a clear "EQ rule storage is full" toast instead of dropping the rule silently. i18n de/en/fr, Swagger updated. Verified on device: adding rules until the cap returned 413 {"error":"eqRulesFull","used":3962,"max":3900}; the store stayed at 3853 bytes (under the limit) and the previously stored rules were intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 42d2c46 commit addff5c

7 files changed

Lines changed: 50 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ neon logo that doubles as the SVG favicon ([`7be5254`](../../commit/7be5254)):
101101

102102
| Change | Commit |
103103
| --- | --- |
104+
| **EQ-rule store overflow guard**: per-path EQ rules are serialized into a single NVS string capped at ~4 KB; adding too many used to silently fail. The device now rejects an over-full save with `HTTP 413` (and reports a failed NVS write as `500`), and the Files tab surfaces a clear *"EQ rule storage is full"* toast instead of losing the rule without feedback | [`PENDING5`](../../commit/PENDING5) |
104105
| **HTTP-sync robustness**: the file-sync manifest is now streamed straight from the network into the JSON parser (instead of buffering the whole payload in a `String` first), roughly halving peak RAM during the parse on large manifests; the cross-core sync progress message is guarded by a spinlock so the web UI can never read a half-written line | [`PENDING3`](../../commit/PENDING3) |
105106
| PWA support: web app manifest + app icon, "add to home screen" with proper icon and name | [`b4287b9`](../../commit/b4287b9) |
106107
| PWA offline fallback: a service worker serves a cyberpunk "ESPuino Offline" page (with auto-reconnect) instead of a black screen when the home-screen app is launched while the player is powered off | [`bd07a7c`](../../commit/bd07a7c) |

REST-API.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,10 @@ paths:
810810
responses:
811811
"200":
812812
description: Rule stored.
813+
"413":
814+
description: "Rule store is full (NVS ~4 KB limit). Body: {\"error\":\"eqRulesFull\",\"used\":N,\"max\":N}. Delete existing rules first."
815+
"500":
816+
description: Saving to NVS failed.
813817
delete:
814818
tags: [Fork]
815819
summary: Delete a per-path equalizer rule.

html/locales/de.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,9 @@
666666
"eqrules": {
667667
"title": "EQ-Regeln (pro Datei/Ordner)",
668668
"desc": "Übersicht der per-Pfad zugewiesenen Equalizer-Profile. Hier kannst du sie ansehen und löschen.",
669-
"empty": "Keine EQ-Regeln gesetzt."
669+
"empty": "Keine EQ-Regeln gesetzt.",
670+
"full": "Der Speicher für EQ-Regeln ist voll — lösche bestehende Regeln, bevor du neue hinzufügst.",
671+
"savefailed": "EQ-Regel konnte nicht gespeichert werden."
670672
},
671673
"backup": {
672674
"title": "Backup",

html/locales/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,9 @@
666666
"eqrules": {
667667
"title": "EQ rules (per file/folder)",
668668
"desc": "Overview of the per-path equalizer profiles. View and delete them here.",
669-
"empty": "No EQ rules set."
669+
"empty": "No EQ rules set.",
670+
"full": "EQ rule storage is full — delete some existing rules before adding new ones.",
671+
"savefailed": "Could not save the EQ rule."
670672
},
671673
"backup": {
672674
"title": "Backup",

html/locales/fr.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,9 @@
655655
"eqrules": {
656656
"title": "Règles EQ (par fichier/dossier)",
657657
"desc": "Aperçu des profils d'égaliseur attribués par chemin. Vous pouvez les consulter et les supprimer ici.",
658-
"empty": "Aucune règle EQ définie."
658+
"empty": "Aucune règle EQ définie.",
659+
"full": "Le stockage des règles EQ est plein — supprimez des règles existantes avant d'en ajouter.",
660+
"savefailed": "Impossible d'enregistrer la règle EQ."
659661
},
660662
"backup": {
661663
"title": "Sauvegarde",

html/management.html

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6845,9 +6845,25 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
68456845
function setEqRule(path, profileKey) {
68466846
const g = eqProfiles[profileKey];
68476847
if (!g) return;
6848-
postData("http://" + host + "/eqrule?path=" + encodeURIComponent(path)
6848+
// fetch (not postData) so we can surface the server's error: NVS caps the rule
6849+
// store at ~4 KB and the device rejects an over-full save with HTTP 413 instead
6850+
// of silently dropping the rule.
6851+
fetch("http://" + host + "/eqrule?path=" + encodeURIComponent(path)
68496852
+ "&low=" + g.gainLowPass + "&band=" + g.gainBandPass + "&high=" + g.gainHighPass
6850-
+ "&profile=" + encodeURIComponent(profileKey), function () { loadEqRules(function () { updateEqIndicator(); updateSelectedNodeBadges(); }); });
6853+
+ "&profile=" + encodeURIComponent(profileKey), { method: "POST" })
6854+
.then(function (resp) {
6855+
if (resp.status === 413) {
6856+
toaster.error(i18next.t("tools.eqrules.full"));
6857+
return;
6858+
}
6859+
if (!resp.ok) {
6860+
toaster.error(i18next.t("tools.eqrules.savefailed"));
6861+
return;
6862+
}
6863+
loadEqRules(function () { updateEqIndicator(); updateSelectedNodeBadges(); });
6864+
flashActionOk();
6865+
})
6866+
.catch(function () { toaster.error(i18next.t("tools.eqrules.savefailed")); });
68516867
}
68526868
// Remove the per-path EQ rule for a file/folder path.
68536869
function deleteEqRule(path) {

src/Web.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2143,6 +2143,10 @@ void handleGetSettings(AsyncWebServerRequest *request) {
21432143
request->send(response);
21442144
}
21452145

2146+
// NVS caps a single string value at 4000 bytes; stay safely below that so a near-full
2147+
// rule set still leaves room for the JSON envelope and a final entry.
2148+
static constexpr size_t EQ_RULES_MAX_BYTES = 3900;
2149+
21462150
// Return all per-path equalizer rules as the stored JSON array.
21472151
void handleGetEqRules(AsyncWebServerRequest *request) {
21482152
request->send(200, "application/json", gPrefsSettings.getString("eqRules", "[]"));
@@ -2183,7 +2187,20 @@ void handleSetEqRule(AsyncWebServerRequest *request) {
21832187

21842188
String out;
21852189
serializeJson(doc, out);
2186-
gPrefsSettings.putString("eqRules", out);
2190+
// NVS caps a single string value at 4000 bytes. If the serialized rule set would
2191+
// exceed that, putString() silently fails (returns 0) and the rule is lost without
2192+
// any feedback. Reject the save up-front with a clear error instead, leaving the
2193+
// previously stored rules untouched, and tell the UI how full the store is.
2194+
if (out.length() > EQ_RULES_MAX_BYTES) {
2195+
char err[160];
2196+
snprintf(err, sizeof(err), "{\"error\":\"eqRulesFull\",\"used\":%u,\"max\":%u}", (unsigned) out.length(), (unsigned) EQ_RULES_MAX_BYTES);
2197+
request->send(413, "application/json", err);
2198+
return;
2199+
}
2200+
if (gPrefsSettings.putString("eqRules", out) == 0) {
2201+
request->send(500, "application/json", "{\"error\":\"eqRulesSaveFailed\"}");
2202+
return;
2203+
}
21872204
AudioPlayer_ReloadEqRules();
21882205
request->send(200, "application/json", "{}");
21892206
}

0 commit comments

Comments
 (0)