Skip to content

Add JSON data handling over ESP-NOW in espNowReceiveCB#5161

Open
spectrenoir06 wants to merge 8 commits intowled:mainfrom
spectrenoir06:main
Open

Add JSON data handling over ESP-NOW in espNowReceiveCB#5161
spectrenoir06 wants to merge 8 commits intowled:mainfrom
spectrenoir06:main

Conversation

@spectrenoir06
Copy link
Contributor

@spectrenoir06 spectrenoir06 commented Dec 6, 2025

I'm building a custom remote and I want to control WLED with it.
The WIZMote packet works great, but it isn’t easily customizable.

Add support for sending JSON API commands over ESP-NOW.
Packet size limits (250B v1 / 1470B v2) are sufficient for most cases, with optional fragmentation for larger commands.
Packets can be broadcast on multiple channels without duplicate processing.

For security, JSON parsing only happens when the sender (the remote) is present in the trusted device list

Summary by CodeRabbit

  • New Features
    • Added ESP-NOW JSON Handler Usermod to receive and process fragmented JSON messages over ESP-NOW. Features automatic fragment reassembly, message deduplication, and direct application of JSON payloads to WLED state. Enables remote control via ESP-NOW protocol. Requires ESP-NOW support to be enabled and sender MAC addresses to be linked as remotes.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2025

Walkthrough

Introduces a new ESP-NOW JSON Handler Usermod that reassembles fragmented JSON payloads received via ESP-NOW protocol, implements message deduplication, and applies deserialized JSON to WLED state. Includes module implementation, configuration file, documentation, and a new usermod identifier constant.

Changes

Cohort / File(s) Summary
ESP-NOW JSON Handler Implementation
usermods/espnow_json_handler/espnow_json_handler.cpp
New usermod class with fragment reassembly state management, 3-byte header parsing (messageId, fragmentIndex, totalFragments), message deduplication via lastProcessedMsgId tracking, 5-second reassembly timeout, dynamic buffer allocation, and JSON deserialization with state application via deserializeState(). Registers static instance with REGISTER_USERMOD.
Usermod Configuration & Documentation
usermods/espnow_json_handler/library.json, usermods/espnow_json_handler/readme.md
Library manifest defining build and dependency metadata; README documenting fragment header structure, usage workflow, compilation instructions, and prerequisites for ESP-NOW and sender MAC linking.
Core Constants
wled00/const.h
Adds USERMOD_ID_ESPNOW_JSON identifier constant with value 59 to usermod ID registry.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • PR #4859: Modifies the Usermod interface onEspNowMessage callback API, potentially conflicting with this new usermod's onEspNowMessage override implementation.

Suggested reviewers

  • blazoncek
  • netmindz
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes a narrower scope (adding JSON handling in espNowReceiveCB) but the actual changeset implements a new usermod (EspNowJsonHandler) for fragment reassembly, deduplication, and JSON processing over ESP-NOW. Revise the title to reflect the actual implementation: e.g., 'Add ESP-NOW JSON Handler usermod for fragmented message reassembly' to accurately represent the new usermod architecture.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
wled00/udp.cpp (2)

956-956: Add defensive length check before accessing data[0].

While the callback is unlikely to be invoked with len == 0, add an explicit check for defensive programming consistency with similar checks elsewhere in this file (e.g., lines 502, 605).

Apply this diff:

   // handle JSON data over ESP-NOW
