Files
dmrlink3/const.py
T
Cort BuffingtonandClaude Sonnet 4.6 e511b4992c Port hblink3 ACL system to dmrlink3
- const.py: add ID_MIN, ID_MAX, PEER_MAX constants
- config.py: port merge_ranges(), acl_build(), acl_check(), process_acls()
  from hblink3; parse USE_ACL/SUB_ACL in [GLOBAL] and REG_ACL per system
- dmrlink.py: import acl_check; gate master_reg_req() with per-system REG_ACL
- bridge.py: remove old file-based build_acl() and allow_sub closure; remove
  -s/--sub-acl CLI arg; call process_acls(CONFIG); use acl_check() with
  global SUB_ACL in group_voice()
- dmrlink-SAMPLE.cfg: document and add USE_ACL, SUB_ACL, REG_ACL entries

ACL semantics: REG_ACL is per-master-system (ignored on peers, which cannot
control who registers on their upstream master); SUB_ACL is global and gates
source IDs from propagating through the bridge. No TGID ACLs — bridge rules
already control TGID propagation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:03:14 -05:00

146 lines
5.4 KiB
Python

#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2013-2026 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
###############################################################################
# IPSC Protocol Constants and Bitmasks -- merged from ipsc_const.py and ipsc_mask.py
# Known IPSC Message Types
CALL_CONFIRMATION = b'\x05'
TXT_MESSAGE_ACK = b'\x54'
CALL_MON_STATUS = b'\x61'
CALL_MON_RPT = b'\x62'
REPEATER_BLOCKED = b'\x63' # signal interference / BSI blocking event
XCMP_XNL = b'\x70'
GROUP_VOICE = b'\x80'
PVT_VOICE = b'\x81'
GROUP_DATA = b'\x83'
PVT_DATA = b'\x84'
RPT_WAKE_UP = b'\x85'
CALL_INTERRUPT_REQ = b'\x86' # call interrupt request
MASTER_REG_REQ = b'\x90'
MASTER_REG_REPLY = b'\x91'
PEER_LIST_REQ = b'\x92'
PEER_LIST_REPLY = b'\x93'
PEER_REG_REQ = b'\x94'
PEER_REG_REPLY = b'\x95'
MASTER_ALIVE_REQ = b'\x96'
MASTER_ALIVE_REPLY = b'\x97'
PEER_ALIVE_REQ = b'\x98'
PEER_ALIVE_REPLY = b'\x99'
DE_REG_REQ = b'\x9A'
DE_REG_REPLY = b'\x9B'
SYSTEM_MAP_REQ = b'\x9C' # system topology query; purpose not fully known
SYSTEM_MAP_REPLY = b'\x9D'
UNKNOWN_9E = b'\x9E' # possibly extended peer registration
WIRELINE = b'\xB2' # MNIS data sub-protocol
REMOTE_PROG_REQ = b'\xE0' # CPS remote programming session request
REMOTE_PROG_REPLY = b'\xE1'
OPCODE_0xF0 = b'\xF0' # observed, benign, no response needed
# IPSC Version Information
IPSC_VER_14 = b'\x00'
IPSC_VER_15 = b'\x00'
IPSC_VER_15A = b'\x00'
IPSC_VER_16 = b'\x01'
IPSC_VER_17 = b'\x02'
IPSC_VER_18 = b'\x02'
IPSC_VER_19 = b'\x03'
IPSC_VER_22 = b'\x04'
LINK_TYPE_IPSC = b'\x04'
# IPSC Version field used in registration packets
IPSC_VER = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
# GROUP_VOICE burst type byte values (data[GV_BURST_TYPE_OFF] returns int in Python 3)
VOICE_HEAD = 0x01
VOICE_TERM = 0x02
SLOT1_VOICE = 0x0A
SLOT2_VOICE = 0x8A
# GROUP_VOICE packet field offsets
GV_CALL_INFO_OFF = 17 # timeslot/end flags byte
GV_RTP_TS_OFF = 22 # 4-byte RTP timestamp (8 kHz clock, +480 per burst)
GV_BURST_TYPE_OFF = 30 # burst type byte (see VOICE_HEAD / SLOT1_VOICE etc.)
GV_BE_FLAG = 0x16 # data[32] value that identifies a burst-E superframe
GV_BE_LC_FLCO_OFF = 56 # FLCO field within burst-E embedded LC
GV_MIN_LEN = 33 # minimum valid GROUP_VOICE packet length
FLCO_GROUP = 0x00 # FLCO value for a group voice call (not Talker Alias)
# Packet type membership lists
ANY_PEER_REQUIRED = [GROUP_VOICE, PVT_VOICE, GROUP_DATA, PVT_DATA, CALL_MON_STATUS,
CALL_MON_RPT, REPEATER_BLOCKED, CALL_INTERRUPT_REQ, XCMP_XNL,
RPT_WAKE_UP, DE_REG_REQ]
PEER_REQUIRED = [PEER_ALIVE_REQ, PEER_ALIVE_REPLY, PEER_REG_REQ, PEER_REG_REPLY]
MASTER_REQUIRED = [PEER_LIST_REPLY, MASTER_ALIVE_REPLY]
USER_PACKETS = [GROUP_VOICE, PVT_VOICE, GROUP_DATA, PVT_DATA]
# RCM Timeslot constants
TS = {b'\x00': '1', b'\x01': '2'}
# DMR ID range constants (matches hblink3)
ID_MIN = 1
ID_MAX = 16776415
PEER_MAX = 4294967295
# ---------------------------------------------------------------------------
# IPSC Bitmasks (from ipsc_mask.py)
# ---------------------------------------------------------------------------
# LINKING STATUS byte flags
# xx.. .... Peer Operational (01 = operational)
# ..xx .... Peer MODE: 00=No Radio, 01=Analog, 10=Digital
# .... xx.. IPSC Slot 1: 10=on, 01=off
# .... ..xx IPSC Slot 2: 10=on, 01=off
PEER_OP_MSK = 0b01000000
PEER_MODE_MSK = 0b00110000
PEER_MODE_ANALOG = 0b00010000
PEER_MODE_DIGITAL = 0b00100000
IPSC_TS1_MSK = 0b00001100
IPSC_TS2_MSK = 0b00000011
# SERVICE FLAGS byte 3
CSBK_MSK = 0b10000000
RPT_MON_MSK = 0b01000000
CON_APP_MSK = 0b00100000
# SERVICE FLAGS byte 4
XNL_STAT_MSK = 0b10000000
XNL_MSTR_MSK = 0b01000000
XNL_SLAVE_MSK = 0b00100000
PKT_AUTH_MSK = 0b00010000
DATA_CALL_MSK = 0b00001000
VOICE_CALL_MSK = 0b00000100
MSTR_PEER_MSK = 0b00000001
# TIMESLOT CALL & STATUS byte (GV_CALL_INFO_OFF = byte 17 of voice/data packets)
# .x.. .... End flag (0=in-progress, 1=end)
# ..x. .... Timeslot (0=TS1, 1=TS2)
END_MSK = 0b01000000
TS_CALL_MSK = 0b00100000
# RTP header bitmasks
RTP_VER_MSK = 0b11000000
RTP_PAD_MSK = 0b00100000
RTP_EXT_MSK = 0b00010000
RTP_CSIC_MSK = 0b00001111
RTP_MRKR_MSK = 0b10000000
RTP_PAY_TYPE_MSK = 0b01111111