An XLX system is a MODE: OUTBOUND system with an XLX_MODULE letter set. XLX's
DMR interface is the homebrew protocol unmodified -- DMRGateway uses the same
CDMRNetwork class for XLX and BrandMeister, and there are no XLX conditionals in
its network layer -- so this adds no new mode, subclass, or data path. The only
protocol difference is one in-band private call at connect that selects the
module, plus pinning the connection to TS2/TG9.
Module binding is static by design. Runtime or end-user module selection would
break the nailed-up systems/bridges model; DMRGateway is the right tool for
user-selectable modules at a repeater or hotspot.
TS2/TG9 is injected by expand_xlx_bridges rather than read from rules.py, so
XLX_BRIDGES is a flat {system: bridge} map with no TS or TGID column. This is
not a default -- it makes a wrong value inexpressible. That matters more here
than elsewhere because every XLX failure is silent: the reflector never
acknowledges a link, and no frame on the wire carries a module identity, so a
mistyped talkgroup would leave HBlink looking healthy while traffic vanished.
The existing conference-bridge retarget path already rewrites the header dst,
flips the slot bit, and regenerates the full and embedded LC, so no XLX-specific
rewrite code is needed -- an XLX system is just a bridge member with a fixed
TS/TGID.
An XLX system in UNIT is a startup error. Reflector module selection *is* a
private call to 4001-4026, so forwarding a unit call to one could silently move
the reflector into a different room for every user connected to it.
Behaviour verified against xlxd source (src/cdmrmmdvmprotocol.cpp):
* Five acceptance gates at :636-655, each of which silently drops the packet:
exactly 55 bytes, DATA_SYNC frame type, TS2, slot type VOICE_LC_HEADER, and
a valid BS/MS data sync. The 55-byte requirement is why the packet carries
two trailing pad bytes; a bare 53-byte DMRD is rejected.
* Only the three header frames are parsed for the link command; the two
terminators have slot type 2 and are ignored, but are sent to frame the call.
* Unlink-then-link is required, not defensive. At :297-325 a link naming a
different module while already linked is discarded and rewritten to the
module already held, so a reconnect that skipped the unlink would silently
stay in the previous room.
* xlxd reads the destination from DMRD header bytes 8-10 and never decodes the
LC (its BPTC decode is commented out at :657-660). The LC is still built
honestly from the same src/dst: a packet whose payload contradicts its header
is a debugging trap for anything that does decode it.
Note that a stock xlxd is built with NB_OF_MODULES = 10, so only modules A-J
exist unless the operator rebuilt it. A-Z is accepted here since the protocol
allows it, but an absent module produces no link and no error.
tests/test_xlx_link.py adds 35 tests: the acceptance gates transcribed from
xlxd's parser, packet structure, XLX_BRIDGES expansion and its guards, and
XLX_MODULE validation. One test reproduces the canned bursts from the 2019
reference implementation byte for byte, from their own decoded src/dst and
colour code, which pins the burst builder against six years of field-proven
behaviour.
No new dependencies: bitarray and dmr_utils3 were already required.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
96 lines
3.2 KiB
Python
Executable File
96 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
###############################################################################
|
|
# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS <n0mjs@me.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
###############################################################################
|
|
|
|
'''
|
|
These are contants used by HBlink. Rather than stuff them into the main program
|
|
file, any new constants should be placed here. It makes them easier to keep track
|
|
of and keeps hblink.py shorter.
|
|
'''
|
|
|
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
|
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
|
__credits__ = ''
|
|
__license__ = 'GNU GPLv3'
|
|
__maintainer__ = 'Cort Buffington, N0MJS'
|
|
__email__ = 'n0mjs@me.com'
|
|
|
|
|
|
# DMR Related constants
|
|
ID_MIN = 1
|
|
ID_MAX = 16776415
|
|
|
|
# Timers
|
|
STREAM_TO = .360
|
|
# Seconds of no audio after which a stream is declared ended. On RF, a gap this
|
|
# long means sync is lost and any resumption is a new stream (late entry), so the
|
|
# stream is over -- we time it out and emit an END.
|
|
STREAM_TIMEOUT = 2
|
|
|
|
# Options from the LC - used for late entry
|
|
LC_OPT = b'\x00\x00\x20'
|
|
|
|
# HomeBrew Protocol Frame Types
|
|
HBPF_VOICE = 0x0
|
|
HBPF_VOICE_SYNC = 0x1
|
|
HBPF_DATA_SYNC = 0x2
|
|
HBPF_SLT_VHEAD = 0x1
|
|
HBPF_SLT_VTERM = 0x2
|
|
|
|
# HomeBrew Protocol Commands
|
|
DMRA = b'DMRA'
|
|
DMRC = b'DMRC'
|
|
DMRD = b'DMRD'
|
|
MSTCL = b'MSTCL'
|
|
MSTNAK = b'MSTNAK'
|
|
MSTPONG = b'MSTPONG'
|
|
MSTN = b'MSTN'
|
|
MSTP = b'MSTP'
|
|
MSTC = b'MSTC'
|
|
RPTL = b'RPTL'
|
|
RPTPING = b'RPTPING'
|
|
RPTCL = b'RPTCL'
|
|
RPTACK = b'RPTACK'
|
|
RPTK = b'RPTK'
|
|
RPTC = b'RPTC'
|
|
RPTP = b'RPTP'
|
|
RPTA = b'RPTA'
|
|
RPTO = b'RPTO'
|
|
|
|
|
|
# Higheset peer ID permitted by HBP
|
|
PEER_MAX = 4294967295
|
|
|
|
# XLX reflector constants. These are protocol values, NOT configuration -- every
|
|
# XLX module presents as TS2/TG9 on the wire and there is no module identifier in
|
|
# any frame. xlxd hardcodes the same pair (DMRMMDVM_REFLECTOR_SLOT = DMR_SLOT2,
|
|
# and dstId 9 in cdmrmmdvmprotocol.cpp), as does DMRGateway (XLX_SLOT/XLX_TG at
|
|
# DMRGateway.cpp:57-58). The "TG 6" seen in hotspot documentation is DMRGateway's
|
|
# configurable RF-side talkgroup, which it rewrites to TG9 before transmitting --
|
|
# HBlink peers directly with xlxd, so TG9 is what we use. Never make these
|
|
# settable: a wrong value produces traffic xlxd silently discards, with no ACK,
|
|
# no module identity on the wire, and therefore no observable symptom.
|
|
XLX_TS = 2
|
|
XLX_TGID = 9
|
|
|
|
# Module letter <-> reflector talkgroup. A-Z maps to 4001-4026; 4000 is UNLINK and
|
|
# is deliberately not a module.
|
|
XLX_UNLINK = 4000
|
|
XLX_TG_BASE = 4000
|