Skip to content

feat: add IFAC network_name and passphrase support for RNode interfaces#718

Merged
torlando-tech merged 4 commits intomainfrom
feat/714-rnode-ifac-support
Mar 27, 2026
Merged

feat: add IFAC network_name and passphrase support for RNode interfaces#718
torlando-tech merged 4 commits intomainfrom
feat/714-rnode-ifac-support

Conversation

@torlando-tech
Copy link
Copy Markdown
Owner

Summary

  • Add optional network_name and passphrase fields to RNode interface configuration, enabling Reticulum's IFAC (Interface Access Code) cryptographic authentication for LoRa radio interfaces
  • Extract shared IfacFields composable from TCPClientFields and reuse for RNode in the interface config dialog advanced options
  • Full-stack implementation: data model → JSON serialization → Kotlin→Python bridge → config generation → ColumbaRNodeInterface init → ViewModel → UI

Closes #714

How it works

IFAC allows interfaces with matching network_name and passphrase to communicate securely by signing and verifying packets using keys derived from these shared credentials. This is especially valuable for LoRa/RNode where anyone with the same frequency settings can receive traffic.

Users can set these optional fields in the Advanced Options when editing an RNode interface.

Files changed (12)

Layer File Change
Data model ReticulumConfig.kt Added networkName/passphrase to InterfaceConfig.RNode
Serialization InterfaceConfigExt.kt RNode JSON includes IFAC when non-null
Deserialization InterfaceRepository.kt RNode JSON parsing reads IFAC fields
Bridge ServiceReticulumProtocol.kt Kotlin→Python JSON passes IFAC for RNode
Python (TCP) reticulum_wrapper.py TCP RNode config file includes IFAC lines
Python (BT/USB) reticulum_wrapper.py Pending config dict includes IFAC keys
Python interface rnode_interface.py ColumbaRNodeInterface.__init__ reads IFAC from config
ViewModel InterfaceManagementViewModel.kt configToState/stateToConfig map IFAC for RNode
UI InterfaceConfigDialog.kt Shared IfacFields composable, RNodeFields in advanced options

Test plan

  • InterfaceConfigExtTest — RNode serialization includes/omits IFAC fields
  • TestTcpRNodeConfigGeneration — TCP RNode config file contains/omits IFAC lines
  • TestCreateConfigFile — BT/USB pending config dict stores/omits IFAC params
  • TestIfacInitColumbaRNodeInterface.__init__ reads IFAC from config dict
  • Kotlin compilation succeeds
  • Manual: create RNode interface with network_name/passphrase, verify connectivity with matching peer

🤖 Generated with Claude Code

Closes #714

Add optional network_name and passphrase fields to RNode interface
configuration, enabling Reticulum's Interface Access Code (IFAC)
cryptographic authentication for LoRa radio interfaces.

Changes across the full stack:
- Add networkName/passphrase to InterfaceConfig.RNode data class
- Update JSON serialization/deserialization in InterfaceConfigExt and
  InterfaceRepository
- Pass IFAC params through Kotlin→Python bridge in ServiceReticulumProtocol
- Generate IFAC config lines for TCP RNode in reticulum_wrapper.py
- Store IFAC params in pending config dict for BT/USB RNode
- Read IFAC from config in ColumbaRNodeInterface.__init__
- Map IFAC fields in ViewModel configToState/stateToConfig
- Extract shared IfacFields composable from TCPClientFields, reuse for
  new RNodeFields in InterfaceConfigDialog advanced options
- Add tests covering all three data flow paths (TCP config file, BT/USB
  pending dict, ColumbaRNodeInterface init)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sentry
Copy link
Copy Markdown
Contributor

