Commit graph

190 commits

Author SHA1 Message Date
torlando-tech
3838c007d9 fix: update mute test to verify mute_transmit(False) instead of unmute_transmit
LXST's Mixer.unmute() has a bug where unmute(True) sets muted=True
instead of muted=False (inverted logic). The implementation correctly
uses mute_transmit(False) to unmute, so update the test to match.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
9f9eef4c52 fix: revert to Python 3.11 and fix delivery status UI refresh
Python 3.13's socket.if_nametoindex() doesn't work on Android/Chaquopy,
causing AutoInterface to crash. Python 3.11 works correctly.

Changes:
- Revert Chaquopy from Python 3.13 to 3.11 (both app and reticulum modules)
- Use pycodec2 wheel with pure Python ctypes wrapper instead of Cython
  extension to avoid Android linker namespace symbol resolution issues
- Remove socket.if_nametoindex patch (not needed on Python 3.11)
- Add refresh trigger for delivery status updates to fix UI not showing
  double checkmarks until next message sent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
6a1f07a901 fix: prevent AutoInterface crash on Android by cleaning up adopted_interfaces
On Android, socket.if_nametoindex() is not available in Chaquopy and
the fallback via netinfo.interface_names_to_indexes() returns None.
The /sys/class/net/ifindex fallback is blocked by SELinux.

AutoInterface adds interfaces to adopted_interfaces before calling
interface_name_to_index(). When the exception was caught, the interface
remained in adopted_interfaces, causing final_init() to crash later.

The fix cleans up adopted_interfaces before raising the exception so
final_init() has nothing to iterate over for interfaces that can't
get their index. AutoInterface now gracefully degrades with a warning.

Also temporarily disabled x86_64 ABI due to wheel resolution issues
for audioop-lts and pycodec2 (only arm64 needed for real devices).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
95c2871a06 refactor: improve code quality by extracting helper methods and fixing async patterns
- CallActionReceiver: Replace GlobalScope with goAsync() pattern to prevent
  process termination before hangup completes. This fixes a potential bug
  where declining/ending calls from notifications could fail silently.

- ReticulumServiceBinder: Extract setupBridges() into 9 focused helper methods
  (setupBleCoordinator, initializeRNodeInterface, setupReticulumBridgeCallback,
  setupAlternativeRelayCallback, setupLocationTelemetryCallback, setupReactionCallback,
  setupPropagationStateCallback, setupLxstCallManager, registerCallBridgeListeners)

- KotlinAudioBridge: Extract startRecording() into 6 focused helper methods
  (calculateMinBufferSize, ensureAudioModeForRecording, createAudioRecord,
  configurePreferredInputDevice, startRecordingAndLaunchLoop, logAudioRoutingState)

- MockReticulumProtocol: Use idiomatic = Unit for empty function bodies

- ServicePersistenceManager: Add ReturnCount suppression for cascading lookup pattern

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
0ba904d28d fix: resolve call startup issues with retry and async IO
- Remove redundant path request code from call_manager.py - LXST's
  Telephony.call() already handles path discovery correctly
- Move AIDL call operations to IO dispatcher to prevent ANR when
  LXST blocks for path discovery (up to 70s)
- Add retry mechanism in CallViewModel for CallManager initialization
  (retries up to 10x with 1s delay after fresh install)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
830853c829 fix: use mute_transmit() instead of unmute_transmit() for LXST calls
LXST's Mixer.unmute() has a bug where unmute(True) sets self.muted=True
instead of self.muted=False (inverted logic). This caused the unmute
button to not work - calling unmute_transmit() actually kept audio muted.

Changes:
- call_manager.py: Use telephone.mute_transmit(muted) with explicit
  True/False instead of separate mute_transmit()/unmute_transmit() calls
- KotlinAudioBridge: Remove software mute (LXST handles at codec level)
- ReticulumServiceBinder: Improve logging for mute state changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
0c91aeb442 feat: implement LXST voice calls with multi-process IPC
Complete voice call implementation for Android with cross-process support:

- Add call AIDL interface (IReticulumService/IReticulumServiceCallback)
- Implement ServiceReticulumProtocol for call IPC to :reticulum process
- Add ReticulumServiceBinder call methods bridging to Python CallManager
- Wire KotlinAudioBridge to Chaquopy audio backend for audio streaming
- Add incoming call notifications with IPC broadcast to UI process
- Fix microphone recording by adding FOREGROUND_SERVICE_MICROPHONE
- Support mute/unmute via software mute in audio bridge
- Add libopus native dependency for Opus codec support

