Skip to content

DExtra (D-Star) Reflector Protocol

Vexillum’s D-Star support (Go package dstar) speaks the DExtra reflector protocol. It accepts two distinct families of packets on the same UDP socket: real DExtra/DSVT wire frames, as sent by D-Star hardware, dongles, and other DExtra-speaking reflectors, and a simpler Vexillum-specific synthetic control layer for basic poll-based clients.

Field Value
Transport UDP only
Default listen port 30001
Authentication None
Protocol variant DExtra only (D-Plus and DCS are not implemented)

Synthetic control tags vs. real DSVT frames

Section titled “Synthetic control tags vs. real DSVT frames”

Vexillum’s D-Star socket accepts two independent packet families. A client should pick one and use it consistently - they are not meant to be mixed within a single connection.

A. Synthetic control tags - a simplified, Vexillum-specific poll/ack/data scheme, tagged DEXP/DEXO/DEXU/DEXD. These exist for lightweight clients that don’t need to speak real DSVT framing.

B. Real DExtra/DSVT wire frames - the actual "DSVT"-tagged header and voice frames used by real D-Star hardware, USB dongles, and other DExtra reflectors, plus a real DExtra reflector-to-reflector connect/keepalive/ disconnect handshake.

Both families can be forwarded through the same module and the same stream-admission logic; the runtime distinguishes them by their first few bytes on every incoming packet. A synthetic DEXD data frame gets its module byte normalized and rebuilt before forwarding; a real DSVT frame is forwarded byte-for-byte, unmodified.

All fields are fixed-width ASCII, space-padded where noted.

Tag Length Layout Purpose
DEXP 15 [0:4] tag DEXP, [4:14] client callsign (10 bytes), [14] module Poll / login
DEXO 15 [0:4] tag DEXO, [4:14] reflector name (10 bytes), [14] module Poll acknowledgment
DEXU 15 [0:4] tag DEXU, [4:14] callsign (10 bytes), [14] module Unlink / disconnect (and its ack)
DEXD 6 - 512 [0:4] tag DEXD, [4] module, [5:] payload Data/voice frame

DEXO and the DEXU acknowledgment echo the reflector’s own name in the callsign field, not the client’s callsign.

All multi-byte integer fields in DSVT frames are little-endian, unlike most of the rest of Vexillum’s wire protocols - this matches real D-Star hardware’s byte order and is worth double-checking in your own parser.

Offset Bytes Field Notes
0 4 Magic ASCII DSVT
4 1 Frame type 0x10 for a header frame
8 1 Fixed flag 0x20
12 2 StreamID Unsigned 16-bit, little-endian
14 1 PacketID Bit 0x40 marks the terminal voice frame that follows
15 41 DV header Includes the module letter at header offset 10 (absolute offset 25)
Offset Bytes Field Notes
0 4 Magic ASCII DSVT
4 1 Frame type 0x20 for a voice frame
8 1 Fixed flag 0x20
12 2 StreamID Unsigned 16-bit, little-endian, matches the header’s StreamID
14 1 PacketID Bit 0x40 set on the terminal voice frame of a transmission
15 12 Voice payload AMBE-encoded voice and data bits

A stream ends when a voice frame’s PacketID has bit 0x40 set.

Real DExtra reflectors exchange an 11-byte connect packet, distinct from both the synthetic poll and the DSVT frames above.

Offset Bytes Field Notes
0 8 Callsign 8-byte ASCII, space-padded
8 1 Local module ASCII module letter
9 1 Remote module ASCII module letter; a space here means disconnect, not connect
10 1 Revision byte 11 → revision 1; first 3 bytes XRF → revision 2; otherwise revision 0

Acknowledgment shape depends on the detected revision:

  • Revision 0 or 1: the reflector echoes the first 10 bytes of the request and appends ACK\0, for a 14-byte total acknowledgment.
  • Revision 2 (XRF-style): the reflector replies with its own 11-byte packet: its own 8-byte callsign, followed by the request’s remote-module byte and local-module byte swapped relative to their positions in the request.

A disconnect uses the same 11-byte shape with the remote-module byte set to a literal space (0x20).

DExtra keepalives are plain 9-byte packets: an 8-byte reflector or client callsign followed by one reserved byte. Any 9-byte packet is treated as a keepalive regardless of its content.

Unlike every other mode documented here, Vexillum’s DExtra reflector drives keepalives itself: every 3 seconds, it sends a 9-byte keepalive (its own name in the first 8 bytes) to every connected session, rather than waiting for the client to ping first.

This does not remove the client’s own responsibility to be heard from: the server-side session still expires after 30 seconds without any inbound packet from that client. A well-behaved client should reply to each received keepalive with one of its own (or otherwise poll periodically), keeping inbound traffic flowing well inside the 30-second window.

Server                Client
  |                     |
  | keepalive (9 bytes) |
  | reflector name      |
  |-------------------->|
  |                     |
  | keepalive (9 bytes) |
  | client callsign     |
  |<--------------------|
