Merge branch 'modularization'

# Conflicts:
#	bptc.py
#	constants.py
#	crc.py
#	dec_dmr.py
#	golay.py
#	hamming.py
#	hb_const.py
#	hb_router.py
#	qr.py
This commit is contained in:
Cort Buffington 2016-12-19 07:50:30 -06:00
commit 8b9dfdf739
13 changed files with 25552 additions and 23004 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ Icon
*.conf *.conf
hblink.cfg hblink.cfg
hb_routing_rules.py hb_routing_rules.py
hb_confbridge_rules.py
*.config *.config
*.json *.json
*.pickle *.pickle

455
hb_confbridge.py Executable file
View File

@ -0,0 +1,455 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 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
###############################################################################
from __future__ import print_function
# Python modules we need
import sys
from bitarray import bitarray
from time import time
from importlib import import_module
# Twisted is pretty important, so I keep it separate
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
from twisted.internet import task
# Things we import from the main hblink module
from hblink import HBSYSTEM, systems, hblink_handler
from dmr_utils.utils import hex_str_3, int_id, get_alias
from dmr_utils import decode, bptc, const
import hb_config
import hb_log
import hb_const
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
__status__ = 'pre-alpha'
# Module gobal varaibles
# Import Bridging rules
# Note: A stanza *must* exist for any MASTER or CLIENT configured in the main
# configuration file and listed as "active". It can be empty,
# but it has to exist.
def make_bridges(_hb_confbridge_bridges):
try:
bridge_file = import_module(_hb_confbridge_bridges)
logger.info('Routing bridges file found and bridges imported')
except ImportError:
sys.exit('Routing bridges file not found or invalid')
# Convert integer GROUP ID numbers from the config into hex strings
# we need to send in the actual data packets.
for _bridge in bridge_file.BRIDGES:
for _system in bridge_file.BRIDGES[_bridge]:
if _system['SYSTEM'] not in CONFIG['SYSTEMS']:
sys.exit('ERROR: Conference bridges found for system not configured main configuration')
_system['TGID'] = hex_str_3(_system['TGID'])
for i, e in enumerate(_system['ON']):
_system['ON'][i] = hex_str_3(_system['ON'][i])
for i, e in enumerate(_system['OFF']):
_system['OFF'][i] = hex_str_3(_system['OFF'][i])
_system['TIMEOUT'] = _system['TIMEOUT']*60
_system['TIMER'] = time() + _system['TIMEOUT']
return bridge_file.BRIDGES
# Import subscriber ACL
# ACL may be a single list of subscriber IDs
# Global action is to allow or deny them. Multiple lists with different actions and ranges
# are not yet implemented.
def build_acl(_sub_acl):
try:
acl_file = import_module(_sub_acl)
for i, e in enumerate(acl_file.ACL):
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
logger.info('ACL file found and ACL entries imported')
except ImportError:
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
ACL_ACTION = 'NONE'
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
# define a differnet function to be used to check the ACL
global allow_sub
if acl_file.ACL_ACTION == 'PERMIT':
def allow_sub(_sub):
if _sub in ACL:
return True
else:
return False
elif acl_file.ACL_ACTION == 'DENY':
def allow_sub(_sub):
if _sub not in ACL:
return True
else:
return False
else:
def allow_sub(_sub):
return True
return acl_file.ACL
# Run this every minute for rule timer updates
def rule_timer_loop():
logger.info('(ALL HBSYSTEMS) Rule timer loop started')
_now = time()
for _bridge in BRIDGES:
for _system in BRIDGES[_bridge]:
if _system['TO_TYPE'] == 'ON':
if _system['ACTIVE'] == True:
if _system['TIMER'] < _now:
_system['ACTIVE'] = False
logger.info('Conference Bridge TIMEOUT: DEACTIVATE System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']))
else:
timeout_in = _system['TIMER'] - _now
logger.info('Conference Bridge ACTIVE (ON timer running): System: %s Bridge: %s, TS: %s, TGID: %s, Timeout in: %ss,', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']), timeout_in)
elif _system['ACTIVE'] == False:
logger.debug('Conference Bridge INACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']))
elif _system['TO_TYPE'] == 'OFF':
if _system['ACTIVE'] == False:
if _system['TIMER'] < _now:
_system['ACTIVE'] = True
logger.info('Conference Bridge TIMEOUT: ACTIVATE System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']))
else:
timeout_in = _system['TIMER'] - _now
logger.info('Conference Bridge INACTIVE (OFF timer running): System: %s Bridge: %s, TS: %s, TGID: %s, Timeout in: %ss,', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']), timeout_in)
elif _system['ACTIVE'] == True:
logger.debug('Conference Bridge ACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']))
else:
logger.debug('Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']))
class routerSYSTEM(HBSYSTEM):
def __init__(self, _name, _config, _logger):
HBSYSTEM.__init__(self, _name, _config, _logger)
# Status information for the system, TS1 & TS2
# 1 & 2 are "timeslot"
# In TX_EMB_LC, 2-5 are burst B-E
self.STATUS = {
1: {
'RX_START': time(),
'RX_SEQ': '\x00',
'RX_RFS': '\x00',
'TX_RFS': '\x00',
'RX_STREAM_ID': '\x00',
'TX_STREAM_ID': '\x00',
'RX_TGID': '\x00\x00\x00',
'TX_TGID': '\x00\x00\x00',
'RX_TIME': time(),
'TX_TIME': time(),
'RX_TYPE': hb_const.HBPF_SLT_VTERM,
'RX_LC': '\x00',
'TX_H_LC': '\x00',
'TX_T_LC': '\x00',
'TX_EMB_LC': {
1: '\x00',
2: '\x00',
3: '\x00',
4: '\x00',
}
},
2: {
'RX_START': time(),
'RX_SEQ': '\x00',
'RX_RFS': '\x00',
'TX_RFS': '\x00',
'RX_STREAM_ID': '\x00',
'TX_STREAM_ID': '\x00',
'RX_TGID': '\x00\x00\x00',
'TX_TGID': '\x00\x00\x00',
'RX_TIME': time(),
'TX_TIME': time(),
'RX_TYPE': hb_const.HBPF_SLT_VTERM,
'RX_LC': '\x00',
'TX_H_LC': '\x00',
'TX_T_LC': '\x00',
'TX_EMB_LC': {
1: '\x00',
2: '\x00',
3: '\x00',
4: '\x00',
}
}
}
def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
pkt_time = time()
dmrpkt = _data[20:53]
_bits = int_id(_data[15])
if _call_type == 'group':
# Check for ACL match, and return if the subscriber is not allowed
if allow_sub(_rf_src) == False:
self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_radio_id), int_id(_dst_id))
return
# Is this a new call stream?
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
if (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + hb_const.STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']):
self._logger.warning('(%s) Packet received with STREAM ID: %s <FROM> SUB: %s REPEATER: %s <TO> TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_radio_id), int_id(_dst_id), _slot)
return
# This is a new call stream
self.STATUS['RX_START'] = pkt_time
self._logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s', \
self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot)
# If we can, use the LC from the voice header as to keep all options intact
if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
decoded = decode.voice_head_term(dmrpkt)
self.STATUS[_slot]['RX_LC'] = decoded['LC']
# If we don't have a voice header then don't wait to decode it from the Embedded LC
# just make a new one from the HBP header. This is good enough, and it saves lots of time
else:
self.STATUS[_slot]['RX_LC'] = const.LC_OPT + _dst_id + _rf_src
for _bridge in BRIDGES:
for _system in BRIDGES[_bridge]:
if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True):
for _target in BRIDGES[_bridge]:
if _target['SYSTEM'] != self._system:
_target_status = systems[_target['SYSTEM']].STATUS
# BEGIN CONTENTION HANDLING
#
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
# From a different group than last RX from this HBSystem, but it has been less than Group Hangtime
# From a different group than last TX to this HBSystem, but it has been less than Group Hangtime
# From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout
# From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
#
if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < self._config['GROUP_HANGTIME'])):
if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq:
self._logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID']))
continue
if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < self._config['GROUP_HANGTIME'])):
if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq:
self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']))
continue
if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < hb_const.STREAM_TO):
if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq:
self._logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID']))
continue
if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < hb_const.STREAM_TO):
if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _seq:
self._logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), _target_status[_target['TS']]['TX_RFS'])
continue
# Set values for the contention handler to test next time there is a frame to forward
_target_status[_target['TS']]['TX_TIME'] = pkt_time
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[_target['TS']]['TX_RFS'] != _rf_src) or (_target_status[_target['TS']]['TX_TGID'] != _target['TGID']):
# Record the DST TGID and Stream ID
_target_status[_target['TS']]['TX_TGID'] = _target['TGID']
_target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id
_target_status[_target['TS']]['TX_RFS'] = _rf_src
# Generate LCs (full and EMB) for the TX stream
dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src
_target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc)
_target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc)
_target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc)
self._logger.debug('(%s) Generating TX FULL and EMB LCs for destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID']))
self._logger.info('(%s) Conference Bridge: %s, Call Bridged to: System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID']))
# Handle any necessary re-writes for the destination
if _system['TS'] != _target['TS']:
_tmp_bits = _bits ^ 1 << 7
else:
_tmp_bits = _bits
# Assemble transmit HBP packet header
_tmp_data = _data[:8] + _target['TGID'] + _data[11:15] + chr(_tmp_bits) + _data[16:20]
# MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET
# MUST RE-WRITE DESTINATION TGID IF DIFFERENT
# if _dst_id != rule['DST_GROUP']:
dmrbits = bitarray(endian='big')
dmrbits.frombytes(dmrpkt)
# Create a voice header packet (FULL LC)
if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197]
# Create a voice terminator packet (FULL LC)
elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM:
dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197]
# Create a Burst B-E packet (Embedded LC)
elif _dtype_vseq in [1,2,3,4]:
dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264]
dmrpkt = dmrbits.tobytes()
_tmp_data = _tmp_data + dmrpkt + _data[53:55]
# Transmit the packet to the destination system
systems[_target['SYSTEM']].send_system(_tmp_data)
#self._logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID']))
# Final actions - Is this a voice terminator?
if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM):
call_duration = pkt_time - self.STATUS['RX_START']
self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \
self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration)
#
# Begin in-band signalling for call end. This has nothign to do with routing traffic directly.
#
# Iterate the rules dictionary
for _bridge in BRIDGES:
for _system in BRIDGES[_bridge]:
if _system['SYSTEM'] == self._system:
# TGID matches a rule source, reset its timer
if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)):
_system['TIMER'] = pkt_time + _system['TIMEOUT']
self._logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER'])
# TGID matches an ACTIVATION trigger
if _dst_id in _system['ON']:
# Set the matching rule as ACTIVE
_system['ACTIVE'] = True
_system['TIMER'] = pkt_time + _system['TIMEOUT']
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
# TGID matches an DE-ACTIVATION trigger
if _dst_id in _system['OFF']:
# Set the matching rule as ACTIVE
_system['ACTIVE'] = False
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
#
# END IN-BAND SIGNALLING
#
# Mark status variables for use later
self.STATUS[_slot]['RX_SEQ'] = _seq
self.STATUS[_slot]['RX_RFS'] = _rf_src
self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq
self.STATUS[_slot]['RX_TGID'] = _dst_id
self.STATUS[_slot]['RX_TIME'] = pkt_time
self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
#************************************************
if __name__ == '__main__':
import argparse
import sys
import os
import signal
from dmr_utils.utils import try_download, mk_id_dict
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)')
parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
cli_args = parser.parse_args()
# Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file)
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = hb_config.build_config(cli_args.CONFIG_FILE)
# Start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger = hb_log.config_logging(CONFIG['LOGGER'])
logger.debug('Logging system started, anything from here on gets logged')
# Set up the signal handler
def sig_handler(_signal, _frame):
logger.info('SHUTDOWN: HBROUTER IS TERMINATING WITH SIGNAL %s', str(_signal))
hblink_handler(_signal, _frame, logger)
logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR')
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, sig_handler)
# ID ALIAS CREATION
# Download
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Build the routing rules file
BRIDGES = make_bridges('hb_confbridge_rules')
# Build the Access Control List
ACL = build_acl('sub_acl')
# HBlink instance creation
logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['ENABLED']:
systems[system] = routerSYSTEM(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
# Initialize the rule timer -- this if for user activated stuff
rule_timer = task.LoopingCall(rule_timer_loop)
rule_timer.start(60)
reactor.run()

View File

@ -0,0 +1,48 @@
'''
THIS EXAMPLE WILL NOT WORK AS IT IS - YOU MUST SPECIFY YOUR OWN VALUES!!!
This file is organized around the "Conference Bridges" that you wish to use. If you're a c-Bridge
person, think of these as "bridge groups". You might also liken them to a "reflector". If a particular
system is "ACTIVE" on a particular conference bridge, any traffid from that system will be sent
to any other system that is active on the bridge as well. This is not an "end to end" method, because
each system must independently be activated on the bridge.
The first level (e.g. "WORLDWIDE" or "STATEWIDE" in the examples) is the name of the conference
bridge. This is any arbitrary ASCII text string you want to use. Under each conference bridge
definition are the following items -- one line for each HBSystem as defined in the main HBlink
configuration file.
* SYSTEM - The name of the sytem as listed in the main hblink configuration file (e.g. hblink.cfg)
This MUST be the exact same name as in the main config file!!!
* TS - Timeslot used for matching traffic to this confernce bridge
* TGID - Talkgroup ID used for matching traffic to this conference bridge
* ON and OFF are LISTS of Talkgroup IDs used to trigger this system off and on. Even if you
only want one (as shown in the ON example), it has to be in list format. None can be
handled with an empty list, such as " 'ON': [] ".
* TO_TYPE is timeout type. If you want to use timers, ON means when it's turned on, it will
turn off afer the timout period and OFF means it will turn back on after the timout
period. If you don't want to use timers, set it to anything else, but 'NONE' might be
a good value for documentation!
* TIMOUT is a value in minutes for the timout timer. No, I won't make it 'seconds', so don't
ask. Timers are performance "expense".
'''
BRIDGES = {
'WORLDWIDE': [
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 1, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10]},
{'SYSTEM': 'CLIENT-1', 'TS': 1, 'TGID': 3100, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10]},
],
'ENGLISH': [
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10]},
{'SYSTEM': 'CLIENT-2', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10]},
],
'STATEWIDE': [
{'SYSTEM': 'MASTER-1', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10]},
{'SYSTEM': 'CLIENT-2', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10]},
]
}
if __name__ == '__main__':
from pprint import pprint
pprint(BRIDGES)

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
############################################################################### ###############################################################################
# hb_router.py -- a call routing applicaiton for hblink.py
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com> # Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -125,7 +124,8 @@ def build_config(_config_file):
'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'), 'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'),
'IP': gethostbyname(config.get(section, 'IP')), 'IP': gethostbyname(config.get(section, 'IP')),
'PORT': config.getint(section, 'PORT'), 'PORT': config.getint(section, 'PORT'),
'PASSPHRASE': config.get(section, 'PASSPHRASE') 'PASSPHRASE': config.get(section, 'PASSPHRASE'),
'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME')
}}) }})
CONFIG['SYSTEMS'][section].update({'CLIENTS': {}}) CONFIG['SYSTEMS'][section].update({'CLIENTS': {}})

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
############################################################################### ###############################################################################
# hb_router.py -- a call routing applicaiton for hblink.py
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com> # Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -21,25 +20,23 @@
from __future__ import print_function from __future__ import print_function
from bitarray import bitarray
import constants
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
<<<<<<< HEAD:enc_dmr.py
__credits__ = 'Jonathan Naylor, G4KLX' __credits__ = 'Jonathan Naylor, G4KLX'
=======
__credits__ = ''
>>>>>>> modularization:hb_const.py
__license__ = 'GNU GPLv3' __license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS' __maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com' __email__ = 'n0mjs@me.com'
# Timers
STREAM_TO = .360
#------------------------------------------------------------------------------ # HomeBrew Protocol Frame Types
# Used to execute the module directly to run built-in tests HBPF_VOICE = 0x0
#------------------------------------------------------------------------------ HBPF_VOICE_SYNC = 0x1
HBPF_DATA_SYNC = 0x2
if __name__ == '__main__': HBPF_SLT_VHEAD = 0x1
HBPF_SLT_VTERM = 0x2
from binascii import b2a_hex as h
from time import time
print(ENC_EMB)

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
############################################################################### ###############################################################################
# hb_router.py -- a call routing applicaiton for hblink.py
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com> # Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
############################################################################### ###############################################################################
# hb_router.py -- a call routing applicaiton for hblink.py
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com> # Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -23,9 +22,9 @@ from __future__ import print_function
# Python modules we need # Python modules we need
import sys import sys
from binascii import b2a_hex as h
from bitarray import bitarray from bitarray import bitarray
from time import time from time import time
from importlib import import_module
# Twisted is pretty important, so I keep it separate # Twisted is pretty important, so I keep it separate
from twisted.internet.protocol import DatagramProtocol from twisted.internet.protocol import DatagramProtocol
@ -33,69 +32,91 @@ from twisted.internet import reactor
from twisted.internet import task from twisted.internet import task
# Things we import from the main hblink module # Things we import from the main hblink module
from hblink import CONFIG, HBSYSTEM, logger, systems, hex_str_3, int_id, sub_alias, peer_alias, tg_alias from hblink import HBSYSTEM, systems, int_id, hblink_handler
import dec_dmr from dmr_utils.utils import hex_str_3, int_id, get_alias
import bptc from dmr_utils import decode, bptc, const
import constants as const import hb_config
import hb_log
import hb_const
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
__status__ = 'pre-alpha'
# Module gobal varaibles
# Import Bridging rules # Import Bridging rules
# Note: A stanza *must* exist for any MASTER or CLIENT configured in the main # Note: A stanza *must* exist for any MASTER or CLIENT configured in the main
# configuration file and listed as "active". It can be empty, # configuration file and listed as "active". It can be empty,
# but it has to exist. # but it has to exist.
try: def make_rules(_hb_routing_rules):
from hb_routing_rules import RULES as RULES_FILE try:
logger.info('Routing rules file found and rules imported') rule_file = import_module(_hb_routing_rules)
except ImportError: logger.info('Routing rules file found and rules imported')
sys.exit('Routing rules file not found or invalid') except ImportError:
sys.exit('Routing rules file not found or invalid')
# Convert integer GROUP ID numbers from the config into hex strings # Convert integer GROUP ID numbers from the config into hex strings
# we need to send in the actual data packets. # we need to send in the actual data packets.
for _system in RULES_FILE: for _system in rule_file.RULES:
for _rule in RULES_FILE[_system]['GROUP_VOICE']: for _rule in rule_file.RULES[_system]['GROUP_VOICE']:
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP']) _rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP']) _rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
_rule['SRC_TS'] = _rule['SRC_TS'] _rule['SRC_TS'] = _rule['SRC_TS']
_rule['DST_TS'] = _rule['DST_TS'] _rule['DST_TS'] = _rule['DST_TS']
for i, e in enumerate(_rule['ON']): for i, e in enumerate(_rule['ON']):
_rule['ON'][i] = hex_str_3(_rule['ON'][i]) _rule['ON'][i] = hex_str_3(_rule['ON'][i])
for i, e in enumerate(_rule['OFF']): for i, e in enumerate(_rule['OFF']):
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i]) _rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
_rule['TIMEOUT']= _rule['TIMEOUT']*60 _rule['TIMEOUT']= _rule['TIMEOUT']*60
_rule['TIMER'] = time() + _rule['TIMEOUT'] _rule['TIMER'] = time() + _rule['TIMEOUT']
if _system not in CONFIG['SYSTEMS']: if _system not in CONFIG['SYSTEMS']:
sys.exit('ERROR: Routing rules found for system not configured main configuration') sys.exit('ERROR: Routing rules found for system not configured main configuration')
for _system in CONFIG['SYSTEMS']: for _system in CONFIG['SYSTEMS']:
if _system not in RULES_FILE: if _system not in rule_file.RULES:
sys.exit('ERROR: Routing rules not found for all systems configured') sys.exit('ERROR: Routing rules not found for all systems configured')
return rule_file.RULES
RULES = RULES_FILE
# Import subscriber ACL # Import subscriber ACL
# ACL may be a single list of subscriber IDs # ACL may be a single list of subscriber IDs
# Global action is to allow or deny them. Multiple lists with different actions and ranges # Global action is to allow or deny them. Multiple lists with different actions and ranges
# are not yet implemented. # are not yet implemented.
try: def build_acl(_sub_acl):
from sub_acl import ACL_ACTION, ACL try:
# uses more memory to build hex strings, but processes MUCH faster when checking for matches acl_file = import_module(_sub_acl)
for i, e in enumerate(ACL): for i, e in enumerate(acl_file.ACL):
ACL[i] = hex_str_3(ACL[i]) acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
logger.info('Subscriber access control file found, subscriber ACL imported') logger.info('ACL file found and ACL entries imported')
except ImportError: except ImportError:
logger.critical('\'sub_acl.py\' not found - all subscriber IDs are valid') logger.info('ACL file not found or invalid - all subscriber IDs are valid')
ACL_ACTION = 'NONE' ACL_ACTION = 'NONE'
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one) # Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
# define a differnet function to be used to check the ACL # define a differnet function to be used to check the ACL
if ACL_ACTION == 'PERMIT': global allow_sub
def allow_sub(_sub): if acl_file.ACL_ACTION == 'PERMIT':
if _sub in ACL: def allow_sub(_sub):
return True if _sub in ACL:
else: return True
return False else:
elif ACL_ACTION == 'DENY': return False
def allow_sub(_sub): elif acl_file.ACL_ACTION == 'DENY':
if _sub not in ACL: def allow_sub(_sub):
if _sub not in ACL:
return True
else:
return False
else:
def allow_sub(_sub):
return True return True
<<<<<<< HEAD
else: else:
return False return False
else: else:
@ -110,6 +131,10 @@ __license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS' __maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com' __email__ = 'n0mjs@me.com'
__status__ = 'pre-alpha' __status__ = 'pre-alpha'
=======
return acl_file.ACL
>>>>>>> modularization
# Run this every minute for rule timer updates # Run this every minute for rule timer updates
@ -122,26 +147,26 @@ def rule_timer_loop():
if _rule['ACTIVE'] == True: if _rule['ACTIVE'] == True:
if _rule['TIMER'] < _now: if _rule['TIMER'] < _now:
_rule['ACTIVE'] = False _rule['ACTIVE'] = False
logger.info('(%s) Rule timout DEACTIVATE: Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS']+1, int_id(_rule['DST_GROUP'])) logger.info('(%s) Rule timout DEACTIVATE: Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS'], int_id(_rule['DST_GROUP']))
else: else:
timeout_in = _rule['TIMER'] - _now timeout_in = _rule['TIMER'] - _now
logger.info('(%s) Rule ACTIVE with ON timer running: Timeout eligible in: %ds, Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, timeout_in, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS']+1, int_id(_rule['DST_GROUP'])) logger.info('(%s) Rule ACTIVE with ON timer running: Timeout eligible in: %ds, Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, timeout_in, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS'], int_id(_rule['DST_GROUP']))
elif _rule['TO_TYPE'] == 'OFF': elif _rule['TO_TYPE'] == 'OFF':
if _rule['ACTIVE'] == False: if _rule['ACTIVE'] == False:
if _rule['TIMER'] < _now: if _rule['TIMER'] < _now:
_rule['ACTIVE'] = True _rule['ACTIVE'] = True
logger.info('(%s) Rule timout ACTIVATE: Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS']+1, int_id(_rule['DST_GROUP'])) logger.info('(%s) Rule timout ACTIVATE: Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS'], int_id(_rule['DST_GROUP']))
else: else:
timeout_in = _rule['TIMER'] - _now timeout_in = _rule['TIMER'] - _now
logger.info('(%s) Rule DEACTIVE with OFF timer running: Timeout eligible in: %ds, Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, timeout_in, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS']+1, int_id(_rule['DST_GROUP'])) logger.info('(%s) Rule DEACTIVE with OFF timer running: Timeout eligible in: %ds, Rule name: %s, Target HBSystem: %s, TS: %s, TGID: %s', _system, timeout_in, _rule['NAME'], _rule['DST_NET'], _rule['DST_TS'], int_id(_rule['DST_GROUP']))
else: else:
logger.debug('Rule timer loop made no rule changes') logger.debug('Rule timer loop made no rule changes')
class routerSYSTEM(HBSYSTEM): class routerSYSTEM(HBSYSTEM):
def __init__(self, *args, **kwargs): def __init__(self, _name, _config, _logger):
HBSYSTEM.__init__(self, *args, **kwargs) HBSYSTEM.__init__(self, _name, _config, _logger)
# Status information for the system, TS1 & TS2 # Status information for the system, TS1 & TS2
# 1 & 2 are "timeslot" # 1 & 2 are "timeslot"
@ -158,7 +183,7 @@ class routerSYSTEM(HBSYSTEM):
'TX_TGID': '\x00\x00\x00', 'TX_TGID': '\x00\x00\x00',
'RX_TIME': time(), 'RX_TIME': time(),
'TX_TIME': time(), 'TX_TIME': time(),
'RX_TYPE': const.HBPF_SLT_VTERM, 'RX_TYPE': hb_const.HBPF_SLT_VTERM,
'RX_LC': '\x00', 'RX_LC': '\x00',
'TX_H_LC': '\x00', 'TX_H_LC': '\x00',
'TX_T_LC': '\x00', 'TX_T_LC': '\x00',
@ -180,7 +205,7 @@ class routerSYSTEM(HBSYSTEM):
'TX_TGID': '\x00\x00\x00', 'TX_TGID': '\x00\x00\x00',
'RX_TIME': time(), 'RX_TIME': time(),
'TX_TIME': time(), 'TX_TIME': time(),
'RX_TYPE': const.HBPF_SLT_VTERM, 'RX_TYPE': hb_const.HBPF_SLT_VTERM,
'RX_LC': '\x00', 'RX_LC': '\x00',
'TX_H_LC': '\x00', 'TX_H_LC': '\x00',
'TX_T_LC': '\x00', 'TX_T_LC': '\x00',
@ -202,22 +227,23 @@ class routerSYSTEM(HBSYSTEM):
# Check for ACL match, and return if the subscriber is not allowed # Check for ACL match, and return if the subscriber is not allowed
if allow_sub(_rf_src) == False: if allow_sub(_rf_src) == False:
logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_radio_id), int_id(_dst_id)) self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_radio_id), int_id(_dst_id))
return return
# Is this a new call stream? # Is this a new call stream?
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
if (self.STATUS[_slot]['RX_TYPE'] != const.HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + const.STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): if (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + hb_const.STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']):
logger.warning('(%s) Packet received with STREAM ID: %s <FROM> SUB: %s REPEATER: %s <TO> TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_radio_id), int_id(_dst_id), _slot) self._logger.warning('(%s) Packet received with STREAM ID: %s <FROM> SUB: %s REPEATER: %s <TO> TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_radio_id), int_id(_dst_id), _slot)
return return
# This is a new call stream # This is a new call stream
self.STATUS['RX_START'] = pkt_time self.STATUS['RX_START'] = pkt_time
logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s', self._system, int_id(_stream_id), sub_alias(_rf_src), int_id(_rf_src), peer_alias(_radio_id), int_id(_radio_id), tg_alias(_dst_id), int_id(_dst_id), _slot) self._logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s', \
self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot)
# If we can, use the LC from the voice header as to keep all options intact # If we can, use the LC from the voice header as to keep all options intact
if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD: if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
decoded = dec_dmr.voice_head_term(dmrpkt) decoded = decode.voice_head_term(dmrpkt)
self.STATUS[_slot]['RX_LC'] = decoded['LC'] self.STATUS[_slot]['RX_LC'] = decoded['LC']
# If we don't have a voice header then don't wait to decode it from the Embedded LC # If we don't have a voice header then don't wait to decode it from the Embedded LC
@ -242,20 +268,20 @@ class routerSYSTEM(HBSYSTEM):
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
# #
if ((rule['DST_GROUP'] != _target_status[rule['DST_TS']]['RX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['RX_TIME']) < RULES[_target]['GROUP_HANGTIME'])): if ((rule['DST_GROUP'] != _target_status[rule['DST_TS']]['RX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['RX_TIME']) < RULES[_target]['GROUP_HANGTIME'])):
if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD: if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
logger.info('(%s) Call not routed to TGID%s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['RX_TGID'])) self._logger.info('(%s) Call not routed to TGID%s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['RX_TGID']))
continue continue
if ((rule['DST_GROUP'] != _target_status[rule['DST_TS']]['TX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['TX_TIME']) < RULES[_target]['GROUP_HANGTIME'])): if ((rule['DST_GROUP'] != _target_status[rule['DST_TS']]['TX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['TX_TIME']) < RULES[_target]['GROUP_HANGTIME'])):
if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD: if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['TX_TGID'])) self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['TX_TGID']))
continue continue
if (rule['DST_GROUP'] == _target_status[rule['DST_TS']]['RX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['RX_TIME']) < const.STREAM_TO): if (rule['DST_GROUP'] == _target_status[rule['DST_TS']]['RX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['RX_TIME']) < hb_const.STREAM_TO):
if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD: if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['RX_TGID'])) self._logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['RX_TGID']))
continue continue
if (rule['DST_GROUP'] == _target_status[rule['DST_TS']]['TX_TGID']) and (_rf_src != _target_status[rule['DST_TS']]['TX_RFS']) and ((pkt_time - _target_status[rule['DST_TS']]['TX_TIME']) < const.STREAM_TO): if (rule['DST_GROUP'] == _target_status[rule['DST_TS']]['TX_TGID']) and (_rf_src != _target_status[rule['DST_TS']]['TX_RFS']) and ((pkt_time - _target_status[rule['DST_TS']]['TX_TIME']) < hb_const.STREAM_TO):
if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD: if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['TX_TGID']), _target_status[rule['DST_TS']]['TX_RFS']) self._logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['TX_TGID']), _target_status[rule['DST_TS']]['TX_RFS'])
continue continue
# Set values for the contention handler to test next time there is a frame to forward # Set values for the contention handler to test next time there is a frame to forward
@ -272,7 +298,8 @@ class routerSYSTEM(HBSYSTEM):
_target_status[rule['DST_TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) _target_status[rule['DST_TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc)
_target_status[rule['DST_TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[rule['DST_TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc)
_target_status[rule['DST_TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) _target_status[rule['DST_TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc)
logger.debug('(%s) Packet DST TGID (%s) does not match SRC TGID(%s) - Generating FULL and EMB LCs', self._system, int_id(rule['DST_GROUP']), int_id(_dst_id)) self._logger.debug('(%s) Packet DST TGID (%s) does not match SRC TGID(%s) - Generating FULL and EMB LCs', self._system, int_id(rule['DST_GROUP']), int_id(_dst_id))
self._logger.info('(%s) Call routed to: System: %s TS: %s, TGID: %s', self._system, _target, rule['DST_TS'], int_id(rule['DST_GROUP']))
# Handle any necessary re-writes for the destination # Handle any necessary re-writes for the destination
if rule['SRC_TS'] != rule['DST_TS']: if rule['SRC_TS'] != rule['DST_TS']:
@ -289,10 +316,10 @@ class routerSYSTEM(HBSYSTEM):
dmrbits = bitarray(endian='big') dmrbits = bitarray(endian='big')
dmrbits.frombytes(dmrpkt) dmrbits.frombytes(dmrpkt)
# Create a voice header packet (FULL LC) # Create a voice header packet (FULL LC)
if _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VHEAD: if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
dmrbits = _target_status[rule['DST_TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[rule['DST_TS']]['TX_H_LC'][98:197] dmrbits = _target_status[rule['DST_TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[rule['DST_TS']]['TX_H_LC'][98:197]
# Create a voice terminator packet (FULL LC) # Create a voice terminator packet (FULL LC)
elif _frame_type == const.HBPF_DATA_SYNC and _dtype_vseq == const.HBPF_SLT_VTERM: elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM:
dmrbits = _target_status[rule['DST_TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[rule['DST_TS']]['TX_T_LC'][98:197] dmrbits = _target_status[rule['DST_TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[rule['DST_TS']]['TX_T_LC'][98:197]
# Create a Burst B-E packet (Embedded LC) # Create a Burst B-E packet (Embedded LC)
elif _dtype_vseq in [1,2,3,4]: elif _dtype_vseq in [1,2,3,4]:
@ -302,14 +329,15 @@ class routerSYSTEM(HBSYSTEM):
# Transmit the packet to the destination system # Transmit the packet to the destination system
systems[_target].send_system(_tmp_data) systems[_target].send_system(_tmp_data)
logger.debug('(%s) Packet routed by rule: %s to %s system: %s', self._system, rule['NAME'], CONFIG['SYSTEMS'][_target]['MODE'], _target) self._logger.debug('(%s) Packet routed by rule: %s to %s system: %s', self._system, rule['NAME'], self._CONFIG['SYSTEMS'][_target]['MODE'], _target)
# Final actions - Is this a voice terminator? # Final actions - Is this a voice terminator?
if (_frame_type == const.HBPF_DATA_SYNC) and (_dtype_vseq == const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != const.HBPF_SLT_VTERM): if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM):
call_duration = pkt_time - self.STATUS['RX_START'] call_duration = pkt_time - self.STATUS['RX_START']
logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s, Duration: %s', self._system, int_id(_stream_id), sub_alias(_rf_src), int_id(_rf_src), peer_alias(_radio_id), int_id(_radio_id), tg_alias(_dst_id), int_id(_dst_id), _slot, call_duration) self._logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) REPEATER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \
self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_radio_id, peer_ids), int_id(_radio_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration)
# #
# Begin in-band signalling for call end. This has nothign to do with routing traffic directly. # Begin in-band signalling for call end. This has nothign to do with routing traffic directly.
@ -322,40 +350,40 @@ class routerSYSTEM(HBSYSTEM):
# TGID matches a rule source, reset its timer # TGID matches a rule source, reset its timer
if _slot == rule['SRC_TS'] and _dst_id == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)): if _slot == rule['SRC_TS'] and _dst_id == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)):
rule['TIMER'] = pkt_time + rule['TIMEOUT'] rule['TIMER'] = pkt_time + rule['TIMEOUT']
logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', self._system, rule['NAME'], rule['TIMER']) self._logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', self._system, rule['NAME'], rule['TIMER'])
# Scan for reciprocal rules and reset their timers as well. # Scan for reciprocal rules and reset their timers as well.
for target_rule in RULES[_target]['GROUP_VOICE']: for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']: if target_rule['NAME'] == rule['NAME']:
target_rule['TIMER'] = pkt_time + target_rule['TIMEOUT'] target_rule['TIMER'] = pkt_time + target_rule['TIMEOUT']
logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', self._system, target_rule['NAME'], _target, rule['TIMER']) self._logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', self._system, target_rule['NAME'], _target, rule['TIMER'])
# TGID matches an ACTIVATION trigger # TGID matches an ACTIVATION trigger
if _dst_id in rule['ON']: if _dst_id in rule['ON']:
# Set the matching rule as ACTIVE # Set the matching rule as ACTIVE
rule['ACTIVE'] = True rule['ACTIVE'] = True
rule['TIMER'] = pkt_time + rule['TIMEOUT'] rule['TIMER'] = pkt_time + rule['TIMEOUT']
logger.info('(%s) Primary routing Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE']) self._logger.info('(%s) Primary routing Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
# Set reciprocal rules for other IPSCs as ACTIVE # Set reciprocal rules for other IPSCs as ACTIVE
for target_rule in RULES[_target]['GROUP_VOICE']: for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']: if target_rule['NAME'] == rule['NAME']:
target_rule['ACTIVE'] = True target_rule['ACTIVE'] = True
target_rule['TIMER'] = pkt_time + target_rule['TIMEOUT'] target_rule['TIMER'] = pkt_time + target_rule['TIMEOUT']
logger.info('(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE']) self._logger.info('(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
# TGID matches an DE-ACTIVATION trigger # TGID matches an DE-ACTIVATION trigger
if _dst_id in rule['OFF']: if _dst_id in rule['OFF']:
# Set the matching rule as ACTIVE # Set the matching rule as ACTIVE
rule['ACTIVE'] = False rule['ACTIVE'] = False
logger.info('(%s) Routing Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE']) self._logger.info('(%s) Routing Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
# Set reciprocal rules for other IPSCs as ACTIVE # Set reciprocal rules for other IPSCs as ACTIVE
_target = rule['DST_NET'] _target = rule['DST_NET']
for target_rule in RULES[_target]['GROUP_VOICE']: for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']: if target_rule['NAME'] == rule['NAME']:
target_rule['ACTIVE'] = False target_rule['ACTIVE'] = False
logger.info('(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE']) self._logger.info('(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
# #
# END IN-BAND SIGNALLING # END IN-BAND SIGNALLING
# #
@ -374,13 +402,80 @@ class routerSYSTEM(HBSYSTEM):
#************************************************ #************************************************
if __name__ == '__main__': if __name__ == '__main__':
logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')
import argparse
import sys
import os
import signal
from dmr_utils.utils import try_download, mk_id_dict
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)')
parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
cli_args = parser.parse_args()
# Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file)
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = hb_config.build_config(cli_args.CONFIG_FILE)
# Start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger = hb_log.config_logging(CONFIG['LOGGER'])
logger.debug('Logging system started, anything from here on gets logged')
# Set up the signal handler
def sig_handler(_signal, _frame):
logger.info('SHUTDOWN: HBROUTER IS TERMINATING WITH SIGNAL %s', str(_signal))
hblink_handler(_signal, _frame, logger)
logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR')
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, sig_handler)
# ID ALIAS CREATION
# Download
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Build the routing rules file
RULES = make_rules('hb_routing_rules')
# Build the Access Control List
ACL = build_acl('sub_acl')
# HBlink instance creation # HBlink instance creation
# HBlink instance creation logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')
for system in CONFIG['SYSTEMS']: for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['ENABLED']:
systems[system] = routerSYSTEM(system) systems[system] = routerSYSTEM(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])

