Skip to content

devices fixes#175

Merged
ewowi merged 2 commits intomainfrom
devices-fixes
May 5, 2026
Merged

devices fixes#175
ewowi merged 2 commits intomainfrom
devices-fixes

Conversation

@ewowi
Copy link
Copy Markdown
Collaborator

@ewowi ewowi commented May 5, 2026

Summary by CodeRabbit

  • New Features

    • Device table now rebuilds on startup for improved discovery initialization.
  • Bug Fixes

    • Strengthened UDP discovery validation with enhanced packet verification.
    • Improved device state handling to prevent data inconsistencies.
    • Fixed device name processing to enhance stability.
  • Chores

    • Updated firmware version date.

Root cause: assigning const char* from a stack-allocated UDPMessage to a
JsonVariant stores a linked pointer (ArduinoJson v7 zero-copy policy for
const char*). When the message goes out of scope after updateDevices()
returns, _state.data holds dangling pointers. The next doc.set(_state.data)
reads garbage from the reused stack frame, corrupting all string fields and
causing the devices array to grow with duplicate garbled entries each cycle.

Fix: wrap all string fields from message structs in String() at assignment
time, forcing ArduinoJson to copy the bytes into its pool immediately.

Additional hardening applied alongside the root fix:
- Validate hostname [a-zA-Z0-9-] in updateDevices() to guard all entry
  paths (including the sendUDP self-update that bypasses receiveUDP)
- Validate hostname in receiveUDP() as defence-in-depth with logging
- Add packageSize self-describing check to reject struct-layout mismatches
- begin() override clears any persisted garbled state loaded from LittleFS
- Always use doc.set(_state.data) (remove the activeClients branch)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Warning

Rate limit exceeded

@ewowi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 12 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f4cee54f-58bf-48ed-b665-a8a152842799

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2bcff and f8b24e3.

📒 Files selected for processing (1)
  • src/MoonBase/Modules/ModuleDevices.h

Walkthrough

This PR updates the firmware build date and strengthens device discovery in ModuleDevices by rebuilding the device table on startup, ensuring JSON state is deep-copied to avoid pointer issues, and validating incoming UDP packets with stricter header and hostname checks.

Changes

Build Version Update

Layer / File(s) Summary
Build Configuration
platformio.ini
Build date macro APP_DATE incremented from 20260504 to 20260505.

Device Discovery Hardening

Layer / File(s) Summary
Initialization
src/MoonBase/Modules/ModuleDevices.h
New begin() override clears the persisted devices array, forcing a rebuild from UDP discovery on startup.
State Integrity
src/MoonBase/Modules/ModuleDevices.h
updateDevices() and updateDevicesWLED() now unconditionally deep-copy _state.data into a working document; device name fields are coerced to String(...) to prevent dangling-pointer issues during storage and comparison.
Packet Validation
src/MoonBase/Modules/ModuleDevices.h
MoonLight UDP handler validates token==255, id==1, package size matches sizeof(UDPMessage), and hostname contains only alphanumeric and hyphen characters; invalid packets logged and rejected.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • MoonModules/MoonLight#172: Modifies UDP discovery packet parsing and device-table updates; this PR adds validation and state-management improvements to the same subsystem.

Poem

🐇 A rabbit hops through devices anew,
Clears the old list when startup rings true,
Deep copies guard against pointers adrift,
While UDP checks sift the wheat from the rift—
Discovery strengthened, the network's more sound!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'devices fixes' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes made. Use a more specific title that describes the key changes, such as 'Fix device table rebuilding and UDP packet validation' or 'Improve device discovery and state handling'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devices-fixes

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
Copy Markdown

@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.

🧹 Nitpick comments (1)
src/MoonBase/Modules/ModuleDevices.h (1)

217-230: ⚡ Quick win

Extract the duplicate hostname validation into a private helper.

The identical charset loop ([a-zA-Z0-9-]) appears verbatim again in receiveUDP() (lines 373–378). A future charset change (e.g., allowing dots) must be applied in two places.

♻️ Proposed refactor — private `isValidHostname` helper

Add to the private: section (near the other helpers, around line 447+):

+ static bool isValidHostname(const char* name, size_t fieldLen) {
+   if (name[0] == '\0') return false;
+   // [a-zA-Z0-9-] only; isprint() NOT used — ESP32 newlib treats 0xA0-0xFF as printable
+   for (size_t j = 0; j < fieldLen - 1 && name[j]; j++) {
+     uint8_t c = (uint8_t)name[j];
+     if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+           (c >= '0' && c <= '9') || c == '-'))
+       return false;
+   }
+   return true;
+ }

Then in updateDevices():

-    bool nameOk = message.header.name[0] != '\0';
-    for (size_t j = 0; nameOk && j < sizeof(message.header.name) - 1 && message.header.name[j]; j++) {
-      uint8_t c = (uint8_t)message.header.name[j];
-      if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-'))
-        nameOk = false;
-    }
-    if (!nameOk) {
+    if (!isValidHostname(message.header.name, sizeof(message.header.name))) {
       EXT_LOGW(MB_TAG, "Skipping device update with invalid name from ...%d", ip[3]);
       return;
     }

And in receiveUDP() (lines 373–382):

-          bool nameOk = message.header.name[0] != '\0';
-          for (size_t j = 0; nameOk && j < sizeof(message.header.name) - 1 && message.header.name[j]; j++) {
-            uint8_t c = (uint8_t)message.header.name[j];
-            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-'))
-              nameOk = false;
-          }
-          if (nameOk)
+          if (isValidHostname(message.header.name, sizeof(message.header.name)))
             updateDevices(message, deviceUDP.remoteIP());
           else
             EXT_LOGW(MB_TAG, "Garbled name in packet from ...%d, rejecting", deviceUDP.remoteIP()[3]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/MoonBase/Modules/ModuleDevices.h` around lines 217 - 230, Extract the
duplicate hostname validation into a single private helper named isValidHostname
that accepts a const char* (or reference to the name buffer) and implements the
current logic: non-empty first char, iterate up to sizeof(name)-1 or until NUL,
and allow only [a-zA-Z0-9-]; then replace the inline loop in updateDevices() and
the identical loop in receiveUDP() to call isValidHostname(...) and preserve the
current behavior (log & return when false). Ensure the helper is declared in the
class private: section and used in both updateDevices() and receiveUDP().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/MoonBase/Modules/ModuleDevices.h`:
- Around line 217-230: Extract the duplicate hostname validation into a single
private helper named isValidHostname that accepts a const char* (or reference to
the name buffer) and implements the current logic: non-empty first char, iterate
up to sizeof(name)-1 or until NUL, and allow only [a-zA-Z0-9-]; then replace the
inline loop in updateDevices() and the identical loop in receiveUDP() to call
isValidHostname(...) and preserve the current behavior (log & return when
false). Ensure the helper is declared in the class private: section and used in
both updateDevices() and receiveUDP().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 69f6fab1-68c9-4eb5-9479-716200064716

📥 Commits

Reviewing files that changed from the base of the PR and between 5175b6f and 2f2bcff.

📒 Files selected for processing (2)
  • platformio.ini
  • src/MoonBase/Modules/ModuleDevices.h

@ewowi ewowi merged commit 7c1283b into main May 5, 2026
39 checks passed
@ewowi ewowi deleted the devices-fixes branch May 5, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant