Skip to content

Wire interface mode into reticulum-kt interfaces (Kotlin backend) - #1188

Merged
torlando-tech merged 6 commits into
mainfrom
fix/issue-1169-mode-wiring
Oct 11, 2026
Merged

torlando-tech merged 6 commits into
mainfrom
fix/issue-1169-mode-wiring

Conversation

@torlando-tech

Copy link
Copy Markdown
Owner

Fixes #1169

The Kotlin backend persisted the user-selected interface mode to the DB and
wrote it to the RNS config file (honored by the Python flavor) but never applied
it to the constructed reticulum-kt interface objects, so every interface
silently ran as the default FULL mode regardless of what the user selected.

What's done

  • Added InterfaceModeMapper with a withMode(name, mode) extension that
    maps Columba's string-valued InterfaceMode onto reticulum-kt's
    InterfaceMode and applies it via modeOverride at every construction
    site: AutoInterface, TCPClient, UDP, TCPServer, RNode (SPP/USB/TCP), and BLE.
  • Tightened createInterface() to return Interface? and marked it
    @VisibleForTesting so the regression test drives the real factory wiring
    directly (no start(), no sockets).
  • Added InterfaceModeWiringTest asserting the constructed object reports the
    configured mode across all mappable modes, that the mode field is actually
    consumed (gateway != full), and that internal is not silently coerced.

INTERNAL is intentionally left unmapped

The pinned reticulum-kt InterfaceMode enum has no INTERNAL value, and its
stray POINT_TO_POINT corresponds to no RNS config mode string. Per #1169 it
must stay a no-op (leave the interface at its default, log a warning) until
reticulum-kt #91 reconciles the enum - coercing it to POINT_TO_POINT or
FULL would change announce re-broadcast behavior without conformance proof.

Verification

The Kotlin backend persisted the user-selected interface mode to the DB and
wrote it to the RNS config (honored by the Python flavor) but never applied it
to the constructed reticulum-kt interface objects, so every interface silently
ran as the default FULL mode. See #1169.

Add InterfaceModeMapper with a withMode() extension that maps Columba's
string-valued InterfaceMode onto reticulum-kt's InterfaceMode and applies it via
modeOverride at every construction site: AutoInterface, TCPClient, UDP,
TCPServer, RNode (SPP/USB/TCP), and BLE.

INTERNAL is intentionally left unmapped: the pinned reticulum-kt InterfaceMode
enum has no INTERNAL value and its stray POINT_TO_POINT corresponds to no RNS
config mode string. Per #1169 it must stay a no-op (leave the interface at its
default, log a warning) until reticulum-kt #91 reconciles the enum - coercing
it to POINT_TO_POINT or FULL would change announce re-broadcast behavior
without conformance proof.

Tighten createInterface() to return Interface? and mark it @VisibleForTesting so
the regression test drives the real factory wiring directly (no start(), no
sockets). InterfaceModeWiringTest asserts the constructed object reports the
configured mode across all mappable modes, that the mode field is actually
consumed (gateway != full), and that internal is not silently coerced.
@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[High impact] The PR appears safe to merge; the remaining BLE test gap is non-blocking.

Fix All in Claude CodeFindings

  1. P2 Three interface types lack tests ▶
Fix with agent prompt
### Issue 1
rns-backend-kt/src/test/kotlin/network/columba/app/rns/backend/kt/InterfaceModeWiringTest.kt:75-77
The new tests cover only `AutoInterface`, `TCPClient`, and `UDP`. None checks the new mode calls for `TCPServer`, `RNode`, or BLE. Removing those calls would leave the suite green while restoring the original bug for those interfaces.

Add coverage for those construction paths, using fake connections where hardware is required.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR applies saved interface modes throughout the Kotlin backend. The latest changes upgrade reticulumKt to v0.0.24 and map internal to ReticulumMode.INTERNAL.

  • Kotlin interfaces now use the mode saved in their config.
  • A saved mode change restarts its running interface.
  • Late radio starts stop registering after an interface is disabled.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Saved interface mode] --> B[mapInterfaceMode]
  B -->|Known mode| C[Set modeOverride]
  B -->|Unknown mode| D[Warn and keep default]
  C --> E[Constructed Reticulum interface]
Loading

Reviews (6) · Last reviewed commit: "feat(rns-backend-kt): wire INTERNAL mode..." · Reviewed by Greptile

