2016-07-20 17:16:27 -04:00
#!/usr/bin/env python
#
2016-11-21 20:13:32 -05:00
###############################################################################
2018-08-07 18:05:27 -04:00
# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS <n0mjs@me.com>
2016-11-21 20:13:32 -05:00
#
# 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
###############################################################################
2016-07-20 17:16:27 -04:00
2016-12-19 09:36:17 -05:00
'''
This program does very little on it ' s own. It is intended to be used as a module
2018-08-07 18:05:27 -04:00
to build applications on top of the HomeBrew Repeater Protocol . By itself , it
will only act as a peer or master for the systems specified in its configuration
2016-12-19 09:36:17 -05:00
file ( usually hblink . cfg ) . It is ALWAYS best practice to ensure that this program
2018-08-07 18:05:27 -04:00
works stand - alone before troubleshooting any applications that use it . It has
sufficient logging to be used standalone as a troubleshooting application .
2016-12-19 09:36:17 -05:00
'''
2016-07-20 17:16:27 -04:00
from __future__ import print_function
2016-07-20 22:25:47 -04:00
# Specifig functions from modules we need
2016-11-23 16:01:05 -05:00
from binascii import b2a_hex as ahex
from binascii import a2b_hex as bhex
2016-07-21 13:41:36 -04:00
from random import randint
2018-09-25 21:17:55 -04:00
from hashlib import sha256 , sha1
from hmac import new as hmac_new , compare_digest
2016-07-24 10:42:42 -04:00
from time import time
2016-11-23 16:01:05 -05:00
from bitstring import BitArray
2018-07-11 19:55:35 -04:00
from importlib import import_module
2018-11-25 11:22:43 -05:00
from collections import deque
2016-07-21 13:41:36 -04:00
2016-07-20 22:25:47 -04:00
# Twisted is pretty important, so I keep it separate
2018-06-19 17:02:38 -04:00
from twisted . internet . protocol import DatagramProtocol , Factory , Protocol
from twisted . protocols . basic import NetstringReceiver
from twisted . internet import reactor , task
2016-07-20 17:16:27 -04:00
2016-07-20 22:25:47 -04:00
# Other files we pull from -- this is mostly for readability and segmentation
import hb_log
import hb_config
2018-11-21 11:24:19 -05:00
import hb_const as const
2018-11-24 11:20:47 -05:00
from dmr_utils . utils import int_id , hex_str_4 , try_download , mk_id_dict
2016-07-20 22:25:47 -04:00
2018-06-19 17:02:38 -04:00
# Imports for the reporting server
import cPickle as pickle
from reporting_const import *
2018-11-24 11:20:47 -05:00
# The module needs logging logging, but handlers, etc. are controlled by the parent
import logging
logger = logging . getLogger ( __name__ )
2016-07-20 22:25:47 -04:00
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
2016-07-20 17:16:27 -04:00
__author__ = ' Cortney T. Buffington, N0MJS '
2018-08-07 18:05:27 -04:00
__copyright__ = ' Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group '
2016-07-23 16:53:22 -04:00
__credits__ = ' Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT '
2016-11-22 21:02:09 -05:00
__license__ = ' GNU GPLv3 '
2016-07-20 17:16:27 -04:00
__maintainer__ = ' Cort Buffington, N0MJS '
__email__ = ' n0mjs@me.com '
2016-07-31 10:41:21 -04:00
# Global variables used whether we are a module or __main__
2016-08-25 21:44:15 -04:00
systems = { }
2016-07-31 10:41:21 -04:00
2018-06-22 10:28:31 -04:00
# Timed loop used for reporting HBP status
2018-06-19 17:02:38 -04:00
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
2018-11-24 11:20:47 -05:00
def config_reports ( _config , _factory ) :
2018-07-03 22:51:20 -04:00
if True : #_config['REPORTS']['REPORT']:
2018-06-19 17:02:38 -04:00
def reporting_loop ( _logger , _server ) :
_logger . debug ( ' Periodic reporting loop started ' )
_server . send_config ( )
2018-11-21 21:55:54 -05:00
2018-11-24 11:20:47 -05:00
logger . info ( ' HBlink TCP reporting server configured ' )
2018-11-21 21:55:54 -05:00
2018-11-24 11:20:47 -05:00
report_server = _factory ( _config )
2018-06-19 17:02:38 -04:00
report_server . clients = [ ]
reactor . listenTCP ( _config [ ' REPORTS ' ] [ ' REPORT_PORT ' ] , report_server )
2018-11-21 21:55:54 -05:00
2018-11-24 11:20:47 -05:00
reporting = task . LoopingCall ( reporting_loop , logger , report_server )
2018-06-19 17:02:38 -04:00
reporting . start ( _config [ ' REPORTS ' ] [ ' REPORT_INTERVAL ' ] )
2018-11-21 21:55:54 -05:00
2018-06-22 10:28:31 -04:00
return report_server
2018-06-19 17:02:38 -04:00
2018-08-07 18:05:27 -04:00
# Shut ourselves down gracefully by disconnecting from the masters and peers.
2018-11-24 11:20:47 -05:00
def hblink_handler ( _signal , _frame ) :
2016-08-25 21:44:15 -04:00
for system in systems :
2018-11-24 11:20:47 -05:00
logger . info ( ' SHUTDOWN: DE-REGISTER SYSTEM: %s ' , system )
2016-11-23 16:01:05 -05:00
systems [ system ] . dereg ( )
2016-07-20 17:16:27 -04:00
2018-11-21 11:24:19 -05:00
# Check a supplied ID against the ACL provided. Returns action (True|False) based
# on matching and the action specified.
def acl_check ( _id , _acl ) :
id = int_id ( _id )
for entry in _acl [ 1 ] :
if entry [ 0 ] < = id < = entry [ 1 ] :
return _acl [ 0 ]
return not _acl [ 0 ]
2018-11-21 21:55:54 -05:00
2016-07-30 12:28:49 -04:00
2018-09-25 21:17:55 -04:00
#************************************************
# OPENBRIDGE CLASS
#************************************************
class OPENBRIDGE ( DatagramProtocol ) :
2018-11-24 11:20:47 -05:00
def __init__ ( self , _name , _config , _report ) :
2018-09-25 21:17:55 -04:00
# Define a few shortcuts to make the rest of the class more readable
self . _CONFIG = _config
self . _system = _name
self . _report = _report
self . _config = self . _CONFIG [ ' SYSTEMS ' ] [ self . _system ]
2018-11-29 11:27:32 -05:00
self . _laststrid = deque ( [ ] , 20 )
2018-09-25 21:17:55 -04:00
def dereg ( self ) :
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) is mode OPENBRIDGE. No De-Registration required, continuing shutdown ' , self . _system )
2018-11-21 21:55:54 -05:00
2018-09-25 21:17:55 -04:00
def send_system ( self , _packet ) :
if _packet [ : 4 ] == ' DMRD ' :
2018-09-26 16:48:48 -04:00
_packet = _packet [ : 11 ] + self . _config [ ' NETWORK_ID ' ] + _packet [ 15 : ]
2018-10-12 15:20:05 -04:00
_packet + = hmac_new ( self . _config [ ' PASSPHRASE ' ] , _packet , sha1 ) . digest ( )
2018-09-25 21:17:55 -04:00
self . transport . write ( _packet , ( self . _config [ ' TARGET_IP ' ] , self . _config [ ' TARGET_PORT ' ] ) )
2018-09-26 16:48:48 -04:00
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
2018-11-24 11:20:47 -05:00
# logger.debug('(%s) TX Packet to OpenBridge %s:%s -- %s', self._system, self._config['TARGET_IP'], self._config['TARGET_PORT'], ahex(_packet))
2018-09-26 16:48:48 -04:00
else :
2018-11-24 11:20:47 -05:00
logger . error ( ' ( %s ) OpenBridge system was asked to send non DMRD packet ' , self . _system )
2018-09-25 21:17:55 -04:00
def dmrd_received ( self , _peer_id , _rf_src , _dst_id , _seq , _slot , _call_type , _frame_type , _dtype_vseq , _stream_id , _data ) :
pass
#print(int_id(_peer_id), int_id(_rf_src), int_id(_dst_id), int_id(_seq), _slot, _call_type, _frame_type, repr(_dtype_vseq), int_id(_stream_id))
2018-09-26 16:48:48 -04:00
def datagramReceived ( self , _packet , _sockaddr ) :
2018-09-25 21:17:55 -04:00
# Keep This Line Commented Unless HEAVILY Debugging!
2018-11-24 11:20:47 -05:00
#logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_packet))
2018-09-25 21:17:55 -04:00
2018-09-26 16:48:48 -04:00
if _packet [ : 4 ] == ' DMRD ' : # DMRData -- encapsulated DMR data frame
2018-10-12 16:03:36 -04:00
_data = _packet [ : 53 ]
_hash = _packet [ 53 : ]
2018-10-12 16:52:03 -04:00
_ckhs = hmac_new ( self . _config [ ' PASSPHRASE ' ] , _data , sha1 ) . digest ( )
2018-11-21 21:55:54 -05:00
2018-09-26 16:48:48 -04:00
if compare_digest ( _hash , _ckhs ) and _sockaddr == self . _config [ ' TARGET_SOCK ' ] :
_peer_id = _data [ 11 : 15 ]
2018-09-25 21:17:55 -04:00
_seq = _data [ 4 ]
_rf_src = _data [ 5 : 8 ]
_dst_id = _data [ 8 : 11 ]
_bits = int_id ( _data [ 15 ] )
_slot = 2 if ( _bits & 0x80 ) else 1
2018-11-29 14:54:51 -05:00
#_call_type = 'unit' if (_bits & 0x40) else 'group'
if _bits & 0x40 :
_call_type = ' unit '
elif ( _bits & 0x23 ) == 0x23 :
_call_type = ' vcsbk '
else :
_call_type = ' group '
2018-09-25 21:17:55 -04:00
_frame_type = ( _bits & 0x30 ) >> 4
_dtype_vseq = ( _bits & 0xF ) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F
_stream_id = _data [ 16 : 20 ]
2018-11-24 11:20:47 -05:00
#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))
2018-11-21 21:55:54 -05:00
2018-11-21 11:24:19 -05:00
# Sanity check for OpenBridge -- all calls must be on Slot 1
if _slot != 1 :
2018-11-24 11:20:47 -05:00
logger . error ( ' ( %s ) OpenBridge packet discarded because it was not received on slot 1. SID: %s , TGID %s ' , self . _system , int_id ( _rf_src ) , int_id ( _dst_id ) )
2018-11-21 11:24:19 -05:00
return
2018-11-21 21:55:54 -05:00
2018-11-21 11:24:19 -05:00
# ACL Processing
if self . _CONFIG [ ' GLOBAL ' ] [ ' USE_ACL ' ] :
if not acl_check ( _rf_src , self . _CONFIG [ ' GLOBAL ' ] [ ' SUB_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if _stream_id not in self . _laststrid :
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _rf_src ) )
self . _laststrid . append ( _stream_id )
2018-11-21 11:24:19 -05:00
return
if _slot == 1 and not acl_check ( _dst_id , self . _CONFIG [ ' GLOBAL ' ] [ ' TG1_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if _stream_id not in self . _laststrid :
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
2018-11-25 11:22:43 -05:00
self . _laststrid . append ( _stream_id )
2018-11-21 11:24:19 -05:00
return
if self . _config [ ' USE_ACL ' ] :
if not acl_check ( _rf_src , self . _config [ ' SUB_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if _stream_id not in self . _laststrid :
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _rf_src ) )
self . _laststrid . append ( _stream_id )
2018-11-21 11:24:19 -05:00
return
if not acl_check ( _dst_id , self . _config [ ' TG1_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if _stream_id not in self . _laststrid :
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
self . _laststrid . append ( _stream_id )
2018-11-21 11:24:19 -05:00
return
2018-11-21 21:55:54 -05:00
2018-09-25 21:17:55 -04:00
# Userland actions -- typically this is the function you subclass for an application
self . dmrd_received ( _peer_id , _rf_src , _dst_id , _seq , _slot , _call_type , _frame_type , _dtype_vseq , _stream_id , _data )
2018-09-26 16:48:48 -04:00
else :
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) OpenBridge HMAC failed, packet discarded - OPCODE: %s DATA: %s HMAC LENGTH: %s HMAC: %s ' , self . _system , _packet [ : 4 ] , repr ( _packet [ : 53 ] ) , len ( _packet [ 53 : ] ) , repr ( _packet [ 53 : ] ) )
2018-11-21 21:55:54 -05:00
2018-09-25 21:17:55 -04:00
2016-07-20 17:16:27 -04:00
#************************************************
2016-07-24 10:42:42 -04:00
# HB MASTER CLASS
2016-07-20 17:16:27 -04:00
#************************************************
2018-09-26 16:41:00 -04:00
class HBSYSTEM ( DatagramProtocol ) :
2018-11-24 11:20:47 -05:00
def __init__ ( self , _name , _config , _report ) :
2016-11-21 20:46:07 -05:00
# Define a few shortcuts to make the rest of the class more readable
2016-11-21 20:54:23 -05:00
self . _CONFIG = _config
self . _system = _name
2018-06-22 10:28:31 -04:00
self . _report = _report
2016-11-21 20:54:23 -05:00
self . _config = self . _CONFIG [ ' SYSTEMS ' ] [ self . _system ]
2018-11-25 11:22:43 -05:00
self . _laststrid1 = ' '
self . _laststrid2 = ' '
2018-11-21 21:55:54 -05:00
2018-09-26 16:41:00 -04:00
# Define shortcuts and generic function names based on the type of system we are
if self . _config [ ' MODE ' ] == ' MASTER ' :
self . _peers = self . _CONFIG [ ' SYSTEMS ' ] [ self . _system ] [ ' PEERS ' ]
self . send_system = self . send_peers
self . maintenance_loop = self . master_maintenance_loop
self . datagramReceived = self . master_datagramReceived
self . dereg = self . master_dereg
2018-11-21 21:55:54 -05:00
2018-09-26 16:41:00 -04:00
elif self . _config [ ' MODE ' ] == ' PEER ' :
self . _stats = self . _config [ ' STATS ' ]
self . send_system = self . send_master
self . maintenance_loop = self . peer_maintenance_loop
self . datagramReceived = self . peer_datagramReceived
self . dereg = self . peer_dereg
2016-09-01 15:33:47 -04:00
2016-07-21 13:41:36 -04:00
def startProtocol ( self ) :
2018-08-07 18:05:27 -04:00
# Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds
2016-11-21 20:54:23 -05:00
self . _system_maintenance = task . LoopingCall ( self . maintenance_loop )
self . _system_maintenance_loop = self . _system_maintenance . start ( self . _CONFIG [ ' GLOBAL ' ] [ ' PING_TIME ' ] )
2018-11-21 21:55:54 -05:00
2018-09-26 16:41:00 -04:00
# Aliased in __init__ to maintenance_loop if system is a master
def master_maintenance_loop ( self ) :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) Master maintenance loop started ' , self . _system )
2018-11-27 12:21:46 -05:00
remove_list = [ ]
2018-08-07 18:05:27 -04:00
for peer in self . _peers :
_this_peer = self . _peers [ peer ]
# Check to see if any of the peers have been quiet (no ping) longer than allowed
2018-11-26 12:52:45 -05:00
if _this_peer [ ' LAST_PING ' ] + ( self . _CONFIG [ ' GLOBAL ' ] [ ' PING_TIME ' ] * self . _CONFIG [ ' GLOBAL ' ] [ ' MAX_MISSED ' ] ) < time ( ) :
2018-11-27 12:21:46 -05:00
remove_list . append ( peer )
for peer in remove_list :
logger . info ( ' ( %s ) Peer %s ( %s ) has timed out and is being removed ' , self . _system , self . _peers [ peer ] [ ' CALLSIGN ' ] , self . _peers [ peer ] [ ' RADIO_ID ' ] )
# Remove any timed out peers from the configuration
del self . _CONFIG [ ' SYSTEMS ' ] [ self . _system ] [ ' PEERS ' ] [ peer ]
2018-11-21 21:55:54 -05:00
# Aliased in __init__ to maintenance_loop if system is a peer
2018-09-26 16:41:00 -04:00
def peer_maintenance_loop ( self ) :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) Peer maintenance loop started ' , self . _system )
2018-09-26 16:41:00 -04:00
if self . _stats [ ' PING_OUTSTANDING ' ] :
self . _stats [ ' NUM_OUTSTANDING ' ] + = 1
# If we're not connected, zero out the stats and send a login request RPTL
if self . _stats [ ' CONNECTION ' ] != ' YES ' or self . _stats [ ' NUM_OUTSTANDING ' ] > = self . _CONFIG [ ' GLOBAL ' ] [ ' MAX_MISSED ' ] :
self . _stats [ ' PINGS_SENT ' ] = 0
self . _stats [ ' PINGS_ACKD ' ] = 0
self . _stats [ ' NUM_OUTSTANDING ' ] = 0
self . _stats [ ' PING_OUTSTANDING ' ] = False
self . _stats [ ' CONNECTION ' ] = ' RPTL_SENT '
self . send_master ( ' RPTL ' + self . _config [ ' RADIO_ID ' ] )
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Sending login request to master %s : %s ' , self . _system , self . _config [ ' MASTER_IP ' ] , self . _config [ ' MASTER_PORT ' ] )
2018-09-26 16:41:00 -04:00
# If we are connected, sent a ping to the master and increment the counter
if self . _stats [ ' CONNECTION ' ] == ' YES ' :
self . send_master ( ' RPTPING ' + self . _config [ ' RADIO_ID ' ] )
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) RPTPING Sent to Master. Total Sent: %s , Total Missed: %s , Currently Outstanding: %s ' , self . _system , self . _stats [ ' PINGS_SENT ' ] , self . _stats [ ' PINGS_SENT ' ] - self . _stats [ ' PINGS_ACKD ' ] , self . _stats [ ' NUM_OUTSTANDING ' ] )
2018-09-26 16:41:00 -04:00
self . _stats [ ' PINGS_SENT ' ] + = 1
self . _stats [ ' PING_OUTSTANDING ' ] = True
2016-09-01 15:33:47 -04:00
2018-09-26 16:41:00 -04:00
def send_peers ( self , _packet ) :
2018-08-07 18:05:27 -04:00
for _peer in self . _peers :
self . send_peer ( _peer , _packet )
2018-11-24 11:20:47 -05:00
#logger.debug('(%s) Packet sent to peer %s', self._system, self._peers[_peer]['RADIO_ID'])
2016-09-01 15:33:47 -04:00
2018-08-07 18:05:27 -04:00
def send_peer ( self , _peer , _packet ) :
2018-08-02 15:21:51 -04:00
if _packet [ : 4 ] == ' DMRD ' :
2018-08-07 18:05:27 -04:00
_packet = _packet [ : 11 ] + _peer + _packet [ 15 : ]
self . transport . write ( _packet , self . _peers [ _peer ] [ ' SOCKADDR ' ] )
2016-07-24 10:42:42 -04:00
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
2018-11-24 11:20:47 -05:00
#logger.debug('(%s) TX Packet to %s on port %s: %s', self._peers[_peer]['RADIO_ID'], self._peers[_peer]['IP'], self._peers[_peer]['PORT'], ahex(_packet))
2018-09-26 16:41:00 -04:00
def send_master ( self , _packet ) :
if _packet [ : 4 ] == ' DMRD ' :
_packet = _packet [ : 11 ] + self . _config [ ' RADIO_ID ' ] + _packet [ 15 : ]
self . transport . write ( _packet , self . _config [ ' MASTER_SOCKADDR ' ] )
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
2018-11-24 11:20:47 -05:00
# logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet))
2018-09-26 16:41:00 -04:00
2018-08-07 18:05:27 -04:00
def dmrd_received ( self , _peer_id , _rf_src , _dst_id , _seq , _slot , _call_type , _frame_type , _dtype_vseq , _stream_id , _data ) :
2016-07-31 10:41:21 -04:00
pass
2018-11-21 21:55:54 -05:00
2018-09-26 16:41:00 -04:00
def master_dereg ( self ) :
2018-08-07 18:05:27 -04:00
for _peer in self . _peers :
self . send_peer ( _peer , ' MSTCL ' + _peer )
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) De-Registration sent to Peer: %s ( %s ) ' , self . _system , self . _peers [ _peer ] [ ' CALLSIGN ' ] , self . _peers [ _peer ] [ ' RADIO_ID ' ] )
2018-11-21 21:55:54 -05:00
2018-09-26 16:41:00 -04:00
def peer_dereg ( self ) :
self . send_master ( ' RPTCL ' + self . _config [ ' RADIO_ID ' ] )
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) De-Registration sent to Master: %s : %s ' , self . _system , self . _config [ ' MASTER_SOCKADDR ' ] [ 0 ] , self . _config [ ' MASTER_SOCKADDR ' ] [ 1 ] )
2018-11-21 21:55:54 -05:00
2018-09-26 16:41:00 -04:00
# Aliased in __init__ to datagramReceived if system is a master
def master_datagramReceived ( self , _data , _sockaddr ) :
2016-08-01 22:30:40 -04:00
# Keep This Line Commented Unless HEAVILY Debugging!
2018-11-24 11:20:47 -05:00
# logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data))
2016-09-01 15:33:47 -04:00
2016-08-01 22:30:40 -04:00
# Extract the command, which is various length, all but one 4 significant characters -- RPTCL
_command = _data [ : 4 ]
2016-09-01 15:33:47 -04:00
2016-08-01 22:30:40 -04:00
if _command == ' DMRD ' : # DMRData -- encapsulated DMR data frame
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 11 : 15 ]
if _peer_id in self . _peers \
and self . _peers [ _peer_id ] [ ' CONNECTION ' ] == ' YES ' \
and self . _peers [ _peer_id ] [ ' SOCKADDR ' ] == _sockaddr :
2016-08-15 18:18:16 -04:00
_seq = _data [ 4 ]
2016-08-01 22:30:40 -04:00
_rf_src = _data [ 5 : 8 ]
_dst_id = _data [ 8 : 11 ]
2016-08-15 18:18:16 -04:00
_bits = int_id ( _data [ 15 ] )
2016-08-26 22:31:20 -04:00
_slot = 2 if ( _bits & 0x80 ) else 1
2018-11-29 14:54:51 -05:00
#_call_type = 'unit' if (_bits & 0x40) else 'group'
if _bits & 0x40 :
_call_type = ' unit '
elif ( _bits & 0x23 ) == 0x23 :
_call_type = ' vcsbk '
else :
_call_type = ' group '
2016-11-01 20:49:13 -04:00
_frame_type = ( _bits & 0x30 ) >> 4
2016-09-22 10:12:38 -04:00
_dtype_vseq = ( _bits & 0xF ) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F
2016-08-15 18:18:16 -04:00
_stream_id = _data [ 16 : 20 ]
2018-11-24 11:20:47 -05:00
#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))
2018-11-21 11:24:19 -05:00
# ACL Processing
if self . _CONFIG [ ' GLOBAL ' ] [ ' USE_ACL ' ] :
if not acl_check ( _rf_src , self . _CONFIG [ ' GLOBAL ' ] [ ' SUB_ACL ' ] ) :
if self . _laststrid != _stream_id :
2018-11-25 11:22:43 -05:00
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _rf_src ) )
if _slot == 1 :
self . _laststrid1 = _stream_id
else :
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 1 and not acl_check ( _dst_id , self . _CONFIG [ ' GLOBAL ' ] [ ' TG1_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid1 != _stream_id :
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
self . _laststrid1 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 2 and not acl_check ( _dst_id , self . _CONFIG [ ' GLOBAL ' ] [ ' TG2_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid2 != _stream_id :
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
if self . _config [ ' USE_ACL ' ] :
if not acl_check ( _rf_src , self . _config [ ' SUB_ACL ' ] ) :
if self . _laststrid != _stream_id :
2018-11-25 11:22:43 -05:00
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _rf_src ) )
if _slot == 1 :
self . _laststrid1 = _stream_id
else :
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 1 and not acl_check ( _dst_id , self . _config [ ' TG1_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid1 != _stream_id :
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
self . _laststrid1 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 2 and not acl_check ( _dst_id , self . _config [ ' TG2_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid2 != _stream_id :
logger . info ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
2016-09-01 15:33:47 -04:00
2018-08-07 18:05:27 -04:00
# The basic purpose of a master is to repeat to the peers
2016-08-01 22:30:40 -04:00
if self . _config [ ' REPEAT ' ] == True :
2018-08-07 18:05:27 -04:00
for _peer in self . _peers :
if _peer != _peer_id :
#self.send_peer(_peer, _data)
self . send_peer ( _peer , _data [ : 11 ] + _peer + _data [ 15 : ] )
#self.send_peer(_peer, _data[:11] + self._config['RADIO_ID'] + _data[15:])
2018-11-24 11:20:47 -05:00
#logger.debug('(%s) Packet on TS%s from %s (%s) for destination ID %s repeated to peer: %s (%s) [Stream ID: %s]', self._system, _slot, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id), int_id(_dst_id), self._peers[_peer]['CALLSIGN'], int_id(_peer), int_id(_stream_id))
2016-09-01 15:33:47 -04:00
2018-11-21 11:24:19 -05:00
2016-08-01 22:30:40 -04:00
# Userland actions -- typically this is the function you subclass for an application
2018-08-07 18:05:27 -04:00
self . dmrd_received ( _peer_id , _rf_src , _dst_id , _seq , _slot , _call_type , _frame_type , _dtype_vseq , _stream_id , _data )
2016-09-01 15:33:47 -04:00
2016-08-01 22:30:40 -04:00
elif _command == ' RPTL ' : # RPTLogin -- a repeater wants to login
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 4 : 8 ]
2018-11-25 16:07:55 -05:00
# Check to see if we've reached the maximum number of allowed peers
2018-11-25 16:13:36 -05:00
if len ( self . _peers ) < self . _config [ ' MAX_PEERS ' ] :
2018-11-25 16:07:55 -05:00
# Check for valid Radio ID
if acl_check ( _peer_id , self . _CONFIG [ ' GLOBAL ' ] [ ' REG_ACL ' ] ) and acl_check ( _peer_id , self . _config [ ' REG_ACL ' ] ) :
# Build the configuration data strcuture for the peer
self . _peers . update ( { _peer_id : {
' CONNECTION ' : ' RPTL-RECEIVED ' ,
' PINGS_RECEIVED ' : 0 ,
' LAST_PING ' : time ( ) ,
' SOCKADDR ' : _sockaddr ,
' IP ' : _sockaddr [ 0 ] ,
' PORT ' : _sockaddr [ 1 ] ,
' SALT ' : randint ( 0 , 0xFFFFFFFF ) ,
' RADIO_ID ' : str ( int ( ahex ( _peer_id ) , 16 ) ) ,
' CALLSIGN ' : ' ' ,
' RX_FREQ ' : ' ' ,
' TX_FREQ ' : ' ' ,
' TX_POWER ' : ' ' ,
' COLORCODE ' : ' ' ,
' LATITUDE ' : ' ' ,
' LONGITUDE ' : ' ' ,
' HEIGHT ' : ' ' ,
' LOCATION ' : ' ' ,
' DESCRIPTION ' : ' ' ,
' SLOTS ' : ' ' ,
' URL ' : ' ' ,
' SOFTWARE_ID ' : ' ' ,
' PACKAGE_ID ' : ' ' ,
} } )
logger . info ( ' ( %s ) Repeater Logging in with Radio ID: %s , %s : %s ' , self . _system , int_id ( _peer_id ) , _sockaddr [ 0 ] , _sockaddr [ 1 ] )
_salt_str = hex_str_4 ( self . _peers [ _peer_id ] [ ' SALT ' ] )
self . send_peer ( _peer_id , ' RPTACK ' + _salt_str )
self . _peers [ _peer_id ] [ ' CONNECTION ' ] = ' CHALLENGE_SENT '
logger . info ( ' ( %s ) Sent Challenge Response to %s for login: %s ' , self . _system , int_id ( _peer_id ) , self . _peers [ _peer_id ] [ ' SALT ' ] )
else :
self . transport . write ( ' MSTNAK ' + _peer_id , _sockaddr )
logger . warning ( ' ( %s ) Invalid Login from Radio ID: %s Denied by Registation ACL ' , self . _system , int_id ( _peer_id ) )
2016-08-01 22:30:40 -04:00
else :
2018-08-07 18:05:27 -04:00
self . transport . write ( ' MSTNAK ' + _peer_id , _sockaddr )
2018-11-25 16:07:55 -05:00
logger . warning ( ' ( %s ) Registration denied from Radio ID: %s Maximum number of peers exceeded ' , self . _system , int_id ( _peer_id ) )
2016-09-01 15:33:47 -04:00
2016-08-01 22:30:40 -04:00
elif _command == ' RPTK ' : # Repeater has answered our login challenge
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 4 : 8 ]
if _peer_id in self . _peers \
and self . _peers [ _peer_id ] [ ' CONNECTION ' ] == ' CHALLENGE_SENT ' \
and self . _peers [ _peer_id ] [ ' SOCKADDR ' ] == _sockaddr :
_this_peer = self . _peers [ _peer_id ]
_this_peer [ ' LAST_PING ' ] = time ( )
2016-08-01 22:30:40 -04:00
_sent_hash = _data [ 8 : ]
2018-08-07 18:05:27 -04:00
_salt_str = hex_str_4 ( _this_peer [ ' SALT ' ] )
2016-11-25 09:14:36 -05:00
_calc_hash = bhex ( sha256 ( _salt_str + self . _config [ ' PASSPHRASE ' ] ) . hexdigest ( ) )
2016-08-01 22:30:40 -04:00
if _sent_hash == _calc_hash :
2018-08-07 18:05:27 -04:00
_this_peer [ ' CONNECTION ' ] = ' WAITING_CONFIG '
self . send_peer ( _peer_id , ' RPTACK ' + _peer_id )
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Peer %s has completed the login exchange successfully ' , self . _system , _this_peer [ ' RADIO_ID ' ] )
2016-08-01 22:30:40 -04:00
else :
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Peer %s has FAILED the login exchange successfully ' , self . _system , _this_peer [ ' RADIO_ID ' ] )
2018-08-07 18:05:27 -04:00
self . transport . write ( ' MSTNAK ' + _peer_id , _sockaddr )
del self . _peers [ _peer_id ]
2016-08-01 22:30:40 -04:00
else :
2018-08-07 18:05:27 -04:00
self . transport . write ( ' MSTNAK ' + _peer_id , _sockaddr )
2018-11-24 11:20:47 -05:00
logger . warning ( ' ( %s ) Login challenge from Radio ID that has not logged in: %s ' , self . _system , int_id ( _peer_id ) )
2016-09-01 15:33:47 -04:00
2016-08-01 22:30:40 -04:00
elif _command == ' RPTC ' : # Repeater is sending it's configuraiton OR disconnecting
if _data [ : 5 ] == ' RPTCL ' : # Disconnect command
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 5 : 9 ]
if _peer_id in self . _peers \
and self . _peers [ _peer_id ] [ ' CONNECTION ' ] == ' YES ' \
and self . _peers [ _peer_id ] [ ' SOCKADDR ' ] == _sockaddr :
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Peer is closing down: %s ( %s ) ' , self . _system , self . _peers [ _peer_id ] [ ' CALLSIGN ' ] , int_id ( _peer_id ) )
2018-08-07 18:05:27 -04:00
self . transport . write ( ' MSTNAK ' + _peer_id , _sockaddr )
del self . _peers [ _peer_id ]
2016-11-18 09:27:57 -05:00
2016-09-01 15:36:31 -04:00
else :
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 4 : 8 ] # Configure Command
if _peer_id in self . _peers \
and self . _peers [ _peer_id ] [ ' CONNECTION ' ] == ' WAITING_CONFIG ' \
and self . _peers [ _peer_id ] [ ' SOCKADDR ' ] == _sockaddr :
_this_peer = self . _peers [ _peer_id ]
_this_peer [ ' CONNECTION ' ] = ' YES '
_this_peer [ ' LAST_PING ' ] = time ( )
_this_peer [ ' CALLSIGN ' ] = _data [ 8 : 16 ]
_this_peer [ ' RX_FREQ ' ] = _data [ 16 : 25 ]
_this_peer [ ' TX_FREQ ' ] = _data [ 25 : 34 ]
_this_peer [ ' TX_POWER ' ] = _data [ 34 : 36 ]
_this_peer [ ' COLORCODE ' ] = _data [ 36 : 38 ]
_this_peer [ ' LATITUDE ' ] = _data [ 38 : 46 ]
_this_peer [ ' LONGITUDE ' ] = _data [ 46 : 55 ]
_this_peer [ ' HEIGHT ' ] = _data [ 55 : 58 ]
_this_peer [ ' LOCATION ' ] = _data [ 58 : 78 ]
_this_peer [ ' DESCRIPTION ' ] = _data [ 78 : 97 ]
_this_peer [ ' SLOTS ' ] = _data [ 97 : 98 ]
_this_peer [ ' URL ' ] = _data [ 98 : 222 ]
_this_peer [ ' SOFTWARE_ID ' ] = _data [ 222 : 262 ]
_this_peer [ ' PACKAGE_ID ' ] = _data [ 262 : 302 ]
self . send_peer ( _peer_id , ' RPTACK ' + _peer_id )
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Peer %s ( %s ) has sent repeater configuration ' , self . _system , _this_peer [ ' CALLSIGN ' ] , _this_peer [ ' RADIO_ID ' ] )
2016-07-24 13:07:43 -04:00
else :
2018-08-07 18:05:27 -04:00
self . transport . write ( ' MSTNAK ' + _peer_id , _sockaddr )
2018-11-24 11:20:47 -05:00
logger . warning ( ' ( %s ) Peer info from Radio ID that has not logged in: %s ' , self . _system , int_id ( _peer_id ) )
2018-08-07 18:05:27 -04:00
elif _command == ' RPTP ' : # RPTPing -- peer is pinging us
_peer_id = _data [ 7 : 11 ]
if _peer_id in self . _peers \
and self . _peers [ _peer_id ] [ ' CONNECTION ' ] == " YES " \
and self . _peers [ _peer_id ] [ ' SOCKADDR ' ] == _sockaddr :
self . _peers [ _peer_id ] [ ' PINGS_RECEIVED ' ] + = 1
self . _peers [ _peer_id ] [ ' LAST_PING ' ] = time ( )
self . send_peer ( _peer_id , ' MSTPONG ' + _peer_id )
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) Received and answered RPTPING from peer %s ( %s ) ' , self . _system , self . _peers [ _peer_id ] [ ' CALLSIGN ' ] , int_id ( _peer_id ) )
2016-08-01 22:30:40 -04:00
else :
2018-08-07 18:05:27 -04:00
self . transport . write ( ' MSTNAK ' + _peer_id , _sockaddr )
2018-11-27 12:21:46 -05:00
logger . warning ( ' ( %s ) Ping from Radio ID that is not logged in: %s ' , self . _system , int_id ( _peer_id ) )
2016-09-01 15:33:47 -04:00
2016-07-21 13:41:36 -04:00
else :
2018-11-24 11:20:47 -05:00
logger . error ( ' ( %s ) Unrecognized command. Raw HBP PDU: %s ' , self . _system , ahex ( _data ) )
2018-06-23 14:16:51 -04:00
2018-09-26 16:41:00 -04:00
# Aliased in __init__ to datagramReceived if system is a peer
def peer_datagramReceived ( self , _data , _sockaddr ) :
2016-08-01 22:30:40 -04:00
# Keep This Line Commented Unless HEAVILY Debugging!
2018-11-24 11:20:47 -05:00
# logger.debug('(%s) RX packet from %s -- %s', self._system, _sockaddr, ahex(_data))
2016-09-01 15:33:47 -04:00
2016-07-26 21:51:04 -04:00
# Validate that we receveived this packet from the master - security check!
2018-08-07 18:05:27 -04:00
if self . _config [ ' MASTER_SOCKADDR ' ] == _sockaddr :
2016-07-26 21:51:04 -04:00
# Extract the command, which is various length, but only 4 significant characters
2016-09-01 15:36:31 -04:00
_command = _data [ : 4 ]
2016-07-26 21:51:04 -04:00
if _command == ' DMRD ' : # DMRData -- encapsulated DMR data frame
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 11 : 15 ]
if self . _config [ ' LOOSE ' ] or _peer_id == self . _config [ ' RADIO_ID ' ] : # Validate the Radio_ID unless using loose validation
2016-07-31 10:41:21 -04:00
_seq = _data [ 4 : 5 ]
_rf_src = _data [ 5 : 8 ]
_dst_id = _data [ 8 : 11 ]
2016-08-26 22:31:20 -04:00
_bits = int_id ( _data [ 15 ] )
_slot = 2 if ( _bits & 0x80 ) else 1
2018-11-29 14:54:51 -05:00
#_call_type = 'unit' if (_bits & 0x40) else 'group'
if _bits & 0x40 :
_call_type = ' unit '
elif ( _bits & 0x23 ) == 0x23 :
_call_type = ' vcsbk '
else :
_call_type = ' group '
2016-11-16 12:05:01 -05:00
_frame_type = ( _bits & 0x30 ) >> 4
2016-09-22 10:12:38 -04:00
_dtype_vseq = ( _bits & 0xF ) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F
2016-08-26 22:31:20 -04:00
_stream_id = _data [ 16 : 20 ]
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) DMRD - Sequence: %s , RF Source: %s , Destination ID: %s ' , self . _system , int_id ( _seq ) , int_id ( _rf_src ) , int_id ( _dst_id ) )
2018-11-21 21:55:54 -05:00
2018-11-21 11:24:19 -05:00
# ACL Processing
if self . _CONFIG [ ' GLOBAL ' ] [ ' USE_ACL ' ] :
if not acl_check ( _rf_src , self . _CONFIG [ ' GLOBAL ' ] [ ' SUB_ACL ' ] ) :
if self . _laststrid != _stream_id :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY GLOBAL ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _rf_src ) )
2018-11-25 11:22:43 -05:00
if _slot == 1 :
self . _laststrid1 = _stream_id
else :
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 1 and not acl_check ( _dst_id , self . _CONFIG [ ' GLOBAL ' ] [ ' TG1_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid1 != _stream_id :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS1 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
2018-11-25 11:22:43 -05:00
self . _laststrid1 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 2 and not acl_check ( _dst_id , self . _CONFIG [ ' GLOBAL ' ] [ ' TG2_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid2 != _stream_id :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY GLOBAL TS2 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
2018-11-25 11:22:43 -05:00
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
if self . _config [ ' USE_ACL ' ] :
if not acl_check ( _rf_src , self . _config [ ' SUB_ACL ' ] ) :
if self . _laststrid != _stream_id :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) CALL DROPPED WITH STREAM ID %s FROM SUBSCRIBER %s BY SYSTEM ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _rf_src ) )
2018-11-25 11:22:43 -05:00
if _slot == 1 :
self . _laststrid1 = _stream_id
else :
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 1 and not acl_check ( _dst_id , self . _config [ ' TG1_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid1 != _stream_id :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS1 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
2018-11-25 11:22:43 -05:00
self . _laststrid1 = _stream_id
2018-11-21 11:24:19 -05:00
return
if _slot == 2 and not acl_check ( _dst_id , self . _config [ ' TG2_ACL ' ] ) :
2018-11-25 11:22:43 -05:00
if self . _laststrid2 != _stream_id :
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) CALL DROPPED WITH STREAM ID %s ON TGID %s BY SYSTEM TS2 ACL ' , self . _system , int_id ( _stream_id ) , int_id ( _dst_id ) )
2018-11-25 11:22:43 -05:00
self . _laststrid2 = _stream_id
2018-11-21 11:24:19 -05:00
return
2018-11-21 21:55:54 -05:00
2016-09-01 15:33:47 -04:00
2016-07-31 10:41:21 -04:00
# Userland actions -- typically this is the function you subclass for an application
2018-08-07 18:05:27 -04:00
self . dmrd_received ( _peer_id , _rf_src , _dst_id , _seq , _slot , _call_type , _frame_type , _dtype_vseq , _stream_id , _data )
2016-09-01 15:33:47 -04:00
2016-07-26 21:51:04 -04:00
elif _command == ' MSTN ' : # Actually MSTNAK -- a NACK from the master
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 6 : 10 ]
if self . _config [ ' LOOSE ' ] or _peer_id == self . _config [ ' RADIO_ID ' ] : # Validate the Radio_ID unless using loose validation
2018-11-24 11:20:47 -05:00
logger . warning ( ' ( %s ) MSTNAK Received. Resetting connection to the Master. ' , self . _system )
2016-07-26 21:51:04 -04:00
self . _stats [ ' CONNECTION ' ] = ' NO ' # Disconnect ourselves and re-register
2016-09-01 15:33:47 -04:00
2016-07-26 21:51:04 -04:00
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
2018-02-02 15:14:30 -05:00
if self . _stats [ ' CONNECTION ' ] == ' RPTL_SENT ' : # If we've sent a login request...
2016-07-26 21:51:04 -04:00
_login_int32 = _data [ 6 : 10 ]
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Repeater Login ACK Received with 32bit ID: %s ' , self . _system , int_id ( _login_int32 ) )
2016-07-26 21:51:04 -04:00
_pass_hash = sha256 ( _login_int32 + self . _config [ ' PASSPHRASE ' ] ) . hexdigest ( )
2016-11-23 16:01:05 -05:00
_pass_hash = bhex ( _pass_hash )
2018-09-26 16:41:00 -04:00
self . send_master ( ' RPTK ' + self . _config [ ' RADIO_ID ' ] + _pass_hash )
2016-07-26 21:51:04 -04:00
self . _stats [ ' CONNECTION ' ] = ' AUTHENTICATED '
2016-09-01 15:33:47 -04:00
2016-07-26 21:51:04 -04:00
elif self . _stats [ ' CONNECTION ' ] == ' AUTHENTICATED ' : # If we've sent the login challenge...
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 6 : 10 ]
if self . _config [ ' LOOSE ' ] or _peer_id == self . _config [ ' RADIO_ID ' ] : # Validate the Radio_ID unless using loose validation
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Repeater Authentication Accepted ' , self . _system )
2016-07-26 21:51:04 -04:00
_config_packet = self . _config [ ' RADIO_ID ' ] + \
self . _config [ ' CALLSIGN ' ] + \
self . _config [ ' RX_FREQ ' ] + \
self . _config [ ' TX_FREQ ' ] + \
self . _config [ ' TX_POWER ' ] + \
self . _config [ ' COLORCODE ' ] + \
self . _config [ ' LATITUDE ' ] + \
self . _config [ ' LONGITUDE ' ] + \
self . _config [ ' HEIGHT ' ] + \
self . _config [ ' LOCATION ' ] + \
self . _config [ ' DESCRIPTION ' ] + \
self . _config [ ' SLOTS ' ] + \
self . _config [ ' URL ' ] + \
self . _config [ ' SOFTWARE_ID ' ] + \
self . _config [ ' PACKAGE_ID ' ]
2016-09-01 15:33:47 -04:00
2018-09-26 16:41:00 -04:00
self . send_master ( ' RPTC ' + _config_packet )
2016-07-26 21:51:04 -04:00
self . _stats [ ' CONNECTION ' ] = ' CONFIG-SENT '
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Repeater Configuration Sent ' , self . _system )
2016-07-26 21:51:04 -04:00
else :
self . _stats [ ' CONNECTION ' ] = ' NO '
2018-11-24 11:20:47 -05:00
logger . error ( ' ( %s ) Master ACK Contained wrong ID - Connection Reset ' , self . _system )
2016-09-01 15:33:47 -04:00
2016-07-26 21:51:04 -04:00
elif self . _stats [ ' CONNECTION ' ] == ' CONFIG-SENT ' : # If we've sent out configuration to the master
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 6 : 10 ]
if self . _config [ ' LOOSE ' ] or _peer_id == self . _config [ ' RADIO_ID ' ] : # Validate the Radio_ID unless using loose validation
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Repeater Configuration Accepted ' , self . _system )
2016-12-26 21:36:22 -05:00
if self . _config [ ' OPTIONS ' ] :
2018-09-26 16:41:00 -04:00
self . send_master ( ' RPTO ' + self . _config [ ' RADIO_ID ' ] + self . _config [ ' OPTIONS ' ] )
2016-12-26 10:47:34 -05:00
self . _stats [ ' CONNECTION ' ] = ' OPTIONS-SENT '
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Sent options: ( %s ) ' , self . _system , self . _config [ ' OPTIONS ' ] )
2016-12-26 10:47:34 -05:00
else :
self . _stats [ ' CONNECTION ' ] = ' YES '
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Connection to Master Completed ' , self . _system )
2016-12-26 10:47:34 -05:00
else :
self . _stats [ ' CONNECTION ' ] = ' NO '
2018-11-24 11:20:47 -05:00
logger . error ( ' ( %s ) Master ACK Contained wrong ID - Connection Reset ' , self . _system )
2016-12-26 10:47:34 -05:00
elif self . _stats [ ' CONNECTION ' ] == ' OPTIONS-SENT ' : # If we've sent out options to the master
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 6 : 10 ]
if self . _config [ ' LOOSE ' ] or _peer_id == self . _config [ ' RADIO_ID ' ] : # Validate the Radio_ID unless using loose validation
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Repeater Options Accepted ' , self . _system )
2016-07-26 21:51:04 -04:00
self . _stats [ ' CONNECTION ' ] = ' YES '
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) Connection to Master Completed with options ' , self . _system )
2016-07-26 21:51:04 -04:00
else :
self . _stats [ ' CONNECTION ' ] = ' NO '
2018-11-24 11:20:47 -05:00
logger . error ( ' ( %s ) Master ACK Contained wrong ID - Connection Reset ' , self . _system )
2016-09-01 15:33:47 -04:00
2018-08-07 18:05:27 -04:00
elif _command == ' MSTP ' : # Actually MSTPONG -- a reply to RPTPING (send by peer)
_peer_id = _data [ 7 : 11 ]
if self . _config [ ' LOOSE ' ] or _peer_id == self . _config [ ' RADIO_ID ' ] : # Validate the Radio_ID unless using loose validation
2018-02-02 15:14:30 -05:00
self . _stats [ ' PING_OUTSTANDING ' ] = False
self . _stats [ ' NUM_OUTSTANDING ' ] = 0
2016-07-26 21:51:04 -04:00
self . _stats [ ' PINGS_ACKD ' ] + = 1
2018-11-24 11:20:47 -05:00
logger . debug ( ' ( %s ) MSTPONG Received. Pongs Since Connected: %s ' , self . _system , self . _stats [ ' PINGS_ACKD ' ] )
2016-09-01 15:33:47 -04:00
2016-07-26 21:51:04 -04:00
elif _command == ' MSTC ' : # Actually MSTCL -- notify us the master is closing down
2018-08-07 18:05:27 -04:00
_peer_id = _data [ 5 : 9 ]
if self . _config [ ' LOOSE ' ] or _peer_id == self . _config [ ' RADIO_ID ' ] : # Validate the Radio_ID unless using loose validation
2016-07-26 21:51:04 -04:00
self . _stats [ ' CONNECTION ' ] = ' NO '
2018-11-24 11:20:47 -05:00
logger . info ( ' ( %s ) MSTCL Recieved ' , self . _system )
2016-09-01 15:33:47 -04:00
2016-07-26 21:51:04 -04:00
else :
2018-11-24 11:20:47 -05:00
logger . error ( ' ( %s ) Received an invalid command in packet: %s ' , self . _system , ahex ( _data ) )
2016-07-20 17:16:27 -04:00
2018-06-19 17:02:38 -04:00
#
# Socket-based reporting section
#
class report ( NetstringReceiver ) :
def __init__ ( self , factory ) :
self . _factory = factory
def connectionMade ( self ) :
self . _factory . clients . append ( self )
2018-11-25 11:23:58 -05:00
logger . info ( ' HBlink reporting client connected: %s ' , self . transport . getPeer ( ) )
2018-06-19 17:02:38 -04:00
def connectionLost ( self , reason ) :
2018-11-25 11:23:58 -05:00
logger . info ( ' HBlink reporting client disconnected: %s ' , self . transport . getPeer ( ) )
2018-06-19 17:02:38 -04:00
self . _factory . clients . remove ( self )
def stringReceived ( self , data ) :
self . process_message ( data )
def process_message ( self , _message ) :
opcode = _message [ : 1 ]
if opcode == REPORT_OPCODES [ ' CONFIG_REQ ' ] :
2018-11-25 11:23:58 -05:00
logger . info ( ' HBlink reporting client sent \' CONFIG_REQ \' : %s ' , self . transport . getPeer ( ) )
2018-06-19 17:02:38 -04:00
self . send_config ( )
else :
2018-11-25 11:23:58 -05:00
logger . error ( ' got unknown opcode ' )
2018-11-21 21:55:54 -05:00
2018-06-19 17:02:38 -04:00
class reportFactory ( Factory ) :
2018-11-24 11:20:47 -05:00
def __init__ ( self , config ) :
2018-06-19 17:02:38 -04:00
self . _config = config
2018-11-21 21:55:54 -05:00
2018-06-19 17:02:38 -04:00
def buildProtocol ( self , addr ) :
if ( addr . host ) in self . _config [ ' REPORTS ' ] [ ' REPORT_CLIENTS ' ] or ' * ' in self . _config [ ' REPORTS ' ] [ ' REPORT_CLIENTS ' ] :
2018-11-24 11:20:47 -05:00
logger . debug ( ' Permitting report server connection attempt from: %s : %s ' , addr . host , addr . port )
2018-06-19 17:02:38 -04:00
return report ( self )
else :
2018-11-24 11:20:47 -05:00
logger . error ( ' Invalid report server connection attempt from: %s : %s ' , addr . host , addr . port )
2018-06-19 17:02:38 -04:00
return None
2018-11-21 21:55:54 -05:00
2018-06-19 17:02:38 -04:00
def send_clients ( self , _message ) :
for client in self . clients :
client . sendString ( _message )
2018-11-21 21:55:54 -05:00
2018-06-19 17:02:38 -04:00
def send_config ( self ) :
serialized = pickle . dumps ( self . _config [ ' SYSTEMS ' ] , protocol = pickle . HIGHEST_PROTOCOL )
self . send_clients ( REPORT_OPCODES [ ' CONFIG_SND ' ] + serialized )
2018-11-21 21:55:54 -05:00
2016-07-20 22:25:47 -04:00
2018-11-24 11:20:47 -05:00
# ID ALIAS CREATION
# Download
def mk_aliases ( _config ) :
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 ' )
return peer_ids , subscriber_ids , talkgroup_ids
2016-07-20 17:16:27 -04:00
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
#************************************************
if __name__ == ' __main__ ' :
2016-11-23 16:01:05 -05:00
# Python modules we need
import argparse
import sys
import os
import signal
2018-11-21 21:55:54 -05:00
2016-11-23 16:01:05 -05:00
# 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 )
2018-11-21 21:55:54 -05:00
2016-11-23 16:01:05 -05:00
# 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 ' ] )
2018-11-25 11:36:14 -05:00
logger . info ( ' \n \n Copyright (c) 2013, 2014, 2015, 2016, 2018 \n \t The Founding Members of the K0USY Group. All rights reserved. \n ' )
2016-11-23 16:01:05 -05:00
logger . debug ( ' Logging system started, anything from here on gets logged ' )
2018-11-25 11:36:14 -05:00
2016-11-23 16:01:05 -05:00
# Set up the signal handler
def sig_handler ( _signal , _frame ) :
logger . info ( ' SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s ' , str ( _signal ) )
2018-11-24 11:20:47 -05:00
hblink_handler ( _signal , _frame )
2016-11-23 16:01:05 -05:00
logger . info ( ' SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR ' )
reactor . stop ( )
2018-11-21 21:55:54 -05:00
2016-11-23 16:01:05 -05:00
# Set signal handers so that we can gracefully exit if need be
for sig in [ signal . SIGTERM , signal . SIGINT ] :
signal . signal ( sig , sig_handler )
2018-11-21 21:55:54 -05:00
2018-11-24 11:20:47 -05:00
peer_ids , subscriber_ids , talkgroup_ids = mk_aliases ( CONFIG )
2018-06-19 17:02:38 -04:00
# INITIALIZE THE REPORTING LOOP
2018-11-24 11:20:47 -05:00
report_server = config_reports ( CONFIG , reportFactory )
2016-11-23 16:01:05 -05:00
2016-08-25 21:44:15 -04:00
# HBlink instance creation
2018-11-25 11:36:14 -05:00
logger . info ( ' HBlink \' HBlink.py \' -- SYSTEM STARTING... ' )
2016-08-25 21:44:15 -04:00
for system in CONFIG [ ' SYSTEMS ' ] :
if CONFIG [ ' SYSTEMS ' ] [ system ] [ ' ENABLED ' ] :
2018-09-25 21:17:55 -04:00
if CONFIG [ ' SYSTEMS ' ] [ system ] [ ' MODE ' ] == ' OPENBRIDGE ' :
2018-11-24 11:20:47 -05:00
systems [ system ] = OPENBRIDGE ( system , CONFIG , report_server )
2018-09-25 21:17:55 -04:00
else :
2018-11-24 11:20:47 -05:00
systems [ system ] = HBSYSTEM ( system , CONFIG , report_server )
2016-08-25 21:44:15 -04:00
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 ] )
2018-11-25 11:22:43 -05:00
2018-11-21 11:58:16 -05:00
reactor . run ( )