mirror of https://github.com/nostar/urfd.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
7.6 KiB
185 lines
7.6 KiB
# Investigation and Fix Plan: Flexible DMR Mode
|
|
|
|
## Problem Description
|
|
|
|
User wants to support two modes of operation for DMR:
|
|
|
|
1. **XLX Mode** (Default): Legacy behaviors. MMDVM clients "link" to a module.
|
|
2. **Mini DMR Mode** (New): MMDVM clients do not "link". Modules are mapped to Talkgroups. Clients "subscribe" to TGs.
|
|
|
|
## Analysis
|
|
|
|
- **Modes**:
|
|
- `XLXCompatibility`: Legacy mode.
|
|
- `Mini DMR Mode`: Direct TG mapping.
|
|
- **Subscription Logic**:
|
|
- **Single Mode**: Only one TG allowed per timeslot. New TG replaces old.
|
|
- **Multi Mode**: Multiple subscriptions allowed per timeslot.
|
|
- **Scanner / Hold**: If >1 subscription, hold onto active TG for X seconds (default 5s) after idle before switching.
|
|
- **Timeouts**:
|
|
- Dynamic subscriptions expire after configurable time (default 10 mins).
|
|
- Configurable per connection via Options string/password.
|
|
- Static subscriptions (via config/options) do not expire.
|
|
- **Scope**:
|
|
- Only TGs defined in the Reflector's Module Map (plus 4000) are valid.
|
|
- **Anti-Kerchunk**:
|
|
- If a client Subscribes via PTT (first time), ignore/mute that transmission to prevent broadcasting unnecessary noise.
|
|
|
|
## Proposed Changes
|
|
|
|
### Configuration
|
|
|
|
- [ ] Modify `JsonKeys.h` / `Configure.h` / `Configure.cpp`:
|
|
- `Dmr.XlxCompatibility` (bool, default true).
|
|
- `Dmr.ModuleMap` (map/object).
|
|
- `Dmr.SingleMode` (bool, default false).
|
|
- `Dmr.DefaultTimeout` (int, default 600s).
|
|
- `Dmr.HoldTime` (int, default 5s).
|
|
|
|
### Client State (`DMRMMDVMClient`)
|
|
|
|
- [ ] Add `Subscription` structure:
|
|
- `TalkgroupId`
|
|
- `Timeslot`
|
|
- `Expiry` (timestamp or 0 for static)
|
|
- [ ] Add `ScannerState`:
|
|
- `CurrentSpeakingTG`
|
|
- `HoldExpiry`
|
|
- [ ] Add `Subscriptions` container (list/map).
|
|
|
|
### Reflector Logic (`DMRMMDVMProtocol.cpp`)
|
|
|
|
- [ ] **Options Parsing**:
|
|
- Parse "Options" string (e.g., `TS1=4001;AUTO=600`) from RPTC Description/Password.
|
|
- [ ] **Incoming Packet (`OnDvHeaderPacketIn`)**:
|
|
- If `!XlxCompatibility`:
|
|
- **Validate**: TG must be in `ModuleMap` or 4000.
|
|
- **Unsubscribe**: If TG 4000, remove subscription (or all depending on logic).
|
|
- **Subscribe**:
|
|
- Thread-safe update of subscriptions via `CDMRScanner`.
|
|
- **First PTT Logic**: If this is a *new* dynamic subscription, flag stream as `Muted` or don't propagate.
|
|
- [ ] **Outgoing/Queue Handling (`HandleQueue`)**:
|
|
- Filter logic:
|
|
- Thread-safe check of `CheckPacketAccess(tg)`.
|
|
- Scanner Logic handled internally in `CDMRScanner` with mutex protection.
|
|
|
|
## Architecture Diagram
|
|
|
|
```mermaid
|
|
graph TD
|
|
Client[MMDVM Client] -->|UDP Packet| Protocol[DMRMMDVMProtocol]
|
|
Protocol -->|Parse Header| CheckMode{XlxCompatibility?}
|
|
|
|
%% XLX Path
|
|
CheckMode -->|True| XLXLogic[Legacy XLX Logic]
|
|
XLXLogic -->|TG 9| Core[Reflector Core]
|
|
|
|
%% Mini DMR Path
|
|
CheckMode -->|False| MiniLogic[Mini DMR Logic]
|
|
|
|
subgraph CDMRScanner ["class CDMRScanner"]
|
|
MiniLogic -->|Check Access| ScannerState{State Check}
|
|
ScannerState -->|Blocked| Drop[Drop Packet]
|
|
ScannerState -->|Allowed| UpdateTimer[Update Hold Timer]
|
|
end
|
|
|
|
UpdateTimer -->|Mapped TG| Core
|
|
|
|
%% Configuration Flow
|
|
Config[RPTC Packet] -->|Description/Opts| Parser[Options Parser]
|
|
Parser -->|Update| Subs[Subscription List]
|
|
Subs -.-> ScannerState
|
|
```
|
|
|
|
## Cross-Protocol Traffic Flow (Outbound)
|
|
|
|
```mermaid
|
|
graph TD
|
|
Src[Source Protocol e.g. YSF] -->|Audio on Module B| Core[Reflector Core]
|
|
Core -->|Queue Packet| DMRQueue[DMRMMDVMProtocol::HandleQueue]
|
|
|
|
subgraph "Handle Queue Logic"
|
|
DMRQueue --> Encode1[Encode Buffer TS1]
|
|
DMRQueue --> Encode2[Encode Buffer TS2]
|
|
|
|
Encode1 --> ClientCheck{Client Subscribed?}
|
|
Encode2 --> ClientCheck
|
|
|
|
ClientCheck -->|TG + TS1| Send1[Send TS1 Buffer]
|
|
ClientCheck -->|TG + TS2| Send2[Send TS2 Buffer]
|
|
ClientCheck -->|No| Drop[Drop]
|
|
end
|
|
|
|
Send1 --> Client[MMDVM Client]
|
|
Send2 --> Client
|
|
``` %% Mini DMR Logic
|
|
MapLookup -->|Yes| Map[Map Module B -> TG 4002]
|
|
Map -->|TG 4002| ScannerCheck{Scanner Check}
|
|
|
|
subgraph CDMRScanner
|
|
ScannerCheck -->|Client Subscribed?| SubCheck{Subscribed?}
|
|
SubCheck -->|No| Drop[Drop]
|
|
SubCheck -->|Yes| HoldCheck{Hold Timer Active?}
|
|
|
|
HoldCheck -->|Held by other TG| Drop
|
|
HoldCheck -->|Free / Same TG| Allowed[Allow]
|
|
end
|
|
|
|
Allowed --> SendMini[Send UDP Packet TG 4002]
|
|
```
|
|
|
|
## Architecture Decision
|
|
|
|
- **Unified Protocol Class**: We will keep `DMRMMDVMProtocol` as the single class handling the UDP/DMR wire protocol.
|
|
- **Reasoning**: Both "XLX" and "Mini DMR" modes share identical packet structures, parsing, connection handshakes (RPTL/RPTK), and keepalive mechanisms. Splitting them would require either duplicating this transport logic or creating a complex inheritance hierarchy.
|
|
- **Logic Separation**: instead of polluting `DMRMMDVMProtocol.cpp` with mixed logic:
|
|
- **Legacy/XLX Logic**: Remains inline (simple routing 9->9).
|
|
- **New/Mini Logic**: Encapsulated in `CDMRScanner`. The Protocol class will call checking methods on the scanner.
|
|
- **Toggle**: A simple `if (m_XlxCompatibility)` check at the routing decision points (packet ingress/egress) will switch behavior.
|
|
|
|
## Safety & Robustness Logic
|
|
|
|
- **Concurrency**:
|
|
- `CDMRScanner` will encapsulate all state (`Subscriptions`, `HoldTimer`, `CurrentTG`) protected by an internal `std::recursive_mutex`.
|
|
- **Deadlock Prevention**: `CDMRScanner` methods will be leaf-node operations (never calling out to other complex locked systems).
|
|
- Access to `CDMRScanner` from `DMRMMDVMProtocol` will be done via thread-safe public methods only.
|
|
- **Memory Safety**:
|
|
- Avoid raw `char*` manipulation for Options parsing; use `std::string`.
|
|
- Input Description field will be clamped to `RPTC` max length (checked in `IsValidConfigPacket` before parsing).
|
|
- No fixed-size buffers for variable lists (use `std::vector` for TGs).
|
|
|
|
## Testing Strategy (TDD)
|
|
|
|
- **Objective**: Verify complex logic (Subscription management, Timeout, Scanner checks) in isolation without needing full network stack (mocking `DMRMMDVMProtocol/Client`).
|
|
- **Plan**:
|
|
- Create `reflector/DMRScanner.h/cpp` (or similar) to encapsulate the logic:
|
|
- `class CDMRScanner`:
|
|
- `AddSubscription(tg, ts, timeout)`
|
|
- `RemoveSubscription(tg, ts)`
|
|
- `IsSubscribed(tg)`
|
|
- `CheckPacketAccess(tg)` -> Validates against Hold timer & Single Mode.
|
|
- **Safety Tests**: Verify behavior under high-concurrency (if possible in unit test) or logic edge cases.
|
|
- Create `reflector/test_dmr.cpp`:
|
|
- A standalone test file similar to `test_audio.cpp`.
|
|
- **Scenarios**:
|
|
1. **Single Mode**: Add TG1, Add TG2 -> Assert TG1 removed.
|
|
2. **Scanner Hold**: Packet from TG1 accepted. Immediately Packet from TG2 -> Rejected (Hold active). Wait 5s -> Packet from TG2 Accepted.
|
|
3. **Timeout**: Add TG dynamic (timeout 1s). Wait 2s -> Assert TG removed.
|
|
4. **Options Parsing**: Feed "TS1=1,2;AUTO=300" string -> Verify Subscriptions present.
|
|
5. **Buffer Safety**: Feed malformed/oversized Option strings -> Verify no crash/leak.
|
|
- **Build**: Add `test_dmr` target to `Makefile`.
|
|
|
|
## Verification Plan
|
|
|
|
- [ ] **Run TDD Tests**: `make test_dmr && ./reflector/test_dmr`
|
|
- [ ] **Manual Verification**:
|
|
- **Test Configurations**:
|
|
- Single Mode: Verify PTT on TG A drops TG B.
|
|
- Multi Mode: Verify PTT on A adds A (keeping B).
|
|
- **Test Scanner**:
|
|
- Sub to A and B. Transmit on A. Verify B is blocked during Hold time.
|
|
- **Test Timeout**:
|
|
- Set short timeout. Verify subscription drops.
|
|
- **Test Kerchunk**:
|
|
- PTT on new TG. Verify not heard by others. Second PTT heard.
|