Parameter Value
Session timeout 30 seconds
Server keepalive interval 3 seconds
Cleanup tick interval 10 seconds
Stream idle timeout 3 seconds
Session cap 4096

Follows the general session/stream lifecycle described in the reference overview, with a notably shorter session timeout (30 seconds) than the other modes documented here, reflecting the server-driven 3-second keepalive cadence.

Synthetic poll:

Client                   Server
  |                        |
  | DEXP                   |
  | callsign, module       |
  |----------------------->|
  |                        |
  | DEXO                   |
  | reflector name, module |
  |<-----------------------|

Real DExtra connect (revision 0/1):

Client                           Server
  |                                |
  | connect (11 bytes)             |
  | callsign, localModule,         |
  | remoteModule, revision         |
  |------------------------------->|
  |                                |
  | ack (14 bytes)                 |
  | first 10 request bytes + ACK\0 |
  |<-------------------------------|

Real DExtra connect (revision 2 / XRF-style):

Client                          Server
  |                               |
  | connect (11 bytes, "XRF..." ) |
  |------------------------------>|
  |                               |
  | ack (11 bytes)                |
  | reflector callsign,           |
  | modules swapped               |
  |<------------------------------|
  • Vexillum admits one active stream per module at a time; a competing sender on the same module is ignored until the current stream either ends explicitly or goes idle for 3 seconds.
  • A module value of 0 is treated as a broadcast/unset module on either side of the admission check - a session or a stream with module 0 is not module-filtered.
  • A stream ends when a DSVT voice frame’s PacketID has bit 0x40 set.
  • Real DSVT frames are forwarded byte-for-byte, including their original PacketID and StreamID. Synthetic DEXD frames are rebuilt (module byte normalized) before forwarding, so their exact byte layout on the wire may differ slightly between what was sent and what other sessions receive.
DISCONNECTED
  |
  v
CONNECTING (sent DEXP poll or real connect handshake)
  |
  v
READY (received DEXO / connect ack)
  |
  +-- PTT active --> TRANSMITTING
  |
  +-- incoming DSVT / DEXD frame --> RECEIVING

Terminal conditions: transport failure, session timeout (30 seconds without inbound traffic), inbound disconnect/unlink, or user shutdown.

DSVT’s StreamID field is little-endian, unlike the header/tag conventions used elsewhere in Vexillum’s other mode protocols. A parser copy-pasted from a big-endian mode will silently byte-swap the stream ID.

Mixing synthetic and real frames on one session

Section titled “Mixing synthetic and real frames on one session”

A session that polls with synthetic DEXP and then sends real DSVT voice frames (or vice versa) is not a documented or tested combination. Pick one packet family for a given client implementation.

Because Vexillum’s DExtra reflector - not the client - drives the 3-second keepalive cadence, it’s easy to assume the session is self-sustaining. It is not: the 30-second session timeout is driven by the last inbound packet from the client, so a client that never sends anything back will still time out even while receiving keepalives.

Only the DExtra protocol variant is implemented. A client written for D-Plus or DCS framing will not interoperate with this reflector, even though some port and terminology conventions are shared across D-Star reflector types.

A synthetic poll for callsign KC1AWV, module B:

Field Value
Tag DEXP
Callsign KC1AWV··· (space-padded to 10)
Module B

A real DSVT header frame opening stream 0x1234 on module B:

Field Value
Magic DSVT
Frame type 0x10
Fixed flag 0x20
StreamID 0x1234 (little-endian on the wire: bytes 0x34 0x12)
PacketID 0x80
Module (header offset 10) B

The corresponding terminal voice frame:

Field Value
PacketID 0x40 (terminal frame bit set)

DExtra is implemented as part of Vexillum’s dstar mode runtime (internal/modes/dstar). The DSVT wire frame layout and the reflector-to-reflector connect handshake follow the real DExtra protocol’s conventions; the synthetic control-tag layer, session timeouts, and server-driven keepalive cadence are Vexillum-specific.

  • Only the DExtra protocol variant is supported - D-Plus and DCS are not implemented.
  • No authentication.
  • One active stream per module at a time.
  • Synthetic and real frame families are both accepted on the same socket with no negotiation step; a client is expected to pick one.
  1. Decide whether your client will speak the synthetic control tags or real DSVT framing - most new client implementations should prefer the synthetic scheme unless real D-Star hardware interoperability is required.
  2. Implement DEXP / DEXO poll and acknowledgment, or the real 11-byte connect handshake and its revision-dependent acknowledgment.
  3. Implement DEXD data forwarding, or real DSVT header/voice frame parsing including the little-endian StreamID.
  4. Add keepalive handling, remembering the server drives the cadence.
  5. Add DEXU unlink, or the real 11-byte disconnect shape.
  6. Test against a running Vexillum DExtra instance before assuming compatibility with other DExtra reflector implementations.