Skip to content

NXDN Reflector Protocol

Vexillum’s NXDN mode is a minimal reflector protocol: a client polls with a talkgroup, and the reflector accepts or ignores it depending on whether that talkgroup matches its own configuration. There is no authentication and no per-module partitioning - every accepted session shares one talkgroup and one active stream.

Field Value
Transport UDP only
Default listen port 41400
Authentication None
Default talkgroup 61000

A client should handle:

  • NXDNP poll, gated by matching talkgroup
  • NXDNU disconnect, also gated by matching talkgroup
  • NXDND fixed-size data frames, with group-call and stream-end flag bits
  • A single shared stream per reflector instance - there is no module concept

All integer fields are big-endian. Tags are 5-byte ASCII.

Tag Length Layout Purpose
NXDNP 17 [0:5] tag NXDNP, [5:15] callsign (10 bytes), [15:17] talkgroup (u16) Poll / login
NXDNU 17 [0:5] tag NXDNU, [5:15] callsign (10 bytes), [15:17] talkgroup (u16) Disconnect
NXDND 43 [0:5] tag NXDND, [5:7] source ID (u16), [7:9] destination ID (u16), [9] flags, [9:43] rest of frame Data frame
Bit Mask Meaning
0 0x01 Group call
3 0x08 Stream end

Byte offset 9 of the frame carries both bits. The remaining 33 bytes (offsets 9-42, flags byte included) are forwarded to other sessions verbatim.

Every NXDNP poll, NXDNU disconnect, and NXDND data frame carries a talkgroup or destination ID. The reflector only acts on a packet whose talkgroup/destination matches its own single configured talkgroup:

  • A poll or disconnect whose talkgroup doesn’t match the reflector’s configured talkgroup is silently ignored - no session is created, no acknowledgment is sent, and (for disconnect) no session is removed.
  • A data frame is only forwarded if its destination ID equals the configured talkgroup and the group-call flag bit is set. A frame that fails either check is dropped without forwarding, even from an already-polled session.

There is exactly one talkgroup per reflector instance - unlike DMR, there’s no concept of switching talkgroups mid-session.

Parameter Value
Session timeout 120 seconds
Cleanup tick interval 30 seconds
Stream idle timeout 3 seconds
Session cap 4096

Follows the general session/stream lifecycle described in the reference overview.

Unlike DMR or M17, which partition streams per module, Vexillum’s NXDN reflector admits one active stream across the entire instance, regardless of talkgroup or sender. The first sender to have a group-call data frame accepted holds the stream exclusively until it ends explicitly or goes idle for 3 seconds; every other sender’s data frames are dropped in the meantime, even though they all share the same single configured talkgroup anyway.

Client                         Server
  |                              |
  | NXDNP                        |
  | callsign, talkgroup          |
  |----------------------------->|
  |                              |
  | NXDNP                        |
  | callsign = echoed, talkgroup |
  |<-----------------------------|

The reflector only replies if the poll’s talkgroup matches its configured talkgroup. Unlike YSF’s poll acknowledgment, NXDN’s ack echoes back the exact callsign and talkgroup the client sent - not the reflector’s own identity.

  • The first accepted NXDND group-call frame (destination matches the configured talkgroup) opens the reflector’s single active stream.
  • While a stream is active, only frames from that same sender are forwarded.
  • A stream ends when a frame’s flags byte has bit 0x08 set, or when the stream is idle for 3 seconds.
  • Accepted frames are forwarded verbatim to every other polled session, regardless of that session’s own talkgroup value (since all sessions share the reflector’s one configured talkgroup by construction).
DISCONNECTED
  |
  v
CONNECTING (sent NXDNP)
  |
  v
READY (received matching NXDNP ack)
  |
  +-- PTT active --> TRANSMITTING
  |
  +-- incoming NXDND frame --> RECEIVING

Terminal conditions: transport failure, session timeout (120 seconds without traffic), inbound NXDNU, or user shutdown.

If a poll’s talkgroup doesn’t exactly match the reflector’s configured value, the reflector does nothing - no ack, no error, no log line visible to the client. A client that doesn’t hear back from a poll should double-check its configured talkgroup before assuming a network problem.

A data frame with the correct destination ID but without bit 0x01 set in its flags byte is dropped, even from a session that has already successfully polled.

Because there’s only one talkgroup per instance, there’s also only one stream. Don’t design a client assuming it can run two independent conversations against a single NXDN instance.

A poll for callsign W1AW, talkgroup 65000:

Field Value
Tag NXDNP
Callsign W1AW······ (space-padded to 10)
Talkgroup 65000 (bytes 0xFD 0xE8)

A data frame, source 100, destination 65000, group call:

Field Value
Tag NXDND
Source ID 100 (bytes 0x00 0x64)
Destination ID 65000 (bytes 0xFD 0xE8)
Flags 0x01 (group call)

NXDN is implemented as a Vexillum mode runtime (internal/modes/nxdn). The tag names and general shape borrow NXDN terminology, but the wire format, session model, and single-stream-per-instance behavior are entirely Vexillum-specific.

  • One talkgroup per reflector instance.
  • One active stream reflector-wide, not per-talkgroup or per-module.
  • No authentication.
  • No real NXDN frame structure (ICV, RAN, or channel access parsing) - data frames are opaque payload beyond the flags byte.
  1. Confirm your poll’s talkgroup exactly matches the reflector’s configured talkgroup before debugging anything else.
  2. Implement NXDNP poll and acknowledgment.
  3. Implement NXDND data forwarding, including the group-call and stream-end flag bits.
  4. Add NXDNU disconnect.
  5. Test against a running Vexillum NXDN instance before assuming compatibility with real NXDN infrastructure.