The pushed frame serialized the delivery wall-clock time instead of the
post's own stored timestamp, and left the flag byte's low 2 attempt bits
always zero. Firmware sends the stored post timestamp and ORs in a fresh
random attempt so a retried post produces a different packet hash and
ACK; with a fixed timestamp two retries within the same second would be
byte-identical and dropped as mesh duplicates. The serialized timestamp
now also equals the push_post_timestamp sync watermark, matching the
firmware invariant.
GlobalRateLimiter.acquire() enforced the inter-message gap inside an
async-with that released the lock before returning, so the caller's
radio transmission ran with no lock held and every room's sync loop
could push concurrently. acquire() now returns with the lock held
(released via release() in a finally, including on cancellation during
the gap wait), uses a monotonic clock so wall-clock jumps cannot skip
or stretch the gap, and push_post_to_client acquires it only around the
sync-state write plus the blocking send, so the ACK-timeout clock
starts when the transmission actually begins. Regression tests cover
two rooms sharing one limiter (no concurrent sends, gap enforced) and
lock release when the send raises.
The room post limit was 160 Python characters; firmware caps post text
at MAX_POST_TEXT_LEN = 160-9 = 151 bytes (the 160-byte encrypted text
budget minus the timestamp/flags/author-prefix header). Store posts
truncated to 151 UTF-8 bytes at a codepoint boundary, and clamp again
when pushing so previously stored oversized posts cannot produce
oversized frames.
standardize default region on mesh.default_region (remove legacy region_default_scope usage)
add/align CLI support for owner.info, path.hash.mode, and loop.detect with validation
wire UI terminal get/set + autocomplete/help for owner.info, path.hash.mode, loop.detect
extend update_radio_config to persist owner_info for UI set owner.info
add and use shared packet utility for advert creation + default-region transport scoping
refactor repeater and room-server advert paths to reuse shared packet logic
Merge the maintainer's fix-general-tidy branch (neighbor discovery,
keygen, API endpoints, web-asset rebuild, HTTP server config/control
commands, and an independent #286 room-server push/ACK/guest fix) into
the companion cleanup branch.
Both branches fixed#286 in parallel with byte-identical push-ACK CRC
logic. In the six overlapping files the maintainer's implementation is
kept (ACL replay-detection/session helpers, encoded path-len with legacy
fallback, expected_crc/ack_timeout_s injector API, dispatcher ACK
helpers); this branch's unique companion work is preserved on top
(sender_prefix persistence + migration, boot-state hardening /
CompanionStateLoadError, MessageQueue.max_size, older-core fallbacks).
Conflict resolution took the maintainer's side across the overlap, then
fixed two integration seams the merge introduced and updated this
branch's tests to the maintainer's API:
- room_server: timeout used undefined `hops`; aligned to `path_len`.
- packet_router: PATH helper was invoked twice (maintainer's
unconditional call plus this branch's conditional local-identity
call); dropped the now-redundant conditional block.
Pin openhop_core to @dev (was @feature/publish-workflow-message-handling)
so this can merge to the repeater's dev; core dev carries the required
sender_prefix and PathUtils.is_valid_path_len APIs.
Full suite green (1040 passed) against openhop_core dev and
refactor/companion-housekeeping; ruff clean.
Room server pushes waited on dispatcher.wait_for_ack, but nothing in the
repeater could ever resolve it, so every push timed out and re-pushed on
the backoff schedule (issue #286's duplicate floods — worst for virtual
companions on the same instance, whose ACKs never even cross the air):
- no core AckHandler is registered (all RX lands in the router fallback),
so received ACK CRCs were never fed to dispatcher ACK matching
- inject_packet waited on packet.get_crc(), a packet-hash CRC that no ACK
sender produces; the crypto CRC only the room server knows was ignored
- PATH returns (how a flood-received DM is ACKed) were decrypted by
PathHelper, but it read the encoded path_len wire byte as a raw count —
an empty 3-byte-hash path (0x80) parsed as a 128-byte truncated
payload — and the router's PATH branch never ran PathHelper for
room/repeater destinations when any companion bridge existed
Fixes:
- router ACK branch feeds discrete ACK CRCs (RF and locally injected) to
dispatcher._register_ack_received
- PATH packets addressed to a local server identity are processed by
PathHelper regardless of companion bridges; PathHelper decodes the
encoded path_len via PathUtils, keeps the encoded byte in out_path_len,
and registers the embedded ACK via ack_received_fn
- inject_packet accepts expected_ack_crc/ack_timeout; the room server
passes its crypto CRC and a hop-count-based timeout (the encoded byte
would have produced a ~4-minute direct-push wait)
Verified live on a real mesh: push to a same-instance virtual companion
resolves via the PATH-embedded ACK (encoded path_len 0x80) and push to a
firmware client resolves via the discrete RF ACK, no re-push loops.
Fixes#286Fixes#341
push_post_to_client sent posts as TXT_TYPE_SIGNED_PLAIN (plaintext =
timestamp + flags + author_prefix + text) but computed its expected ACK
over the plain-DM span (timestamp + attempt byte + text). Signed-plain
receivers (firmware BaseChatMesh::onPeerDataRecv, and openhop_core's
text handler since it gained signed-message ACKs) reply with
sha256(decrypted[0 : 9 + strlen(text)] || client_pubkey)[:4], so the
server never recognized any push ACK: every push timed out and the same
posts were re-pushed forever (issue #286's repeating 'Push timed out'
log and duplicate unread floods).
Hash the exact plaintext span that is encrypted and sent, plus the
client pubkey. Verified against openhop_core's TextMessageHandler: the
ACK it emits for a pushed post now matches the stored pending_ack_crc.
Fixes#286
- Changed blank-password login behavior to create/manage guest sessions (when read-only is enabled), including replay protection and session timestamp tracking.
- Preserved and centralized `sync_since` handling during successful auth/session updates.
- Updated login handling to return explicit `(success, permissions)` and reset room-server sync guard state in SQLite on successful room-server login.
- Updated identity/banner output in mesh CLI from `pyMC_*` to `openHop_*` and adjusted default version fallback.
- Extended path handling to support encoded path-length semantics via `PathUtils`, with legacy fallback support for older/malformed packets.
- Added bundled ACK extraction from PATH payload extras and callback-based ACK propagation.
- Hardened room-server push logic: max-failure early skip, corrected expected ACK CRC calculation, path encoding compatibility, persisted sync/last-activity fields, and passed expected CRC + timeout into packet injection.
- Enhanced packet router ACK flow with dispatcher ACK registration helper, support for multipart ACK wrapper handling, configurable ACK wait timeout/CRC in injection, and ensured PATH helper processing always runs.
- Updated daemon wiring to pass ACK callback into `PathHelper`,
- make dispatcher dedupe configurable (prevent dbl deduping), and only register duplicate logging hook when dedupe is enabled.
- Expanded tests to cover guest blank-password replay tracking, updated room-server injector ACK expectations, and new duplicate-logging-hook gating behavior.
- Updated byte representations in tests to use lowercase hex format for consistency.
- Reformatted code for better readability, including line breaks and indentation adjustments.
- Consolidated multiple lines into single lines where appropriate to enhance clarity.
- Ensured that all test cases maintain consistent formatting and style across the test suite.
- Introduced tests for TraceHelper and DiscoveryHelper to validate packet forwarding and discovery request handling.
- Implemented tests for LoginHelper to ensure identity registration and login packet processing.
- Added tests for IdentityManager to cover identity registration, lookup, and filtering.
- Created tests for MeshCLI to verify command handling, configuration setting, and error paths.