Audio routing now works correctly between devices with earpiece default.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
bb8c49930e test: add comprehensive unit tests for LXST voice call feature
Adds unit test coverage for the voice call implementation:

Kotlin tests:
- CallBridgeTest: Tests call state management, Python callbacks,
  mute/speaker toggles, and lifecycle methods
- KotlinAudioBridgeTest: Tests audio playback/recording, device
  enumeration, speaker/microphone routing
- CallViewModelTest: Tests state observation, UI actions, duration
  formatting, and status text generation
- AudioCoordinatorTest: Tests audio focus, wake locks, and audio
  routing during calls
- CallNotificationHelperTest: Tests notification channels, incoming/
  ongoing call notifications, and cancellation

Python tests:
- test_call_manager.py (58 tests): Tests CallManager initialization,
  call actions, callback handlers, and module-level functions
- test_chaquopy_audio_backend.py (59 tests): Tests ChaquopyAudioBackend,
  ChaquopyPlayer, ChaquopyRecorder, and LXST-compatible API

All 117 Python tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:04:40 -05:00
torlando-tech
fa59db20b8 feat: add LXST voice call support
Implement real-time voice calling over Reticulum using LXST Telephony:

- KotlinAudioBridge: Android AudioTrack/AudioRecord wrapper for Python
- chaquopy_audio_backend.py: Chaquopy-compatible replacement for Pyjnius soundcard
- CallBridge: Bidirectional state management between Python LXST and Kotlin UI
- call_manager.py: LXST Telephone wrapper with Kotlin callbacks

- VoiceCallScreen: Active call UI with mute/speaker controls
- IncomingCallScreen: Incoming call UI with pulsing animation
- CallViewModel: Call state management with Hilt DI
- Call button added to MessagingScreen TopAppBar

- CallNotificationHelper: Incoming/ongoing call notifications with full-screen intent
- CallActionReceiver: Notification action handling (answer/decline/end)
- AudioCoordinator: Audio focus, wake locks, proximity sensor

- RECORD_AUDIO for microphone access
- MODIFY_AUDIO_SETTINGS for audio routing
- USE_FULL_SCREEN_INTENT for incoming call overlay

