# Q502 TCPClientInterface Investigation

**Date:** 2026-01-25
**Issue:** Q502's TCPClientInterface not loading despite valid configuration

## Summary

Q502's RNS installation fails to load TCPClientInterface configured in `~/.config/reticulum/config`, preventing hub connection. MacBook with identical RNS version (1.1.3) and config successfully loads TCPClientInterface and connects to hub.

## Evidence

### Configuration Files (Identical)

**Q502:**
```ini
[[Styrene Community Hub]]
type = TCPClientInterface
enabled = true
target_host = 192.168.0.102
target_port = 4242
```

**MacBook:**
```ini
[[Styrene Community Hub]]
type = TCPClientInterface
enabled = true
target_host = 192.168.0.102
target_port = 4242
```

### RNS Versions (Identical)

- Q502: `RNS 1.1.3`
- MacBook: `RNS 1.1.3`

### Interface Loading Behavior (Different)

**Q502 Logs:**
```
INFO - Using existing Reticulum config at /home/styrene/.config/reticulum
INFO - Initializing RNS in standalone mode
INFO - Initializing RNS with default config
INFO - Network interfaces establishing...
[Error] AutoInterface[AutoInterface] No multicast echoes received on wlp3s0
```

**NO TCPClientInterface logs** - interface silently not loaded

**MacBook Logs:**
```
INFO - Using existing Reticulum config at /Users/cwilson/.config/reticulum
INFO - Initializing RNS in standalone mode
INFO - Initializing RNS with default config
INFO - Network interfaces establishing...
Connected to hub at 6fc8bf22aa293588... (1 hops)
```

**TCPClientInterface successfully connects to hub**

### Python Import Test

**TCPClientInterface is not a Python class:**
```python
>>> from RNS.Interfaces import TCPClientInterface
ImportError: cannot import name 'TCPClientInterface' from 'RNS.Interfaces'
```

**Only TCPInterface exists:**
```python
>>> from RNS.Interfaces import TCPInterface
>>> # Success
```

**Available interfaces (both machines):**
- AutoInterface
- TCPInterface (NOT TCPClientInterface)
- UDPInterface
- SerialInterface
- RNodeInterface
- etc.

## Key Discovery

`TCPClientInterface` is **not a Python class** but a **config-time alias**. RNS's config parser translates `type = TCPClientInterface` to TCPInterface with client-mode parameters during config loading.

This means the issue is NOT:
- ❌ Missing config file
- ❌ Wrong config syntax
- ❌ Missing Python module
- ❌ RNS version mismatch

The issue IS:
- ✅ RNS config parser not loading TCPClientInterface on Q502
- ✅ Something preventing RNS from parsing/activating that interface
- ✅ Silent failure during interface initialization

## Hypotheses

### 1. Config Parser Failure (Most Likely)

RNS config parser may be silently failing to parse or activate the TCPClientInterface section on Q502:

**Possible causes:**
- Config file encoding issue (UTF-8 vs ASCII)
- Whitespace/invisible character corruption
- Python configparser version difference
- File permissions preventing full read

**Test:**
```bash
# Check file encoding
file ~/.config/reticulum/config

# Check for hidden characters
cat -A ~/.config/reticulum/config | grep "TCPClient"

# Check permissions
ls -la ~/.config/reticulum/config
```

### 2. Initialization Order Issue

TCPInterface may require some system state or dependency that's available on MacBook but not Q502:

**Possible causes:**
- Network stack difference (Linux vs macOS)
- Socket library version
- IPv6 vs IPv4 preference
- DNS resolution timing

### 3. Silent Error During Interface Init

TCPClientInterface may be attempting to initialize but failing silently:

**Possible causes:**
- Port already in use
- Permission denied for network socket
- Firewall blocking socket creation
- Resource limit (ulimit, file descriptors)

### 4. RNS Storage State Corruption

Some state in `~/.config/reticulum/storage/` may be preventing interface load:

**Test:**
```bash
# Backup and clear storage
mv ~/.config/reticulum/storage ~/.config/reticulum/storage.bak
mkdir ~/.config/reticulum/storage

# Restart daemon
```

## Next Steps

1. **Enable RNS Debug Logging:**
   ```bash
   cd ~/styrene-tui
   RNS_LOGLEVEL=7 .venv/bin/python -c "
   import RNS
   import logging
   logging.basicConfig(level=logging.DEBUG)
   rns = RNS.Reticulum()
   print('Interfaces:', [type(i).__name__ for i in RNS.Transport.interfaces])
   "
   ```

2. **Test Minimal RNS Config:**
   Create temp config with ONLY TCPClientInterface (no AutoInterface):
   ```ini
   [reticulum]
   enable_transport = no
   share_instance = no

   [interfaces]

   [[Test Hub]]
   type = TCPClientInterface
   enabled = true
   target_host = 192.168.0.102
   target_port = 4242
   ```

3. **Compare Working vs Non-Working:**
   - Run same test on MacBook
   - Diff the debug output
   - Identify where Q502 diverges

4. **Check System Dependencies:**
   ```bash
   # Network tools available?
   which nc netstat ss

   # Python socket module OK?
   python -c "import socket; print(socket.AF_INET)"

   # Can create TCP socket?
   python -c "import socket; s=socket.socket(); s.connect(('192.168.0.102', 4242)); print('OK')"
   ```

5. **Nuclear Option - Reinstall RNS:**
   ```bash
   cd ~/styrene-tui
   .venv/bin/pip uninstall -y rns
   .venv/bin/pip install rns==1.1.3
   ```

## Comparison Matrix

| Aspect | Q502 | MacBook | Match? |
|--------|------|---------|--------|
| RNS Version | 1.1.3 | 1.1.3 | ✅ |
| Config File | Valid | Valid | ✅ |
| Config Content | Correct | Correct | ✅ |
| Python Version | 3.11 | 3.14 | ❌ |
| OS | NixOS Linux | macOS | ❌ |
| Network | WiFi | WiFi | ✅ |
| TCP to 192.168.0.102 | ✅ `nc` works | ✅ `nc` works | ✅ |
| AutoInterface | Loads (errors) | Disabled | ⚠️ |
| TCPClientInterface | ❌ Not loaded | ✅ Loads | ❌ |
| Hub Connection | ❌ Fails | ✅ Works | ❌ |

## Workaround

If root cause cannot be quickly identified, consider alternative connectivity:

1. **UDP Interface:** Replace TCPClientInterface with UDPInterface
2. **Direct RNode:** USB RNode interface on Q502
3. **AutoInterface Only:** Accept local-only discovery (no fleet)
4. **SSH Tunnel:** Port forward through SSH to bypass RNS TCP issue

## Success Criteria

- [ ] Q502 logs show TCPClientInterface initialization
- [ ] Q502 logs show "Connected to hub at 6fc8bf22aa293588..."
- [ ] Hub shows Q502 as external client (total: 2)
- [ ] MacBook discovers Q502 in node table
- [ ] Messages route Q502 ↔ MacBook via hub

## Related Files

- Config: `~/.config/reticulum/config`
- Storage: `~/.config/reticulum/storage/`
- Styrene Config: `~/.styrene/config.yaml`
- Daemon: `~/styrene-tui/src/styrene/daemon.py`
- RNS Service: `~/styrene-tui/src/styrene/services/rns_service.py`
