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
A blank-password login replied success (read-only guest) without ever
creating an ACL entry or storing the ECDH shared secret. The client app
believed it was logged in, but the room server's text handler and sync
loop only see ACL members: the client's posts were dropped without a
delivery ACK (send shows failed) and posts were never pushed to it.
Rooms with only an admin password configured were fully affected since
every guest login is blank-password.
Add the guest to the ACL with guest permissions, the shared secret, and
sync_since (mirroring the password path), reject when the ACL is full,
and refresh activity timestamps on repeat blank logins.
Refs #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.
- Added a default upper bound for randomized pre-send jitter in discovery responses to avoid collisions when multiple repeaters respond simultaneously.
- Introduced a new parameter `response_jitter_ms` in the `DiscoveryHelper` constructor to configure the jitter.
- Updated the `_send_packet_async` method to apply the jitter before sending responses.
- Added tests to verify the correct application of jitter and ensure functionality when jitter is disabled.
- Updated the `LoginHelper` class to wrap the login handler in an `AnonRequestHandler`, allowing for proper handling of anonymous requests.
- Introduced methods for formatting region names based on flood policies and added support for retrieving transport keys from SQLite storage.
- Enhanced the constructor to accept additional parameters for SQLite handler and configuration, improving flexibility for owner-info and feature-flag replies.
- Added tests to validate the new functionality and ensure correct behavior of region name formatting and owner/features callbacks.
- 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.
Also extend the compat scanner to catch datetime.UTC used as an
attribute (datetime.datetime.now(datetime.UTC)) in addition to
direct imports, so this form cannot be reintroduced undetected.
Co-Authored-By: Zindello <josh@zindello.com.au>
- 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.
Problem 1 — _recent_drops: the list was evicted with pop(0), which is an
O(n) memmove every time a drop is recorded. With maxlen=20 this is
negligible today, but pop(0) on a list is always O(n) and the pattern is
worth eliminating.
Problem 2 — _known_neighbors cap: the eviction path did
set(list(self._known_neighbors)[500:])
which first materialises the entire set as a list (O(n) allocation) before
slicing. itertools.islice works directly on the set iterator and only
allocates the 500 kept items, halving peak memory pressure during cleanup.
Changes:
* Import itertools (already absent from this file)
* Import deque from collections alongside OrderedDict
* self._recent_drops initialised as deque(maxlen=20); self._max_recent_drops
removed (maxlen is the single source of truth)
* Drop-recording block: rebuild deque from generator (preserves pubkey dedup
filter) then append — automatic eviction replaces the explicit pop(0) guard
* Known-neighbors cap: itertools.islice(self._known_neighbors, 500) replaces
list(self._known_neighbors)[500:]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added local_identity parameter to RepeaterDaemon for improved trace path matching.
- Refactored trace path handling in TraceHelper to support multi-byte hashes and structured hops.
- Updated methods to ensure compatibility with new trace data formats and improved logging.
- Enhanced tests to validate new trace processing logic and path handling.
- Modify TX modes: forward, monitor, and add no_tx, allowing for flexible packet handling.
- Updated configuration and API endpoints to support the new modes.
- Adjusted logic in RepeaterHandler to manage packet processing based on the selected mode.
- Enhanced CLI commands to reflect the new mode settings.
- Added tests for each TX mode to ensure correct behavior.
- Introduced `total_rx_airtime_ms` in `AirtimeManager` to track received packet airtime.
- Added `record_rx` method to log received airtime in `AirtimeManager`.
- Updated `RepeaterHandler` to count received packets and log RX airtime using the new method.
- Enhanced statistics reporting in `get_stats` to include total received airtime.
- Updated `ProtocolRequestHelper` to include total RX airtime in the RepeaterStats structure for better monitoring.
- Enhanced readability by adding descriptive suffixes to packet type labels in both rrdtool_handler.py and sqlite_handler.py.
- Aligned packet type definitions with the new naming conventions from pyMC_core, ensuring consistency across the codebase.
- Updated PacketRouter to deliver packets to all companion bridges when the destination is not recognized, ensuring better handling of ephemeral destinations.
- Refactored CompanionFrameServer to separate contact serialization and persistence logic, allowing for non-blocking database operations.
- Introduced a unique index for companion contacts in SQLite to support upsert functionality, enhancing data integrity and performance.
- Improved AdvertHelper to run database operations in a separate thread, preventing event loop blocking and maintaining responsiveness.
- Simplified KISS modem setup instructions in README.md by removing unnecessary details.
- Refactored ConfigManager in config_manager.py to improve code clarity and efficiency, including changes to the save_to_file method and live_update_daemon method.
- Updated logging and error handling for better debugging and maintenance.
- Adjusted method signatures for consistency and clarity across the ConfigManager class.
- Modified device_version in frame_server.py to use FIRMWARE_VER_CODE from pyMC_core for better version management.
- Enhanced login.py and protocol_request.py with additional payload type handling and logging improvements.
- Cleaned up auth_endpoints.py for better readability and consistency in response formatting.
- Added a new method `deliver_control_data` in `RepeaterDaemon` to push CONTROL payloads to companion clients.
- Updated `PacketRouter` to invoke the new delivery method for control data packets.
- Introduced `push_control_data` in `CompanionFrameServer` to handle the sending of control data to clients.
- Enhanced `DiscoveryHelper` to support optional debug logging for control handling.
These changes improve the communication and control flow between the repeater and companion clients, facilitating better discovery response handling.
- Added raw RX and trace completion handlers to push data to connected companion clients.
- Enhanced PacketRouter to deliver ACK packets to all companion bridges.
- Introduced methods for retrieving companion statistics based on different types.
- Updated constants for handling new commands and responses in the companion protocol.
This update improves the interaction between the repeater and companion clients, enabling better data flow and monitoring capabilities.
- Add repeater/companion with frame server and constants
- Extend config, sqlite_handler, mesh_cli, packet_router for companion
- Update api_endpoints and auth_endpoints; adjust main entry
Co-authored-by: Cursor <cursoragent@cursor.com>
Refactor packet processing: use processed_packet for forwarding and drop reason checks
Fix: Update zero-hop determination logic in AdvertHelper
Fix: Clone packet in process_packet to prevent modification of the original
Fix: Import copy module for deep copying of packets in process_packet
- Implemented JWT authentication with auto-generated secret if not provided.
- Added API token management functionality.
- Created authentication endpoints for login, token refresh, verification, and password change.
- Introduced API documentation endpoints for Swagger UI and OpenAPI spec.
- Enhanced CORS support for API and documentation endpoints.
- Updated OpenAPI specification to include new authentication and system endpoints.