Supports all LXST quality profiles (Opus/Codec2) for bandwidth-adaptive calls.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:04:40 -05:00
Torlando
abf18472fd
Merge pull request #274 from torlando-tech/feature/settings-about-section
feat: add comprehensive About section to Settings (#262)
2026-01-16 20:20:04 -05:00
torlando-tech
3e3e7041d6 feat: add comprehensive About section to Settings
Implements issue #262 - adds a detailed About section at the bottom of
the Settings screen displaying app information, device details, protocol
versions, identity info, links, and build metadata.

Features:
- App information (version, build code, git commit, build date)
- Device information (Android version, API level, model, manufacturer)
- Protocol versions (Reticulum, LXMF, BLE-Reticulum)
- Identity hash display
- Links to GitHub repository, issue tracker, and Reticulum website
- Legal information and dependency attribution
- Copy to clipboard functionality for system info

Technical changes:
- Add BuildConfig fields for git commit hash and build timestamp
- Create DeviceInfoUtil for collecting and formatting system info
- Add Python methods to retrieve protocol versions
- Add AIDL and Kotlin wrappers for protocol version retrieval
- Update SettingsViewModel with retry logic for fetching versions
- Create AboutCard composable with comprehensive UI
- Add 23 unit tests (8 for DeviceInfoUtil, 15 for AboutCard)

All quality checks passing (detekt, ktlint, cpd, unit tests).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 19:48:40 -05:00
torlando-tech
b3345e66e0 test: update propagation node tests to match None-clearing behavior
The production code was fixed to not pass None to LXMF's
set_outbound_propagation_node() since LXMF doesn't support it.
Updated the tests to verify that the router method is NOT called
when clearing with None, only internal state is cleared.

Fixes two failing tests:
- test_set_propagation_node_clears_when_none
- test_set_outbound_propagation_node_clears_with_none

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 19:00:06 -05:00
torlando-tech
431d139e0b fix: don't pass None to LXMF set_outbound_propagation_node()
LXMF's set_outbound_propagation_node() expects a valid bytes hash and
calls len() on it, which fails with TypeError when passed None.

When clearing the propagation node, just clear our internal tracking
variable instead of calling the LXMF method. LXMF will handle the case
where no valid propagation node is set.

Fixes error: "object of type 'NoneType' has no len()"

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 17:36:27 -05:00
torlando-tech
0d452252a1 fix(#257): add Python tracking + Kotlin guard for propagated message status
Two-layer defense against spurious failure callbacks for propagated messages:

Python layer:
- Track successfully propagated messages in _successfully_propagated dict
- Guard in _on_message_failed to ignore failures for tracked messages
- Cleanup stale tracking entries (24h TTL)
- Immediate state check after propagation retry

Kotlin layer:
- Add isTerminalSuccessStatus() helper
- Guard in handleDeliveryStatusUpdate to block status degradation
  from terminal success states (sent/propagated/delivered) to failed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:55:42 -05:00
torlando-tech
d8a924862a fix: emit 'propagated' status for PROPAGATED method messages
When messages are sent via propagation node, the status should update
to "propagated" (not "sent") when the relay confirms receipt.

Changes:
- Modify _on_message_sent() to check desired_method and emit
  'propagated' status for PROPAGATED method messages
- Add state check after propagation retry in _on_message_failed()
  to detect immediate propagation success
- Handle pending file notifications when propagation is confirmed

This fixes the issue where Message Details showed "Sent" for messages
that were successfully propagated to a relay node.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:55:42 -05:00
torlando-tech
b6e5f331a6 fix: consistent filtering of deprecated propagation nodes in all queries
Problem: Auto-select relay was enabled but no relay was being selected.
Root cause: Different queries had inconsistent filtering:
- getAllAnnouncesPaged() had NO filter - showed deprecated nodes
- getAnnouncesByTypes() filtered deprecated nodes (stampCostFlexibility IS NULL)
- This caused relays to appear in announce stream but not be available for auto-selection

Changes:
- Make all announce queries consistently filter deprecated propagation nodes
- Add logging when pn_announce_data_is_valid() returns False (Python)
- Add debug logging to PropagationNodeManager for auto-selection diagnosis

Deprecated nodes are propagation nodes running old LXMF versions that
don't include stamp cost data in their announces. These cause sync failures
so they are intentionally filtered from relay selection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:42:59 -05:00
torlando-tech
517ece890f Fix test_extract_interface_from_announce_table mock setup
The test was expecting the interface's .name attribute value, but the
code uses type().__name__ to get the class name. Fixed by:
- Setting mock_interface.__class__.__name__ = 'TCPInterface'
- Updating assertion to expect "TCPInterface" (class name)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 19:25:27 -05:00
torlando-tech
467dd09602 Add test for direct LXMF receiving_interface capture
Tests the opportunistic message scenario where LXMF provides
the receiving_interface directly on the message object, rather
than needing to look it up from path_table.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 18:52:13 -05:00
torlando-tech
de43ce9c92 Fix receiving interface capture for AutoInterface messages
AutoInterfacePeer uses 'ifname' attribute (network interface name like
'wlan0') instead of 'name'. For the first opportunistic message before
path_table is populated, the interface was not being captured.

Changes:
- Use class name (type().__name__) to identify interface type reliably
- Extract shared interface display logic to InterfaceUtils.kt
- Both MessageDetailScreen and AnnounceDetailScreen use same utility
- AutoInterface class names map to "Local Network" with Wifi icon

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 18:50:36 -05:00
Tyler Orlando
6d511bd0db Fix Python tests by adding type checking for Mock objects
- Add module-level LXMF field constants (FIELD_FILE_ATTACHMENTS,
  FIELD_IMAGE, FIELD_AUDIO) so code works without LXMF imported
- Use local booleans to track captured hops/interface instead of
  hasattr() which always returns True for Mock objects
- Add isinstance() checks for hop count (int), interface name (str),
  and public key (bytes) before using them
- This ensures JSON serialization doesn't fail when tests use
  MagicMock objects that auto-create attributes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-13 09:56:21 -05:00
torlando-tech
1c1bb78a32 Capture receiving interface and hops for opportunistic messages
For opportunistic (single-packet) messages that arrive before the path_table
is populated, we now capture the receiving interface and hop count directly
from the LXMF message object. This is enabled by a patch to our LXMF fork
that passes packet.receiving_interface and packet.hops to the delivery callback.

Changes:
- Update LXMF dependency to feature/receiving-interface-capture branch
- Check for receiving_interface and receiving_hops attributes on LXMessage first
- Fall back to path_table lookup for link-based messages (unchanged behavior)

This fixes the issue where messages received before an announce wouldn't have
hop count or interface info in the Message Details screen.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:59:47 -05:00
torlando-tech
82f3f7aef7 Fix race condition: register message callback before Python init
Move message received callback registration from setupBridges() to
setupPreInitializationBridges() so it's registered BEFORE Python's
LXMF router starts. This ensures messages that arrive immediately
after initialization use the event-driven path with hop count and
interface data, rather than falling back to polling.

Also clarify in comments that hop/interface capture is only available
when a path exists in path_table (opportunistic messages without an
established path won't have this info).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 23:24:16 -05:00
torlando-tech
21695ebdbb Make message receiving truly event-driven (no polling)
Python callback now sends full message data including content, fields,
public key, hop count, and receiving interface. Kotlin processes the
message directly from the callback JSON instead of polling back to
Python.

This eliminates the round-trip poll and makes message delivery truly
event-driven.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:09:04 -05:00
torlando-tech
9ae5e052f5 Fix interface capture when interface.name is None
The interface object may have a name attribute that is None, resulting
in "None" being captured as the interface name. Now checks that
interface.name is truthy before using it, and skips storing if the
result is "None".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:04:31 -05:00
torlando-tech
59b2c78c42 Include hop count and interface in event-driven callback
The kotlin_message_received_callback was missing hops and receiving_interface
fields. While the current flow triggers a poll that also includes this data,
including it in the callback makes the data available for a future true
event-driven implementation without polling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:55:47 -05:00
torlando-tech
012154bd6d Fix race condition in path_table access using .get()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
90f61ffc23 Capture receiving interface for all messages, not just direct
The path table stores the interface through which we received the
message, regardless of hop count. For multi-hop messages, this is
the interface of the last relay that delivered to us.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
ebfd013302 Add tests for receiving interface card and delivery hop capture
- Add 7 Kotlin tests for getReceivingInterfaceInfo() covering all interface
  type branches (AutoInterface, TCP, BLE, RNode, Serial, unknown, null)
- Add 7 Python tests for _on_lxmf_delivery() hop count and interface capture
- Use performScrollTo() for UI assertions on scrollable content

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
09c4d69162 Fix test mocks to set .name attribute on interface objects
Tests were mocking __str__ but code now uses .name attribute when
available. Added .name to mock interfaces to match actual behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
53d72b3ecd Capture hop count at delivery time instead of poll time
RNS.Transport.hops_to() returns current routing info which may change
after message reception. Now capture hop count and interface immediately
in _on_lxmf_delivery() and store on message object for later retrieval.

This ensures hop count reflects the actual path taken at reception time,
not a potentially different path discovered later.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
f4f3fb6e9f Only capture receiving interface for direct messages (hops=0)
Path table stores outbound routing info, not reception history.
For multi-hop messages, path_entry[5] is the next-hop interface for
sending TO the sender, not the interface that received the message.
Interface info is only accurate when hops=0 (direct delivery).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
bc9eecf2a2 Add unit tests for hop count validation
Tests verify that poll_received_messages correctly:
- Extracts valid hop counts (positive integers)
- Extracts zero hop count (direct delivery)
- Skips negative hop counts (-1)
- Skips None hop counts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
1b871804b5 Validate hop count before storing (skip None or negative values)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
ea73038939 Use interface .name attribute in announce handlers too
Apply same fix to _announce_handler and poll_received_announces
for consistency across all interface name extraction.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
3813ad1936 Use interface .name attribute for cleaner interface names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:50 -05:00
torlando-tech
5db8606bf0 Add hop count and receiving interface to received message info
Capture network metadata when messages are received and display it on the
message info screen:

- Add receivedHopCount and receivedInterface columns to MessageEntity
- Create database migration 28→29
- Capture hop count via RNS.Transport.hops_to() at message reception
- Capture receiving interface from RNS path table at message reception
- Pass new fields through EventHandler and ServicePersistenceManager
- Update Message, MessageUi models and mappers
- Display hop count (Direct/N hops) and interface (WiFi/BLE/LoRa/TCP)
  on received message info screen with appropriate icons

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:50 -05:00
torlando-tech
75f0d6addc Fix race condition in path_table and announce_table access
Use .get() instead of checking membership then accessing, which
eliminates the race condition where another thread could remove
the entry between the two operations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:15:03 -05:00
torlando-tech
da03e074c3 Revert "fix(security): correct destination direction in RMSP hash verification"
This reverts commit 89b458d894.
2026-01-12 21:25:45 -05:00
torlando-tech
89b458d894 fix(security): correct destination direction in RMSP hash verification
Change RNS.Destination direction from IN to OUT when verifying server
identity. Servers announce with OUT direction (announcing their service),
so verification must use the same direction to correctly validate the
destination hash against the public key.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 21:24:25 -05:00
torlando-tech
71fbc2691d fix: remove redundant assignment before return in hash verification
The `server = None` assignment was unreachable since `return None`
immediately follows. Removed the dead code for clarity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 11:52:50 -05:00
torlando-tech
9e9e7f37ed fix: address 5 code review issues from Greptile round 2
1. RMSP cancellation path - add retry logic with backoff for file deletion
   (matching HTTP path behavior)
2. SQLite database close - use explicit try-finally to prevent fd leaks
3. Database error recovery - attempt immediate orphan import instead of
   waiting for app restart
4. Python hash verification - explicitly set server=None on failure
   (defensive coding)
5. CONCURRENT_DOWNLOADS - increase to 10 for I/O-bound HTTP operations

Issue 1 (finally block file deletion) was already correct - only deletes
when success=false.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 23:36:23 -05:00
torlando-tech
da560668ff fix: clarify hash verification flow and fix flaky ImageCache test
- Add explicit log message and comment in RMSP client to clarify that
  hash verification happens before server object creation (addressing
  code review concern about P0 security vulnerability)
- Add resetForTest() to ImageCache to properly reset hit/miss stats
  between tests, fixing flaky getStats test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:22:57 -05:00
torlando-tech
398ebfc957 fix(tests): use explicit global coverage marker in Server D test
Server D needs ["*"] instead of [] for global coverage after the
covers_geohash behavior change.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:44:49 -05:00
torlando-tech
2f5ed6c24f fix: align Python covers_geohash with Kotlin behavior
Both now require explicit "*" for global coverage; empty list means
no coverage data available. This prevents servers with incomplete
announce data from incorrectly matching all geohash queries.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:02:14 -05:00
torlando-tech
90ac143fa3 fix: use fixed 30s path timeout instead of percentage-based
Previous 10s max was too short for mesh networks. Now uses 30s fixed
timeout (or half of total for short timeouts), giving path establishment
reasonable time while still leaving most of the timeout for data transfer.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:57:25 -05:00
torlando-tech
59f8c73b70 security: fix destination direction in hash verification
RMSP servers announce with IN direction (listening for requests),
not OUT. Using OUT direction caused the hash computation to always
fail for legitimate servers, defeating the security check.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:47:03 -05:00
torlando-tech
dfc179384a fix: simplify path waiting with fixed polling interval
Replace exponential backoff with fixed 250ms polling for path requests.
Reduce path timeout to 30% of caller's timeout (max 10s) to avoid
nested timeout confusion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:24:54 -05:00
torlando-tech
2b622f2551 fix: increase tile count limit from 100K to 1M
100K tiles was too restrictive for large region downloads (~100km radius).
Updated limit to 1M in both Kotlin and Python code with corresponding tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:23:30 -05:00
torlando-tech
51570c92fd security: verify destination hash matches public key in RMSP client
Compute expected destination hash from provided public key and compare
with claimed dest_hash to prevent server impersonation attacks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:14:01 -05:00
torlando-tech
1e2b5f47a7 fix: improve RMSP error response detection using msgpack format markers
Instead of unreliably checking if response < 1000 bytes, detect error
responses by checking for msgpack map format markers (0x80-0x8f fixmap,
0xde map16, 0xdf map32). This correctly handles small valid tiles and
avoids misinterpreting binary tile data as errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 18:59:51 -05:00