Files
dmrlink3/const.py
T
Cort BuffingtonandClaude Sonnet 4.6 d38ba50720 Protocol correctness pass from ipsc2hbpc/ipsc2hbp consultation
- const.py: rename CALL_MON_NACK→REPEATER_BLOCKED, UNKNOWN_COLLISION→
  CALL_INTERRUPT_REQ; add seven previously-unknown opcodes (SYSTEM_MAP_REQ/
  REPLY, UNKNOWN_9E, WIRELINE, REMOTE_PROG_REQ/REPLY, OPCODE_0xF0); replace
  BURST_DATA_TYPE dict with integer constants; add GV field offset constants
  (GV_BURST_TYPE_OFF, GV_RTP_TS_OFF, GV_BE_FLAG, GV_BE_LC_FLCO_OFF, etc.)

- dmrlink.py: fix _ts calculation bug (was returning 1 or 33; now correctly
  returns 1 or 2); use burst_type for GROUP_VOICE timeslot detection
  (HEAD/TERM read call_info; SLOT_VOICE reads burst_type bit 7 per protocol);
  master now sends PEER_LIST_REPLY immediately after MASTER_REG_REPLY without
  waiting for PEER_LIST_REQ; peer now sends PEER_LIST_REQ immediately on
  receiving MASTER_REG_REPLY; master sends DE_REG_REPLY and broadcasts updated
  peer list on DE_REG_REQ; peer_list_req sends only to requester (not
  broadcast); master watchdog: any packet from a registered peer refreshes
  keepalive timestamp; new opcodes dispatched at DEBUG instead of error

- bridge.py: replace seq_id-based call boundary detection with per-TS
  call_start tracking using VOICE_HEAD/VOICE_TERM burst types; XPR8400
  firmware churns seq_id every superframe when Talker Alias is interleaved,
  making it unreliable as a call boundary indicator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 13:18:47 -05:00

141 lines
5.3 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'}
# ---------------------------------------------------------------------------
# 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