--message-chunk-size is now treated as a requested maximum.
If the requested value is too large for the Reticulum link budget, the program caps it and logs that it did so.
The cap accounts for file metadata and send_epoch.
Add tests to test_zombie_connection_detection.py (which CI runs) to cover:
- _handle_identity_handshake: non-16-byte rejection, duplicate handling
- _pending_identity_connections cleanup after handshake
- _spawn_peer_interface zombie tracking initialization
These tests cover the same code paths as test_v2_2_identity_handshake.py
but are in a file that CI includes, achieving 100% patch coverage.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Mock-based fixtures with real BLEInterface instances in
stale identity check tests. This ensures coverage.py properly
tracks execution of production code paths.
The Mock approach with method binding executed the production code
but coverage tracking was inconsistent. Using real instances
guarantees proper coverage attribution.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add test for _pending_identity_connections cleanup during successful
identity handshake (lines 1272-1275), achieving 100% patch coverage
for PR #38 changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests verify that:
- Duplicate 16-byte handshake matching known identity is consumed
- Different 16-byte data is also consumed to prevent reassembler errors
- Non-16-byte data is not incorrectly consumed as handshake
- Normal handshake processing works when identity not yet known
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When Kotlin provides the identity via callback (from the identity characteristic read),
the address_to_identity mapping gets set BEFORE the 16-byte handshake data arrives
through _data_received_callback. Previously, _handle_identity_handshake would see the
identity already exists and return False, causing the 16-byte handshake data to be
passed to the reassembler where it fails with "Invalid fragment type 0xXX".
The fix checks if received 16-byte data matches the known identity and consumes it
silently if so. This prevents the handshake data from being misinterpreted as a
fragment.
Symptoms fixed:
- BLEReassembler: Invalid fragment type 0xc9 (first byte of peer identity)
- Messages not flowing even though connections appear established
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When BLE link degrades, 1-byte keepalives may still work while larger data
packets fail. Both sides think the connection is "alive" based on keepalives,
but data can't flow. This causes a deadlock where new connections are
rejected as "duplicates" even though the existing connection is non-functional.
This change adds zombie detection by tracking when real data (not keepalives)
was last received. If an existing connection has only exchanged keepalives
for > 30 seconds (configurable via _zombie_timeout), new connections from
the same identity are allowed and the zombie connection is disconnected.
Changes:
- Add _last_real_data dict to track last real data timestamp per identity
- Add _zombie_timeout (default 30s) for configurable zombie threshold
- Update _check_duplicate_identity with Check 3: zombie detection
- Update _handle_ble_data to track real data activity after keepalive filter
- Initialize tracking in _handle_identity_handshake and _spawn_peer_interface
- Clean up tracking in _process_pending_detaches
- Add comprehensive test suite for zombie detection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a peer disconnects, identity_to_address is NOT cleaned up immediately -
it's only removed after a 2-second grace period. However, _check_duplicate_identity
was not checking if the existing address is still connected before rejecting.
This caused legitimate reconnections from the same identity (after MAC rotation
or reconnection) to be incorrectly rejected as "duplicates" during the grace
period or when cleanup was delayed.
The fix adds two checks before rejecting:
1. If pending_detach exists for this identity (old connection already gone)
2. If existing address is not in connected_peers or peers dict
Also adds TDD tests that demonstrate the bug and verify the fix.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>