9 Commits
Author SHA1 Message Date
Cort BuffingtonandClaude Opus 5 3fa1b32f16 Add XLX reflector support
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>
2026-08-16 10:14:30 -05:00
Cort BuffingtonandClaude Opus 4.8 cbe405c879 rules: per-OBP TGID<->bridge table (OBP_BRIDGES) replacing inline OBP members
OpenBridge systems are point-to-point trunks that pass talkgroups by TGID: no RF
user to key ON/OFF/TIMEOUT triggers, no meaningful timeslot. Configuring them as
inline BRIDGES members was verbose and made every trunk TGID carry dead trigger
machinery. They now live in a compact per-OBP table in rules.py:

    OBP_BRIDGES = { <OPENBRIDGE system> : { <bridge name> : <TGID> } }   # TS 1, or (TGID, TS)

expand_obp_bridges() (bridge.py) expands each row into a synthetic bridge member
with triggers hard-wired inert (TO_TYPE='NONE') and appends it to the named
bridge, so the forwarding path and indexes run UNCHANGED. Because an unmapped
TGID is never a routing key, the fail-closed ingress/egress filter falls out for
free -- no separate ACL. A TGID-heavy trunk now costs zero rule-timer work.

Load-time validation (design spec §3.3/§3.5):
  * OBP system left as an inline BRIDGES member -> hard ERROR
  * OBP_BRIDGES entry that isn't an enabled OPENBRIDGE system -> hard ERROR
  * one TGID -> two bridges on a single OBP (ingress fork) -> hard ERROR
    (the mirror, a bridge with two TGIDs on one OBP, is unrepresentable in the
    {bridge: tgid} dict, so it needs no check)
  * a bridge carrying different TGIDs across two OBPs (renumber-in-transit)
    -> non-blocking WARNING

OBP_BRIDGES is optional (getattr fallback), so non-OBP configs are unaffected.
tests/test_obp_bridges.py covers expansion, TS default/override, and every
validation branch (10 tests). rules_SAMPLE.py + CONFIGURING.md document the model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 12:56:56 -05:00
Cort BuffingtonandClaude Opus 4.8 c9e2166aab Add CONFIGURING.md; slim sample-file comments to point to it
New CONFIGURING.md is the full field reference for hblink.cfg (GLOBAL,
REPORTS, LOGGER, ALIASES, and the SERVER/OUTBOUND/OPENBRIDGE system
modes) and rules.py (BRIDGES/UNIT), matching the cc2obp CONFIGURING.md
house style. hblink-SAMPLE.cfg and rules_SAMPLE.py comments are trimmed
to terse one-liners that point here.

Notable content:
- Server/Client terminology throughout (a "master" is a Server; Clients
  are repeaters/hotspots, or an OUTBOUND HBlink masquerading as one).
- Hard warning that a rules.py SYSTEM with no matching hblink.cfg system
  stops bridge.py from starting.
- ALIASES memory warning: full subscriber DB ~500 MB per process, >1 GB
  if the dashboard loads it too; use dashboard-only or filter via
  FILTER_COUNTRIES.
- BrandMeister prohibits OUTBOUND/Client links and requires OpenBridge.
- BOTH_SLOTS is HBlink-to-HBlink only; no other server accepts it.
- systemd/journal logging note; pointer to ACLS.md for the ACL model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:08:54 -05:00
Cort BuffingtonandClaude Sonnet 4.6 cac5db7363 rename: master/peer → server/repeater/outbound terminology
Replace inherited IPSC terminology with clearer server/client language
that aligns with HBlink4:

- Mode strings: 'MASTER'→'SERVER', 'PEER'→'OUTBOUND'
- Connected clients in server mode: self._peers→self._repeaters,
  'PEERS'→'REPEATERS' dict key, MAX_PEERS→MAX_REPEATERS config key
- Method names: master_*/peer_* → server_*/outbound_*
- send_peer/send_peers/send_master → send_repeater/send_repeaters/send_server
- Config keys: MASTER_IP/PORT/SOCKADDR → SERVER_IP/PORT/SOCKADDR
- hblink.cfg: [MASTER-1]→[SERVER-1], [REPEATER-1]→[OUTBOUND-1]
- Dashboard: PEERS→REPEATERS dict key, "Peer Systems"→"Repeater Systems"

Wire protocol constants (RPTL, MSTPONG, etc.) and DMR-level _peer_id
fields are unchanged — those are on-wire bytes, not internal naming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 15:49:00 -05:00
Cort Buffington fdbfbf2362 READY FOR TESTING
unit calls should be working on HBP and OBP systems. Also adds the ability to send TS data in OBP for unit calls to preserve timeslot routing for unit calls.
2020-03-15 21:23:03 -05:00
Cort Buffington 21f9c680be update sample config 2020-02-17 19:20:10 -06:00
Andy Taylor 24c201ffc2 Add basic XLX support 2019-06-19 07:32:44 +00:00
Cort Buffington f46e4001ac non-programatic updates 2019-03-01 11:30:45 -06:00
Cort Buffington c3eee1b9b1 push changes 2018-12-27 14:31:08 -06:00