update techincal docs;

pull/121/head
Bryan Biedenkapp 4 weeks ago
parent b7f55d9323
commit 18ac540cd2

@ -1,8 +1,8 @@
# DVM Network Stack Technical Documentation # DVM Network Stack Technical Documentation
**Version:** 1.0 **Version:** 1.1
**Date:** December 3, 2025 **Date:** May 8, 2026
**Author:** AI Assistant (based on source code analysis) **Author:** AI Assistant (updated based on current source code analysis)
AI WARNING: This document was mainly generated using AI assistance. As such, there is the possibility of some error or inconsistency. AI WARNING: This document was mainly generated using AI assistance. As such, there is the possibility of some error or inconsistency.
@ -137,14 +137,12 @@ The DVM network stack is organized into a hierarchical class structure with clea
#### Common/Core Network Classes (dvmhost/src/common/network/) #### Common/Core Network Classes (dvmhost/src/common/network/)
These classes provide the foundational networking functionality used by all DVM components (dvmhost, dvmbridge, dvmpatch, dvmfne): These classes provide the foundational networking functionality used by all DVM components (dvmhost, dvmbridge, dvmpatch, and dvmfne):
``` ```
BaseNetwork (Abstract Base Class) BaseNetwork (Abstract Base Class)
├── Network (Peer Implementation - for dvmhost, dvmbridge, dvmpatch) ├── Network (Peer Implementation - for dvmhost, dvmbridge, dvmpatch)
└── FNENetwork (Master Implementation - for dvmfne only)
Core Supporting Classes: Core Supporting Classes:
├── FrameQueue (RTP Frame Management) ├── FrameQueue (RTP Frame Management)
@ -156,18 +154,22 @@ Core Supporting Classes:
├── RTPStreamMultiplex (Multi-Stream Management) ├── RTPStreamMultiplex (Multi-Stream Management)
├── PacketBuffer (Fragmentation/Reassembly) ├── PacketBuffer (Fragmentation/Reassembly)
├── NetRPC / RPCHeader (RPC Transport and Framing Helpers)
├── AdaptiveJitterBuffer (Per-Stream Reordering/Jitter Management)
└── udp::Socket (UDP Transport Layer) └── udp::Socket (UDP Transport Layer)
``` ```
#### FNE-Specific Network Classes (dvmhost/src/fne/network/) #### FNE-Specific Network Classes (dvmhost/src/fne/network/)
These classes extend the core functionality specifically for FNE master operations: These classes extend the core functionality specifically for current FNE runtime operations:
``` ```
FNENetwork (Extends BaseNetwork) TrafficNetwork (Extends BaseNetwork)
├── DiagNetwork (Diagnostic Port Handler) ├── MetadataNetwork (Metadata / Activity / Diagnostic Transfer Listener)
│ └── Uses BaseNetwork functionality on alternate port ├── PeerNetwork (Extends Network for Upstream/Neighbor FNE Links)
├── FNEPeerConnection (Per-Peer Session State, RTPStreamMultiplex)
├── SpanningTree (Peer Topology State)
└── Call Handlers (Traffic Routing & Management): └── Call Handlers (Traffic Routing & Management):
├── TagDMRData (DMR Protocol Handler) ├── TagDMRData (DMR Protocol Handler)
@ -179,15 +181,17 @@ FNENetwork (Extends BaseNetwork)
#### Class Usage by Component: #### Class Usage by Component:
| Class | dvmhost | dvmbridge | dvmpatch | dvmfne | | Class | dvmhost | dvmbridge | dvmpatch | dvmfne |
|-------|---------|-----------|----------|--------| |-------|---------|-----------|----------|--------------------|
| BaseNetwork | ✓ | ✓ | ✓ | ✓ | | BaseNetwork | ✓ | ✓ | ✓ | ✓ |
| Network | ✓ | ✓ | ✓ | ✗ | | Network | ✓ | ✓ | ✓ | ✗ |
| FNENetwork | ✗ | ✗ | ✗ | ✓ | | TrafficNetwork | ✗ | ✗ | ✗ | ✓ |
| MetadataNetwork | ✗ | ✗ | ✗ | ✓ |
| PeerNetwork | ✗ | ✗ | ✗ | ✓ |
| FrameQueue | ✓ | ✓ | ✓ | ✓ | | FrameQueue | ✓ | ✓ | ✓ | ✓ |
| RTPHeader/FNEHeader | ✓ | ✓ | ✓ | ✓ | | RTPHeader/FNEHeader | ✓ | ✓ | ✓ | ✓ |
| DiagNetwork | ✗ | ✗ | ✗ | ✓ |
| TagXXXData | ✗ | ✗ | ✗ | ✓ | | TagXXXData | ✗ | ✗ | ✗ | ✓ |
| FNEPeerConnection | ✗ | ✗ | ✗ | ✓ | | FNEPeerConnection | ✗ | ✗ | ✗ | ✓ |
| SpanningTree | ✗ | ✗ | ✗ | ✓ |
### BaseNetwork Class ### BaseNetwork Class
@ -251,35 +255,50 @@ private:
}; };
``` ```
### FNENetwork Class (Master) ### TrafficNetwork / MetadataNetwork / PeerNetwork Classes
Current FNE runtime networking is split across multiple classes rather than a single `FNENetwork` implementation:
The `FNENetwork` class implements master-side functionality for managing connected peers: - **TrafficNetwork**: Primary master-side traffic/control plane. Manages connected peers, routing, affiliations, ACL distribution, spanning tree state, HA state, and protocol call handlers.
- **MetadataNetwork**: Separate metadata/activity/diagnostic/status listener bound to `master.port + 1`, sharing the same peer/runtime context as `TrafficNetwork`.
- **PeerNetwork**: Upstream or neighbor FNE client link implementation used when this FNE also peers to other masters.
- **Peer Management**: Track connected peers, their capabilities, and metadata Representative current layout:
- **Call Routing**: Route traffic between peers based on talkgroup affiliations
- **Access Control**: Whitelist/blacklist management for RIDs and talkgroups
- **Network Replication**: Distribute peer lists, talkgroup rules, and RID lookups
- **Spanning Tree**: Optional spanning tree protocol for complex network topologies
- **Thread Pool**: Worker threads for asynchronous packet processing
```cpp ```cpp
class FNENetwork : public BaseNetwork { class TrafficNetwork : public BaseNetwork {
private: private:
std::unordered_map<uint32_t, FNEPeerConnection*> m_peers; concurrent::shared_unordered_map<uint32_t, FNEPeerConnection*> m_peers;
std::unordered_map<uint32_t, uint32_t> m_ccPeerMap; concurrent::unordered_map<uint32_t, fne_lookups::AffiliationLookup*> m_peerAffiliations;
concurrent::shared_unordered_map<uint32_t, std::vector<uint32_t>> m_ccPeerMap;
lookups::RadioIdLookup* m_ridLookup; lookups::RadioIdLookup* m_ridLookup;
lookups::TalkgroupRulesLookup* m_tidLookup; lookups::TalkgroupRulesLookup* m_tidLookup;
lookups::PeerListLookup* m_peerListLookup; lookups::PeerListLookup* m_peerListLookup;
ThreadPool m_threadPool; // Worker threads SpanningTree* m_treeRoot;
Timer m_maintainenceTimer; // Peer maintenance timer ThreadPool m_threadPool;
Timer m_maintainenceTimer;
bool m_enableSpanningTree; // Spanning tree enabled
bool m_disallowU2U; // Disallow unit-to-unit calls bool m_enableSpanningTree;
bool m_disallowU2U;
};
class MetadataNetwork : public BaseNetwork {
private:
TrafficNetwork* m_trafficNetwork;
ThreadPool m_threadPool;
};
class PeerNetwork : public Network {
private:
lookups::PeerListLookup* m_pidLookup;
ThreadPool m_threadPool;
}; };
``` ```
At the application level, `HostFNE` owns `m_network` (`TrafficNetwork`), `m_mdNetwork` (`MetadataNetwork`), and `m_peerNetworks` (`PeerNetwork` map). Startup order is `createMasterNetwork()`, then `initializeRESTAPI()`, then `createPeerNetworks()`.
--- ---
## 3. Network Protocol Layers ## 3. Network Protocol Layers
@ -291,7 +310,7 @@ The DVM network stack implements a layered protocol architecture:
- **Socket Operations**: Standard UDP datagram socket (IPv4/IPv6) - **Socket Operations**: Standard UDP datagram socket (IPv4/IPv6)
- **Buffer Sizes**: Configurable send/receive buffers (default 512KB) - **Buffer Sizes**: Configurable send/receive buffers (default 512KB)
- **Non-Blocking**: Asynchronous I/O for high-throughput scenarios - **Non-Blocking**: Asynchronous I/O for high-throughput scenarios
- **Encryption**: Optional AES-256 encryption at transport layer - **Encryption**: Optional AES-256-ECB UDP payload wrapping with preshared key and `0xC0FE` packet leader
### Layer 2: RTP (Real-time Transport Protocol) ### Layer 2: RTP (Real-time Transport Protocol)
@ -647,6 +666,7 @@ Peer ID (4 bytes) + Reserved (4 bytes) + Reason Code (2 bytes)
| 6 | PEER_RESET | Warning | FNE demands connection reset | Reset connection and re-login | | 6 | PEER_RESET | Warning | FNE demands connection reset | Reset connection and re-login |
| 7 | PEER_ACL | **Fatal** | Peer rejected by Access Control List | **Disable network - peer is banned** | | 7 | PEER_ACL | **Fatal** | Peer rejected by Access Control List | **Disable network - peer is banned** |
| 8 | FNE_MAX_CONN | Warning | FNE has reached maximum permitted connections | Wait and retry later | | 8 | FNE_MAX_CONN | Warning | FNE has reached maximum permitted connections | Wait and retry later |
| 9 | FNE_DUPLICATE_CONN | Warning | FNE detected this peer as a duplicate connection | Disconnect, clear remote peer state, and retry with duplicate-connection backoff |
**Severity Levels:** **Severity Levels:**
@ -663,6 +683,8 @@ Peer ID (4 bytes) + Reserved (4 bytes) + Reason Code (2 bytes)
- `FNE_MAX_CONN` (Code 8): If received while in `NET_STAT_RUNNING` state, indicates the FNE is overloaded or shutting down. Peer should implement exponential backoff before reconnecting. - `FNE_MAX_CONN` (Code 8): If received while in `NET_STAT_RUNNING` state, indicates the FNE is overloaded or shutting down. Peer should implement exponential backoff before reconnecting.
- `FNE_DUPLICATE_CONN` (Code 9): The peer transitions to `NET_STAT_WAITING_CONNECT`, clears `m_remotePeerId`, sets the duplicate-connection flag, reduces retry count thresholds, and uses the extended duplicate-connection reconnect delay (`DUPLICATE_CONN_RETRY_TIME`) to avoid rapid reconnect storms.
### NET_SUBFUNC: Secondary Function Codes ### NET_SUBFUNC: Secondary Function Codes
#### Protocol Sub-Functions (PROTOCOL) #### Protocol Sub-Functions (PROTOCOL)
@ -672,6 +694,7 @@ Peer ID (4 bytes) + Reserved (4 bytes) + Reason Code (2 bytes)
| 0x00 | PROTOCOL_SUBFUNC_DMR | DMR protocol data | | 0x00 | PROTOCOL_SUBFUNC_DMR | DMR protocol data |
| 0x01 | PROTOCOL_SUBFUNC_P25 | P25 protocol data | | 0x01 | PROTOCOL_SUBFUNC_P25 | P25 protocol data |
| 0x02 | PROTOCOL_SUBFUNC_NXDN | NXDN protocol data | | 0x02 | PROTOCOL_SUBFUNC_NXDN | NXDN protocol data |
| 0x03 | PROTOCOL_SUBFUNC_P25_P2 | P25 Phase 2 protocol data |
| 0x0F | PROTOCOL_SUBFUNC_ANALOG | Analog audio data | | 0x0F | PROTOCOL_SUBFUNC_ANALOG | Analog audio data |
#### Master Sub-Functions (MASTER) #### Master Sub-Functions (MASTER)
@ -883,7 +906,7 @@ Byte Offset | Field | Size | Description
**RPTC Configuration JSON Schema:** **RPTC Configuration JSON Schema:**
The RPTC configuration uses a nested JSON structure with three main object groups: `info` (system information), `channel` (RF parameters), and `rcon` (remote control/REST API). The RPTC configuration uses a nested JSON structure with three main object groups: `info` (system information), `channel` (RF parameters), and `rcon` (remote control/REST API). Current peers also include the top-level `peerClass` field, which is the canonical way to describe the peer connection class.
```json ```json
{ {
@ -915,14 +938,26 @@ The RPTC configuration uses a nested JSON structure with three main object group
"password": "string", // REST API password "password": "string", // REST API password
"port": 0 // REST API port "port": 0 // REST API port
}, },
"peerClass": 2, // Canonical peer connection class enum
"externalPeer": false, // External network peer flag (optional) "externalPeer": false, // Legacy compatibility flag for neighbor peers
"conventionalPeer": false, // Conventional (non-trunked) mode (optional) "conventionalPeer": false, // Conventional (non-trunked) mode (optional)
"sysView": false, // SysView monitoring peer flag (optional) "sysView": false, // Legacy compatibility flag for SysView peers
"consolePeer": false, // Reporting flag for console peers
"software": "string" // Software identifier (optional) "software": "string" // Software identifier (optional)
} }
``` ```
**PEER_CONN_CLASS Enumeration:**
- **0 (`PEER_CONN_CLASS_UNKNOWN`)**: Unknown class. The FNE normalizes this to `PEER_CONN_CLASS_STANDARD` when processing RPTC.
- **1 (`PEER_CONN_CLASS_NEIGHBOR`)**: Downstream neighbor FNE peer used for FNE-to-FNE interconnects.
- **2 (`PEER_CONN_CLASS_STANDARD`)**: Standard RF site peer. This is the default value emitted by current peer implementations.
- **3 (`PEER_CONN_CLASS_SYSVIEW`)**: SysView monitoring peer.
- **4 (`PEER_CONN_CLASS_CONSOLE`)**: Console client peer.
- **5 (`PEER_CONN_CLASS_INVALID`)**: Sentinel only. Values greater than or equal to this are treated as invalid and normalized to `PEER_CONN_CLASS_STANDARD` by the FNE.
**Example RPTC Configuration:** **Example RPTC Configuration:**
```json ```json
@ -955,10 +990,13 @@ The RPTC configuration uses a nested JSON structure with three main object group
"password": "api_secret", "password": "api_secret",
"port": 9990 "port": 9990
}, },
"peerClass": 2,
"externalPeer": false, "externalPeer": false,
"conventionalPeer": false, "conventionalPeer": false,
"sysView": false, "sysView": false,
"consolePeer": false,
"software": "DVMHOST_R04A00" "software": "DVMHOST_R04A00"
} }
``` ```
@ -968,9 +1006,11 @@ The RPTC configuration uses a nested JSON structure with three main object group
**Top-Level Fields:** **Top-Level Fields:**
- **identity**: Unique identifier for the peer (callsign, site name, etc.) - **identity**: Unique identifier for the peer (callsign, site name, etc.)
- **rxFrequency/txFrequency**: Operating frequencies in Hertz - **rxFrequency/txFrequency**: Operating frequencies in Hertz
- **externalPeer**: Indicates peer is outside the primary network (affects routing) - **peerClass**: Canonical peer connection class. Current peers should send this field rather than relying on legacy boolean flags.
- **externalPeer**: Legacy compatibility flag indicating `PEER_CONN_CLASS_NEIGHBOR`
- **conventionalPeer**: Indicates non-trunked operation mode (affects grant behavior) - **conventionalPeer**: Indicates non-trunked operation mode (affects grant behavior)
- **sysView**: Indicates monitoring-only peer (affiliation viewer, no traffic routing) - **sysView**: Legacy compatibility flag indicating `PEER_CONN_CLASS_SYSVIEW`
- **consolePeer**: Reporting flag indicating `PEER_CONN_CLASS_CONSOLE`
- **software**: Software version string (e.g., `DVMHOST_R04A00`) for compatibility checking - **software**: Software version string (e.g., `DVMHOST_R04A00`) for compatibility checking
**System Information Object (`info`):** **System Information Object (`info`):**
@ -998,10 +1038,13 @@ The RPTC configuration uses a nested JSON structure with three main object group
The FNE master stores the complete configuration JSON in the peer connection object (`FNEPeerConnection::config`) and extracts specific fields for connection management: The FNE master stores the complete configuration JSON in the peer connection object (`FNEPeerConnection::config`) and extracts specific fields for connection management:
- `identity` → Used for peer identification in logs and routing tables - `identity` → Used for peer identification in logs and routing tables
- `software` → Logged for version tracking and compatibility checks - `software` → Logged for version tracking and compatibility checks
- `sysView` → Determines if peer is monitoring-only (no traffic routing) - `peerClass` → Primary peer classification used to determine routing and peer behavior
- `externalPeer` → Used for spanning tree routing decisions (external peers have special routing rules) - `sysView` / `externalPeer` → Accepted for backward compatibility with older peers and translated into the corresponding `peerClass`
- `consolePeer` → Emitted by the FNE when reporting a console-class peer configuration
- `conventionalPeer` → Affects talkgroup affiliation and grant behavior (conventional peers don't require grants) - `conventionalPeer` → Affects talkgroup affiliation and grant behavior (conventional peers don't require grants)
If `peerClass` is absent, the FNE falls back to legacy `sysView` and `externalPeer` booleans. If `peerClass` is `PEER_CONN_CLASS_UNKNOWN` or greater than or equal to `PEER_CONN_CLASS_INVALID`, the FNE normalizes it to `PEER_CONN_CLASS_STANDARD`. After parsing, the FNE reports the resolved class back through the stored configuration JSON and backfills the legacy boolean fields for compatibility with older REST API consumers.
**Step 4: ACK/NAK Response** **Step 4: ACK/NAK Response**
Master responds with ACK (success) or NAK (failure): Master responds with ACK (success) or NAK (failure):
@ -1551,6 +1594,63 @@ The difference between **TDU** and **TDULC**:
- **TDU:** Simple terminator, no additional payload (24 bytes total) - **TDU:** Simple terminator, no additional payload (24 bytes total)
- **TDULC:** Terminator with embedded link control (78 bytes total), provides complete call metadata at termination - **TDULC:** Terminator with embedded link control (78 bytes total), provides complete call metadata at termination
**P25 Phase 2 (TDMA Voice/Data) Network Format:**
P25 Phase 2 traffic is carried under its own network subfunction, `NET_SUBFUNC::PROTOCOL_SUBFUNC_P25_P2 (0x03)`, rather than the classic `PROTOCOL_SUBFUNC_P25` path. `BaseNetwork::writeP25P2()` creates the encapsulated packet and `Network::clock()` handles it in a dedicated receive branch with separate per-slot receive stream tracking.
The encapsulated Phase 2 format is fixed-length:
| Offset | Length | Field | Description |
|--------|--------|-------|-------------|
| 0-23 | 24 | Message Header | Standard P25 network header created by `createP25_MessageHdr()` |
| 24-63 | 40 | P25 Phase 2 Frame Data | Raw Phase 2 payload (`P25_P2_FRAME_LENGTH_BYTES = 40U`) |
**Total P25 Phase 2 Size:** 66 bytes (`P25_P2_PACKET_LENGTH = 66U`)
**Constants:**
- `PROTOCOL_SUBFUNC_P25_P2 = 0x03U` (defined in `RTPFNEHeader.h`)
- `P25_P2_PACKET_LENGTH = 66U` (defined in `BaseNetwork.h`)
- `P25_P2_FRAME_LENGTH_BYTES = 40U` (defined in `P25Defines.h`)
- `MSG_HDR_SIZE = 24U`
- `PACKET_PAD = 8U`
- Total allocated size: 66 + 8 = 74 bytes
**Header Semantics:**
`createP25P2_Message()` starts from the normal P25 message header, then overlays Phase 2-specific metadata:
- The common P25 header is created with `DUID::PDU` and `FrameType::DATA_UNIT`.
- `buffer[14]` carries the DVM control byte.
- `buffer[19]` carries Phase 2 signaling:
- Bit 7 is the slot marker inspected by the receive path.
- The low bits carry the `p25::defines::P2_DUID` value.
- `buffer[23]` contains the encapsulated data count, which is `0x40` (64 bytes: 24-byte header + 40-byte Phase 2 payload).
**Receive-Side Behavior in `Network.cpp`:**
- `Network::clock()` routes `PROTOCOL_SUBFUNC_P25_P2` into `m_rxP25P2Data`, separate from classic `m_rxP25Data`.
- The receive path derives the slot from bit 7 of byte 19 and maintains one active receive stream per slot in `m_rxP25P2StreamId[2]`.
- Promiscuous peers validate multiplexed RTP sequencing per stream using `RTPStreamMultiplex::verifyStream()`.
- Non-promiscuous peers lock a slot to the first active Phase 2 stream until an end-of-call RTP sequence is observed.
- Oversized frames are logged when they exceed `P25_P2_PACKET_LENGTH + PACKET_PAD`.
**Raw P25 Phase 2 Packet Example (layout):**
```
[RTP: 12 bytes] [FNE: 20 bytes] [P25 Phase 2: 66 bytes]
Complete Packet (98 bytes):
[RTP 12] [FNE 20] [P25 header 24 with slot/DUID in byte 19] [40 bytes raw P25 P2 payload] [packet pad]
├─────────── RTP (12) ───────────┤ ├──────────────────────── FNE (20) ────────────────────────┤ ├──────────────────────────── P25 P2 (66) ────────────────────────────┤
P25 Phase 2 payload breakdown:
"P25D" | common P25 header fields | controlByte @ 14 | slot/DUID @ 19 | count=0x40 @ 23 | 40 bytes raw P25 Phase 2 payload
```
**Implementation Note:**
P25 Phase 2 is a separate transport lane in DVM documentation because it has its own subfunction code, receive buffer, reset path (`resetP25P2(slotNo)`), and per-slot stream tracking even though it reuses the generic P25 message header builder.
#### NXDN Message Format #### NXDN Message Format
NXDN frames use a fixed-length network format created by `createNXDN_Message()` in `BaseNetwork.cpp`. The frame size is 70 bytes as defined by `NXDN_PACKET_LENGTH = 70U`. NXDN frames use a fixed-length network format created by `createNXDN_Message()` in `BaseNetwork.cpp`. The frame size is 70 bytes as defined by `NXDN_PACKET_LENGTH = 70U`.
@ -1629,7 +1729,7 @@ The actual NXDN frame data (48 bytes) contains the over-the-air NXDN frame struc
#### Analog Message Format #### Analog Message Format
Analog audio frames use G.711 μ-law encoding and are created by `createAnalog_Message()` in `BaseNetwork.cpp`. The frame size is 324 bytes as defined by `ANALOG_PACKET_LENGTH = 324U`. Analog audio frames are created by `createAnalog_Message()` in `BaseNetwork.cpp`. The current network frame size is 344 bytes as defined by `ANALOG_PACKET_LENGTH = 344U`.
```cpp ```cpp
bool BaseNetwork::writeAnalog(const analog::AnalogData& data) { bool BaseNetwork::writeAnalog(const analog::AnalogData& data) {
@ -1646,11 +1746,11 @@ bool BaseNetwork::writeAnalog(const analog::AnalogData& data) {
**Analog Packet Structure:** **Analog Packet Structure:**
The analog audio packet uses the TAG_ANALOG_DATA identifier and contains 300 bytes of audio samples (calculated as 324 - 20 header - 4 trailer). The analog audio packet uses the `TAG_ANALOG_DATA` identifier (`"ANOD"`) and contains 320 bytes of audio payload (`AUDIO_SAMPLES_LENGTH_BYTES = 320U`). The inline comments in current analog code describe this as 20 ms of audio at 8 kHz, while `analog::data::NetData` still notes that the audio buffer is expected to be MuLaw encoded. The network builder itself follows the 320-byte payload constant.
| Offset | Length | Field | Description | | Offset | Length | Field | Description |
|--------|--------|-------|-------------| |--------|--------|-------|-------------|
| 0-3 | 4 | Analog Tag | "ADIO" (0x4144494F) or similar ASCII identifier | | 0-3 | 4 | Analog Tag | "ANOD" (`TAG_ANALOG_DATA`) |
| 4 | 1 | Sequence Number | Packet sequence number for audio ordering (0-255) | | 4 | 1 | Sequence Number | Packet sequence number for audio ordering (0-255) |
| 5-7 | 3 | Source ID | Radio ID of transmitting station | | 5-7 | 3 | Source ID | Radio ID of transmitting station |
| 8-10 | 3 | Destination ID | Target radio ID or conference ID | | 8-10 | 3 | Destination ID | Target radio ID or conference ID |
@ -1658,51 +1758,33 @@ The analog audio packet uses the TAG_ANALOG_DATA identifier and contains 300 byt
| 14 | 1 | Control Byte | Network control flags | | 14 | 1 | Control Byte | Network control flags |
| 15 | 1 | Frame Type / Group | Bit 7: Group flag (0=private, 1=group)<br/>Bits 0-6: Audio frame type | | 15 | 1 | Frame Type / Group | Bit 7: Group flag (0=private, 1=group)<br/>Bits 0-6: Audio frame type |
| 16-19 | 4 | Reserved | Reserved for future use (0x00000000) | | 16-19 | 4 | Reserved | Reserved for future use (0x00000000) |
| 20-319 | 300 | Audio Data | G.711 μ-law encoded audio samples (300 bytes @ 8kHz) | | 20-339 | 320 | Audio Data | Analog network audio payload (`AUDIO_SAMPLES_LENGTH_BYTES`) |
| 320-323 | 4 | Trailer | Reserved trailer bytes | | 340-343 | 4 | Trailer | Reserved trailer bytes |
**Total Analog Payload Size:** 324 bytes (`ANALOG_PACKET_LENGTH`) **Total Analog Payload Size:** 344 bytes (`ANALOG_PACKET_LENGTH`)
**Constants:** **Constants:**
- `ANALOG_PACKET_LENGTH = 324U` (defined in `BaseNetwork.h`: 20 byte header + 300 byte audio + 4 byte trailer) - `ANALOG_PACKET_LENGTH = 344U` (defined in `BaseNetwork.h`: 20 byte header + `AUDIO_SAMPLES_LENGTH_BYTES` + 4 byte trailer)
- Audio sample size: 300 bytes (AUDIO_SAMPLES_LENGTH_BYTES, calculated as 324 - 20 - 4) - `AUDIO_SAMPLES_LENGTH_BYTES = 320U`
- `PACKET_PAD = 8U` - `PACKET_PAD = 8U`
- Total allocated size: 324 + 8 = 332 bytes - Total allocated size: 344 + 8 = 352 bytes
**Audio Encoding Details:** **Audio Encoding Details:**
- **Codec:** G.711 μ-law (ITU-T G.711) - **Payload Length:** 320 bytes
- **Sample Rate:** 8 kHz (8000 samples per second) - **Sample Rate:** 8 kHz (8000 samples per second)
- **Bit Depth:** 8 bits per sample (1 byte per sample) - **Implementation Note:** `AnalogDefines.h` describes this as 20 ms of 16-bit audio at 8 kHz; `NetData.h` describes the buffer passed into `setAudio()` as MuLaw encoded. The network packetizer currently uses the 320-byte constant and copies that payload verbatim.
- **Frame Duration:** 37.5 ms (300 samples ÷ 8000 samples/sec = 0.0375 sec)
- **Samples per Frame:** 300 samples
- **Bandwidth:** 64 kbit/s (8000 samples/sec × 8 bits/sample)
**Raw Analog Packet Example (hexadecimal):** **Raw Analog Packet Example (layout):**
``` ```
[RTP Header: 12 bytes] [FNE Header: 20 bytes] [Analog Payload: 324 bytes] [RTP Header: 12 bytes] [FNE Header: 20 bytes] [Analog Payload: 344 bytes]
Complete Packet (356 bytes): Complete Packet (376 bytes):
90 FE 00 2A 00 00 1F 40 00 00 4E 20 | A3 5C 00 03 12 34 56 78 00 00 4E 20 01 44 00 00 00 00 00 00 | 41 44 49 4F 05 00 10 00 00 20 00 00 01 00 00 00 80 00 00 00 [300 bytes G.711 audio...] 00 00 00 00 [RTP 12] [FNE 20] ["ANOD" + header bytes + 320 bytes audio + 4 byte trailer]
├─────────── RTP (12) ───────────┤ ├──────────────────────── FNE (20) ────────────────────────┤ ├────────────────────────────────── Analog (324) ──────────────────────────────────┤ ├─────────── RTP (12) ───────────┤ ├──────────────────────── FNE (20) ────────────────────────┤ ├────────────────────────────────── Analog (344) ──────────────────────────────────┤
Analog Payload breakdown: Analog Payload breakdown:
41 44 49 4F 05 00 10 00 00 20 00 00 01 00 00 00 80 00 00 00 [300 bytes audio data...] 00 00 00 00 "ANOD" | Seq | SrcID | DstID | Reserved | Control | FrameType/Group | Reserved | 320 bytes audio | 4 byte trailer
│ │ │ │ │ │ │ │ │ │ │ └──────┴─ Reserved (4 bytes)
│ │ │ │ │ │ │ │ │ │ └─────────────── Frame Type/Group: 0x80 (group call, frame type 0)
│ │ │ │ │ │ │ │ │ └──────────────────── Control: 0x01
│ │ │ │ │ │ │ └──┴────────────────────── Reserved (3 bytes)
│ │ │ │ │ └─[DestID 3rd]──────────────────────── Dest ID: 0x200000 (2097152, conference)
│ │ │ │ └─[DestID 2nd]
│ │ │ └─[DestID 1st]
│ │ └─[SrcID 3rd]──────────────────────────────────────── Src ID: 0x001000 (4096)
│ └─[SrcID 2nd]
│ └─[SrcID 1st]
│ └──────────────────────────────────────────────────────── Sequence: 0x05 (5)
└───────────────────────────────────────────────────────────────── Tag: "ADIO" (0x4144494F)
└────────────────────────────────────────────────────────── Reserved (4 bytes)
└────── Audio samples (300 bytes)
└─── Trailer (4 bytes)
G.711 μ-law Audio Sample Example (first 16 bytes of audio data shown): G.711 μ-law Audio Sample Example (first 16 bytes of audio data shown):
FF FE FD FC FB FA F9 F8 F7 F6 F5 F4 F3 F2 F1 F0 ... FF FE FD FC FB FA F9 F8 F7 F6 F5 F4 F3 F2 F1 F0 ...
@ -1710,15 +1792,9 @@ FF FE FD FC FB FA F9 F8 F7 F6 F5 F4 F3 F2 F1 F0 ...
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴─── Each byte is one 8kHz audio sample (μ-law encoded) └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴─── Each byte is one 8kHz audio sample (μ-law encoded)
``` ```
**G.711 μ-law Encoding:** The current analog network payload is sized for 320 bytes per frame, with the packetizer copying the application-provided buffer directly into the on-wire message.
G.711 μ-law is a logarithmic audio compression standard that provides toll-quality voice at 64 kbit/s:
- **Range:** ±8159 linear PCM values compressed to 256 μ-law values (0x00-0xFF)
- **Characteristics:** Non-linear quantization favoring lower amplitude signals (human speech)
- **Compatibility:** Standard telephony codec, widely supported
- **Signal-to-Noise Ratio:** ~37 dB for speech signals
The 300-byte audio payload represents 37.5 milliseconds of continuous audio, allowing for smooth real-time voice transmission with minimal latency. G.711 μ-law details, if used by the source audio path, remain codec-layer behavior above the network framing described here.
### Ring Buffer Architecture ### Ring Buffer Architecture
@ -1925,7 +2001,7 @@ sha256.buffer(hash, 40U, hash);
### Encryption ### Encryption
Optional AES-256-ECB encryption at transport layer: Optional AES-256-ECB encryption is implemented as UDP payload wrapping at the socket layer:
```cpp ```cpp
void Network::setPresharedKey(const uint8_t* presharedKey) { void Network::setPresharedKey(const uint8_t* presharedKey) {
@ -1946,7 +2022,10 @@ The implementation uses AES-256-ECB (Electronic Codebook mode):
- **Key Size**: 256 bits (32 bytes) - **Key Size**: 256 bits (32 bytes)
- **Block Size**: 128 bits (16 bytes) - **Block Size**: 128 bits (16 bytes)
- **Mode**: ECB - each 16-byte block encrypted independently - **Mode**: ECB - each 16-byte block encrypted independently
- **Padding**: PKCS#7 padding for variable-length payloads - **Padding**: Zero-padding to the AES block boundary
- **Packet Leader**: `0xC0FE` (`AES_WRAPPED_PCKT_MAGIC`) is prepended before encrypted payloads
**Runtime Behavior:** When preshared-key wrapping is enabled, receive logic drops datagrams that do not carry the `0xC0FE` leader. This is transport wrapping around the existing RTP/FNE payload, not SRTP.
**Note**: ECB mode is used for simplicity and performance in this implementation. While ECB has known cryptographic weaknesses (identical plaintext blocks produce identical ciphertext), the primary goal is to provide basic confidentiality for network traffic between trusted peers rather than military-grade security. For high-security deployments, additional network-layer security (IPsec, VPN) should be used. **Note**: ECB mode is used for simplicity and performance in this implementation. While ECB has known cryptographic weaknesses (identical plaintext blocks produce identical ciphertext), the primary goal is to provide basic confidentiality for network traffic between trusted peers rather than military-grade security. For high-security deployments, additional network-layer security (IPsec, VPN) should be used.
@ -2199,28 +2278,30 @@ bool BaseNetwork::writePeerStatus(json::object obj) {
} }
``` ```
### Diagnostic Network Port ### Metadata Network Port
FNE master uses separate port for diagnostics: Current FNE runtime uses a separate `MetadataNetwork` listener for activity log transfer, diagnostic transfer, status transfer, and packet-buffered replication metadata:
```cpp ```cpp
class DiagNetwork : public BaseNetwork { class MetadataNetwork : public BaseNetwork {
private: private:
FNENetwork* m_fneNetwork; TrafficNetwork* m_trafficNetwork;
uint16_t m_port; // Separate diagnostic port uint16_t m_port; // Bound to master port + 1
ThreadPool m_threadPool; ThreadPool m_threadPool;
public: public:
void processNetwork(); // Process diagnostic packets void processNetwork(); // Process metadata / transfer packets
}; };
``` ```
`HostFNE::createMasterNetwork()` creates `TrafficNetwork` on the configured master port and `MetadataNetwork` on `master.port + 1`.
**Benefits:** **Benefits:**
- Isolate diagnostic traffic from operational traffic - Isolate diagnostic traffic from operational traffic
- Different QoS/priority handling - Different QoS/priority handling
- Optional firewall rules - Optional firewall rules
- Reduced congestion on main port - Reduced congestion on main traffic port
### Network Statistics ### Network Statistics
@ -2329,7 +2410,7 @@ Key metrics tracked:
- BaseNetwork: ~20KB - BaseNetwork: ~20KB
- Network (Peer): ~50KB - Network (Peer): ~50KB
- FNENetwork (Master): ~100KB base - TrafficNetwork + MetadataNetwork (FNE runtime): ~100KB base before peer/session maps and call-handler state
- Per-peer state: ~1-2KB - Per-peer state: ~1-2KB
- **Total for 100 peers: ~300-400MB** - **Total for 100 peers: ~300-400MB**
@ -2461,16 +2542,12 @@ Peer A (Initiator) FNE Master Peer B (Recipient)
``` ```
Peer A (Initiator) FNE Master Peer B (Recipient) Peer A (Initiator) FNE Master Peer B (Recipient)
| | |
| P25 PROTOCOL (HDU) | |
| Header Data Unit | |
|-------------------------->|-------------------------->|
| | (Route based on TG) |
| | | | | |
| P25 PROTOCOL (LDU1) | | | P25 PROTOCOL (LDU1) | |
| Logical Data Unit 1 | | | Logical Data Unit 1 | |
| [Voice + LC + LSD] | | | [Voice + LC + LSD + ESS] | |
|-------------------------->|-------------------------->| |-------------------------->|-------------------------->|
| | (Route based on TG) |
| | | | | |
| P25 PROTOCOL (LDU2) | | | P25 PROTOCOL (LDU2) | |
| Logical Data Unit 2 | | | Logical Data Unit 2 | |
@ -2562,7 +2639,7 @@ Peer (dvmhost) FNE (Child) FNE (Parent/KMS)
| | | | | |
| KEY_REQ | | | KEY_REQ | |
| KeyID=0x1234 | | | KeyID=0x1234 | |
| AlgID=0x81 (AES-256) | | | AlgID=0x84 (AES-256) | |
| SrcID=123456 | | | SrcID=123456 | |
|----------------------->| | |----------------------->| |
| | KEY_REQ (forwarded) | | | KEY_REQ (forwarded) |
@ -2599,12 +2676,10 @@ Offset | Length | Field | Description
**Algorithm IDs:** **Algorithm IDs:**
- `0x80`: Unencrypted (cleartext) - `0x80`: Unencrypted (cleartext)
- `0x81`: AES-256 - `0x81`: DES-OFB
- `0x82`: DES-OFB - `0x84`: AES-256
- `0x83`: DES-XL - `0xAA`: ARC4
- `0x84`: ADP (Motorola Advanced Digital Privacy) - Other values: Vendor-specific, implementation-specific, or reserved
- `0x9F`: AES-256-GCM (custom)
- Other values: Vendor-specific or reserved
**KEY_RSP Message Structure:** **KEY_RSP Message Structure:**
@ -2650,7 +2725,7 @@ Offset | Length | Field | Description
**Important Notes:** **Important Notes:**
- KEY_REQ/KEY_RSP are for **call data encryption** (voice/data payload), not network transport encryption - KEY_REQ/KEY_RSP are for **call data encryption** (voice/data payload), not network transport encryption
- Network transport uses AES-256-ECB (see Section 7) - Network transport uses the optional AES-256-ECB UDP wrapper described in Section 9
- Keys are transmitted encrypted over the already-secured network connection - Keys are transmitted encrypted over the already-secured network connection
- Only authorized peers (verified during RPTK) can request keys - Only authorized peers (verified during RPTK) can request keys
- Key material format is algorithm-specific (AES keys, DES keys, etc.) - Key material format is algorithm-specific (AES keys, DES keys, etc.)
@ -2751,16 +2826,21 @@ network:
### Master Configuration (YAML) ### Master Configuration (YAML)
```yaml ```yaml
fne: master:
port: 62031 peerId: 9000100
diagPort: 62032 # Separate diagnostic port address: 0.0.0.0
port: 62031 # Traffic port; metadata listener uses port + 1
# Authentication
password: "SecurePassword123" # Authentication
password: "SecurePassword123"
# Scaling
workers: 8 # Thread pool size # Scaling
softConnLimit: 100 # Soft connection limit workers: 8
connectionLimit: 100
# Transport security
encrypted: false
presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
# Network Features # Network Features
spanningTree: true spanningTree: true
@ -2825,7 +2905,7 @@ fne:
**Enable Packet Dumps:** **Enable Packet Dumps:**
```cpp ```cpp
m_debug = true; // In Network or FNENetwork constructor m_debug = true; // In Network, TrafficNetwork, or MetadataNetwork constructor/config
``` ```
**Wireshark Capture:** **Wireshark Capture:**
@ -2842,7 +2922,7 @@ wireshark capture.pcap
grep "peerId = 1234567" /var/log/dvm/dvmhost.log grep "peerId = 1234567" /var/log/dvm/dvmhost.log
# Search for NAK messages # Search for NAK messages
grep "NAK" /var/log/dvm/dvmfne.log grep "NAK" /var/log/dvm/dvmhost.log
# Monitor in real-time # Monitor in real-time
tail -f /var/log/dvm/dvmhost.log | grep "NET" tail -f /var/log/dvm/dvmhost.log | grep "NET"
@ -2889,6 +2969,7 @@ tail -f /var/log/dvm/dvmhost.log | grep "NET"
| Version | Date | Changes | | Version | Date | Changes |
|---------|------|---------| |---------|------|---------|
| 1.0 | Dec 3, 2025 | Initial documentation based on source code analysis | | 1.0 | Dec 3, 2025 | Initial documentation based on source code analysis |
| 1.1 | May 8, 2026 | Update to match current state of the codebase |
--- ---

@ -1,8 +1,8 @@
# DVM FNE REST API Technical Documentation # DVM FNE REST API Technical Documentation
**Version:** 1.1 **Version:** 1.2
**Date:** December 6, 2025 **Date:** May 8, 2026
**Author:** AI Assistant (based on source code analysis) **Author:** AI Assistant (updated based on current source code analysis)
AI WARNING: This document was mainly generated using AI assistance. As such, there is the possibility of some error or inconsistency. Examples in Section 14 and Appendix C are *strictly* examples only for how the API *could* be used. AI WARNING: This document was mainly generated using AI assistance. As such, there is the possibility of some error or inconsistency. Examples in Section 14 and Appendix C are *strictly* examples only for how the API *could* be used.
@ -33,9 +33,9 @@ The DVM (Digital Voice Modem) FNE (Fixed Network Equipment) REST API provides a
### 1.1 Base Configuration ### 1.1 Base Configuration
**Default Ports:** **Default Port:**
- HTTP: User-configurable (typically 9990) - REST API listener: User-configurable via `system.restPort` (typically `9990`)
- HTTPS: User-configurable (typically 9443) - The same configured port is used for HTTP or HTTPS depending on `system.restSsl`
**Transport:** **Transport:**
- Protocol: HTTP/1.1 or HTTPS - Protocol: HTTP/1.1 or HTTPS
@ -44,7 +44,7 @@ The DVM (Digital Voice Modem) FNE (Fixed Network Equipment) REST API provides a
**SSL/TLS Support:** **SSL/TLS Support:**
- Optional HTTPS with certificate-based security - Optional HTTPS with certificate-based security
- Configurable via `keyFile` and `certFile` parameters - Configurable via `restSslKey` and `restSslCertificate`
- Uses OpenSSL when `ENABLE_SSL` is defined - Uses OpenSSL when `ENABLE_SSL` is defined
### 1.2 API Architecture ### 1.2 API Architecture
@ -52,8 +52,10 @@ The DVM (Digital Voice Modem) FNE (Fixed Network Equipment) REST API provides a
The REST API is built on: The REST API is built on:
- **Request Dispatcher:** Routes HTTP requests to appropriate handlers - **Request Dispatcher:** Routes HTTP requests to appropriate handlers
- **HTTP/HTTPS Server:** Handles network connections - **HTTP/HTTPS Server:** Handles network connections
- **Authentication Layer:** Token-based authentication using SHA-256 - **Authentication Layer:** Token-based authentication using SHA-256 password hashes sent by the client
- **Traffic Network Integration:** Connected peer state, grants, affiliations, spanning tree, and protocol command routing come from `TrafficNetwork`
- **Lookup Tables:** Radio ID, Talkgroup Rules, Peer List, Adjacent Site Map - **Lookup Tables:** Radio ID, Talkgroup Rules, Peer List, Adjacent Site Map
- **Crypto Container:** REST reload and stats support for encrypted key container state
--- ---
@ -108,16 +110,18 @@ Content-Type: application/json
``` ```
**Error Conditions:** **Error Conditions:**
- `400 Bad Request`: Invalid password, malformed auth string, or invalid characters - `400 Bad Request`: Invalid password, malformed auth field, empty auth string, auth longer than 64 characters, or invalid hex characters
- `401 Unauthorized`: Authentication failed
**Notes:** **Notes:**
- Password must be pre-hashed with SHA-256 on client side - Password must be pre-hashed with SHA-256 on client side
- The configured FNE password in YAML is plain text (`system.restPassword`); the server hashes it internally at startup and compares the client-supplied hex digest against that hash
- Token is a 64-bit unsigned integer represented as a string - Token is a 64-bit unsigned integer represented as a string
- Tokens are invalidated when: - Tokens are invalidated when:
- Client authenticates again - Client authenticates again
- Server explicitly invalidates the token - Server explicitly invalidates the token
- Server restarts - Server restarts
- Authentication tokens are bound to the request's `RemoteHost` value
- FNE truncates configured REST passwords longer than 64 characters before hashing
**Example (bash with curl):** **Example (bash with curl):**
```bash ```bash
@ -311,6 +315,7 @@ Content-Type: application/json
``` ```
**Notes:** **Notes:**
- Returns active connected peers plus any peer objects learned from upstream replica peer state
- Forces peer disconnect and requires re-authentication - Forces peer disconnect and requires re-authentication
- Useful for recovering from stuck connections - Useful for recovering from stuck connections
- Peer will need to complete RPTL/RPTK/RPTC sequence again - Peer will need to complete RPTL/RPTK/RPTC sequence again
@ -360,6 +365,91 @@ Content-Type: application/json
--- ---
### 4.5 Endpoint: PUT /peer/nak/byPeerId
**Method:** `PUT`
**Description:** Send a connection NAK to a currently known peer using its peer ID.
**Request Headers:**
```
X-DVM-Auth-Token: {token}
Content-Type: application/json
```
**Request Body:**
```json
{
"peerId": 10001,
"tag": "RPTL",
"reason": 6
}
```
**Request Fields:**
- `peerId` (required): Target peer identifier
- `tag` (required): Four-character or related network tag identifying the exchange being rejected, such as `RPTL`, `RPTK`, or `RPTC`
- `reason` (required): Numeric `NET_CONN_NAK_REASON` value
**Response:**
```json
{
"status": 200,
"message": "OK"
}
```
**Notes:**
- This endpoint sends a wire-level peer NAK through `TrafficNetwork::writePeerNAK()`
- Common reason codes include general failure, unauthorized, bad connection state, invalid config data, peer reset, peer ACL, max connections, and duplicate connection
- The handler validates only the JSON types; the caller must supply a semantically valid `tag` and `reason`
---
### 4.6 Endpoint: PUT /peer/nak/byAddress
**Method:** `PUT`
**Description:** Send a connection NAK to a peer by explicit network address/port rather than by current tracked connection object.
**Request Headers:**
```
X-DVM-Auth-Token: {token}
Content-Type: application/json
```
**Request Body:**
```json
{
"address": "192.168.1.100",
"port": 54321,
"peerId": 10001,
"tag": "RPTK",
"reason": 8
}
```
**Request Fields:**
- `address` (required): Destination IP address or hostname
- `port` (required): Destination UDP port
- `peerId` (required): Peer identifier to encode in the NAK
- `tag` (required): Network tag associated with the rejected exchange
- `reason` (required): Numeric `NET_CONN_NAK_REASON` value
**Response:**
```json
{
"status": 200,
"message": "OK"
}
```
**Notes:**
- The endpoint resolves the supplied address and port before sending the NAK
- Useful for forcing a specific rejection even when a full tracked peer object is not available locally
---
## 5. Radio ID (RID) Management ## 5. Radio ID (RID) Management
The Radio ID (RID) management endpoints allow dynamic modification of the radio ID whitelist/blacklist. The Radio ID (RID) management endpoints allow dynamic modification of the radio ID whitelist/blacklist.
@ -1287,6 +1377,8 @@ X-DVM-Auth-Token: {token}
"lastPing": "Fri Dec 6 10:30:45 2025", "lastPing": "Fri Dec 6 10:30:45 2025",
"pingsReceived": 1234, "pingsReceived": 1234,
"missedMetadataUpdates": 0, "missedMetadataUpdates": 0,
"isConsole": false,
"isSysView": false,
"isNeighbor": false, "isNeighbor": false,
"isReplica": false "isReplica": false
} }
@ -1299,6 +1391,8 @@ X-DVM-Auth-Token: {token}
"cryptoKeyLastLoadTime": "Fri Dec 6 08:15:30 2025" "cryptoKeyLastLoadTime": "Fri Dec 6 08:15:30 2025"
}, },
"totalCallsProcessed": 5678, "totalCallsProcessed": 5678,
"totalCallCollisions": 12,
"totalActiveCalls": 1,
"ridTotalEntries": 150, "ridTotalEntries": 150,
"tgTotalEntries": 45, "tgTotalEntries": 45,
"peerListTotalEntries": 8, "peerListTotalEntries": 8,
@ -1317,6 +1411,8 @@ X-DVM-Auth-Token: {token}
- `lastPing`: Last ping timestamp (human-readable format) - `lastPing`: Last ping timestamp (human-readable format)
- `pingsReceived`: Total pings received from this peer - `pingsReceived`: Total pings received from this peer
- `missedMetadataUpdates`: Number of missed metadata updates - `missedMetadataUpdates`: Number of missed metadata updates
- `isConsole`: Whether this peer is classified as a console peer
- `isSysView`: Whether this peer is classified as a SysView peer
- `isNeighbor`: Whether this is a neighbor FNE peer - `isNeighbor`: Whether this is a neighbor FNE peer
- `isReplica`: Whether this peer participates in replication - `isReplica`: Whether this peer participates in replication
@ -1329,6 +1425,8 @@ X-DVM-Auth-Token: {token}
**Statistics Totals:** **Statistics Totals:**
- `totalCallsProcessed`: Total number of calls processed since FNE startup - `totalCallsProcessed`: Total number of calls processed since FNE startup
- `totalCallCollisions`: Total number of tracked call collisions since FNE startup
- `totalActiveCalls`: Current internal active-call counter value
- `ridTotalEntries`: Total entries in radio ID lookup table - `ridTotalEntries`: Total entries in radio ID lookup table
- `tgTotalEntries`: Total entries in talkgroup rules table - `tgTotalEntries`: Total entries in talkgroup rules table
- `peerListTotalEntries`: Total entries in authorized peer list - `peerListTotalEntries`: Total entries in authorized peer list
@ -1344,7 +1442,106 @@ X-DVM-Auth-Token: {token}
--- ---
### 9.7 Endpoint: GET /report-affiliations ### 9.7 Endpoint: GET /stat-reset-total-calls
**Method:** `GET`
**Description:** Reset the `totalCallsProcessed` counter exposed by `/stats`.
**Request Headers:**
```
X-DVM-Auth-Token: {token}
```
**Response:**
```json
{
"status": 200
}
```
**Notes:**
- Resets only the total-calls counter
- Does not restart the FNE or affect peer connectivity
---
### 9.8 Endpoint: GET /stat-reset-active-calls
**Method:** `GET`
**Description:** Reset the `totalActiveCalls` counter exposed by `/stats`.
**Request Headers:**
```
X-DVM-Auth-Token: {token}
```
**Response:**
```json
{
"status": 200
}
```
**Notes:**
- Resets the active-call counter maintained by `TrafficNetwork`
---
### 9.9 Endpoint: GET /stat-reset-call-collisions
**Method:** `GET`
**Description:** Reset the `totalCallCollisions` counter exposed by `/stats`.
**Request Headers:**
```
X-DVM-Auth-Token: {token}
```
**Response:**
```json
{
"status": 200
}
```
**Notes:**
- Resets the call-collision counter only
---
### 9.10 Endpoint: GET /report-unit-regs
**Method:** `GET`
**Description:** Return the current global unit registration table.
**Request Headers:**
```
X-DVM-Auth-Token: {token}
```
**Response:**
```json
{
"status": 200,
"totalUnits": 3,
"units": [123456, 234567, 345678]
}
```
**Response Fields:**
- `totalUnits`: Count of registered units returned
- `units[]`: Flat array of registered source IDs
**Notes:**
- Data is sourced from the global affiliation/unit registration table maintained by `TrafficNetwork`
---
### 9.11 Endpoint: GET /report-affiliations
**Method:** `GET` **Method:** `GET`
@ -1359,6 +1556,7 @@ X-DVM-Auth-Token: {token}
```json ```json
{ {
"status": 200, "status": 200,
"totalAffiliations": 2,
"affiliations": [ "affiliations": [
{ {
"peerId": 10001, "peerId": 10001,
@ -1387,6 +1585,7 @@ X-DVM-Auth-Token: {token}
``` ```
**Response Fields:** **Response Fields:**
- `totalAffiliations`: Count of peer affiliation groups returned by the API
- `affiliations[]`: Array of peer affiliation records - `affiliations[]`: Array of peer affiliation records
- `peerId`: Peer ID where affiliations are registered - `peerId`: Peer ID where affiliations are registered
- `affiliations[]`: Array of affiliation records for this peer - `affiliations[]`: Array of affiliation records for this peer
@ -1400,7 +1599,50 @@ X-DVM-Auth-Token: {token}
--- ---
### 9.8 Endpoint: GET /spanning-tree ### 9.12 Endpoint: GET /report-grants
**Method:** `GET`
**Description:** Return the currently active talkgroup grant table.
**Request Headers:**
```
X-DVM-Auth-Token: {token}
```
**Response:**
```json
{
"status": 200,
"totalGrants": 2,
"grants": [
{
"srcId": 123456,
"dstId": 1,
"ssrc": 305419896
},
{
"srcId": 234567,
"dstId": 2,
"ssrc": 2271560481
}
]
}
```
**Response Fields:**
- `totalGrants`: Count of grant entries returned
- `grants[]`: Array of active grant records
- `srcId`: Current granted source radio ID
- `dstId`: Destination talkgroup or unit ID holding the grant
- `ssrc`: RTP SSRC associated with the active grant when present, otherwise `0`
**Notes:**
- Data is built from the global grant source-ID and SSRC tables
---
### 9.13 Endpoint: GET /spanning-tree
**Method:** `GET` **Method:** `GET`
@ -1718,13 +1960,7 @@ Error responses include HTTP status code and JSON error object:
**Invalid Content-Type:** **Invalid Content-Type:**
When Content-Type is not `application/json`, the server returns a plain text error response: When `Content-Type` is not `application/json`, the server returns an HTTP 400 status payload rather than processing the request body as JSON.
```
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Invalid Content-Type. Expected: application/json
```
**Not a JSON Object:** **Not a JSON Object:**
```json ```json
@ -1762,21 +1998,19 @@ Examples of field validation errors:
} }
``` ```
### 12.3 Resource Errors Additional authentication request validation errors include:
- `"password was not a valid string"`
- `"auth cannot be empty"`
- `"auth cannot be longer than 64 characters"`
- `"auth contains invalid characters"`
**Peer Not Found:** ### 12.3 Resource Errors
```json
{
"status": 400,
"message": "cannot find peer"
}
```
**Talkgroup Not Found:** **Talkgroup Not Found:**
```json ```json
{ {
"status": 400, "status": 400,
"message": "cannot find talkgroup" "message": "failed to find specified TGID to delete"
} }
``` ```
@ -1784,10 +2018,14 @@ Examples of field validation errors:
```json ```json
{ {
"status": 400, "status": 400,
"message": "cannot find RID" "message": "failed to find specified RID to delete"
} }
``` ```
**Notes:**
- `PUT /peer/delete` returns `200 OK` even when the peer ID does not exist
- `PUT /peer/reset` also returns `200 OK` before the internal reset attempt is evaluated; missing peers are primarily visible in server logs
**Invalid Command:** **Invalid Command:**
```json ```json
{ {
@ -2168,6 +2406,8 @@ if __name__ == "__main__":
| GET | /peer/count | Get peer count | Yes | | GET | /peer/count | Get peer count | Yes |
| PUT | /peer/reset | Reset peer connection | Yes | | PUT | /peer/reset | Reset peer connection | Yes |
| PUT | /peer/connreset | Reset upstream connection | Yes | | PUT | /peer/connreset | Reset upstream connection | Yes |
| PUT | /peer/nak/byPeerId | Send peer NAK by peer ID | Yes |
| PUT | /peer/nak/byAddress | Send peer NAK by address | Yes |
| GET | /rid/query | Query radio IDs | Yes | | GET | /rid/query | Query radio IDs | Yes |
| PUT | /rid/add | Add radio ID | Yes | | PUT | /rid/add | Add radio ID | Yes |
| PUT | /rid/delete | Delete radio ID | Yes | | PUT | /rid/delete | Delete radio ID | Yes |
@ -2190,7 +2430,12 @@ if __name__ == "__main__":
| GET | /reload-peers | Reload peer list from disk | Yes | | GET | /reload-peers | Reload peer list from disk | Yes |
| GET | /reload-crypto | Reload crypto keys from disk | Yes | | GET | /reload-crypto | Reload crypto keys from disk | Yes |
| GET | /stats | Get FNE statistics | Yes | | GET | /stats | Get FNE statistics | Yes |
| GET | /stat-reset-total-calls | Reset total calls counter | Yes |
| GET | /stat-reset-active-calls | Reset active calls counter | Yes |
| GET | /stat-reset-call-collisions | Reset call collisions counter | Yes |
| GET | /report-unit-regs | Get registered units | Yes |
| GET | /report-affiliations | Get affiliations | Yes | | GET | /report-affiliations | Get affiliations | Yes |
| GET | /report-grants | Get active grants | Yes |
| GET | /spanning-tree | Get network topology | Yes | | GET | /spanning-tree | Get network topology | Yes |
| PUT | /dmr/rid | DMR radio operations | Yes | | PUT | /dmr/rid | DMR radio operations | Yes |
| PUT | /p25/rid | P25 radio operations | Yes | | PUT | /p25/rid | P25 radio operations | Yes |
@ -2202,27 +2447,27 @@ if __name__ == "__main__":
### REST API Configuration (YAML) ### REST API Configuration (YAML)
```yaml ```yaml
restApi: system:
# Enable REST API # Enable REST API
enable: true restEnable: true
# Bind address (0.0.0.0 = all interfaces) # Bind address (0.0.0.0 = all interfaces)
address: 0.0.0.0 restAddress: 127.0.0.1
# Port number # Port number used for HTTP or HTTPS depending on restSsl
port: 9990 restPort: 9990
# SHA-256 hashed password (pre-hash before putting in config)
password: "your_secure_password"
# SSL/TLS Configuration (optional) # SSL/TLS Configuration (optional)
ssl: restSsl: false
enable: false restSslCertificate: web.crt
keyFile: /path/to/private.key restSslKey: web.key
certFile: /path/to/certificate.crt
# REST API authentication password stored in plain text;
# Enable debug logging # clients must still send SHA-256(password) to /auth
debug: false restPassword: "PASSWORD"
# Enable verbose REST API logging
restDebug: false
``` ```
--- ---
@ -2344,6 +2589,7 @@ monitor_affiliations("fne.example.com", 9990, "your_token", 1)
|---------|------|---------| |---------|------|---------|
| 1.0 | Dec 3, 2025 | Initial documentation based on source code analysis | | 1.0 | Dec 3, 2025 | Initial documentation based on source code analysis |
| 1.1 | Dec 6, 2025 | Added missing endpoints: `/reload-peers`, `/reload-crypto`, `/stats` | | 1.1 | Dec 6, 2025 | Added missing endpoints: `/reload-peers`, `/reload-crypto`, `/stats` |
| 1.2 | May 8, 2026 | Updated for current FNE REST implementation, added peer NAK endpoints, stats reset/report endpoints, and corrected REST config/auth details |
--- ---

Loading…
Cancel
Save

Powered by TurnKey Linux.