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.
Status
Section titled “Status”| 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.
Packet format: synthetic control tags
Section titled “Packet format: synthetic control tags”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.
Packet format: real DExtra/DSVT frames
Section titled “Packet format: real DExtra/DSVT frames”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.
Header frame (56 bytes)
Section titled “Header frame (56 bytes)”| 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) |
Voice frame (27 bytes)
Section titled “Voice frame (27 bytes)”| 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.
Reflector-to-reflector connect handshake
Section titled “Reflector-to-reflector connect handshake”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).
Keepalive behavior
Section titled “Keepalive behavior”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.
Session behavior
Section titled “Session behavior”| 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.
Session handshake
Section titled “Session handshake”Synthetic poll:
Real DExtra connect (revision 0/1):
Real DExtra connect (revision 2 / XRF-style):
Stream behavior and ending a transmission
Section titled “Stream behavior and ending a transmission”- 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
0is treated as a broadcast/unset module on either side of the admission check - a session or a stream with module0is not module-filtered. - A stream ends when a DSVT voice frame’s
PacketIDhas bit0x40set. - Real DSVT frames are forwarded byte-for-byte, including their original
PacketIDandStreamID. SyntheticDEXDframes 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.
Minimal client state machine
Section titled “Minimal client state machine”Terminal conditions: transport failure, session timeout (30 seconds without inbound traffic), inbound disconnect/unlink, or user shutdown.
Common implementation mistakes
Section titled “Common implementation mistakes”Assuming DSVT integers are big-endian
Section titled “Assuming DSVT integers are big-endian”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.
Not replying to the server’s keepalive
Section titled “Not replying to the server’s keepalive”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.
Expecting D-Plus or DCS behavior
Section titled “Expecting D-Plus or DCS behavior”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.
Example packet values
Section titled “Example packet values”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) |
Relationship to Vexillum
Section titled “Relationship to Vexillum”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.
Current limitations
Section titled “Current limitations”- 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.
Development recommendations
Section titled “Development recommendations”- 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.
- Implement
DEXP/DEXOpoll and acknowledgment, or the real 11-byte connect handshake and its revision-dependent acknowledgment. - Implement
DEXDdata forwarding, or real DSVT header/voice frame parsing including the little-endianStreamID. - Add keepalive handling, remembering the server drives the cadence.
- Add
DEXUunlink, or the real 11-byte disconnect shape. - Test against a running Vexillum DExtra instance before assuming compatibility with other DExtra reflector implementations.