sentry bot commented Mar 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR delivers a clean full-stack implementation of IFAC (network_name / passphrase) support for RNode LoRa interfaces, mirroring the existing TCP path end-to-end: data model → JSON serialization/deserialization → Kotlin→Python bridge → config file / pending-config dict → ColumbaRNodeInterface.__init__ → ViewModel → UI (both the settings dialog and the RNode wizard). The IfacFields composable refactor is a nice DRY improvement. The previous review concern about asymmetric .trim() on passphrase has been resolved — it is now applied consistently for both TCP and RNode in InterfaceManagementViewModel and RNodeWizardViewModel.\n\nKey changes:\n- InterfaceConfig.RNode gains nullable networkName/passphrase fields; serialization/deserialization are symmetric.\n- reticulum_wrapper.py writes IFAC lines to the TCP RNode INI config and stores them in the BT/USB pending dict.\n- rnode_interface.py reads the raw passphrase into ifac_netkey; the updated comment correctly notes RNS.Transport performs key derivation.\n- Edit mode in the RNode wizard now skips straight to the review step with showAdvancedSettings = true, which is a sensible UX improvement (and required for IFAC fields to be visible on edit).\n- Test coverage spans all four new code paths (serialization, TCP config gen, BT/USB pending dict, __init__ init).\n\nOne P2 suggestion: the network_name and passphrase strings are the first user-entered free-text values written unquoted into the Reticulum INI config file. A defensive newline-strip on the Python write path would match the single-line constraint already enforced by the UI.

Confidence Score: 5/5

Safe to merge; the implementation is correct and the only open item is a defensive best-practice in the Python config writer.

All four code paths are covered by tests, the full-stack data flow is consistent, previous review concerns about passphrase trim asymmetry are resolved, and the one remaining P2 (newline strip) is a minor hardening suggestion rather than a functional bug given the UI already enforces single-line input.

python/reticulum_wrapper.py — unquoted string values written to INI config without newline sanitization.

Important Files Changed

Filename Overview
reticulum/src/main/java/com/lxmf/messenger/reticulum/model/ReticulumConfig.kt Adds nullable networkName and passphrase fields (defaulting to null) to InterfaceConfig.RNode with KDoc entries; clean, non-breaking addition.
reticulum/src/main/java/com/lxmf/messenger/reticulum/model/InterfaceConfigExt.kt Serializes networkName/passphrase to JSON only when non-null, consistent with the existing stAlock/ltAlock pattern.
app/src/main/java/com/lxmf/messenger/repository/InterfaceRepository.kt Deserializes network_name/passphrase from JSON using optString+ifEmpty { null }, cleanly mapping empty strings to null.
app/src/main/java/com/lxmf/messenger/reticulum/protocol/ServiceReticulumProtocol.kt Bridges networkName/passphrase to the Python JSON payload for RNode via safe null-safe let blocks.
python/reticulum_wrapper.py TCP path writes network_name/passphrase to config file unquoted; BT/USB path stores them in the pending config dict; no sanitization for newlines, consistent with existing file-writing pattern.
python/rnode_interface.py Reads network_name and passphrase from the config dict into ifac_netname/ifac_netkey; comment now correctly notes RNS.Transport performs key derivation.
app/src/main/java/com/lxmf/messenger/viewmodel/InterfaceManagementViewModel.kt configToState and stateToConfig both handle IFAC for RNode; passphrase now consistently uses .trim() for both TCP and RNode paths, resolving the prior asymmetry.
app/src/main/java/com/lxmf/messenger/viewmodel/RNodeWizardViewModel.kt Adds networkName/passphrase/passphraseVisible to wizard state with update functions; edit mode now skips directly to the review step with showAdvancedSettings = true.
app/src/main/java/com/lxmf/messenger/ui/components/InterfaceConfigDialog.kt Extracts IfacFields composable shared by TCPClientFields and the new RNodeFields composable; clean refactor with no functional regressions.
app/src/main/java/com/lxmf/messenger/ui/screens/rnode/ReviewConfigStep.kt Inlines IFAC fields in the wizard review card using RNodeWizardState; duplication vs IfacFields is justified by the different state type.
reticulum/src/test/java/com/lxmf/messenger/reticulum/model/InterfaceConfigExtTest.kt Tests that IFAC fields are included in JSON when set and omitted when null; good round-trip coverage.
python/test_rnode_interface.py New TestIfacInit class verifies ColumbaRNodeInterface.__init__ correctly reads and defaults IFAC fields from config dict.
python/test_wrapper_config.py Tests TCP RNode config file includes/omits network_name and passphrase lines correctly.
python/test_wrapper_initialization.py Tests BT/USB pending config dict stores IFAC params (or None) as expected.