Comment on lines +64 to +66
/** Build the interface via the production factory wiring (no start(), no sockets). */
private fun buildInterface(config: InterfaceConfig): network.reticulum.interfaces.Interface {
val iface = NativeInterfaceFactory.createInterface(config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Three interface types lack tests

The new tests cover only AutoInterface, TCPClient, and UDP. None checks the new mode calls for TCPServer, RNode, or BLE. Removing those calls would leave the suite green while restoring the original bug for those interfaces.

Add coverage for those construction paths, using fake connections where hardware is required.

Prompt To Fix With AI
This is a comment left during a code review.
Path: rns-backend-kt/src/test/kotlin/network/columba/app/rns/backend/kt/InterfaceModeWiringTest.kt
Line: 64-66

Comment:
**Three interface types lack tests**

The new tests cover only `AutoInterface`, `TCPClient`, and `UDP`. None checks the new mode calls for `TCPServer`, `RNode`, or BLE. Removing those calls would leave the suite green while restoring the original bug for those interfaces.

Add coverage for those construction paths, using fake connections where hardware is required.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed on the new head: added TCPServer (via the real factory), RNode (driving the real RNodeConnectionHelper with a mocked host bridge), and BLE (real BLEInterface + the same .withMode call from startBleInterface). 8/8 green.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All six interface types now include internal in their mode-iteration lists: autoInterface (line 87), tcpClient (line 101), tcpServer (line 124), udp (line 138), rnode (line 166), and ble (line 201). Each asserts effectiveMode matches expectedReticulumMode(mode) which returns InterfaceMode.INTERNAL for the internal string. Verified locally: 11/11 InterfaceModeWiringTest tests pass including the per-type INTERNAL coverage.

@codecov

codecov Bot commented Oct 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

… greploop P2)

Greploop 4/5 flagged that InterfaceModeWiringTest only covered AutoInterface,
TCPClient, UDP. Close the gap:
- TCPServer via the real factory (binds only in start(), safe in JVM)
- RNode by driving the real RNodeConnectionHelper with a mocked host bridge
- BLE by constructing a real BLEInterface + the same .withMode call from
  startBleInterface

8/8 green. P1 (live mode edit on a running interface) is a false positive -
Torlando confirmed it works in practice.
@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile Please review the new commit (addresses P2: added RNode, BLE, TCPServer mode-wiring coverage).

…1188 P1)

Greploop 4/5 (P1): syncInterfaces is diff-based and only starts names not
already running, so saving a new mode for a running interface was a silent
no-op - the object kept the mode it was started with.

Track the applied mode per running interface (runningModes) and, during
sync, restart any already-running interface whose saved mode changed. A
same-mode sync does not restart, so an unrelated field edit does not churn
the connection. registerAndTrack now takes the config so the mode is
recorded at the single funnel where interfaces become running; stopInterface
clears it.

Adds 2 regression tests: savedModeEditOnRunningInterface_restartsAndAppliesMode
and unchangedRunningInterface_isNotRestartedOnSync. 10/10 green.
@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile Please re-review the new commit - fixes the saved-mode-edit issue (P1) by restarting running interfaces whose mode changed during sync.

…#1188 4/5)

Greploop 4/5: saving a disable/delete for a running RNode started a
replacement in the background; if the user disabled or deleted the interface
before the async start opened its connection, the pending start still called
registerAndTrack, reviving the interface and letting it carry traffic.

Add a per-name start-generation counter (startGenerations). An async RNode/BLE
start advances and captures the generation synchronously on the calling
thread (before the coroutine runs), so a stop/restart/delete that lands after
the start is launched but before it registers sees a higher generation.
registerAndTrack refuses to register when the captured generation no longer
matches the current one.

stopInterface bumps the generation (invalidating any in-flight start for that
name) and syncInterfaces invalidates in-flight starts whose name is no longer
desired - the case the runningInterfaces stop loop does not reach.

The RNode start is launched (not suspended) so the sync thread is never
blocked on a radio connection and a disable from that thread can interleave.

Adds a deterministic regression test: disabledInFlightAsyncStart_doesNotRevive
Interface parks an in-flight RNode start in a mock host bridge, disables it
while parked, releases it, and asserts the interface never registers. 11/11
green.
@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile Please re-review the new commit - fixes the async-start supersede race (4/5 Issue 1) with a per-name start-generation guard.

#1188)

Greploop 3/5 Issue 1 (P1): the start paths call iface.start() before
registerAndTrack's generation check, so when a start is rejected (the interface
was stopped/restarted/deleted mid-connection) the object had already opened a
live radio connection and spawned background coroutines, but never entered
runningInterfaces - so stopInterface and shutdownAll could not find or stop it.
The rejected radio kept running as an orphan.

Detach the rejected interface in the rejection path so a superseded start
releases its connection and coroutines (mirrors the cleanup stopInterface does
for tracked interfaces). Combined with the per-name start-generation guard this
also closes the disable-mid-connection case: the stale start neither registers
nor leaks.

11/11 green.
@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile Please re-review the new commit - detaches the superseded radio interface on rejection (fixes 3/5 Issue 1 resource leak).

reticulum-kt #91 is resolved (v0.0.24): the InterfaceMode enum now has
INTERNAL with the RNS 1.3.6+ semi-transport semantics (relays + path
discovery, announce re-broadcast gated on the next-hop mode).

- Bump reticulumKt v0.0.22 -> v0.0.24
- InterfaceModeMapper: INTERNAL maps 1:1 to ReticulumMode.INTERNAL
  (was -> null, left at FULL default pending #91); remove the stale
  warning path
- InterfaceModeWiringTest: internal is now wired across all 6 interface
  types (Auto, TCPClient, TCPServer, UDP, RNode, BLE); the dedicated
  INTERNAL test asserts modeOverride and effectiveMode are INTERNAL
@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile review

@torlando-tech
torlando-tech merged commit 22e1720 into main Oct 11, 2026
27 of 28 checks passed
@torlando-tech
torlando-tech deleted the fix/issue-1169-mode-wiring branch October 11, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kotlin backend: mode is silently dropped for all interface types (TCP, RNode/SPP, UDP)

1 participant