DMR Reflector Protocol
DMR is Vexillum’s implementation of a Homebrew/HBP-style DMR repeater protocol. A client (typically a hotspot, repeater controller, or gateway) logs in, authenticates with a shared password, sends a configuration block, and then exchanges voice/data frames scoped to a talkgroup and a module.
Status
Section titled “Status”| Field | Value |
|---|---|
| Transport | UDP only |
| Default listen port | 62031 |
| Authentication | Required - SHA-256 challenge/response |
| Default talkgroup | 7 |
| Module command base | 64000 |
Design overview
Section titled “Design overview”A client should handle:
RPTL/RPTACK/RPTKlogin and authentication handshakeRPTCconfiguration submission- Optional
RPTOstartup module selection RPTPING/MSTPONGkeepaliveDMRDvoice/data frames, gated to slot 2, a specific talkgroup, and a module- Optional
DMRAtalker alias andDMRGGPS position sidecar frames RPTCLexplicit logoff
Vexillum’s DMR reflector organizes traffic into modules (channels), each named
by a single letter. A connected repeater is assigned a module either by its
RPTO startup option or by sending a private call to a virtual “module switch”
DMR ID (see Module switching via private calls).
Group voice traffic is only forwarded within the same module, on slot 2, on the
configured talkgroup.
Authentication
Section titled “Authentication”Vexillum’s DMR authentication follows the classic Homebrew challenge/response
shape: a random seed, a SHA-256 digest over seed || password, and
constant-time comparison.
- On
RPTL, the reflector generates a 4-byte seed (an internal counter, incremented per session, offset by the constant0x9E3779B9) and sends it back inRPTACK. - The client computes
SHA256(seed || password)and sends the 32-byte digest inRPTK. - The reflector recomputes the same digest and compares it to the client’s digest using a constant-time comparison. The challenge must be presented within 20 seconds of being issued or it is rejected.
- On success, the reflector marks the session authenticated and sends
RPTACKagain, this time carrying the repeater ID instead of a seed.
Any mismatch - wrong repeater ID, expired challenge, or wrong digest - gets
MSTNAK instead.
Packet format
Section titled “Packet format”All integer fields are big-endian. Callsign and text fields are ASCII, space-padded.
Login and authentication
Section titled “Login and authentication”| Tag | Length | Layout | Purpose |
|---|---|---|---|
RPTL |
8 |
[0:4] tag RPTL, [4:8] repeater ID (u32) |
Start login |
RPTACK (challenge) |
10 |
[0:6] tag RPTACK, [6:10] 4-byte seed |
Challenge seed |
RPTK |
40 |
[0:4] tag RPTK, [4:8] repeater ID (u32), [8:40] SHA-256 digest |
Authentication response |
RPTACK (auth ok) |
10 |
[0:6] tag RPTACK, [6:10] repeater ID (u32) |
Authentication confirmed |
MSTNAK |
6 |
Literal ASCII MSTNAK |
Reject |
Configuration and control
Section titled “Configuration and control”| Tag | Length | Layout | Purpose |
|---|---|---|---|
RPTC |
302 |
See RPTC configuration block below | Repeater configuration |
RPTO |
≥ 8 |
[0:4] tag RPTO, [4:8] repeater ID (u32), [8:] free-text options |
Startup module selection |
RPTPING |
11 |
[0:7] tag RPTPING, [7:11] repeater ID (u32) |
Keepalive |
MSTPONG |
11 |
[0:7] tag MSTPONG, [7:11] repeater ID (u32) |
Keepalive reply |
RPTCL |
9 or 13 |
[0:5] tag RPTCL, [5:9] repeater ID (u32); a longer 13-byte variant is also accepted, with its trailing 4 bytes unused |
Logoff |
Voice/data and sidecar frames
Section titled “Voice/data and sidecar frames”| Tag | Length | Layout | Purpose |
|---|---|---|---|
DMRD |
55 |
[0:4] tag DMRD, [4] reserved (unused by this implementation), [5:8] source ID (u24), [8:11] destination ID (u24), [11:15] repeater ID (u32), [15] flags, [16:55] DMR payload |
Voice/data frame |
DMRA |
15 |
[0:4] tag DMRA, [4:7] source ID (u24), [7] alias type, [8:15] 7-byte alias chunk |
Talker alias sidecar |
DMRG |
14 |
[0:4] tag DMRG, [4:7] source ID (u24), [7:14] 7-byte position chunk |
GPS position sidecar |
DMRD flags byte
Section titled “DMRD flags byte”| Bits | Mask | Meaning |
|---|---|---|
7 |
0x80 |
Slot 2 selector - must be set; this reflector ignores slot 1 frames entirely |
6 |
0x40 |
Call type: 1 = private call, 0 = group call |
5–4 |
0x30 |
Frame type (2 = voice LC terminator, used for end-of-stream detection) |
3–0 |
0x0F |
Burst/voice slot type |
A terminator frame - the signal that ends a transmission - has frame type 2
and burst/slot value 2, which packs into a single flags byte value of 0xA2
(1010 0010: slot2 set, group call, frame type 2, burst 2).
RPTC configuration block
Section titled “RPTC configuration block”RPTC is always exactly 302 bytes. Text fields are ASCII and are trimmed of
non-printable bytes and surrounding whitespace when parsed; unused space within
a field should be padded with spaces.
| Offset | Bytes | Field | Notes |
|---|---|---|---|
0 |
4 |
Tag | ASCII RPTC |
4 |
4 |
Repeater ID | Unsigned 32-bit integer |
8 |
8 |
Callsign | Uppercased, letters/digits///- only |
16 |
9 |
RX frequency | ASCII text, e.g. 438800000 |
25 |
9 |
TX frequency | ASCII text, e.g. 431200000 |
34 |
2 |
Power | ASCII text, e.g. 25 |
36 |
2 |
Color code | ASCII text, e.g. 01 |
38 |
8 |
Latitude | ASCII text, e.g. 40.7128 |
46 |
9 |
Longitude | ASCII text, e.g. -74.0060 |
55 |
3 |
Reserved | Not parsed by this implementation |
58 |
20 |
Site name | ASCII text |
78 |
19 |
Description | ASCII text |
97 |
1 |
Slots | 1=Slot 1 only, 2=Slot 2 only, 3=Duplex, 4=Simplex/DMO |
98 |
124 |
URL | ASCII text |
222 |
40 |
Software ID | ASCII text, e.g. MMDVM |
262 |
40 |
Package ID | ASCII text, e.g. MMDVMHost |
RPTC is only accepted after successful authentication. Its repeater ID must
match the ID established during login.
Session behavior
Section titled “Session behavior”| Parameter | Value |
|---|---|
| Challenge TTL | 20 seconds |
| Session timeout | 120 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. A session
must complete both authentication (RPTL/RPTK) and configuration (RPTC)
before any DMRD, DMRA, or DMRG frame is accepted from it - an
authenticated-but-unconfigured session is otherwise fully tracked and can still
RPTPING.
Talkgroup, slot, and module gating
Section titled “Talkgroup, slot, and module gating”A DMRD frame is only forwarded if all of the following hold:
- The session is authenticated and configured.
- The flags byte has the slot-2 bit set (
0x80). Slot 1 traffic is dropped unconditionally by this implementation. - The call type is a group call (flags bit
0x40clear). - The destination ID equals the reflector’s configured talkgroup (default
7).
When forwarded, the destination ID and repeater ID fields are rewritten per recipient: the destination ID is rewritten to the reflector’s configured talkgroup, and the repeater ID is rewritten to each recipient’s own repeater ID. The source ID and voice payload are left untouched.
Private calls (flags bit 0x40 set) never carry voice; they are reserved
entirely for the module-switching control-plane trick described next.
Module switching via private calls
Section titled “Module switching via private calls”This is a Vexillum-specific extension, not part of any external Homebrew
specification. When UserControl is enabled (the default), a private call’s
destination ID is interpreted as a command rather than a talkgroup:
| Destination ID | Effect |
|---|---|
ModuleCommandBase (default 64000) |
Unlink - clears the session’s current module |
ModuleCommandBase + 1 … ModuleCommandBase + 26 |
Switch to module A…Z, e.g. 64001 → module A, 64026 → module Z |
StatusCommand (default 65000) |
Reserved; currently accepted and silently ignored |
A repeater’s active module can also be set directly via the RPTO startup
option, using either startup=<letter>, module=<letter>,
module=<numeric ID> (interpreted the same way as a private-call module-switch
ID), or a bare single letter.
Session handshake
Section titled “Session handshake”Any step that fails validation gets MSTNAK in place of RPTACK, and the
client should restart from RPTL.
Keepalive behavior
Section titled “Keepalive behavior”RPTPING requires an existing session whose repeater ID matches; a mismatch
gets MSTNAK.
Ending a transmission
Section titled “Ending a transmission”Send a final DMRD frame with the flags byte set to 0xA2 (slot 2, group call,
frame type 2, burst 2). The reflector releases the module’s active stream
lock immediately on seeing this terminator, allowing the next sender on that
module to start without waiting for the 3-second idle timeout.
Minimal client state machine
Section titled “Minimal client state machine”Terminal conditions: transport failure, session timeout (120 seconds without
traffic), inbound RPTCL acknowledgment, or user shutdown.
Common implementation mistakes
Section titled “Common implementation mistakes”Forgetting the slot-2 flag bit
Section titled “Forgetting the slot-2 flag bit”This reflector silently drops every DMRD frame that doesn’t have the 0x80
slot-2 bit set in its flags byte - there is no slot 1 support at all. A client
built against real slot 1/slot 2 dual-slot hardware must map its “active”
traffic onto slot 2 for this reflector, or it will see nothing forwarded and no
error.
Sending RPTC before authentication
Section titled “Sending RPTC before authentication”RPTC is rejected with MSTNAK if the session hasn’t completed the RPTK
challenge yet, even if the repeater ID is correct.
Missing the 20-second challenge window
Section titled “Missing the 20-second challenge window”A RPTK response sent more than 20 seconds after RPTL’s RPTACK is rejected
regardless of whether the digest is correct. Compute and send the digest
immediately.
Expecting private calls to carry voice
Section titled “Expecting private calls to carry voice”Private calls never forward voice on this reflector. Sending a private-call
DMRD frame expecting one-to-one voice routing will only work if the
destination ID happens to match a module-switch or unlink command ID, and even
then no voice is exchanged.
Assuming the RPTC reserved gap is available
Section titled “Assuming the RPTC reserved gap is available”Bytes 55-57 of the RPTC block are not parsed by this implementation. Don’t
rely on the reflector reading anything placed there.
Example packet values
Section titled “Example packet values”An RPTL login for repeater ID 312000:
| Field | Value |
|---|---|
| Tag | RPTL |
| Repeater ID | 312000 |
A DMRD group-call voice frame on slot 2, source 123456, destination
(talkgroup) 7, repeater 1001:
| Field | Value |
|---|---|
| Tag | DMRD |
| Source ID | 123456 |
| Destination ID | 7 |
| Repeater ID | 1001 |
| Flags | 0x80 (slot 2, group call, mid-stream) |
The same stream’s terminator frame:
| Field | Value |
|---|---|
| Flags | 0xA2 (slot 2, group call, frame type 2, burst 2) |
Relationship to Vexillum
Section titled “Relationship to Vexillum”DMR is implemented as a Vexillum mode runtime (internal/modes/dmr). The
login/authentication/configuration handshake and packet tag names follow the
conventions of the Homebrew/HBP-family DMR repeater protocol; the
module-switching-via-private-call mechanism, session timeouts, and session caps
described above are Vexillum-specific.
Current limitations
Section titled “Current limitations”- Slot 1 is not supported; only slot-2 group-call traffic is forwarded.
- One shared password for all repeaters connecting to an instance - no per-repeater credentials.
- One active voice stream per module at a time.
DMRA/DMRGsidecar data updates session metadata but triggers no reply packet.- The
StatusCommandprivate-call ID is accepted but currently has no effect.
Development recommendations
Section titled “Development recommendations”- Implement
RPTL/RPTACK/RPTKand verify the SHA-256 digest against a known seed and password. - Add
RPTCand confirm the 302-byte field layout round-trips. - Add
RPTPING/MSTPONG. - Add
DMRDwith the slot-2 flag set, targeting the configured talkgroup and module. - Add the terminator frame (
0xA2) and confirm the next sender is admitted immediately afterward. - Add
RPTCLlogoff. - If needed, add
RPTOstartup module selection and the module-switch private-call commands. - Test against a running Vexillum DMR instance with the actual configured password before assuming compatibility with any other Homebrew-protocol implementation.