Sequence Diagram

sequenceDiagram
    participant UI as InterfaceConfigDialog / ReviewConfigStep
    participant VM as ViewModel (IMVM / RNodeWizardVM)
    participant Repo as InterfaceRepository
    participant Proto as ServiceReticulumProtocol
    participant Py as reticulum_wrapper.py
    participant IF as ColumbaRNodeInterface

    UI->>VM: onConfigUpdate(networkName, passphrase)
    VM->>Repo: save InterfaceConfig.RNode(networkName, passphrase)
    Repo-->>VM: persisted

    VM->>Proto: send RNode JSON (network_name, passphrase)
    Proto->>Py: JSON over Kotlin→Python bridge

    alt TCP connection mode
        Py->>Py: _create_config_file()<br/>write network_name / passphrase to INI
        Note over Py: RNS reads config → starts TCP RNode
    else BT / USB connection mode
        Py->>Py: store in _pending_rnode_configs dict
        Py->>IF: ColumbaRNodeInterface(config)
        IF->>IF: ifac_netname = config["network_name"]<br/>ifac_netkey = config["passphrase"]
        Note over IF: RNS.Transport derives IFAC key
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: python/reticulum_wrapper.py
Line: 1582-1588

Comment:
**Strip newlines from IFAC strings before writing to config**

`network_name` and `passphrase` are the first user-supplied arbitrary strings written directly into the Reticulum INI config file (all other values, such as `frequency`, `bandwidth`, `interface_mode`, etc., are numeric or known-safe enum strings). An embedded newline — e.g. from a crafted JSON payload sent to the service — would split the value across lines, either silently truncating it or injecting spurious INI content below it.

The Android UI prevents this via `singleLine = true`, but a defensive strip on the Python side would close the gap at the write boundary:

```suggestion
                    # IFAC parameters
                    network_name = iface.get("network_name")
                    if network_name:
                        config_lines.append(f"    network_name = {network_name.replace(chr(10), '').replace(chr(13), '')}")

                    passphrase = iface.get("passphrase")
                    if passphrase:
                        config_lines.append(f"    passphrase = {passphrase.replace(chr(10), '').replace(chr(13), '')}")
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (4): Last reviewed commit: "fix: skip to Review step when editing ex..." | Re-trigger Greptile

torlando-tech and others added 3 commits March 26, 2026 20:31
Apply .trim() to passphrase in both TCP and RNode stateToConfig paths
to prevent silent IFAC mismatch from accidental whitespace. Clarify
that ifac_netkey holds the raw passphrase (RNS.Transport derives the
actual key).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The InterfaceConfigDialog is not used for RNode interfaces — editing
goes through the RNode wizard. Add network_name and passphrase fields
to the wizard's Review & Configure step (under Advanced Settings),
wire through RNodeWizardState, and persist on save/load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In edit mode, jump directly to REVIEW_CONFIGURE with advanced settings
expanded so the user sees their existing config immediately instead of
starting at Device Discovery with default values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@torlando-tech torlando-tech merged commit f2de242 into main Mar 27, 2026
14 checks passed
@torlando-tech torlando-tech deleted the feat/714-rnode-ifac-support branch March 27, 2026 02:13
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.

Add IFAC network_name and passphrase support for RNode interfaces

1 participant