platformio test discovers and runs whatever test_* directories exist, so
it never notices when test/native-suite-count drifts from the actual
directory count. That reconciliation lived only in bin/run-tests.sh, which
CI does not invoke - so a stale or unbumped count sailed through PRs
(develop itself shipped 38 dirs against a 37 file).
Add a standalone suite-count-check job to test_native.yml that mirrors
run-tests.sh's exact counting logic and fails on a mismatch. It runs on
every PR via main_matrix's test-native job, with no build step so it fails
fast. Bump native-suite-count to 38 to match the current suites.
clod helped too
* Bound remote-initiated key verification sessions
Opening a session raises a 30 second banner and a client notification and
occupies the only verification slot, all before the peer has authenticated
anything, and nothing limited how often that could happen.
Adds an absolute session cap that incoming packets cannot refresh, a
cooldown between remote-initiated sessions measured from when the previous
one ended, and refreshes the idle deadline only when the protocol actually
advances rather than on any arriving packet.
The busy path now sets ignoreRequest so it no longer answers with a NAK.
Also replaces the getTime() - 60 timeout comparison, which underflowed
before the clock passed 60.
* Use elapsed-time comparison for the session timeout and condense comments
perhapsDecode fell back to the not-yet-verified key held during a key
verification handshake for any incoming PKI unicast. That key is supplied
by whoever opened the handshake and proves only that they hold it, not that
they are the node they claim to be, so until the session ended they could
send DMs on any port that decrypted and were marked pki_encrypted.
perhapsEncode already restricts the pending key to KEY_VERIFICATION_APP.
The receive path now applies the same rule.
The packet-auth-policy change extends the DecodeState enum with DECODE_OPAQUE
and DECODE_POLICY_REJECT. test_E1_perhaps_decode_fuzz drives arbitrary
ciphertext through perhapsDecode and asserted the verdict was one of the
original three states; random ciphertext that matches no channel with no PKI
attempt now returns DECODE_OPAQUE, tripping the assertion. Broaden the check to
accept all five valid verdicts, matching the test's stated 'any verdict is
fine' contract.
The send_bell append writes bytes[size] and bytes[size+1] while the guard only checked size < DATA_PAYLOAD_LEN, overrunning the 233-byte payload array by one when size == DATA_PAYLOAD_LEN - 1. Guard on size + 1.
writeSecret is a setter, so calling it on the NETWORK_CONFIG get path was a no-op: the buffer
already holds the stored psk, never the sentinel. MQTT_CONFIG returned the broker password
verbatim.
Both get paths now return secretReserved when req.from != 0, and the matching set paths call
writeSecret so a read-modify-write round trip keeps the stored value.
* Null-check packet allocations in allocForSending and its callers
Sibling of 0ae44d701.
* Null-check allocDataProtobuf, allocAckNak and allocErrorResponse callers
Second tier of the same nullable contract.
* Keep telemetry sleep scheduling on allocation failure
Allocation failure now marks the telemetry invalid instead of returning
early, so power-saving SENSOR nodes still arm deep sleep.
setPassKey drew the session passkey outside cryptLock, unlike the signing
and key verification paths. Also replaces random() for the key verification
nonce with the hardware RNG.
* Initial plan
* fix: declare sa as const pointer in t5s3_epaper variant.cpp
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
splitConfiguredMessages capped the split loop at
CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT and then appended [Exit]
unconditionally, so messages[] could be written one past its last index
and messagesCount could exceed the array size.
A client's "regenerate keys" action sends a blank SecurityConfig carrying
only the new private key rather than the config it read from the device,
so assigning it wholesale cleared admin_key, is_managed, serial_enabled,
debug_log_api_enabled, admin_channel_enabled and packet_signature_policy.
Losing the admin keys locks the owner out of remote admin with no recourse
but a physical connection to the node.
Detect that bare-rotation shape and swap in just the keypair, leaving the
rest of the security config intact. Deliberately clearing admin keys still
works through a SET that leaves the private key alone.
Fixes#11073
- t5s3-epaper: the board's 3.3V octal (AP_3v3) PSRAM is unreliable at the qio_opi
default of 80MHz (Total PSRAM reads 0, display dead / boot-loop); clock it at 40MHz.
- EInkParallelDisplay: skip EInk init and no-op display ops when the PSRAM framebuffer
is unavailable, so the node boots headless instead of hard-faulting on a NULL buffer.
- src/platform/esp32: weak software __atomic_*_{1,2,4} so esp32s3 links on toolchains
(e.g. macOS) that lack the sized libcalls GCC emits under -mdisable-hardware-atomics.
Removed:
- MessageStore::addFromPacket and addFromString, superseded by
tryAddFromPacket
- GeoCoord rangeRadiansToMeters, distanceTo, bearingTo
- Router::rawSend, declared virtual with no override and no caller
- ContentHandler handleHotspot, handleFs, handleAdminSettings,
handleAdminSettingsApply, handleDeleteFsContent and their commented
route registrations, plus the now unreachable htmlDeleteDir and the
handleUpdateFs declaration that had no definition
- ContentHelper replaceAll
- OnScreenKeyboardModule popup chain: showPopup, clearPopup, drawPopup,
drawPopupOverlay and their state, unreachable since the frame based UI
was replaced by baseUI
- DebugRenderer drawDebugInfoTrampoline, drawDebugInfoSettingsTrampoline
and the orphaned drawFrameSettings
- NodeListRenderer calculateMaxScroll, drawColumns and a stale extern
haveGlyphs declaration with no definition
- UIRenderer::haveGlyphs, Screen::blink,
NotificationRenderer::showKeyboardMessagePopupWithTitle,
VirtualKeyboard::getInputText
- InkHUD touchNavLeft, touchNavRight, Applet::getActiveNodeCount,
ThreadedMessageApplet::saveMessagesToFlash
- TwoButton::setHandlerUp, TwoButtonExtended setHandlerUp,
setJoystickDownHandlers, setJoystickUpHandlers
- CannedMessageModule LaunchRepeatDestination, isCharInputAllowed,
hasMessages
- TrafficManagementModule resetStats, recordRouterHopPreserved,
saturatingIncrement
- UnitConversions::MetersPerSecondToMilesPerHour
- EncryptedStorage getSessionRemainingSeconds
- BMI270Sensor::writeRegisters, GPS::hasFlow, FSCommon copyFile,
SerialConsole consolePrintf, buzz playLongPressLeadUp,
memGet displayPercentHeapFree
native-suite-count drifted from the actual test/test_*/ directory count: #10669
added two suites (test_admin_session_repro, test_pki_admin_fallback) but bumped
the count by only one, and #11037 added test_xmodem without bumping it at all.
The file reads 35 against 37 real suites, which bin/run-tests.sh reports as AMBER
on every full run. Correct it to 37.
* Stop accelerometer thread when double-tap/wake-on-motion disabled at runtime
double_tap_as_button_press and wake_on_tap_or_motion are applied live: the
OFF->ON edge calls accelerometerThread->start(), but there was no ON->OFF
branch, so turning either flag off left the sensor thread running (polling
I2C, drawing power) until reboot. Worse, because enabled stayed true, a later
OFF->ON edge was a no-op (the enabled==false guard blocked re-start), leaving
the feature un-restartable without a reboot.
Add the symmetric ON->OFF branch in both handlers. When a flag goes true->off
and the other consumer of the shared thread is also off, call
accelerometerThread->disable() (stops runOnce polling and clears enabled so a
later re-enable can start() again). Each branch checks the other flag first so
disabling one feature never stops the sensor while the other still needs it.
* Keep accelerometer thread running when its sensor drives the compass
* Guard accelerometer thread config toggles against a null thread pointer
* AdminModule: factor shared accelerometer start/stop into a helper
The device and display config handlers had mirror-image blocks reconciling the
shared accelerometer thread. Extract reconcileAccelerometerThread(wasOn, nowOn,
otherFeatureOn) so the null guard, edge logic, compass (providesHeading) guard,
and rationale live in one place; each call site is now a single call. Behavior
is unchanged. Also drops the redundant per-field assignment that the
whole-struct `config.device = ...` / `config.display = ...` overwrites anyway.
---------
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
* Add RAK WisMesh Pod variant and fix Tag LPCOMP wake on user shutdown
* chore: trunk fmt - replace Unicode em-dash in Pod comment
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use hardware RNG for session passkey and PKC extra nonce
The admin session passkey and the Curve25519 extra nonce were drawn
from Arduino random(), which is not a CSPRNG. Source them from
HardwareRNG::fill, mirroring the signing path, and fall back to the
seeded CSPRNG (CryptRNG) only when no hardware source is available.
* AdminModule: make session passkey expiry rollover-safe
session_time was compared as millis()/1000 seconds with additive
thresholds, which breaks across the millis() wrap and could keep a stale
admin session key valid. Store session_time in millis() and use
Throttle::isWithinTimespanMs for the 150s refresh and 300s validity
windows.
* AdminModule: track session passkey validity with an explicit flag
session_time == 0 was used as the uninitialized sentinel, but millis()
is legitimately 0 in the first millisecond of uptime, so a passkey
issued then would be treated as no session. Use a dedicated
session_passkey_valid flag instead.
* AdminModule: camelCase the session passkey validity flag
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* TrafficManagement: gate role/NodeInfo cache writes on signer authenticity
The tier-3 role cache and the PSRAM NodeInfo response cache were updated
from any received NodeInfo with no authenticity check, so a spoofed
NodeInfo could set a node's cached role (granting dedup exceptions) or
poison the cached user served in direct responses. Skip both cache
writes when a known signer's NodeInfo arrives unsigned, matching the
identity-update gate on the direct-response path.
* TrafficManagement: hoist shared NodeInfo signer lookup
Compute the sender node lookup and unauthenticated-signer check once per
NodeInfo packet and reuse it for both the cache-refresh gate and the
direct-response identity gate, avoiding a second O(N) getMeshNode scan.
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
decryptForHash accepted chIndex == getNumChannels() before reading
getHash(chIndex), which indexes one past hashes[MAX_NUM_CHANNELS].
Use >= so an out-of-range index is rejected before the array read.