View File

@ -12,8 +12,8 @@ NOTES:
period. If you don't want to use timers, set it to anything else, but 'NONE' might be period. If you don't want to use timers, set it to anything else, but 'NONE' might be
a good value for documentation! a good value for documentation!
* TIMOUT is a value in minutes for the timout timer. No, I won't make it 'seconds', so don't * TIMOUT is a value in minutes for the timout timer. No, I won't make it 'seconds', so don't
ask. Timers are performance "expense". ask. Timers are performance "expense".
DO YOU THINK THIS FILE IS TOO COMPLICATED? DO YOU THINK THIS FILE IS TOO COMPLICATED?
Because you guys all want more and more features, this file is getting complicated. I have Because you guys all want more and more features, this file is getting complicated. I have
dabbled with using a parser to make it easier to build. I'm torn. There is a HUGE benefit dabbled with using a parser to make it easier to build. I'm torn. There is a HUGE benefit

View File

@ -99,4 +99,4 @@ LOCATION: Anywhere, USA
DESCRIPTION: This is a cool repeater DESCRIPTION: This is a cool repeater
URL: www.w1abc.org URL: www.w1abc.org
SOFTWARE_ID: HBlink SOFTWARE_ID: HBlink
PACKAGE_ID: v0.1 PACKAGE_ID: v0.1

345
hblink.py
View File

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
############################################################################### ###############################################################################
# hb_router.py -- a call routing applicaiton for hblink.py
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com> # Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -21,27 +20,15 @@
from __future__ import print_function from __future__ import print_function
# Python modules we need
import argparse
import sys
import os
import signal
# Specifig functions from modules we need # Specifig functions from modules we need
from binascii import b2a_hex as h from binascii import b2a_hex as ahex
from binascii import a2b_hex as a from binascii import a2b_hex as bhex
from socket import gethostbyname
from random import randint from random import randint
from hashlib import sha256 from hashlib import sha256
from time import time from time import time
from urllib import URLopener from bitstring import BitArray
from csv import reader as csv_reader
#from bitstring import BitArray
import socket import socket
# Debugging functions
from pprint import pprint
# Twisted is pretty important, so I keep it separate # Twisted is pretty important, so I keep it separate
from twisted.internet.protocol import DatagramProtocol from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor from twisted.internet import reactor
@ -50,6 +37,7 @@ from twisted.internet import task
# Other files we pull from -- this is mostly for readability and segmentation # Other files we pull from -- this is mostly for readability and segmentation
import hb_log import hb_log
import hb_config import hb_config
from dmr_utils.utils import int_id, hex_str_4
# Does anybody read this stuff? There's a PEP somewhere that says I should do this. # Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
@ -63,141 +51,24 @@ __email__ = 'n0mjs@me.com'
# Global variables used whether we are a module or __main__ # Global variables used whether we are a module or __main__
systems = {} systems = {}
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)')
parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
cli_args = parser.parse_args()
# Ensure we have a path for the config file, if one wasn't specified, then use the execution directory
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = hb_config.build_config(cli_args.CONFIG_FILE)
# Call the external routing to start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger = hb_log.config_logging(CONFIG['LOGGER'])
logger.debug('Logging system started, anything from here on gets logged')
# Download and build dictionaries for mapping number to aliases
# Used by applications. These lookups take time, please do not shove them
# into this file everywhere and send a pull request!!!
# Download a new file if it doesn't exist, or is older than the stale time
def try_download(_path, _file, _url, _stale):
now = time()
url = URLopener()
file_exists = os.path.isfile(_path+_file) == True
if file_exists:
file_old = (os.path.getmtime(_path+_file) + _stale) < now
if not file_exists or (file_exists and file_old):
try:
url.retrieve(_url, _path+_file)
logger.info('ID ALIAS MAPPER: DOWNLOAD: \'%s\' successfully downloaded', _file)
except IOError:
logger.warning('ID ALIAS MAPPER: DOWNLOAD: \'%s\' could not be downloaded', _file)
else:
logger.info('ID ALIAS MAPPER: DOWNLOAD: \'%s\' is current, not downloaded', _file)
url.close()
def mk_id_dict(_path, _file):
dict = {}
try:
with open(_path+_file, 'rU') as _handle:
ids = csv_reader(_handle, dialect='excel', delimiter=',')
for row in ids:
dict[int(row[0])] = (row[1])
logger.info('ID ALIAS MAPPER: IMPORT: %s IDs from FILE %s', len(dict), _file)
_handle.close
except IOError:
logger.warning('ID ALIAS MAPPER: IMPORT: FILE %s not found; aliases will not be available', _file)
return dict
def get_info(_id, _dict):
if _id in _dict:
return _dict[_id]
return _id
# Download files and build the dictionaries
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
# These are the functions you should use to look up IDs in the dictionaries
def sub_alias(_sub_id):
return get_info(int_id(_sub_id), subscriber_ids)
def peer_alias(_peer_id):
return get_info(int_id(_peer_id), peer_ids)
def tg_alias(_tgid):
return get_info(int_id(_tgid), talkgroup_ids)
# Shut ourselves down gracefully by disconnecting from the masters and clients. # Shut ourselves down gracefully by disconnecting from the masters and clients.
def handler(_signal, _frame): def hblink_handler(_signal, _frame, _logger):
logger.info('*** HBLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_system = systems[system] _logger.info('SHUTDOWN: DE-REGISTER SYSTEM: %s', system)
if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER': systems[system].dereg()
for client in CONFIG['SYSTEMS'][system]['CLIENTS']:
this_system.send_client(client, 'MSTCL'+client)
logger.info('(%s) Sending De-Registration to Client: %s (%s)', system, CONFIG['SYSTEMS'][system]['CLIENTS'][client]['CALLSIGN'], CONFIG['SYSTEMS'][system]['CLIENTS'][client]['RADIO_ID'])
elif CONFIG['SYSTEMS'][system]['MODE'] == 'CLIENT':
this_system.send_master('RPTCL'+CONFIG['SYSTEMS'][system]['RADIO_ID'])
logger.info('(%s) De-Registering From the Master', system)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, handler)
#************************************************
# UTILITY FUNCTIONS
#************************************************
# Create a 3 byte hex string from an integer
def hex_str_3(_int_id):
try:
#return hex(_int_id)[2:].rjust(6,'0').decode('hex')
return format(_int_id,'x').rjust(6,'0').decode('hex')
except TypeError:
logger.error('hex_str_3: invalid integer length')
# Create a 4 byte hex string from an integer
def hex_str_4(_int_id):
try:
#return hex(_int_id)[2:].rjust(8,'0').decode('hex')
return format(_int_id,'x').rjust(8,'0').decode('hex')
except TypeError:
logger.error('hex_str_4: invalid integer length')
# Convert a hex string to an int (radio ID, etc.)
def int_id(_hex_string):
return int(h(_hex_string), 16)
#************************************************ #************************************************
# AMBE CLASS: Used to parse out AMBE and send to gateway # AMBE CLASS: Used to parse out AMBE and send to gateway
#************************************************ #************************************************
class AMBE: class AMBE:
_sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) def __init__(self, _config, _logger):
_exp_ip = CONFIG['AMBE']['EXPORT_IP'] self._CONFIG = _config
_exp_port = CONFIG['AMBE']['EXPORT_PORT']
self._sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
self._exp_ip = self._CONFIG['AMBE']['EXPORT_IP']
self._exp_port = self._CONFIG['AMBE']['EXPORT_PORT']
def parseAMBE(self, _client, _data): def parseAMBE(self, _client, _data):
_seq = int_id(_data[4:5]) _seq = int_id(_data[4:5])
@ -214,7 +85,7 @@ class AMBE:
_client, _seq, _srcID, _dstID, _rptID, _bits, _slot, _callType, _frameType, _voiceSeq, _streamID ) _client, _seq, _srcID, _dstID, _rptID, _bits, _slot, _callType, _frameType, _voiceSeq, _streamID )
#logger.debug('Frame 1:(%s)', self.ByteToHex(_data)) #logger.debug('Frame 1:(%s)', self.ByteToHex(_data))
_dmr_frame = BitArray('0x'+h(_data[20:])) _dmr_frame = BitArray('0x'+ahex(_data[20:]))
_ambe = _dmr_frame[0:108] + _dmr_frame[156:264] _ambe = _dmr_frame[0:108] + _dmr_frame[156:264]
#_sock.sendto(_ambe.tobytes(), ("127.0.0.1", 31000)) #_sock.sendto(_ambe.tobytes(), ("127.0.0.1", 31000))
@ -223,94 +94,103 @@ class AMBE:
self._sock.sendto(ambeBytes[9:18], (self._exp_ip, self._exp_port)) self._sock.sendto(ambeBytes[9:18], (self._exp_ip, self._exp_port))
self._sock.sendto(ambeBytes[18:27], (self._exp_ip, self._exp_port)) self._sock.sendto(ambeBytes[18:27], (self._exp_ip, self._exp_port))
#************************************************ #************************************************
# HB MASTER CLASS # HB MASTER CLASS
#************************************************ #************************************************
class HBSYSTEM(DatagramProtocol): class HBSYSTEM(DatagramProtocol):
def __init__(self, *args, **kwargs): def __init__(self, _name, _config, _logger):
if len(args) == 1: # Define a few shortcuts to make the rest of the class more readable
# Define a few shortcuts to make the rest of the class more readable self._CONFIG = _config
self._system = args[0] self._system = _name
self._config = CONFIG['SYSTEMS'][self._system] self._logger = _logger
self._config = self._CONFIG['SYSTEMS'][self._system]
# Define shortcuts and generic function names based on the type of system we are
if self._config['MODE'] == 'MASTER': # Define shortcuts and generic function names based on the type of system we are
self._clients = CONFIG['SYSTEMS'][self._system]['CLIENTS'] if self._config['MODE'] == 'MASTER':
self.send_system = self.send_clients self._clients = self._CONFIG['SYSTEMS'][self._system]['CLIENTS']
self.maintenance_loop = self.master_maintenance_loop self.send_system = self.send_clients
self.datagramReceived = self.master_datagramReceived self.maintenance_loop = self.master_maintenance_loop
self.datagramReceived = self.master_datagramReceived
elif self._config['MODE'] == 'CLIENT': self.dereg = self.master_dereg
self._stats = self._config['STATS']
self.send_system = self.send_master elif self._config['MODE'] == 'CLIENT':
self.maintenance_loop = self.client_maintenance_loop self._stats = self._config['STATS']
self.datagramReceived = self.client_datagramReceived self.send_system = self.send_master
self.maintenance_loop = self.client_maintenance_loop
# Configure for AMBE audio export if enabled self.datagramReceived = self.client_datagramReceived
if self._config['EXPORT_AMBE']: self.dereg = self.client_dereg
self._ambe = AMBE()
else: # Configure for AMBE audio export if enabled
# If we didn't get called correctly, log it and quit. if self._config['EXPORT_AMBE']:
logger.error('(%s) HBMASTER was not called with an argument. Terminating', self._system) self._ambe = AMBE()
sys.exit()
def startProtocol(self): def startProtocol(self):
# Set up periodic loop for tracking pings from clients. Run every 'PING_TIME' seconds # Set up periodic loop for tracking pings from clients. Run every 'PING_TIME' seconds
self._system_maintenance = task.LoopingCall(self.maintenance_loop) self._system_maintenance = task.LoopingCall(self.maintenance_loop)
self._system_maintenance_loop = self._system_maintenance.start(CONFIG['GLOBAL']['PING_TIME']) self._system_maintenance_loop = self._system_maintenance.start(self._CONFIG['GLOBAL']['PING_TIME'])
# Aliased in __init__ to maintenance_loop if system is a master # Aliased in __init__ to maintenance_loop if system is a master
def master_maintenance_loop(self): def master_maintenance_loop(self):
logger.debug('(%s) Master maintenance loop started', self._system) self._logger.debug('(%s) Master maintenance loop started', self._system)
for client in self._clients: for client in self._clients:
_this_client = self._clients[client] _this_client = self._clients[client]
# Check to see if any of the clients have been quiet (no ping) longer than allowed # Check to see if any of the clients have been quiet (no ping) longer than allowed
if _this_client['LAST_PING']+CONFIG['GLOBAL']['PING_TIME']*CONFIG['GLOBAL']['MAX_MISSED'] < time(): if _this_client['LAST_PING']+self._CONFIG['GLOBAL']['PING_TIME']*self._CONFIG['GLOBAL']['MAX_MISSED'] < time():
logger.info('(%s) Client %s (%s) has timed out', self._system, _this_client['CALLSIGN'], _this_client['RADIO_ID']) self._logger.info('(%s) Client %s (%s) has timed out', self._system, _this_client['CALLSIGN'], _this_client['RADIO_ID'])
# Remove any timed out clients from the configuration # Remove any timed out clients from the configuration
del CONFIG['SYSTEMS'][self._system]['CLIENTS'][client] del self._CONFIG['SYSTEMS'][self._system]['CLIENTS'][client]
# Aliased in __init__ to maintenance_loop if system is a client # Aliased in __init__ to maintenance_loop if system is a client
def client_maintenance_loop(self): def client_maintenance_loop(self):
logger.debug('(%s) Client maintenance loop started', self._system) self._logger.debug('(%s) Client maintenance loop started', self._system)
# If we're not connected, zero out the stats and send a login request RPTL # If we're not connected, zero out the stats and send a login request RPTL
if self._stats['CONNECTION'] == 'NO' or self._stats['CONNECTION'] == 'RTPL_SENT': if self._stats['CONNECTION'] == 'NO' or self._stats['CONNECTION'] == 'RTPL_SENT':
self._stats['PINGS_SENT'] = 0 self._stats['PINGS_SENT'] = 0
self._stats['PINGS_ACKD'] = 0 self._stats['PINGS_ACKD'] = 0
self._stats['CONNECTION'] = 'RTPL_SENT' self._stats['CONNECTION'] = 'RTPL_SENT'
self.send_master('RPTL'+self._config['RADIO_ID']) self.send_master('RPTL'+self._config['RADIO_ID'])
logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT']) self._logger.info('(%s) Sending login request to master %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'])
# If we are connected, sent a ping to the master and increment the counter # If we are connected, sent a ping to the master and increment the counter
if self._stats['CONNECTION'] == 'YES': if self._stats['CONNECTION'] == 'YES':
self.send_master('RPTPING'+self._config['RADIO_ID']) self.send_master('RPTPING'+self._config['RADIO_ID'])
self._stats['PINGS_SENT'] += 1 self._stats['PINGS_SENT'] += 1
logger.debug('(%s) RPTPING Sent to Master. Pings Since Connected: %s', self._system, self._stats['PINGS_SENT']) self._logger.debug('(%s) RPTPING Sent to Master. Pings Since Connected: %s', self._system, self._stats['PINGS_SENT'])
def send_clients(self, _packet): def send_clients(self, _packet):
for _client in self._clients: for _client in self._clients:
self.send_client(_client, _packet) self.send_client(_client, _packet)
#logger.debug('(%s) Packet sent to client %s', self._system, self._clients[_client]['RADIO_ID']) #self._logger.debug('(%s) Packet sent to client %s', self._system, self._clients[_client]['RADIO_ID'])
def send_client(self, _client, _packet): def send_client(self, _client, _packet):
_ip = self._clients[_client]['IP'] _ip = self._clients[_client]['IP']
_port = self._clients[_client]['PORT'] _port = self._clients[_client]['PORT']
self.transport.write(_packet, (_ip, _port)) self.transport.write(_packet, (_ip, _port))
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
#logger.debug('(%s) TX Packet to %s on port %s: %s', self._clients[_client]['RADIO_ID'], self._clients[_client]['IP'], self._clients[_client]['PORT'], h(_packet)) #self._logger.debug('(%s) TX Packet to %s on port %s: %s', self._clients[_client]['RADIO_ID'], self._clients[_client]['IP'], self._clients[_client]['PORT'], ahex(_packet))
def send_master(self, _packet): def send_master(self, _packet):
self.transport.write(_packet, (self._config['MASTER_IP'], self._config['MASTER_PORT'])) self.transport.write(_packet, (self._config['MASTER_IP'], self._config['MASTER_PORT']))
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
#logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], h(_packet)) #self._logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet))
def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
pass pass
def master_dereg(self):
for _client in self._clients:
self.send_client(_client, 'MSTCL'+_client)
self._logger.info('(%s) De-Registration sent to Client: %s (%s)', self._system, self._clients[_client]['CALLSIGN'], self._clients[_client]['RADIO_ID'])
def client_dereg(self):
self.send_master('RPTCL'+self._config['RADIO_ID'])
self._logger.info('(%s) De-Registeration sent to Master: %s:%s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'])
# Aliased in __init__ to datagramReceived if system is a master # Aliased in __init__ to datagramReceived if system is a master
def master_datagramReceived(self, _data, (_host, _port)): def master_datagramReceived(self, _data, (_host, _port)):
# Keep This Line Commented Unless HEAVILY Debugging! # Keep This Line Commented Unless HEAVILY Debugging!
#logger.debug('(%s) RX packet from %s:%s -- %s', self._system, _host, _port, h(_data)) #self._logger.debug('(%s) RX packet from %s:%s -- %s', self._system, _host, _port, ahex(_data))
# Extract the command, which is various length, all but one 4 significant characters -- RPTCL # Extract the command, which is various length, all but one 4 significant characters -- RPTCL
_command = _data[:4] _command = _data[:4]
@ -330,7 +210,7 @@ class HBSYSTEM(DatagramProtocol):
_frame_type = (_bits & 0x30) >> 4 _frame_type = (_bits & 0x30) >> 4
_dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F
_stream_id = _data[16:20] _stream_id = _data[16:20]
#logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id)) #self._logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id))
# If AMBE audio exporting is configured... # If AMBE audio exporting is configured...
if self._config['EXPORT_AMBE']: if self._config['EXPORT_AMBE']:
@ -341,7 +221,7 @@ class HBSYSTEM(DatagramProtocol):
for _client in self._clients: for _client in self._clients:
if _client != _radio_id: if _client != _radio_id:
self.send_client(_client, _data) self.send_client(_client, _data)
logger.debug('(%s) Packet on TS%s from %s (%s) for destination ID %s repeated to client: %s (%s) [Stream ID: %s]', self._system, _slot, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id), int_id(_dst_id), self._clients[_client]['CALLSIGN'], int_id(_client), int_id(_stream_id)) self._logger.debug('(%s) Packet on TS%s from %s (%s) for destination ID %s repeated to client: %s (%s) [Stream ID: %s]', self._system, _slot, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id), int_id(_dst_id), self._clients[_client]['CALLSIGN'], int_id(_client), int_id(_stream_id))
# Userland actions -- typically this is the function you subclass for an application # Userland actions -- typically this is the function you subclass for an application
self.dmrd_received(_radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data) self.dmrd_received(_radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data)
@ -356,7 +236,7 @@ class HBSYSTEM(DatagramProtocol):
'IP': _host, 'IP': _host,
'PORT': _port, 'PORT': _port,
'SALT': randint(0,0xFFFFFFFF), 'SALT': randint(0,0xFFFFFFFF),
'RADIO_ID': str(int(h(_radio_id), 16)), 'RADIO_ID': str(int(ahex(_radio_id), 16)),
'CALLSIGN': '', 'CALLSIGN': '',
'RX_FREQ': '', 'RX_FREQ': '',
'TX_FREQ': '', 'TX_FREQ': '',
@ -372,14 +252,14 @@ class HBSYSTEM(DatagramProtocol):
'SOFTWARE_ID': '', 'SOFTWARE_ID': '',
'PACKAGE_ID': '', 'PACKAGE_ID': '',
}}) }})
logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._system, int_id(_radio_id), _host, _port) self._logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._system, int_id(_radio_id), _host, _port)
_salt_str = hex_str_4(self._clients[_radio_id]['SALT']) _salt_str = hex_str_4(self._clients[_radio_id]['SALT'])
self.send_client(_radio_id, 'RPTACK'+_salt_str) self.send_client(_radio_id, 'RPTACK'+_salt_str)
self._clients[_radio_id]['CONNECTION'] = 'CHALLENGE_SENT' self._clients[_radio_id]['CONNECTION'] = 'CHALLENGE_SENT'
logger.info('(%s) Sent Challenge Response to %s for login: %s', self._system, int_id(_radio_id), self._clients[_radio_id]['SALT']) self._logger.info('(%s) Sent Challenge Response to %s for login: %s', self._system, int_id(_radio_id), self._clients[_radio_id]['SALT'])
else: else:
self.transport.write('MSTNAK'+_radio_id, (_host, _port)) self.transport.write('MSTNAK'+_radio_id, (_host, _port))
logger.warning('(%s) Invalid Login from Radio ID: %s', self._system, int_id(_radio_id)) self._logger.warning('(%s) Invalid Login from Radio ID: %s', self._system, int_id(_radio_id))
elif _command == 'RPTK': # Repeater has answered our login challenge elif _command == 'RPTK': # Repeater has answered our login challenge
_radio_id = _data[4:8] _radio_id = _data[4:8]
@ -391,18 +271,18 @@ class HBSYSTEM(DatagramProtocol):
_this_client['LAST_PING'] = time() _this_client['LAST_PING'] = time()
_sent_hash = _data[8:] _sent_hash = _data[8:]
_salt_str = hex_str_4(_this_client['SALT']) _salt_str = hex_str_4(_this_client['SALT'])
_calc_hash = a(sha256(_salt_str+self._config['PASSPHRASE']).hexdigest()) _calc_hash = bhex(sha256(_salt_str+self._config['PASSPHRASE']).hexdigest())
if _sent_hash == _calc_hash: if _sent_hash == _calc_hash:
_this_client['CONNECTION'] = 'WAITING_CONFIG' _this_client['CONNECTION'] = 'WAITING_CONFIG'
self.send_client(_radio_id, 'RPTACK'+_radio_id) self.send_client(_radio_id, 'RPTACK'+_radio_id)
logger.info('(%s) Client %s has completed the login exchange successfully', self._system, _this_client['RADIO_ID']) self._logger.info('(%s) Client %s has completed the login exchange successfully', self._system, _this_client['RADIO_ID'])
else: else:
logger.info('(%s) Client %s has FAILED the login exchange successfully', self._system, _this_client['RADIO_ID']) self._logger.info('(%s) Client %s has FAILED the login exchange successfully', self._system, _this_client['RADIO_ID'])
self.transport.write('MSTNAK'+_radio_id, (_host, _port)) self.transport.write('MSTNAK'+_radio_id, (_host, _port))
del self._clients[_radio_id] del self._clients[_radio_id]
else: else:
self.transport.write('MSTNAK'+_radio_id, (_host, _port)) self.transport.write('MSTNAK'+_radio_id, (_host, _port))
logger.warning('(%s) Login challenge from Radio ID that has not logged in: %s', self._system, int_id(_radio_id)) self._logger.warning('(%s) Login challenge from Radio ID that has not logged in: %s', self._system, int_id(_radio_id))
elif _command == 'RPTC': # Repeater is sending it's configuraiton OR disconnecting elif _command == 'RPTC': # Repeater is sending it's configuraiton OR disconnecting
if _data[:5] == 'RPTCL': # Disconnect command if _data[:5] == 'RPTCL': # Disconnect command
@ -411,7 +291,7 @@ class HBSYSTEM(DatagramProtocol):
and self._clients[_radio_id]['CONNECTION'] == 'YES' \ and self._clients[_radio_id]['CONNECTION'] == 'YES' \
and self._clients[_radio_id]['IP'] == _host \ and self._clients[_radio_id]['IP'] == _host \
and self._clients[_radio_id]['PORT'] == _port: and self._clients[_radio_id]['PORT'] == _port:
logger.info('(%s) Client is closing down: %s (%s)', self._system, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id)) self._logger.info('(%s) Client is closing down: %s (%s)', self._system, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id))
self.transport.write('MSTNAK'+_radio_id, (_host, _port)) self.transport.write('MSTNAK'+_radio_id, (_host, _port))
del self._clients[_radio_id] del self._clients[_radio_id]
@ -440,10 +320,10 @@ class HBSYSTEM(DatagramProtocol):
_this_client['PACKAGE_ID'] = _data[262:302] _this_client['PACKAGE_ID'] = _data[262:302]
self.send_client(_radio_id, 'RPTACK'+_radio_id) self.send_client(_radio_id, 'RPTACK'+_radio_id)
logger.info('(%s) Client %s (%s) has sent repeater configuration', self._system, _this_client['CALLSIGN'], _this_client['RADIO_ID']) self._logger.info('(%s) Client %s (%s) has sent repeater configuration', self._system, _this_client['CALLSIGN'], _this_client['RADIO_ID'])
else: else:
self.transport.write('MSTNAK'+_radio_id, (_host, _port)) self.transport.write('MSTNAK'+_radio_id, (_host, _port))
logger.warning('(%s) Client info from Radio ID that has not logged in: %s', self._system, int_id(_radio_id)) self._logger.warning('(%s) Client info from Radio ID that has not logged in: %s', self._system, int_id(_radio_id))
elif _command == 'RPTP': # RPTPing -- client is pinging us elif _command == 'RPTP': # RPTPing -- client is pinging us
_radio_id = _data[7:11] _radio_id = _data[7:11]
@ -453,18 +333,18 @@ class HBSYSTEM(DatagramProtocol):
and self._clients[_radio_id]['PORT'] == _port: and self._clients[_radio_id]['PORT'] == _port:
self._clients[_radio_id]['LAST_PING'] = time() self._clients[_radio_id]['LAST_PING'] = time()
self.send_client(_radio_id, 'MSTPONG'+_radio_id) self.send_client(_radio_id, 'MSTPONG'+_radio_id)
logger.debug('(%s) Received and answered RPTPING from client %s (%s)', self._system, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id)) self._logger.debug('(%s) Received and answered RPTPING from client %s (%s)', self._system, self._clients[_radio_id]['CALLSIGN'], int_id(_radio_id))
else: else:
self.transport.write('MSTNAK'+_radio_id, (_host, _port)) self.transport.write('MSTNAK'+_radio_id, (_host, _port))
logger.warning('(%s) Client info from Radio ID that has not logged in: %s', self._system, int_id(_radio_id)) self._logger.warning('(%s) Client info from Radio ID that has not logged in: %s', self._system, int_id(_radio_id))
else: else:
logger.error('(%s) Unrecognized command from: %s. Packet: %s', self._system, int_id(_radio_id), h(_data)) self._logger.error('(%s) Unrecognized command from: %s. Packet: %s', self._system, int_id(_radio_id), ahex(_data))
# Aliased in __init__ to datagramReceived if system is a client # Aliased in __init__ to datagramReceived if system is a client
def client_datagramReceived(self, _data, (_host, _port)): def client_datagramReceived(self, _data, (_host, _port)):
# Keep This Line Commented Unless HEAVILY Debugging! # Keep This Line Commented Unless HEAVILY Debugging!
# logger.debug('(%s) RX packet from %s:%s -- %s', self._system, _host, _port, h(_data)) # self._logger.debug('(%s) RX packet from %s:%s -- %s', self._system, _host, _port, ahex(_data))
# Validate that we receveived this packet from the master - security check! # Validate that we receveived this packet from the master - security check!
if self._config['MASTER_IP'] == _host and self._config['MASTER_PORT'] == _port: if self._config['MASTER_IP'] == _host and self._config['MASTER_PORT'] == _port:
@ -482,7 +362,7 @@ class HBSYSTEM(DatagramProtocol):
_frame_type = (_bits & 0x30) >> 4 _frame_type = (_bits & 0x30) >> 4
_dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F
_stream_id = _data[16:20] _stream_id = _data[16:20]
#logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id)) #self._logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id))
# If AMBE audio exporting is configured... # If AMBE audio exporting is configured...
if self._config['EXPORT_AMBE']: if self._config['EXPORT_AMBE']:
@ -494,22 +374,22 @@ class HBSYSTEM(DatagramProtocol):
elif _command == 'MSTN': # Actually MSTNAK -- a NACK from the master elif _command == 'MSTN': # Actually MSTNAK -- a NACK from the master
_radio_id = _data[4:8] _radio_id = _data[4:8]
if _radio_id == self._config['RADIO_ID']: # Validate the source and intended target if _radio_id == self._config['RADIO_ID']: # Validate the source and intended target
logger.warning('(%s) MSTNAK Received', self._system) self._logger.warning('(%s) MSTNAK Received', self._system)
self._stats['CONNECTION'] = 'NO' # Disconnect ourselves and re-register self._stats['CONNECTION'] = 'NO' # Disconnect ourselves and re-register
elif _command == 'RPTA': # Actually RPTACK -- an ACK from the master elif _command == 'RPTA': # Actually RPTACK -- an ACK from the master
# Depending on the state, an RPTACK means different things, in each clause, we check and/or set the state # Depending on the state, an RPTACK means different things, in each clause, we check and/or set the state
if self._stats['CONNECTION'] == 'RTPL_SENT': # If we've sent a login request... if self._stats['CONNECTION'] == 'RTPL_SENT': # If we've sent a login request...
_login_int32 = _data[6:10] _login_int32 = _data[6:10]
logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._system, int_id(_login_int32)) self._logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._system, int_id(_login_int32))
_pass_hash = sha256(_login_int32+self._config['PASSPHRASE']).hexdigest() _pass_hash = sha256(_login_int32+self._config['PASSPHRASE']).hexdigest()
_pass_hash = a(_pass_hash) _pass_hash = bhex(_pass_hash)
self.send_master('RPTK'+self._config['RADIO_ID']+_pass_hash) self.send_master('RPTK'+self._config['RADIO_ID']+_pass_hash)
self._stats['CONNECTION'] = 'AUTHENTICATED' self._stats['CONNECTION'] = 'AUTHENTICATED'
elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge... elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge...
if _data[6:10] == self._config['RADIO_ID']: if _data[6:10] == self._config['RADIO_ID']:
logger.info('(%s) Repeater Authentication Accepted', self._system) self._logger.info('(%s) Repeater Authentication Accepted', self._system)
_config_packet = self._config['RADIO_ID']+\ _config_packet = self._config['RADIO_ID']+\
self._config['CALLSIGN']+\ self._config['CALLSIGN']+\
self._config['RX_FREQ']+\ self._config['RX_FREQ']+\
@ -528,32 +408,32 @@ class HBSYSTEM(DatagramProtocol):
self.send_master('RPTC'+_config_packet) self.send_master('RPTC'+_config_packet)
self._stats['CONNECTION'] = 'CONFIG-SENT' self._stats['CONNECTION'] = 'CONFIG-SENT'
logger.info('(%s) Repeater Configuration Sent', self._system) self._logger.info('(%s) Repeater Configuration Sent', self._system)
else: else:
self._stats['CONNECTION'] = 'NO' self._stats['CONNECTION'] = 'NO'
logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) self._logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system)
elif self._stats['CONNECTION'] == 'CONFIG-SENT': # If we've sent out configuration to the master elif self._stats['CONNECTION'] == 'CONFIG-SENT': # If we've sent out configuration to the master
if _data[6:10] == self._config['RADIO_ID']: if _data[6:10] == self._config['RADIO_ID']:
logger.info('(%s) Repeater Configuration Accepted', self._system) self._logger.info('(%s) Repeater Configuration Accepted', self._system)
self._stats['CONNECTION'] = 'YES' self._stats['CONNECTION'] = 'YES'
logger.info('(%s) Connection to Master Completed', self._system) self._logger.info('(%s) Connection to Master Completed', self._system)
else: else:
self._stats['CONNECTION'] = 'NO' self._stats['CONNECTION'] = 'NO'
logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) self._logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system)
elif _command == 'MSTP': # Actually MSTPONG -- a reply to RPTPING (send by client) elif _command == 'MSTP': # Actually MSTPONG -- a reply to RPTPING (send by client)
if _data [7:11] == self._config['RADIO_ID']: if _data [7:11] == self._config['RADIO_ID']:
self._stats['PINGS_ACKD'] += 1 self._stats['PINGS_ACKD'] += 1
logger.debug('(%s) MSTPONG Received. Pongs Since Connected: %s', self._system, self._stats['PINGS_ACKD']) self._logger.debug('(%s) MSTPONG Received. Pongs Since Connected: %s', self._system, self._stats['PINGS_ACKD'])
elif _command == 'MSTC': # Actually MSTCL -- notify us the master is closing down elif _command == 'MSTC': # Actually MSTCL -- notify us the master is closing down
if _data[5:9] == self._config['RADIO_ID']: if _data[5:9] == self._config['RADIO_ID']:
self._stats['CONNECTION'] = 'NO' self._stats['CONNECTION'] = 'NO'
logger.info('(%s) MSTCL Recieved', self._system) self._logger.info('(%s) MSTCL Recieved', self._system)
else: else:
logger.error('(%s) Received an invalid command in packet: %s', self._system, h(_data)) self._logger.error('(%s) Received an invalid command in packet: %s', self._system, ahex(_data))
#************************************************ #************************************************
@ -561,12 +441,51 @@ class HBSYSTEM(DatagramProtocol):
#************************************************ #************************************************
if __name__ == '__main__': if __name__ == '__main__':
logger.info('HBlink \'HBlink.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') # Python modules we need
import argparse
import sys
import os
import signal
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)')
parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
cli_args = parser.parse_args()
# Ensure we have a path for the config file, if one wasn't specified, then use the execution directory
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = hb_config.build_config(cli_args.CONFIG_FILE)
# Call the external routing to start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger = hb_log.config_logging(CONFIG['LOGGER'])
logger.debug('Logging system started, anything from here on gets logged')
# Set up the signal handler
def sig_handler(_signal, _frame):
logger.info('SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s', str(_signal))
hblink_handler(_signal, _frame, logger)
logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR')
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, sig_handler)
# HBlink instance creation # HBlink instance creation
logger.info('HBlink \'HBlink.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')
for system in CONFIG['SYSTEMS']: for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['ENABLED']: if CONFIG['SYSTEMS'][system]['ENABLED']:
systems[system] = HBSYSTEM(system) systems[system] = HBSYSTEM(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])

File diff suppressed because it is too large Load Diff

152
rs129.py
View File

@ -1,152 +0,0 @@
#!/usr/bin/env python
#
###############################################################################
# hb_router.py -- a call routing applicaiton for hblink.py
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# Copyright (C) 2015 by Jonathan Naylor G4KLX
#
# 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
###############################################################################
from __future__ import print_function
from binascii import b2a_hex as h
START_MASK = [0x96, 0x96, 0x96]
END_MASK = [0x99, 0x99, 0x99]
NUM_BYTES = 9
NPAR = 3;
POLY= [64, 56, 14, 1, 0, 0, 0, 0, 0, 0, 0, 0]
EXP_TABLE = (
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1D, 0x3A, 0x74, 0xE8, 0xCD, 0x87, 0x13, 0x26,
0x4C, 0x98, 0x2D, 0x5A, 0xB4, 0x75, 0xEA, 0xC9, 0x8F, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0,
0x9D, 0x27, 0x4E, 0x9C, 0x25, 0x4A, 0x94, 0x35, 0x6A, 0xD4, 0xB5, 0x77, 0xEE, 0xC1, 0x9F, 0x23,
0x46, 0x8C, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x5D, 0xBA, 0x69, 0xD2, 0xB9, 0x6F, 0xDE, 0xA1,
0x5F, 0xBE, 0x61, 0xC2, 0x99, 0x2F, 0x5E, 0xBC, 0x65, 0xCA, 0x89, 0x0F, 0x1E, 0x3C, 0x78, 0xF0,
0xFD, 0xE7, 0xD3, 0xBB, 0x6B, 0xD6, 0xB1, 0x7F, 0xFE, 0xE1, 0xDF, 0xA3, 0x5B, 0xB6, 0x71, 0xE2,
0xD9, 0xAF, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0D, 0x1A, 0x34, 0x68, 0xD0, 0xBD, 0x67, 0xCE,
0x81, 0x1F, 0x3E, 0x7C, 0xF8, 0xED, 0xC7, 0x93, 0x3B, 0x76, 0xEC, 0xC5, 0x97, 0x33, 0x66, 0xCC,
0x85, 0x17, 0x2E, 0x5C, 0xB8, 0x6D, 0xDA, 0xA9, 0x4F, 0x9E, 0x21, 0x42, 0x84, 0x15, 0x2A, 0x54,
0xA8, 0x4D, 0x9A, 0x29, 0x52, 0xA4, 0x55, 0xAA, 0x49, 0x92, 0x39, 0x72, 0xE4, 0xD5, 0xB7, 0x73,
0xE6, 0xD1, 0xBF, 0x63, 0xC6, 0x91, 0x3F, 0x7E, 0xFC, 0xE5, 0xD7, 0xB3, 0x7B, 0xF6, 0xF1, 0xFF,
0xE3, 0xDB, 0xAB, 0x4B, 0x96, 0x31, 0x62, 0xC4, 0x95, 0x37, 0x6E, 0xDC, 0xA5, 0x57, 0xAE, 0x41,
0x82, 0x19, 0x32, 0x64, 0xC8, 0x8D, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xDD, 0xA7, 0x53, 0xA6,
0x51, 0xA2, 0x59, 0xB2, 0x79, 0xF2, 0xF9, 0xEF, 0xC3, 0x9B, 0x2B, 0x56, 0xAC, 0x45, 0x8A, 0x09,
0x12, 0x24, 0x48, 0x90, 0x3D, 0x7A, 0xF4, 0xF5, 0xF7, 0xF3, 0xFB, 0xEB, 0xCB, 0x8B, 0x0B, 0x16,
0x2C, 0x58, 0xB0, 0x7D, 0xFA, 0xE9, 0xCF, 0x83, 0x1B, 0x36, 0x6C, 0xD8, 0xAD, 0x47, 0x8E, 0x01,
0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1D, 0x3A, 0x74, 0xE8, 0xCD, 0x87, 0x13, 0x26, 0x4C,
0x98, 0x2D, 0x5A, 0xB4, 0x75, 0xEA, 0xC9, 0x8F, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x9D,
0x27, 0x4E, 0x9C, 0x25, 0x4A, 0x94, 0x35, 0x6A, 0xD4, 0xB5, 0x77, 0xEE, 0xC1, 0x9F, 0x23, 0x46,
0x8C, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x5D, 0xBA, 0x69, 0xD2, 0xB9, 0x6F, 0xDE, 0xA1, 0x5F,
0xBE, 0x61, 0xC2, 0x99, 0x2F, 0x5E, 0xBC, 0x65, 0xCA, 0x89, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, 0xFD,
0xE7, 0xD3, 0xBB, 0x6B, 0xD6, 0xB1, 0x7F, 0xFE, 0xE1, 0xDF, 0xA3, 0x5B, 0xB6, 0x71, 0xE2, 0xD9,
0xAF, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0D, 0x1A, 0x34, 0x68, 0xD0, 0xBD, 0x67, 0xCE, 0x81,
0x1F, 0x3E, 0x7C, 0xF8, 0xED, 0xC7, 0x93, 0x3B, 0x76, 0xEC, 0xC5, 0x97, 0x33, 0x66, 0xCC, 0x85,
0x17, 0x2E, 0x5C, 0xB8, 0x6D, 0xDA, 0xA9, 0x4F, 0x9E, 0x21, 0x42, 0x84, 0x15, 0x2A, 0x54, 0xA8,
0x4D, 0x9A, 0x29, 0x52, 0xA4, 0x55, 0xAA, 0x49, 0x92, 0x39, 0x72, 0xE4, 0xD5, 0xB7, 0x73, 0xE6,
0xD1, 0xBF, 0x63, 0xC6, 0x91, 0x3F, 0x7E, 0xFC, 0xE5, 0xD7, 0xB3, 0x7B, 0xF6, 0xF1, 0xFF, 0xE3,
0xDB, 0xAB, 0x4B, 0x96, 0x31, 0x62, 0xC4, 0x95, 0x37, 0x6E, 0xDC, 0xA5, 0x57, 0xAE, 0x41, 0x82,
0x19, 0x32, 0x64, 0xC8, 0x8D, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xDD, 0xA7, 0x53, 0xA6, 0x51,
0xA2, 0x59, 0xB2, 0x79, 0xF2, 0xF9, 0xEF, 0xC3, 0x9B, 0x2B, 0x56, 0xAC, 0x45, 0x8A, 0x09, 0x12,
0x24, 0x48, 0x90, 0x3D, 0x7A, 0xF4, 0xF5, 0xF7, 0xF3, 0xFB, 0xEB, 0xCB, 0x8B, 0x0B, 0x16, 0x2C,
0x58, 0xB0, 0x7D, 0xFA, 0xE9, 0xCF, 0x83, 0x1B, 0x36, 0x6C, 0xD8, 0xAD, 0x47, 0x8E, 0x01, 0x00
)
LOG_TABLE = (
0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1A, 0xC6, 0x03, 0xDF, 0x33, 0xEE, 0x1B, 0x68, 0xC7, 0x4B,
0x04, 0x64, 0xE0, 0x0E, 0x34, 0x8D, 0xEF, 0x81, 0x1C, 0xC1, 0x69, 0xF8, 0xC8, 0x08, 0x4C, 0x71,
0x05, 0x8A, 0x65, 0x2F, 0xE1, 0x24, 0x0F, 0x21, 0x35, 0x93, 0x8E, 0xDA, 0xF0, 0x12, 0x82, 0x45,
0x1D, 0xB5, 0xC2, 0x7D, 0x6A, 0x27, 0xF9, 0xB9, 0xC9, 0x9A, 0x09, 0x78, 0x4D, 0xE4, 0x72, 0xA6,
0x06, 0xBF, 0x8B, 0x62, 0x66, 0xDD, 0x30, 0xFD, 0xE2, 0x98, 0x25, 0xB3, 0x10, 0x91, 0x22, 0x88,
0x36, 0xD0, 0x94, 0xCE, 0x8F, 0x96, 0xDB, 0xBD, 0xF1, 0xD2, 0x13, 0x5C, 0x83, 0x38, 0x46, 0x40,
0x1E, 0x42, 0xB6, 0xA3, 0xC3, 0x48, 0x7E, 0x6E, 0x6B, 0x3A, 0x28, 0x54, 0xFA, 0x85, 0xBA, 0x3D,
0xCA, 0x5E, 0x9B, 0x9F, 0x0A, 0x15, 0x79, 0x2B, 0x4E, 0xD4, 0xE5, 0xAC, 0x73, 0xF3, 0xA7, 0x57,
0x07, 0x70, 0xC0, 0xF7, 0x8C, 0x80, 0x63, 0x0D, 0x67, 0x4A, 0xDE, 0xED, 0x31, 0xC5, 0xFE, 0x18,
0xE3, 0xA5, 0x99, 0x77, 0x26, 0xB8, 0xB4, 0x7C, 0x11, 0x44, 0x92, 0xD9, 0x23, 0x20, 0x89, 0x2E,
0x37, 0x3F, 0xD1, 0x5B, 0x95, 0xBC, 0xCF, 0xCD, 0x90, 0x87, 0x97, 0xB2, 0xDC, 0xFC, 0xBE, 0x61,
0xF2, 0x56, 0xD3, 0xAB, 0x14, 0x2A, 0x5D, 0x9E, 0x84, 0x3C, 0x39, 0x53, 0x47, 0x6D, 0x41, 0xA2,
0x1F, 0x2D, 0x43, 0xD8, 0xB7, 0x7B, 0xA4, 0x76, 0xC4, 0x17, 0x49, 0xEC, 0x7F, 0x0C, 0x6F, 0xF6,
0x6C, 0xA1, 0x3B, 0x52, 0x29, 0x9D, 0x55, 0xAA, 0xFB, 0x60, 0x86, 0xB1, 0xBB, 0xCC, 0x3E, 0x5A,
0xCB, 0x59, 0x5F, 0xB0, 0x9C, 0xA9, 0xA0, 0x51, 0x0B, 0xF5, 0x16, 0xEB, 0x7A, 0x75, 0x2C, 0xD7,
0x4F, 0xAE, 0xD5, 0xE9, 0xE6, 0xE7, 0xAD, 0xE8, 0x74, 0xD6, 0xF4, 0xEA, 0xA8, 0x50, 0x58, 0xAF
)
# multiplication using logarithms
def log_mult(a, b):
if a == 0 or b == 0:
return 0
x = LOG_TABLE[a]
y = LOG_TABLE[b]
z = EXP_TABLE[x + y]
return z
# Reed-Solomon (12,9) encoder
def encode(_msg):
assert len(_msg) == 9, 'RS129_encode error: Message not 9 bytes: %s' % print_hex(_msg)
parity = [0x00, 0x00, 0x00]
for i in range(NUM_BYTES):
dbyte = _msg[i] ^ parity[NPAR - 1]
for j in range(NPAR - 1, 0, -1):
parity[j] = parity[j - 1] ^ log_mult(POLY[j], dbyte)
parity[0] = log_mult(POLY[0], dbyte)
return [parity[2], parity[1], parity[0]]
# Apply DMR XOR LC Header MASK
def lc_header_mask(_parity):
xor = [0,0,0]
for i in range(len(_parity)):
xor[i] = _parity[i] ^ START_MASK[i]
return xor
# Apply DMR XOR LC Terminator MASK
def lc_terminator_mask(_parity):
xor = [0,0,0]
for i in range(len(_parity)):
xor[i] = _parity[i] ^ END_MASK[i]
return xor
# All Inclusive function to take an LC string and provide the RS129 string to append
def lc_header_encode(_message):
bin_message = bytearray(_message)
parity = encode(bin_message)
masked_parity = lc_header_mask(parity)
return chr(masked_parity[0]) + chr(masked_parity[1]) + chr(masked_parity[2])
# All Inclusive function to take an LC string and provide the RS129 string to append
def lc_terminator_encode(_message):
bin_message = bytearray(_message)
parity = encode(bin_message)
masked_parity = lc_terminator_mask(parity)
return chr(masked_parity[0]) + chr(masked_parity[1]) + chr(masked_parity[2])
if __name__ == '__main__':
# For testing the code
def print_hex(_list):
print('[{}]'.format(', '.join(hex(x) for x in _list)))
# Validation Example
message = '\x00\x10\x20\x00\x0c\x30\x2f\x9b\xe5'
message = bytearray(message)
parity_should_be = '\xda\x4d\x5a'
print('Original Message: {}'.format(h(message)))
print('Masked Parity Should be: {}'.format(h(parity_should_be)))
parity = lc_header_encode(message)
print('Calculated Masked Parity is: {}'.format(h(parity)))

File diff suppressed because it is too large Load Diff