-  if (data[0] == '{') {
+  if (len > 0 && data[0] == '{') {
     if (requestJSONBufferLock(18)) {

955-966: Consider adding debug output for JSON handling.

For consistency with the WiZ Mote handler (line 928 area) and to aid troubleshooting, consider adding debug output when JSON is processed.

Example addition:

  // handle JSON data over ESP-NOW
  if (len > 0 && data[0] == '{') {
+   DEBUG_PRINTLN(F("ESP-NOW: Processing JSON"));
    if (requestJSONBufferLock(18)) {
      DeserializationError error = deserializeJson(*pDoc, data, len);
      JsonObject root = pDoc->as<JsonObject>();
      if (!error && !root.isNull()) {
        deserializeState(root);
+     } else {
+       DEBUG_PRINTLN(F("ESP-NOW: JSON parse failed"));
      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc5b504 and f2797c0.

📒 Files selected for processing (1)
  • wled00/udp.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.cpp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for C++ source files (.cpp)

Files:

  • wled00/udp.cpp
🧠 Learnings (3)
📓 Common learnings
Learnt from: willmmiles
Repo: wled/WLED PR: 4859
File: usermods/udp_name_sync/udp_name_sync.cpp:62-70
Timestamp: 2025-08-21T00:00:17.196Z
Learning: In WLED codebase, UDP packet processing code must never rely on null termination being present on the wire and must always validate packet size first. The codebase already has some vulnerabilities where malformed data can crash the software, so avoiding introduction of new ones is a priority.
Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.
Learnt from: ksedgwic
Repo: wled/WLED PR: 4883
File: usermods/usermod_v2_skystrip/rest_json_client.h:6-14
Timestamp: 2025-08-29T00:26:15.808Z
Learning: WLED uses a vendored ArduinoJson library (version 6) located at "src/dependencies/json/ArduinoJson-v6.h" which is included through wled.h. Usermods should not directly include ArduinoJson headers but instead rely on wled.h for ArduinoJson symbols. The standard pattern is to include wled.h and use JsonObject, JsonArray, DynamicJsonDocument, etc. without additional includes.
Learnt from: ksedgwic
Repo: wled/WLED PR: 4883
File: usermods/usermod_v2_skystrip/rest_json_client.h:6-14
Timestamp: 2025-08-29T00:26:15.808Z
Learning: In WLED projects, ArduinoJson.h is not directly included via #include <ArduinoJson.h> - the ArduinoJson symbols are made available through the WLED build system and wled.h transitive includes, so explicitly adding #include <ArduinoJson.h> is not necessary and may not work.
📚 Learning: 2025-10-10T18:34:06.550Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

Applied to files:

  • wled00/udp.cpp
📚 Learning: 2025-08-21T00:00:17.196Z
Learnt from: willmmiles
Repo: wled/WLED PR: 4859
File: usermods/udp_name_sync/udp_name_sync.cpp:62-70
Timestamp: 2025-08-21T00:00:17.196Z
Learning: In WLED codebase, UDP packet processing code must never rely on null termination being present on the wire and must always validate packet size first. The codebase already has some vulnerabilities where malformed data can crash the software, so avoiding introduction of new ones is a priority.

Applied to files:

  • wled00/udp.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp32c3dev_qio)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32S3_wroom2)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32dev_debug)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
🔇 Additional comments (2)
wled00/udp.cpp (2)

955-967: JSON over ESP-NOW implementation follows secure pattern.

The implementation correctly:

  • Relies on the trusted sender check (lines 936-947) for security
  • Passes len to deserializeJson, avoiding null termination issues per learnings
  • Acquires the JSON buffer lock before parsing
  • Returns early to prevent double-processing

This is actually more secure than the UDP JSON API (line 654), which lacks authentication.

However, see separate comments for minor defensive improvements and design verification.


955-966: Verify that JSON packets intentionally bypass sync mode requirements.

The early return at line 965 means JSON packets bypass the checks at line 969 (broadcast flag, 'W' magic byte, useESPNowSync setting, WiFi connection state). This differs from WLED sync packets but matches the WiZ Mote pattern (lines 950-953).

Given the PR objectives (custom remote, similar to WiZMote), this is likely intentional, but please confirm this design choice is correct.

@netmindz netmindz added enhancement connectivity Issue regarding protocols, WiFi connection or availability of interfaces labels Dec 7, 2025
@DedeHai
Copy link
Collaborator

DedeHai commented Dec 21, 2025

Sounds like a good addition. What I am not too sure about is to use "{" as an indicator and the lack of suporting longer JSONs. For a simle single command there is already the way to go through a remote.json file which does exactly what you want to achieve, just not in a dynamic way.

@spectrenoir06
Copy link
Contributor Author

remote.json is to remaps a wizmote button to a commande ( on the wled side ) right ?
I like the idea to have different remote who do different thing.
image

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 22, 2025

remote.json is to remaps a wizmote button to a commande ( on the wled side ) right ?

correct, you can have up to 256 button functions to map to any JSON command.
See https://github.com/DedeHai/WLED-ESPNow-Remote for examples

@spectrenoir06
Copy link
Contributor Author

I already looked at your repo and really like it.
My remote is using the esp32-c6 ( because i want to play with zigbee / thread ), and is using 2 AAA battery ( with a power latch to have good battery )
for now both work pretty great the wizmote ( who is comment in my code ) + json
image

image

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 23, 2025

the advantage of using remote.json is you can change remote functionality without changing the firmware on the remote. Disadvantage is that you need that file on each WLED device.

@blazoncek would such an implementation clash with any of your ESPNow work? What are your thoughts on this in general?

@blazoncek
Copy link
Contributor

For purposes like this (home made ESP-NOW remote) you have usermod callback which can be used for handling custom payloads. Write a usermod and it can be included.

If you have found readily available (generic?) remote that is sending JSON payloads (and preferably WLED's JSON API) then I would request to implement split packets as well. Otherwise this is enhancement just for (one) specific use.

Be mindful that 99% of WLED users are not electronics tinkerers and they will prefer to buy off the shelf remote (like Wizmote) which don't benefit from this enhancement.

However this is just my view on the topic.

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 23, 2025

I do see some use-cases like writing custom sync sequence for art installations, though that could also be done using the remote.json. I am not opposed to adding this if its done properly. Its not much code for a feature some may find useful.

@blazoncek
Copy link
Contributor

am not opposed to adding this if its done properly

I would require split packet processing as ~250 bytes is really very little for JSON.

not much code for a feature some may find useful

I do agree.

@spectrenoir06
Copy link
Contributor Author

spectrenoir06 commented Dec 24, 2025

I'm not fan of the way UsermodManager::onEspNowMessage is done right now, because he don't check if the remote is in the whitelist.

One of the problem with the Wizmote, to send 1 pqt it send a espnow pqt to every channels.
Because the esp only listen on the same channel as the one use for the wifi.

I think be able to send a "small" pqt is important so a remote can do the same. ( spam all channel )

But yes I should also think about a way send the "cmd" in a multiple pqt ( probally only on one channel ), but both should be possible

@blazoncek
Copy link
Contributor

he don't check if the remote is in the whitelist.

You can do that in usermod.

One of the problem with the Wizmote

That is not a problem. That's a benefit.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@usermods/espnow_json_handler/espnow_json_handler.cpp`:
- Around line 12-16: The header comment incorrectly states a 4-byte fragment
header while the implementation and constant ESPNOW_FRAGMENT_HEADER_SIZE use 3
bytes; update the comment to reflect the actual 3-byte layout (Byte 0:
message_id, Byte 1: fragment_index, Byte 2: total_fragments, Byte 3+: JSON
payload) OR alternatively change ESPNOW_FRAGMENT_HEADER_SIZE and the code paths
that parse/construct the header (search for ESPNOW_FRAGMENT_HEADER_SIZE and any
code using bytes 0-2 for message_id/fragment_index/total_fragments) so they
consistently use a 4-byte header—pick one approach and make the code, constant,
and header comment consistent.
- Around line 88-93: Add a timeout for incomplete fragment reassembly by
recording a start timestamp when beginning a new message in onEspNowMessage()
(set reassemblyStartTime = millis() when allocating/initializing
reassemblyBuffer and setting lastMsgId), define a REASSEMBLY_TIMEOUT_MS
constant, and in loop() check if reassemblyBuffer is non-null and millis() -
reassemblyStartTime > REASSEMBLY_TIMEOUT_MS then call cleanupReassembly() (and
DEBUG_PRINTLN a timeout message) to free stale state; ensure cleanupReassembly()
clears reassemblyBuffer and resets reassemblyStartTime/lastMsgId appropriately.
- Around line 54-64: The onEspNowMessage handler currently accepts fragmented
JSON without validating the sender MAC, so add a whitelist check: inside
onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) (before
returning true for handled messages) verify the sender MAC against the core
linked_remotes list (or call the same core validation routine used for other
ESP-NOW handlers) and return false if the sender is not authorized; ensure you
reference the sender pointer and use the same comparison semantics/format as
linked_remotes to avoid bypassing core checks.
🧹 Nitpick comments (3)
usermods/espnow_json_handler/espnow_json_handler.cpp (3)

23-29: Inconsistent indentation within the class.

The private member declarations use extra leading whitespace compared to other sections of the class. Consider aligning indentation consistently throughout the file.

♻️ Suggested fix
 private:
-  // Fragment reassembly state
-    uint8_t lastMsgId = 0;
-    uint8_t lastProcessedMsgId = 0;
-    uint8_t fragmentsReceived = 0;
-    uint8_t* reassemblyBuffer = nullptr;
-    size_t reassemblySize = 0;
+    // Fragment reassembly state
+    uint8_t lastMsgId = 0;
+    uint8_t lastProcessedMsgId = 0;
+    uint8_t fragmentsReceived = 0;
+    uint8_t* reassemblyBuffer = nullptr;
+    size_t reassemblySize = 0;

78-86: Deduplication tracks only the most recent message ID.

The current deduplication approach using a single lastProcessedMsgId works for multi-channel broadcast from the same sender but may incorrectly filter out messages from different senders using the same ID. This is likely acceptable for the intended use case (single remote broadcasting on multiple channels) but worth documenting as a known limitation.


130-140: Consider adding a named constant for the JSON buffer lock ID.

The magic number 18 in requestJSONBufferLock(18) could be documented or defined as a constant for clarity. Based on learnings, this pattern is used throughout WLED for lock identification.

…mbly timeout handling +

use whitelist for remote
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@usermods/espnow_json_handler/espnow_json_handler.cpp`:
- Around line 26-42: The code treats 0 as a valid messageId but also uses 0 as
the initial lastProcessedMsgId, causing messageId 0 to be wrongly skipped and
leaving lastMsgId unchanged after cleanup prevents retries; change the initial
values of lastMsgId and lastProcessedMsgId to a sentinel (e.g., UINT8_MAX)
instead of 0 and update cleanupReassembly() to reset lastMsgId to that sentinel
as well; locate the variables lastMsgId, lastProcessedMsgId and the
cleanupReassembly() function and apply the sentinel initialization and reset so
messageId 0 is processed and retries restart the timeout.
- Around line 63-75: The onEspNowMessage method currently relies on the external
last_signal_src string; instead, build a local 12-char hex MAC string from the
sender parameter (e.g., using sprintf with "%02x" for sender[0]..sender[5]) and
use that local sender string when iterating linked_remotes to set knownRemote
and to log/return decisions; update comparisons that reference last_signal_src
to use your new local senderStr so the function is self-contained (symbols:
onEspNowMessage, sender parameter, linked_remotes, last_signal_src).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@usermods/espnow_json_handler/espnow_json_handler.cpp`:
- Around line 140-149: Add a hard cap on the total reassembly buffer size (e.g.,
define MAX_REASSEMBLY_SIZE = 4096 or 8192) and check it before calling realloc:
compute newSize = reassemblySize + fragmentDataSize and if newSize >
MAX_REASSEMBLY_SIZE log an error, call cleanupReassembly(), and return true to
fail gracefully; otherwise proceed with the existing realloc on reassemblyBuffer
and the +1 null terminator. Ensure the cap check is placed immediately before
the realloc in the fragment handling path so reassemblySize, fragmentDataSize,
reassemblyBuffer, and cleanupReassembly() are used as shown.
♻️ Duplicate comments (2)
usermods/espnow_json_handler/espnow_json_handler.cpp (2)

26-27: Use a sentinel value for message ID tracking.

With lastProcessedMsgId initialized to 0, a legitimate message with messageId == 0 will be incorrectly skipped as "already processed" on first reception. Initialize both to a sentinel like 0xFF and reset lastMsgId in cleanupReassembly().

🛠️ Suggested fix
-    uint8_t lastMsgId = 0;
-    uint8_t lastProcessedMsgId = 0;
+    uint8_t lastMsgId = 0xFF;
+    uint8_t lastProcessedMsgId = 0xFF;

And in cleanupReassembly():

     void cleanupReassembly() {
         if (reassemblyBuffer) {
             free(reassemblyBuffer);
             reassemblyBuffer = nullptr;
         }
+        lastMsgId = 0xFF;
         fragmentsReceived = 0;

78-89: Derive sender MAC string from the function parameter instead of relying on last_signal_src.

The whitelist check depends on the global last_signal_src being set by the caller before this callback runs. This creates a fragile coupling to implicit state. Format the sender MAC locally from the sender parameter to make the function self-contained.

🛠️ Suggested fix
     bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) override {
-    
+        // Format sender MAC for whitelist comparison
+        char senderStr[13];
+        sprintf_P(senderStr, PSTR("%02x%02x%02x%02x%02x%02x"),
+            sender[0], sender[1], sender[2], sender[3], sender[4], sender[5]);
+
         bool knownRemote = false;
         for (const auto& mac : linked_remotes) {
-            if (strlen(mac.data()) == 12 && strcmp(last_signal_src, mac.data()) == 0) {
+            if (strlen(mac.data()) == 12 && strcmp(senderStr, mac.data()) == 0) {
                 knownRemote = true;
                 break;
             }
         }
         if (!knownRemote) {
             DEBUG_PRINT(F("ESP Now Message Received from Unlinked Sender: "));
-            DEBUG_PRINTLN(last_signal_src);
+            DEBUG_PRINTLN(senderStr);
             return false; // Not handled
         }
🧹 Nitpick comments (1)
usermods/espnow_json_handler/espnow_json_handler.cpp (1)

167-177: Consider defining a named constant for the JSON buffer lock ID.

The magic number 18 in requestJSONBufferLock(18) should ideally be a named constant for clarity and maintainability. Also, when root.isNull() is true but error succeeded, the error message on line 174 would print an empty/misleading error string.

♻️ Suggested improvement
+// At file scope or in class
+static constexpr uint8_t JSON_LOCK_ESPNOW = 18;

             // Process the complete JSON message
-            if (requestJSONBufferLock(18)) {
+            if (requestJSONBufferLock(JSON_LOCK_ESPNOW)) {
                 DeserializationError error = deserializeJson(*pDoc, reassemblyBuffer, reassemblySize);
                 JsonObject root = pDoc->as<JsonObject>();
-                if (!error && !root.isNull()) {
-                    deserializeState(root);
-                }
-                else {
-                    DEBUG_PRINTF_P(PSTR("ESP-NOW JSON deserialization error: %s\n"), error.c_str());
+                if (error) {
+                    DEBUG_PRINTF_P(PSTR("ESP-NOW JSON deserialization error: %s\n"), error.c_str());
+                } else if (root.isNull()) {
+                    DEBUG_PRINTLN(F("ESP-NOW JSON: empty or non-object payload"));
+                } else {
+                    deserializeState(root);
                 }
                 releaseJSONBufferLock();
             }

Based on learnings, magic numbers should be replaced with meaningful constants when they represent specific identifiers.

Comment on lines +140 to +149
// Allocate or reallocate buffer
size_t fragmentDataSize = len - ESPNOW_FRAGMENT_HEADER_SIZE;
size_t newSize = reassemblySize + fragmentDataSize;

uint8_t* newBuffer = (uint8_t*)realloc(reassemblyBuffer, newSize + 1); // +1 for null terminator
if (!newBuffer) {
DEBUG_PRINTLN(F("ESP-NOW fragment reassembly: memory allocation failed"));
cleanupReassembly();
return true; // Handled by failing gracefully
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add an upper bound on total reassembly buffer size to prevent memory exhaustion.

A malicious or buggy sender could specify totalFragments=255 with ~250-byte payloads, causing up to ~62KB of heap allocation. On memory-constrained ESP devices, this could exhaust available heap. Add a reasonable cap (e.g., 4KB or 8KB) for total reassembled message size.

🛠️ Suggested fix

Add a constant and check:

 `#define` ESPNOW_FRAGMENT_HEADER_SIZE 3
 `#define` ESPNOW_REASSEMBLY_TIMEOUT_MS 5000
+#define ESPNOW_MAX_REASSEMBLY_SIZE 4096  // 4KB max JSON payload
         size_t fragmentDataSize = len - ESPNOW_FRAGMENT_HEADER_SIZE;
         size_t newSize = reassemblySize + fragmentDataSize;
 
+        if (newSize > ESPNOW_MAX_REASSEMBLY_SIZE) {
+            DEBUG_PRINTLN(F("ESP-NOW fragment reassembly: max size exceeded"));
+            cleanupReassembly();
+            return true; // Handled by failing gracefully
+        }
+
         uint8_t* newBuffer = (uint8_t*)realloc(reassemblyBuffer, newSize + 1);
🤖 Prompt for AI Agents
In `@usermods/espnow_json_handler/espnow_json_handler.cpp` around lines 140 - 149,
Add a hard cap on the total reassembly buffer size (e.g., define
MAX_REASSEMBLY_SIZE = 4096 or 8192) and check it before calling realloc: compute
newSize = reassemblySize + fragmentDataSize and if newSize > MAX_REASSEMBLY_SIZE
log an error, call cleanupReassembly(), and return true to fail gracefully;
otherwise proceed with the existing realloc on reassemblyBuffer and the +1 null
terminator. Ensure the cap check is placed immediately before the realloc in the
fragment handling path so reassemblySize, fragmentDataSize, reassemblyBuffer,
and cleanupReassembly() are used as shown.

@spectrenoir06
Copy link
Contributor Author

  • Moved the code into a usermod
  • Added support for receiving fragmented packets (removing the 250-byte limit)
  • Allows sending the same packet on all channels when the channel is unknown (e.g. WiZ Mote)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

connectivity Issue regarding protocols, WiFi connection or availability of interfaces enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants