2014-01-03 16:01:43 -05:00
#!/usr/bin/env python
#
2016-11-23 08:50:56 -05:00
###############################################################################
# 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
###############################################################################
2013-08-02 16:31:55 -04:00
2014-01-03 16:03:41 -05:00
#NOTE: This program uses a configuration file specified on the command line
# if none is specified, then dmrlink.cfg in the same directory as this
# file will be tried. Finally, if that does not exist, this process
# will terminate
2013-06-27 17:15:54 -04:00
from __future__ import print_function
2013-12-12 17:23:46 -05:00
2013-10-12 12:08:06 -04:00
import ConfigParser
2014-01-03 16:01:43 -05:00
import argparse
2013-07-11 20:45:09 -04:00
import sys
2013-10-13 18:01:50 -04:00
import csv
2013-12-13 13:07:18 -05:00
import os
2013-12-15 10:45:39 -05:00
import logging
2014-05-17 14:18:45 -04:00
import signal
2013-07-11 20:45:09 -04:00
2013-12-15 10:45:39 -05:00
from logging . config import dictConfig
2013-12-12 17:23:46 -05:00
from hmac import new as hmac_new
2016-12-15 14:08:41 -05:00
from binascii import b2a_hex as ahex
from binascii import a2b_hex as bhex
2013-12-12 17:23:46 -05:00
from hashlib import sha1
from socket import inet_ntoa as IPAddr
2014-05-14 21:58:58 -04:00
from socket import inet_aton as IPHexStr
2016-11-23 17:37:37 -05:00
from time import time
from cPickle import dump as pickle_dump
2013-11-26 17:05:08 -05:00
from twisted . internet . protocol import DatagramProtocol
from twisted . internet import reactor
from twisted . internet import task
2016-12-15 14:08:41 -05:00
from ipsc . ipsc_const import *
from ipsc . ipsc_mask import *
from dmrlink_config import build_config
from dmrlink_log import config_logging
2016-12-15 16:04:45 -05:00
from dmr_utils . utils import hex_str_2 , hex_str_3 , hex_str_4 , int_id
2016-12-15 14:08:41 -05:00
2016-11-23 08:50:56 -05:00
__author__ = ' Cortney T. Buffington, N0MJS '
__copyright__ = ' Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group '
__credits__ = ' Adam Fast, KC0YLK; Dave Kierzkowski, KD8EYF; Steve Zingman, N4IRS; Mike Zingman, N4IRR '
__license__ = ' GNU GPLv3 '
__maintainer__ = ' Cort Buffington, N0MJS '
__email__ = ' n0mjs@me.com '
2014-01-02 12:16:23 -05:00
2016-12-15 16:04:45 -05:00
2016-12-15 14:08:41 -05:00
# Global variables used whether we are a module or __main__
2016-11-23 17:37:37 -05:00
systems = { }
2013-11-26 17:05:08 -05:00
2016-12-18 22:51:13 -05:00
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports ( _config ) :
if _config [ ' REPORTS ' ] [ ' REPORT_NETWORKS ' ] == ' PICKLE ' :
def reporting_loop ( _logger ) :
_logger . debug ( ' Periodic Reporting Loop Started (PICKLE) ' )
2016-12-15 14:08:41 -05:00
try :
2016-12-18 22:51:13 -05:00
with open ( _config [ ' REPORTS ' ] [ ' REPORT_PATH ' ] + ' dmrlink_stats.pickle ' , ' wb ' ) as file :
pickle_dump ( _config [ ' SYSTEMS ' ] , file , 2 )
2016-12-15 14:08:41 -05:00
file . close ( )
except IOError as detail :
2016-12-18 22:51:13 -05:00
_logger . error ( ' I/O Error: %s ' , detail )
2014-05-14 21:58:58 -04:00
2016-12-18 22:51:13 -05:00
elif _config [ ' REPORTS ' ] [ ' REPORT_NETWORKS ' ] == ' PRINT ' :
def reporting_loop ( _logger ) :
_logger . debug ( ' Periodic Reporting Loop Started (PRINT) ' )
for system in _config [ ' SYSTEMS ' ] :
print_master ( _config , system )
print_peer_list ( _config , system )
2013-07-29 14:38:59 -04:00
2013-07-29 14:23:37 -04:00
else :
2016-12-18 22:51:13 -05:00
def reporting_loop ( _logger ) :
_logger . debug ( ' Periodic Reporting Loop Started (NULL) ' )
return reporting_loop
2013-12-11 21:56:39 -05:00
# Process the MODE byte in registration/peer list packets for determining master and peer capabilities
2013-12-11 21:53:55 -05:00
#
def process_mode_byte ( _hex_mode ) :
2016-12-15 14:08:41 -05:00
_mode = int ( ahex ( _hex_mode ) , 16 )
2013-12-11 21:53:55 -05:00
# Determine whether or not the peer is operational
2013-12-12 08:42:56 -05:00
_peer_op = bool ( _mode & PEER_OP_MSK )
# Determine whether or not timeslot 1 is linked
_ts1 = bool ( _mode & IPSC_TS1_MSK )
# Determine whether or not timeslot 2 is linked
_ts2 = bool ( _mode & IPSC_TS2_MSK )
2013-12-11 21:53:55 -05:00
# Determine the operational mode of the peer
if _mode & PEER_MODE_MSK == PEER_MODE_MSK :
_peer_mode = ' UNKNOWN '
elif not _mode & PEER_MODE_MSK :
_peer_mode = ' NO_RADIO '
elif _mode & PEER_MODE_ANALOG :
_peer_mode = ' ANALOG '
elif _mode & PEER_MODE_DIGITAL :
_peer_mode = ' DIGITAL '
2013-12-12 08:42:56 -05:00
return {
' PEER_OP ' : _peer_op ,
' PEER_MODE ' : _peer_mode ,
' TS_1 ' : _ts1 ,
' TS_2 ' : _ts2
}
2013-12-11 21:56:39 -05:00
# Process the FLAGS bytes in registration replies for determining what services are available
#
def process_flags_bytes ( _hex_flags ) :
2016-12-15 14:08:41 -05:00
_byte3 = int ( ahex ( _hex_flags [ 2 ] ) , 16 )
_byte4 = int ( ahex ( _hex_flags [ 3 ] ) , 16 )
2013-12-12 08:42:56 -05:00
_csbk = bool ( _byte3 & CSBK_MSK )
_rpt_mon = bool ( _byte3 & RPT_MON_MSK )
_con_app = bool ( _byte3 & CON_APP_MSK )
_xnl_con = bool ( _byte4 & XNL_STAT_MSK )
_xnl_master = bool ( _byte4 & XNL_MSTR_MSK )
_xnl_slave = bool ( _byte4 & XNL_SLAVE_MSK )
_auth = bool ( _byte4 & PKT_AUTH_MSK )
_data = bool ( _byte4 & DATA_CALL_MSK )
_voice = bool ( _byte4 & VOICE_CALL_MSK )
_master = bool ( _byte4 & MSTR_PEER_MSK )
return {
2013-12-12 17:12:36 -05:00
' CSBK ' : _csbk ,
' RCM ' : _rpt_mon ,
' CON_APP ' : _con_app ,
' XNL_CON ' : _xnl_con ,
' XNL_MASTER ' : _xnl_master ,
' XNL_SLAVE ' : _xnl_slave ,
' AUTH ' : _auth ,
' DATA ' : _data ,
' VOICE ' : _voice ,
' MASTER ' : _master
2013-12-12 08:42:56 -05:00
}
2016-07-10 09:53:36 -04:00
2014-05-17 11:40:19 -04:00
# Build a peer list - used when a peer registers, re-regiseters or times out
#
def build_peer_list ( _peers ) :
concatenated_peers = ' '
for peer in _peers :
hex_ip = IPHexStr ( _peers [ peer ] [ ' IP ' ] )
hex_port = hex_str_2 ( _peers [ peer ] [ ' PORT ' ] )
mode = _peers [ peer ] [ ' MODE ' ]
concatenated_peers + = peer + hex_ip + hex_port + mode
peer_list = hex_str_2 ( len ( concatenated_peers ) ) + concatenated_peers
return peer_list
2013-07-28 23:22:04 -04:00
2013-11-26 17:05:08 -05:00
# Gratuitous print-out of the peer list.. Pretty much debug stuff.
2013-07-20 09:28:52 -04:00
#
2016-12-18 22:51:13 -05:00
def print_peer_list ( _config , _network ) :
_peers = _config [ ' SYSTEMS ' ] [ _network ] [ ' PEERS ' ]
2013-11-13 17:19:32 -05:00
2016-12-18 22:51:13 -05:00
_status = _config [ ' SYSTEMS ' ] [ _network ] [ ' MASTER ' ] [ ' STATUS ' ] [ ' PEER_LIST ' ]
2013-10-24 16:48:16 -04:00
#print('Peer List Status for {}: {}' .format(_network, _status))
2013-09-10 16:28:18 -04:00
2016-12-18 22:51:13 -05:00
if _status and not _config [ ' SYSTEMS ' ] [ _network ] [ ' PEERS ' ] :
2013-09-10 16:28:18 -04:00
print ( ' We are the only peer for: %s ' % _network )
2013-09-10 21:36:35 -04:00
print ( ' ' )
2013-07-28 23:33:14 -04:00
return
2013-11-13 17:19:32 -05:00
2013-07-31 13:33:31 -04:00
print ( ' Peer List for: %s ' % _network )
2013-11-13 17:19:32 -05:00
for peer in _peers . keys ( ) :
_this_peer = _peers [ peer ]
_this_peer_stat = _this_peer [ ' STATUS ' ]
2016-12-18 22:51:13 -05:00
if peer == _config [ ' SYSTEMS ' ] [ _network ] [ ' LOCAL ' ] [ ' RADIO_ID ' ] :
2013-07-31 21:56:49 -04:00
me = ' (self) '
else :
me = ' '
2013-12-11 15:50:03 -05:00
2014-09-05 11:16:06 -04:00
print ( ' \t RADIO ID: {} {} ' . format ( int_id ( peer ) , me ) )
2013-11-13 17:19:32 -05:00
print ( ' \t \t IP Address: {} : {} ' . format ( _this_peer [ ' IP ' ] , _this_peer [ ' PORT ' ] ) )
2016-12-18 22:51:13 -05:00
if _this_peer [ ' MODE_DECODE ' ] and _config [ ' REPORTS ' ] [ ' PRINT_PEERS_INC_MODE ' ] :
2013-12-12 17:12:36 -05:00
print ( ' \t \t Mode Values: ' )
for name , value in _this_peer [ ' MODE_DECODE ' ] . items ( ) :
print ( ' \t \t \t {} : {} ' . format ( name , value ) )
2016-12-18 22:51:13 -05:00
if _this_peer [ ' FLAGS_DECODE ' ] and _config [ ' REPORTS ' ] [ ' PRINT_PEERS_INC_FLAGS ' ] :
2013-12-12 17:12:36 -05:00
print ( ' \t \t Service Flags: ' )
for name , value in _this_peer [ ' FLAGS_DECODE ' ] . items ( ) :
print ( ' \t \t \t {} : {} ' . format ( name , value ) )
2013-11-13 17:19:32 -05:00
print ( ' \t \t Status: {} , KeepAlives Sent: {} , KeepAlives Outstanding: {} , KeepAlives Missed: {} ' . format ( _this_peer_stat [ ' CONNECTED ' ] , _this_peer_stat [ ' KEEP_ALIVES_SENT ' ] , _this_peer_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] , _this_peer_stat [ ' KEEP_ALIVES_MISSED ' ] ) )
2014-05-15 23:21:54 -04:00
print ( ' \t \t KeepAlives Received: {} , Last KeepAlive Received at: {} ' . format ( _this_peer_stat [ ' KEEP_ALIVES_RECEIVED ' ] , _this_peer_stat [ ' KEEP_ALIVE_RX_TIME ' ] ) )
2013-07-31 13:33:31 -04:00
print ( ' ' )
2013-11-13 17:19:32 -05:00
2013-11-26 17:05:08 -05:00
# Gratuitous print-out of Master info.. Pretty much debug stuff.
2013-10-24 16:48:16 -04:00
#
2016-12-18 22:51:13 -05:00
def print_master ( _config , _network ) :
if _config [ ' SYSTEMS ' ] [ _network ] [ ' LOCAL ' ] [ ' MASTER_PEER ' ] :
print ( ' DMRlink is the Master for %s ' % _network )
2014-05-08 09:39:41 -04:00
else :
2016-12-18 22:51:13 -05:00
_master = _config [ ' SYSTEMS ' ] [ _network ] [ ' MASTER ' ]
2014-05-08 09:39:41 -04:00
print ( ' Master for %s ' % _network )
2016-12-18 22:51:13 -05:00
print ( ' \t RADIO ID: {} ' . format ( int ( ahex ( _master [ ' RADIO_ID ' ] ) , 16 ) ) )
if _master [ ' MODE_DECODE ' ] and _config [ ' REPORTS ' ] [ ' PRINT_PEERS_INC_MODE ' ] :
2014-05-08 09:39:41 -04:00
print ( ' \t \t Mode Values: ' )
for name , value in _master [ ' MODE_DECODE ' ] . items ( ) :
2016-12-18 22:51:13 -05:00
print ( ' \t \t \t {} : {} ' . format ( name , value ) )
if _master [ ' FLAGS_DECODE ' ] and _config [ ' REPORTS ' ] [ ' PRINT_PEERS_INC_FLAGS ' ] :
2014-05-08 09:39:41 -04:00
print ( ' \t \t Service Flags: ' )
for name , value in _master [ ' FLAGS_DECODE ' ] . items ( ) :
2016-12-18 22:51:13 -05:00
print ( ' \t \t \t {} : {} ' . format ( name , value ) )
2014-05-08 09:39:41 -04:00
print ( ' \t \t Status: {} , KeepAlives Sent: {} , KeepAlives Outstanding: {} , KeepAlives Missed: {} ' . format ( _master [ ' STATUS ' ] [ ' CONNECTED ' ] , _master [ ' STATUS ' ] [ ' KEEP_ALIVES_SENT ' ] , _master [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] , _master [ ' STATUS ' ] [ ' KEEP_ALIVES_MISSED ' ] ) )
2014-05-15 23:21:54 -04:00
print ( ' \t \t KeepAlives Received: {} , Last KeepAlive Received at: {} ' . format ( _master [ ' STATUS ' ] [ ' KEEP_ALIVES_RECEIVED ' ] , _master [ ' STATUS ' ] [ ' KEEP_ALIVE_RX_TIME ' ] ) )
2016-12-15 13:12:56 -05:00
2015-07-27 21:10:35 -04:00
2014-10-05 23:11:56 -04:00
2013-07-20 09:28:52 -04:00
#************************************************
2016-11-23 17:37:37 -05:00
# IPSC CLASS
2013-07-11 20:45:09 -04:00
#************************************************
2013-06-27 17:15:54 -04:00
class IPSC ( DatagramProtocol ) :
2016-12-15 16:04:45 -05:00
def __init__ ( self , _name , _config , _logger ) :
# Housekeeping: create references to the configuration and status data for this IPSC instance.
# Some configuration objects that are used frequently and have lengthy names are shortened
# such as (self._master_sock) expands to (self._config['MASTER']['IP'], self._config['MASTER']['PORT']).
# Note that many of them reference each other... this is the Pythonic way.
#
self . _system = _name
self . _CONFIG = _config
self . _logger = _logger
self . _config = self . _CONFIG [ ' SYSTEMS ' ] [ self . _system ]
#
self . _local = self . _config [ ' LOCAL ' ]
self . _local_id = self . _local [ ' RADIO_ID ' ]
#
self . _master = self . _config [ ' MASTER ' ]
self . _master_stat = self . _master [ ' STATUS ' ]
self . _master_sock = self . _master [ ' IP ' ] , self . _master [ ' PORT ' ]
#
self . _peers = self . _config [ ' PEERS ' ]
#
# This is a regular list to store peers for the IPSC. At times, parsing a simple list is much less
# Spendy than iterating a list of dictionaries... Maybe I'll find a better way in the future. Also
# We have to know when we have a new peer list, so a variable to indicate we do (or don't)
#
args = ( )
# Packet 'constructors' - builds the necessary control packets for this IPSC instance.
# This isn't really necessary for anything other than readability (reduction of code golf)
#
# General Items
self . TS_FLAGS = ( self . _local [ ' MODE ' ] + self . _local [ ' FLAGS ' ] )
#
# Peer Link Maintenance Packets
self . MASTER_REG_REQ_PKT = ( MASTER_REG_REQ + self . _local_id + self . TS_FLAGS + IPSC_VER )
self . MASTER_ALIVE_PKT = ( MASTER_ALIVE_REQ + self . _local_id + self . TS_FLAGS + IPSC_VER )
self . PEER_LIST_REQ_PKT = ( PEER_LIST_REQ + self . _local_id )
self . PEER_REG_REQ_PKT = ( PEER_REG_REQ + self . _local_id + IPSC_VER )
self . PEER_REG_REPLY_PKT = ( PEER_REG_REPLY + self . _local_id + IPSC_VER )
self . PEER_ALIVE_REQ_PKT = ( PEER_ALIVE_REQ + self . _local_id + self . TS_FLAGS )
self . PEER_ALIVE_REPLY_PKT = ( PEER_ALIVE_REPLY + self . _local_id + self . TS_FLAGS )
#
# Master Link Maintenance Packets
# self.MASTER_REG_REPLY_PKT is not static and must be generated when it is sent
self . MASTER_ALIVE_REPLY_PKT = ( MASTER_ALIVE_REPLY + self . _local_id + self . TS_FLAGS + IPSC_VER )
self . PEER_LIST_REPLY_PKT = ( PEER_LIST_REPLY + self . _local_id )
#
# General Link Maintenance Packets
self . DE_REG_REQ_PKT = ( DE_REG_REQ + self . _local_id )
self . DE_REG_REPLY_PKT = ( DE_REG_REPLY + self . _local_id )
#
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) IPSC Instance Created: %s , %s : %s ' , self . _system , int_id ( self . _local [ ' RADIO_ID ' ] ) , self . _local [ ' IP ' ] , self . _local [ ' PORT ' ] )
#******************************************************
# SUPPORT FUNCTIONS FOR HANDLING IPSC OPERATIONS
#******************************************************
# Determine if the provided peer ID is valid for the provided network
#
def valid_peer ( self , _peerid ) :
if _peerid in self . _peers :
return True
return False
# Determine if the provided master ID is valid for the provided network
#
def valid_master ( self , _peerid ) :
if self . _master [ ' RADIO_ID ' ] == _peerid :
return True
else :
return False
# De-register a peer from an IPSC by removing it's information
#
def de_register_peer ( self , _peerid ) :
# Iterate for the peer in our data
if _peerid in self . _peers . keys ( ) :
del self . _peers [ _peerid ]
2016-12-19 19:49:36 -05:00
self . _logger . info ( ' ( %s ) Peer De-Registration Requested for: %s ' , self . _system , int_id ( _peerid ) )
2016-12-18 22:51:13 -05:00
return
else :
2016-12-19 19:49:36 -05:00
self . _logger . warning ( ' ( %s ) Peer De-Registration Requested for: %s , but we don \' t have a listing for this peer ' , self . _system , int_id ( _peerid ) )
2016-12-18 22:51:13 -05:00
pass
# Take a received peer list and the network it belongs to, process and populate the
# data structure in my_ipsc_config with the results, and return a simple list of peers.
#
def process_peer_list ( self , _data ) :
# Create a temporary peer list to track who we should have in our list -- used to find old peers we should remove.
_temp_peers = [ ]
# Determine the length of the peer list for the parsing iterator
_peer_list_length = int ( ahex ( _data [ 5 : 7 ] ) , 16 )
# Record the number of peers in the data structure... we'll use it later (11 bytes per peer entry)
self . _local [ ' NUM_PEERS ' ] = _peer_list_length / 11
self . _logger . info ( ' ( %s ) Peer List Received from Master: %s peers in this IPSC ' , self . _system , self . _local [ ' NUM_PEERS ' ] )
# Iterate each peer entry in the peer list. Skip the header, then pull the next peer, the next, etc.
for i in range ( 7 , _peer_list_length + 7 , 11 ) :
# Extract various elements from each entry...
_hex_radio_id = ( _data [ i : i + 4 ] )
_hex_address = ( _data [ i + 4 : i + 8 ] )
_ip_address = IPAddr ( _hex_address )
_hex_port = ( _data [ i + 8 : i + 10 ] )
_port = int ( ahex ( _hex_port ) , 16 )
_hex_mode = ( _data [ i + 10 : i + 11 ] )
# Add this peer to a temporary PeerID list - used to remove any old peers no longer with us
_temp_peers . append ( _hex_radio_id )
# This is done elsewhere for the master too, so we use a separate function
_decoded_mode = process_mode_byte ( _hex_mode )
# If this entry WAS already in our list, update everything except the stats
# in case this was a re-registration with a different mode, flags, etc.
if _hex_radio_id in self . _peers . keys ( ) :
self . _peers [ _hex_radio_id ] [ ' IP ' ] = _ip_address
self . _peers [ _hex_radio_id ] [ ' PORT ' ] = _port
self . _peers [ _hex_radio_id ] [ ' MODE ' ] = _hex_mode
self . _peers [ _hex_radio_id ] [ ' MODE_DECODE ' ] = _decoded_mode
self . _peers [ _hex_radio_id ] [ ' FLAGS ' ] = ' '
self . _peers [ _hex_radio_id ] [ ' FLAGS_DECODE ' ] = ' '
self . _logger . debug ( ' ( %s ) Peer Updated: %s ' , self . _system , self . _peers [ _hex_radio_id ] )
# If this entry was NOT already in our list, add it.
if _hex_radio_id not in self . _peers . keys ( ) :
self . _peers [ _hex_radio_id ] = {
' IP ' : _ip_address ,
' PORT ' : _port ,
' MODE ' : _hex_mode ,
' MODE_DECODE ' : _decoded_mode ,
' FLAGS ' : ' ' ,
' FLAGS_DECODE ' : ' ' ,
' STATUS ' : {
' CONNECTED ' : False ,
' KEEP_ALIVES_SENT ' : 0 ,
' KEEP_ALIVES_MISSED ' : 0 ,
' KEEP_ALIVES_OUTSTANDING ' : 0 ,
' KEEP_ALIVES_RECEIVED ' : 0 ,
' KEEP_ALIVE_RX_TIME ' : 0
}
}
self . _logger . debug ( ' ( %s ) Peer Added: %s ' , self . _system , self . _peers [ _hex_radio_id ] )
# Finally, check to see if there's a peer already in our list that was not in this peer list
# and if so, delete it.
for peer in self . _peers . keys ( ) :
if peer not in _temp_peers :
self . de_register_peer ( peer )
self . _logger . warning ( ' ( %s ) Peer Deleted (not in new peer list): %s ' , self . _system , int_id ( peer ) )
2013-09-10 16:28:18 -04:00
2013-10-28 21:34:29 -04:00
#************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
2016-12-18 22:51:13 -05:00
def call_mon_status ( self , _data ) :
self . _logger . debug ( ' ( %s ) Repeater Call Monitor Origin Packet Received: %s ' , self . _system , ahex ( _data ) )
2013-10-28 21:34:29 -04:00
2016-12-18 22:51:13 -05:00
def call_mon_rpt ( self , _data ) :
self . _logger . debug ( ' ( %s ) Repeater Call Monitor Repeating Packet Received: %s ' , self . _system , ahex ( _data ) )
2013-10-28 21:34:29 -04:00
2016-12-18 22:51:13 -05:00
def call_mon_nack ( self , _data ) :
self . _logger . debug ( ' ( %s ) Repeater Call Monitor NACK Packet Received: %s ' , self . _system , ahex ( _data ) )
2013-10-28 21:34:29 -04:00
2016-12-18 22:51:13 -05:00
def xcmp_xnl ( self , _data ) :
self . _logger . debug ( ' ( %s ) XCMP/XNL Packet Received: %s ' , self . _system , ahex ( _data ) )
2013-11-21 12:47:43 -05:00
2016-12-18 22:51:13 -05:00
def repeater_wake_up ( self , _data ) :
self . _logger . debug ( ' ( %s ) Repeater Wake-Up Packet Received: %s ' , self . _system , ahex ( _data ) )
2013-11-22 16:43:47 -05:00
2016-12-18 22:51:13 -05:00
def group_voice ( self , _src_sub , _dst_sub , _ts , _end , _peerid , _data ) :
self . _logger . debug ( ' ( %s ) Group Voice Packet Received From: %s , IPSC Peer %s , Destination %s ' , self . _system , int_id ( _src_sub ) , int_id ( _peerid ) , int_id ( _dst_sub ) )
2013-10-28 21:34:29 -04:00
2016-12-18 22:51:13 -05:00
def private_voice ( self , _src_sub , _dst_sub , _ts , _end , _peerid , _data ) :
self . _logger . debug ( ' ( %s ) Private Voice Packet Received From: %s , IPSC Peer %s , Destination %s ' , self . _system , int_id ( _src_sub ) , int_id ( _peerid ) , int_id ( _dst_sub ) )
2013-10-28 21:34:29 -04:00
2016-12-18 22:51:13 -05:00
def group_data ( self , _src_sub , _dst_sub , _ts , _end , _peerid , _data ) :
self . _logger . debug ( ' ( %s ) Group Data Packet Received From: %s , IPSC Peer %s , Destination %s ' , self . _system , int_id ( _src_sub ) , int_id ( _peerid ) , int_id ( _dst_sub ) )
2013-10-28 21:34:29 -04:00
2016-12-18 22:51:13 -05:00
def private_data ( self , _src_sub , _dst_sub , _ts , _end , _peerid , _data ) :
self . _logger . debug ( ' ( %s ) Private Data Packet Received From: %s , IPSC Peer %s , Destination %s ' , self . _system , int_id ( _src_sub ) , int_id ( _peerid ) , int_id ( _dst_sub ) )
2013-10-28 21:34:29 -04:00
2016-12-18 22:51:13 -05:00
def unknown_message ( self , _packettype , _peerid , _data ) :
self . _logger . error ( ' ( %s ) Unknown Message - Type: %s From: %s Packet: %s ' , ahex ( _packettype ) , self . _system , int_id ( _peerid ) , ahex ( _data ) )
2013-10-28 21:34:29 -04:00
2014-05-14 15:18:33 -04:00
#************************************************
# IPSC SPECIFIC MAINTENANCE FUNCTIONS
#************************************************
2014-09-05 14:57:47 -04:00
2014-12-20 10:14:54 -05:00
# Simple function to send packets - handy to have it all in one place for debugging
#
def send_packet ( self , _packet , ( _host , _port ) ) :
2016-09-09 23:05:47 -04:00
if self . _local [ ' AUTH_ENABLED ' ] :
2016-12-15 14:08:41 -05:00
_hash = bhex ( ( hmac_new ( self . _local [ ' AUTH_KEY ' ] , _packet , sha1 ) ) . hexdigest ( ) [ : 20 ] )
2016-09-09 23:05:47 -04:00
_packet = _packet + _hash
2014-12-20 10:14:54 -05:00
self . transport . write ( _packet , ( _host , _port ) )
2014-12-20 11:05:03 -05:00
# USE THE FOLLOWING ONLY UNDER DIRE CIRCUMSTANCES -- PERFORMANCE IS ADVERSLY AFFECTED!
2016-12-18 22:51:13 -05:00
#self._logger.debug('(%s) TX Packet to %s on port %s: %s', self._system, _host, _port, ahex(_packet))
2014-12-20 10:14:54 -05:00
# Accept a complete packet, ready to be sent, and send it to all active peers + master in an IPSC
#
def send_to_ipsc ( self , _packet ) :
2016-09-09 23:05:47 -04:00
if self . _local [ ' AUTH_ENABLED ' ] :
2016-12-15 14:08:41 -05:00
_hash = bhex ( ( hmac_new ( self . _local [ ' AUTH_KEY ' ] , _packet , sha1 ) ) . hexdigest ( ) [ : 20 ] )
2016-09-09 23:05:47 -04:00
_packet = _packet + _hash
2014-12-20 10:14:54 -05:00
# Send to the Master
if self . _master [ ' STATUS ' ] [ ' CONNECTED ' ] :
2016-09-09 23:05:47 -04:00
self . transport . write ( _packet , ( self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] ) )
2014-12-20 10:14:54 -05:00
# Send to each connected Peer
for peer in self . _peers . keys ( ) :
if self . _peers [ peer ] [ ' STATUS ' ] [ ' CONNECTED ' ] :
2016-09-09 23:13:44 -04:00
self . transport . write ( _packet , ( self . _peers [ peer ] [ ' IP ' ] , self . _peers [ peer ] [ ' PORT ' ] ) )
2014-12-20 10:14:54 -05:00
2014-09-05 14:57:47 -04:00
# FUNTIONS FOR IPSC MAINTENANCE ACTIVITIES WE RESPOND TO
2014-09-05 20:35:37 -04:00
# SOMEONE HAS SENT US A KEEP ALIVE - WE MUST ANSWER IT
2014-09-05 14:57:47 -04:00
def peer_alive_req ( self , _data , _peerid , _host , _port ) :
_hex_mode = ( _data [ 5 ] )
_hex_flags = ( _data [ 6 : 10 ] )
_decoded_mode = process_mode_byte ( _hex_mode )
_decoded_flags = process_flags_bytes ( _hex_flags )
self . _peers [ _peerid ] [ ' MODE ' ] = _hex_mode
self . _peers [ _peerid ] [ ' MODE_DECODE ' ] = _decoded_mode
self . _peers [ _peerid ] [ ' FLAGS ' ] = _hex_flags
self . _peers [ _peerid ] [ ' FLAGS_DECODE ' ] = _decoded_flags
2016-09-09 23:05:47 -04:00
self . send_packet ( self . PEER_ALIVE_REPLY_PKT , ( _host , _port ) )
2014-09-05 14:57:47 -04:00
self . reset_keep_alive ( _peerid ) # Might as well reset our own counter, we know it's out there...
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Keep-Alive reply sent to Peer %s , %s : %s ' , self . _system , int_id ( _peerid ) , _host , _port )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# SOMEONE WANTS TO REGISTER WITH US - WE'RE COOL WITH THAT
2014-09-05 14:57:47 -04:00
def peer_reg_req ( self , _peerid , _host , _port ) :
2016-09-09 23:05:47 -04:00
self . send_packet ( self . PEER_REG_REPLY_PKT , ( _host , _port ) )
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) Peer Registration Request From: %s , %s : %s ' , self . _system , int_id ( _peerid ) , _host , _port )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# SOMEONE HAS ANSWERED OUR KEEP-ALIVE REQUEST - KEEP TRACK OF IT
2014-09-05 14:57:47 -04:00
def peer_alive_reply ( self , _peerid ) :
self . reset_keep_alive ( _peerid )
self . _peers [ _peerid ] [ ' STATUS ' ] [ ' KEEP_ALIVES_RECEIVED ' ] + = 1
2015-06-12 09:28:48 -04:00
self . _peers [ _peerid ] [ ' STATUS ' ] [ ' KEEP_ALIVE_RX_TIME ' ] = int ( time ( ) )
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Keep-Alive Reply (we sent the request) Received from Peer %s , %s : %s ' , self . _system , int_id ( _peerid ) , self . _peers [ _peerid ] [ ' IP ' ] , self . _peers [ _peerid ] [ ' PORT ' ] )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# SOMEONE HAS ANSWERED OUR REQEST TO REGISTER WITH THEM - KEEP TRACK OF IT
2014-09-05 14:57:47 -04:00
def peer_reg_reply ( self , _peerid ) :
if _peerid in self . _peers . keys ( ) :
self . _peers [ _peerid ] [ ' STATUS ' ] [ ' CONNECTED ' ] = True
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) Registration Reply From: %s , %s : %s ' , self . _system , int_id ( _peerid ) , self . _peers [ _peerid ] [ ' IP ' ] , self . _peers [ _peerid ] [ ' PORT ' ] )
2016-07-10 09:53:36 -04:00
2014-09-05 20:35:37 -04:00
# OUR MASTER HAS ANSWERED OUR KEEP-ALIVE REQUEST - KEEP TRACK OF IT
2014-09-05 14:57:47 -04:00
def master_alive_reply ( self , _peerid ) :
self . reset_keep_alive ( _peerid )
self . _master [ ' STATUS ' ] [ ' KEEP_ALIVES_RECEIVED ' ] + = 1
2015-06-12 09:28:48 -04:00
self . _master [ ' STATUS ' ] [ ' KEEP_ALIVE_RX_TIME ' ] = int ( time ( ) )
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Keep-Alive Reply (we sent the request) Received from the Master %s , %s : %s ' , self . _system , int_id ( _peerid ) , self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# OUR MASTER HAS SENT US A PEER LIST - PROCESS IT
2014-09-05 14:57:47 -04:00
def peer_list_reply ( self , _data , _peerid ) :
2016-12-18 22:51:13 -05:00
self . _master [ ' STATUS ' ] [ ' PEER_LIST ' ] = True
2014-09-05 14:57:47 -04:00
if len ( _data ) > 18 :
2016-12-18 22:51:13 -05:00
self . process_peer_list ( _data )
self . _logger . debug ( ' ( %s ) Peer List Reply Received From Master %s , %s : %s ' , self . _system , int_id ( _peerid ) , self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# OUR MASTER HAS ANSWERED OUR REQUEST TO REGISTER - LOTS OF INFORMATION TO TRACK
2014-09-05 14:57:47 -04:00
def master_reg_reply ( self , _data , _peerid ) :
_hex_mode = _data [ 5 ]
_hex_flags = _data [ 6 : 10 ]
_num_peers = _data [ 10 : 12 ]
_decoded_mode = process_mode_byte ( _hex_mode )
_decoded_flags = process_flags_bytes ( _hex_flags )
2016-12-15 14:08:41 -05:00
self . _local [ ' NUM_PEERS ' ] = int ( ahex ( _num_peers ) , 16 )
2014-09-05 14:57:47 -04:00
self . _master [ ' RADIO_ID ' ] = _peerid
self . _master [ ' MODE ' ] = _hex_mode
self . _master [ ' MODE_DECODE ' ] = _decoded_mode
self . _master [ ' FLAGS ' ] = _hex_flags
self . _master [ ' FLAGS_DECODE ' ] = _decoded_flags
self . _master_stat [ ' CONNECTED ' ] = True
self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] = 0
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) Registration response (we requested reg) from the Master: %s , %s : %s ( %s peers) ' , self . _system , int_id ( _peerid ) , self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] , self . _local [ ' NUM_PEERS ' ] )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# WE ARE MASTER AND SOMEONE HAS REQUESTED REGISTRATION FROM US - ANSWER IT
2014-09-05 14:57:47 -04:00
def master_reg_req ( self , _data , _peerid , _host , _port ) :
_ip_address = _host
_port = _port
_hex_mode = _data [ 5 ]
_hex_flags = _data [ 6 : 10 ]
_decoded_mode = process_mode_byte ( _hex_mode )
_decoded_flags = process_flags_bytes ( _hex_flags )
self . MASTER_REG_REPLY_PKT = ( MASTER_REG_REPLY + self . _local_id + self . TS_FLAGS + hex_str_2 ( self . _local [ ' NUM_PEERS ' ] ) + IPSC_VER )
2016-09-09 23:05:47 -04:00
self . send_packet ( self . MASTER_REG_REPLY_PKT , ( _host , _port ) )
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) Master Registration Packet Received from peer %s , %s : %s ' , self . _system , int_id ( _peerid ) , _host , _port )
2014-05-14 15:18:33 -04:00
2014-09-05 14:57:47 -04:00
# If this entry was NOT already in our list, add it.
if _peerid not in self . _peers . keys ( ) :
self . _peers [ _peerid ] = {
' IP ' : _ip_address ,
' PORT ' : _port ,
' MODE ' : _hex_mode ,
' MODE_DECODE ' : _decoded_mode ,
' FLAGS ' : _hex_flags ,
' FLAGS_DECODE ' : _decoded_flags ,
' STATUS ' : {
' CONNECTED ' : True ,
' KEEP_ALIVES_SENT ' : 0 ,
' KEEP_ALIVES_MISSED ' : 0 ,
' KEEP_ALIVES_OUTSTANDING ' : 0 ,
' KEEP_ALIVES_RECEIVED ' : 0 ,
2015-06-12 09:28:48 -04:00
' KEEP_ALIVE_RX_TIME ' : int ( time ( ) )
2014-09-05 14:57:47 -04:00
}
}
self . _local [ ' NUM_PEERS ' ] = len ( self . _peers )
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Peer Added To Peer List: %s , %s : %s (IPSC now has %s Peers) ' , self . _system , self . _peers [ _peerid ] , _host , _port , self . _local [ ' NUM_PEERS ' ] )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# WE ARE MASTER AND SOEMONE SENT US A KEEP-ALIVE - ANSWER IT, TRACK IT
2014-09-05 14:57:47 -04:00
def master_alive_req ( self , _peerid , _host , _port ) :
if _peerid in self . _peers . keys ( ) :
self . _peers [ _peerid ] [ ' STATUS ' ] [ ' KEEP_ALIVES_RECEIVED ' ] + = 1
2015-06-12 09:28:48 -04:00
self . _peers [ _peerid ] [ ' STATUS ' ] [ ' KEEP_ALIVE_RX_TIME ' ] = int ( time ( ) )
2016-09-09 23:05:47 -04:00
self . send_packet ( self . MASTER_ALIVE_REPLY_PKT , ( _host , _port ) )
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Master Keep-Alive Request Received from peer %s , %s : %s ' , self . _system , int_id ( _peerid ) , _host , _port )
2014-09-05 14:57:47 -04:00
else :
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) Master Keep-Alive Request Received from *UNREGISTERED* peer %s , %s : %s ' , self . _system , int_id ( _peerid ) , _host , _port )
2014-09-05 14:57:47 -04:00
2014-09-05 20:35:37 -04:00
# WE ARE MASTER AND A PEER HAS REQUESTED A PEER LIST - SEND THEM ONE
2014-09-05 14:57:47 -04:00
def peer_list_req ( self , _peerid ) :
if _peerid in self . _peers . keys ( ) :
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Peer List Request from peer %s ' , self . _system , int_id ( _peerid ) )
2016-09-09 23:05:47 -04:00
self . send_to_ipsc ( self . PEER_LIST_REPLY_PKT + build_peer_list ( self . _peers ) )
2014-09-05 14:57:47 -04:00
else :
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) Peer List Request Received from *UNREGISTERED* peer %s ' , self . _system , int_id ( _peerid ) )
2014-09-05 14:57:47 -04:00
2013-12-05 16:20:59 -05:00
# Reset the outstanding keep-alive counter for _peerid...
# Used when receiving acks OR when we see traffic from a repeater, since they ignore keep-alives when transmitting
#
def reset_keep_alive ( self , _peerid ) :
if _peerid in self . _peers . keys ( ) :
self . _peers [ _peerid ] [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] = 0
2015-06-12 09:28:48 -04:00
self . _peers [ _peerid ] [ ' STATUS ' ] [ ' KEEP_ALIVE_RX_TIME ' ] = int ( time ( ) )
2013-12-05 16:20:59 -05:00
if _peerid == self . _master [ ' RADIO_ID ' ] :
self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] = 0
2013-07-20 09:28:52 -04:00
2014-04-28 22:42:47 -04:00
2014-09-05 20:35:37 -04:00
# THE NEXT SECTION DEFINES FUNCTIONS THAT MUST BE DIFFERENT FOR HASHED AND UNHASHED PACKETS
# HASHED MEANS AUTHENTICATED IPSC
# UNHASHED MEANS UNAUTHENTICATED IPSC
# NEXT THREE FUNCITONS ARE FOR AUTHENTICATED PACKETS
2013-11-26 17:05:08 -05:00
# Take a packet to be SENT, calculate auth hash and return the whole thing
2013-09-10 16:28:18 -04:00
#
2016-09-09 23:05:47 -04:00
def hashed_packet ( self , _key , _data ) :
2016-12-15 14:08:41 -05:00
_hash = bhex ( ( hmac_new ( _key , _data , sha1 ) ) . hexdigest ( ) [ : 20 ] )
2013-11-26 17:05:08 -05:00
return _data + _hash
2013-09-10 16:28:18 -04:00
2013-10-30 14:36:45 -04:00
# Remove the hash from a packet and return the payload
#
2016-09-09 23:05:47 -04:00
def strip_hash ( self , _data ) :
2013-10-30 14:36:45 -04:00
return _data [ : - 10 ]
2013-09-10 16:28:18 -04:00
# Take a RECEIVED packet, calculate the auth hash and verify authenticity
#
2016-09-09 23:05:47 -04:00
def validate_auth ( self , _key , _data ) :
2013-10-30 14:36:45 -04:00
_payload = self . strip_hash ( _data )
2013-09-10 16:28:18 -04:00
_hash = _data [ - 10 : ]
2016-12-15 14:08:41 -05:00
_chk_hash = bhex ( ( hmac_new ( _key , _payload , sha1 ) ) . hexdigest ( ) [ : 20 ] )
2013-09-10 16:28:18 -04:00
if _chk_hash == _hash :
return True
else :
return False
2013-07-20 09:28:52 -04:00
2014-05-14 15:18:33 -04:00
#************************************************
# TIMED LOOP - CONNECTION MAINTENANCE
#************************************************
# Timed loop initialization (called by the twisted reactor)
#
def startProtocol ( self ) :
# Timed loops for:
# IPSC connection establishment and maintenance
# Reporting/Housekeeping
#
2014-09-05 20:35:37 -04:00
# IF WE'RE NOT THE MASTER...
2014-05-14 15:18:33 -04:00
if not self . _local [ ' MASTER_PEER ' ] :
2014-05-15 18:17:54 -04:00
self . _peer_maintenance = task . LoopingCall ( self . peer_maintenance_loop )
self . _peer_maintenance_loop = self . _peer_maintenance . start ( self . _local [ ' ALIVE_TIMER ' ] )
#
2014-09-05 20:35:37 -04:00
# IF WE ARE THE MASTER...
2014-05-15 18:17:54 -04:00
if self . _local [ ' MASTER_PEER ' ] :
self . _master_maintenance = task . LoopingCall ( self . master_maintenance_loop )
self . _master_maintenance_loop = self . _master_maintenance . start ( self . _local [ ' ALIVE_TIMER ' ] )
2015-06-07 16:46:29 -04:00
2014-05-15 18:17:54 -04:00
# Timed loop used for IPSC connection Maintenance when we are the MASTER
#
def master_maintenance_loop ( self ) :
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) MASTER Connection Maintenance Loop Started ' , self . _system )
2015-06-12 09:28:48 -04:00
update_time = int ( time ( ) )
2014-05-15 23:21:54 -04:00
2014-05-16 11:20:21 -04:00
for peer in self . _peers . keys ( ) :
keep_alive_delta = update_time - self . _peers [ peer ] [ ' STATUS ' ] [ ' KEEP_ALIVE_RX_TIME ' ]
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Time Since Last KeepAlive Request from Peer %s : %s seconds ' , self . _system , int_id ( peer ) , keep_alive_delta )
2014-05-16 10:02:45 -04:00
if keep_alive_delta > 120 :
2016-12-18 22:51:13 -05:00
self . de_register_peer ( peer )
2016-09-09 23:05:47 -04:00
self . send_to_ipsc ( self . PEER_LIST_REPLY_PKT + build_peer_list ( self . _peers ) )
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) Timeout Exceeded for Peer %s , De-registering ' , self . _system , int_id ( peer ) )
2014-05-16 10:02:45 -04:00
2014-05-15 18:17:54 -04:00
# Timed loop used for IPSC connection Maintenance when we are a PEER
2014-05-14 15:18:33 -04:00
#
2014-05-15 18:17:54 -04:00
def peer_maintenance_loop ( self ) :
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) PEER Connection Maintenance Loop Started ' , self . _system )
2013-12-13 13:07:18 -05:00
2013-08-30 17:23:12 -04:00
# If the master isn't connected, we have to do that before we can do anything else!
2013-10-21 21:54:51 -04:00
#
2013-11-26 17:05:08 -05:00
if not self . _master_stat [ ' CONNECTED ' ] :
2016-09-09 23:05:47 -04:00
self . send_packet ( self . MASTER_REG_REQ_PKT , self . _master_sock )
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) Registering with the Master: %s : %s ' , self . _system , self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] )
2013-07-20 09:28:52 -04:00
2013-08-30 17:23:12 -04:00
# Once the master is connected, we have to send keep-alives.. and make sure we get them back
2013-11-26 17:05:08 -05:00
elif self . _master_stat [ ' CONNECTED ' ] :
2013-08-30 17:23:12 -04:00
# Send keep-alive to the master
2016-09-09 23:05:47 -04:00
self . send_packet ( self . MASTER_ALIVE_PKT , self . _master_sock )
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Keep Alive Sent to the Master: %s , %s : %s ' , self . _system , int_id ( self . _master [ ' RADIO_ID ' ] ) , self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] )
2013-07-20 09:28:52 -04:00
2013-08-30 17:23:12 -04:00
# If we had a keep-alive outstanding by the time we send another, mark it missed.
2013-07-28 16:53:56 -04:00
if ( self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] ) > 0 :
self . _master_stat [ ' KEEP_ALIVES_MISSED ' ] + = 1
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) Master Keep-Alive Missed: %s : %s ' , self . _system , self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] )
2013-07-20 09:28:52 -04:00
2013-11-26 17:05:08 -05:00
# If we have missed too many keep-alives, de-register the master and start over.
2013-07-28 16:53:56 -04:00
if self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] > = self . _local [ ' MAX_MISSED ' ] :
self . _master_stat [ ' CONNECTED ' ] = False
2013-12-09 17:14:02 -05:00
self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] = 0
2016-12-18 22:51:13 -05:00
self . _logger . error ( ' ( %s ) Maximum Master Keep-Alives Missed -- De-registering the Master: %s : %s ' , self . _system , self . _master [ ' IP ' ] , self . _master [ ' PORT ' ] )
2013-08-30 17:23:12 -04:00
# Update our stats before we move on...
2013-08-01 16:09:20 -04:00
self . _master_stat [ ' KEEP_ALIVES_SENT ' ] + = 1
self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] + = 1
2013-07-20 09:28:52 -04:00
else :
2013-10-21 21:54:51 -04:00
# This is bad. If we get this message, we need to reset the state and try again
2016-12-18 22:51:13 -05:00
self . _logger . error ( ' ->> ( %s ) Master in UNKOWN STATE: %s : %s ' , self . _system , self . _master_sock )
2013-11-26 17:05:08 -05:00
self . _master_stat [ ' CONNECTED ' ] = False
2013-10-21 21:54:51 -04:00
2013-08-30 17:23:12 -04:00
2013-10-21 21:54:51 -04:00
# If the master is connected and we don't have a peer-list yet....
#
2013-11-26 17:05:08 -05:00
if ( self . _master_stat [ ' CONNECTED ' ] == True ) and ( self . _master_stat [ ' PEER_LIST ' ] == False ) :
2013-08-30 17:23:12 -04:00
# Ask the master for a peer-list
2014-05-14 15:18:33 -04:00
if self . _local [ ' NUM_PEERS ' ] :
2016-09-09 23:05:47 -04:00
self . send_packet ( self . PEER_LIST_REQ_PKT , self . _master_sock )
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ), No Peer List - Requesting One From the Master ' , self . _system )
2014-05-14 15:18:33 -04:00
else :
self . _master_stat [ ' PEER_LIST ' ] = True
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ), Skip asking for a Peer List, we are the only Peer ' , self . _system )
2013-07-20 09:28:52 -04:00
2013-10-21 21:54:51 -04:00
2013-10-28 23:39:45 -04:00
# If we do have a peer-list, we need to register with the peers and send keep-alives...
2013-10-21 21:54:51 -04:00
#
2013-11-26 17:05:08 -05:00
if self . _master_stat [ ' PEER_LIST ' ] :
2013-08-30 17:23:12 -04:00
# Iterate the list of peers... so we do this for each one.
2014-05-16 11:20:21 -04:00
for peer in self . _peers . keys ( ) :
2013-11-13 17:19:32 -05:00
2013-08-30 17:23:12 -04:00
# We will show up in the peer list, but shouldn't try to talk to ourselves.
2014-05-16 11:20:21 -04:00
if peer == self . _local_id :
2013-07-20 09:28:52 -04:00
continue
2013-11-13 17:19:32 -05:00
2013-08-30 17:23:12 -04:00
# If we haven't registered to a peer, send a registration
2014-05-16 11:20:21 -04:00
if not self . _peers [ peer ] [ ' STATUS ' ] [ ' CONNECTED ' ] :
2016-09-09 23:05:47 -04:00
self . send_packet ( self . PEER_REG_REQ_PKT , ( self . _peers [ peer ] [ ' IP ' ] , self . _peers [ peer ] [ ' PORT ' ] ) )
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) Registering with Peer %s , %s : %s ' , self . _system , int_id ( peer ) , self . _peers [ peer ] [ ' IP ' ] , self . _peers [ peer ] [ ' PORT ' ] )
2013-11-13 17:19:32 -05:00
2013-08-30 17:23:12 -04:00
# If we have registered with the peer, then send a keep-alive
2014-05-16 11:20:21 -04:00
elif self . _peers [ peer ] [ ' STATUS ' ] [ ' CONNECTED ' ] :
2016-09-09 23:05:47 -04:00
self . send_packet ( self . PEER_ALIVE_REQ_PKT , ( self . _peers [ peer ] [ ' IP ' ] , self . _peers [ peer ] [ ' PORT ' ] ) )
2016-12-18 22:51:13 -05:00
self . _logger . debug ( ' ( %s ) Keep-Alive Sent to the Peer %s , %s : %s ' , self . _system , int_id ( peer ) , self . _peers [ peer ] [ ' IP ' ] , self . _peers [ peer ] [ ' PORT ' ] )
2013-11-13 17:19:32 -05:00
2013-08-30 17:23:12 -04:00
# If we have a keep-alive outstanding by the time we send another, mark it missed.
2014-05-16 11:20:21 -04:00
if self . _peers [ peer ] [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] > 0 :
self . _peers [ peer ] [ ' STATUS ' ] [ ' KEEP_ALIVES_MISSED ' ] + = 1
2016-12-18 22:51:13 -05:00
self . _logger . info ( ' ( %s ) Peer Keep-Alive Missed for %s , %s : %s ' , self . _system , int_id ( peer ) , self . _peers [ peer ] [ ' IP ' ] , self . _peers [ peer ] [ ' PORT ' ] )
2013-11-13 17:19:32 -05:00
2013-08-30 17:23:12 -04:00
# If we have missed too many keep-alives, de-register the peer and start over.
2014-05-16 11:20:21 -04:00
if self . _peers [ peer ] [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] > = self . _local [ ' MAX_MISSED ' ] :
self . _peers [ peer ] [ ' STATUS ' ] [ ' CONNECTED ' ] = False
2013-12-13 13:07:18 -05:00
#del peer # Becuase once it's out of the dictionary, you can't use it for anything else.
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) Maximum Peer Keep-Alives Missed -- De-registering the Peer: %s , %s : %s ' , self . _system , int_id ( peer ) , self . _peers [ peer ] [ ' IP ' ] , self . _peers [ peer ] [ ' PORT ' ] )
2013-08-01 16:09:20 -04:00
2013-08-30 17:23:12 -04:00
# Update our stats before moving on...
2014-05-16 11:20:21 -04:00
self . _peers [ peer ] [ ' STATUS ' ] [ ' KEEP_ALIVES_SENT ' ] + = 1
self . _peers [ peer ] [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] + = 1
2013-07-20 09:28:52 -04:00
2014-05-14 15:18:33 -04:00
#************************************************
# MESSAGE RECEIVED - TAKE ACTION
#************************************************
2013-07-20 09:28:52 -04:00
2013-11-26 17:05:08 -05:00
# Actions for received packets by type: For every packet received, there are some things that we need to do:
2013-08-30 17:23:12 -04:00
# Decode some of the info
# Check for auth and authenticate the packet
# Strip the hash from the end... we don't need it anymore
#
2013-11-26 17:05:08 -05:00
# Once they're done, we move on to the processing or callbacks for each packet type.
2013-07-20 09:28:52 -04:00
#
2014-05-14 15:18:33 -04:00
# Callbacks are iterated in the order of "more likely" to "less likely" to reduce processing time
#
2013-06-27 17:15:54 -04:00
def datagramReceived ( self , data , ( host , port ) ) :
2013-07-15 13:04:48 -04:00
_packettype = data [ 0 : 1 ]
_peerid = data [ 1 : 5 ]
2014-08-13 18:14:46 -04:00
_ipsc_seq = data [ 5 : 6 ]
2016-07-10 09:53:36 -04:00
2014-05-14 15:18:33 -04:00
# AUTHENTICATE THE PACKET
2016-09-09 23:51:11 -04:00
if self . _local [ ' AUTH_ENABLED ' ] :
if not self . validate_auth ( self . _local [ ' AUTH_KEY ' ] , data ) :
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) AuthError: IPSC packet failed authentication. Type %s : Peer: %s , %s : %s ' , self . _system , ahex ( _packettype ) , int_id ( _peerid ) , host , port )
2016-09-09 23:51:11 -04:00
return
2013-10-21 21:54:51 -04:00
2016-09-09 23:51:11 -04:00
# REMOVE SHA-1 AUTHENTICATION HASH: WE NO LONGER NEED IT
else :
data = self . strip_hash ( data )
2013-06-29 00:36:39 -04:00
2014-05-14 15:18:33 -04:00
# PACKETS THAT WE RECEIVE FROM ANY VALID PEER OR VALID MASTER
2013-11-26 17:05:08 -05:00
if _packettype in ANY_PEER_REQUIRED :
2016-12-21 20:59:16 -05:00
if not ( self . valid_master ( _peerid ) == False or self . valid_peer ( _peerid ) == False ) :
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) PeerError: Peer not in peer-list: %s , %s : %s ' , self . _system , int_id ( _peerid ) , host , port )
2013-07-31 21:46:03 -04:00
return
2013-10-21 21:54:51 -04:00
2014-05-14 15:18:33 -04:00
# ORIGINATED BY SUBSCRIBER UNITS - a.k.a someone transmitted
2013-11-26 17:05:08 -05:00
if _packettype in USER_PACKETS :
2014-08-13 18:14:46 -04:00
# Extract IPSC header not already extracted
2013-10-21 21:54:51 -04:00
_src_sub = data [ 6 : 9 ]
_dst_sub = data [ 9 : 12 ]
2014-08-13 18:14:46 -04:00
_call_type = data [ 12 : 13 ]
_unknown_1 = data [ 13 : 17 ]
_call_info = int_id ( data [ 17 : 18 ] )
2016-12-18 22:51:13 -05:00
_ts = bool ( _call_info & TS_CALL_MSK ) + 1
2014-08-13 18:14:46 -04:00
_end = bool ( _call_info & END_MSK )
2015-03-24 12:06:42 -04:00
# Extract RTP Header Fields
2014-08-13 18:14:46 -04:00
'''
Coming soon kids ! ! !
Looks like version , padding , extention , CSIC , payload type and SSID never change .
The things we might care about are below .
_rtp_byte_1 = int_id ( data [ 18 : 19 ] )
_rtp_byte_2 = int_id ( data [ 19 : 20 ] )
_rtp_seq = int_id ( data [ 20 : 22 ] )
_rtp_tmstmp = int_id ( data [ 22 : 26 ] )
2015-03-24 12:06:42 -04:00
_rtp_ssid = int_id ( data [ 26 : 30 ] )
# Extract RTP Payload Data Fields
_payload_type = int_id ( data [ 30 : 31 ] )
2014-08-13 18:14:46 -04:00
'''
2013-10-21 21:54:51 -04:00
# User Voice and Data Call Types:
2013-11-26 17:05:08 -05:00
if _packettype == GROUP_VOICE :
2013-12-05 16:20:59 -05:00
self . reset_keep_alive ( _peerid )
2016-12-18 22:51:13 -05:00
self . group_voice ( _src_sub , _dst_sub , _ts , _end , _peerid , data )
2013-10-21 21:54:51 -04:00
return
2013-11-26 17:05:08 -05:00
elif _packettype == PVT_VOICE :
2013-12-05 16:20:59 -05:00
self . reset_keep_alive ( _peerid )
2016-12-18 22:51:13 -05:00
self . private_voice ( _src_sub , _dst_sub , _ts , _end , _peerid , data )
2013-10-21 21:54:51 -04:00
return
2013-11-26 17:05:08 -05:00
elif _packettype == GROUP_DATA :
2013-12-09 17:14:02 -05:00
self . reset_keep_alive ( _peerid )
2016-12-18 22:51:13 -05:00
self . group_data ( _src_sub , _dst_sub , _ts , _end , _peerid , data )
2013-10-21 21:54:51 -04:00
return
2013-11-26 17:05:08 -05:00
elif _packettype == PVT_DATA :
2013-12-09 17:14:02 -05:00
self . reset_keep_alive ( _peerid )
2016-12-18 22:51:13 -05:00
self . private_data ( _src_sub , _dst_sub , _ts , _end , _peerid , data )
2013-10-21 21:54:51 -04:00
return
return
2016-12-15 13:12:56 -05:00
2014-05-14 15:18:33 -04:00
# MOTOROLA XCMP/XNL CONTROL PROTOCOL: We don't process these (yet)
2013-11-26 17:05:08 -05:00
elif _packettype == XCMP_XNL :
2016-12-18 22:51:13 -05:00
self . xcmp_xnl ( data )
2013-10-21 21:54:51 -04:00
return
2016-12-15 13:12:56 -05:00
2014-05-14 15:18:33 -04:00
# ORIGINATED BY PEERS, NOT IPSC MAINTENANCE: Call monitoring is all we've found here so far
2014-05-18 16:30:11 -04:00
elif _packettype == CALL_MON_STATUS :
2016-12-18 22:51:13 -05:00
self . call_mon_status ( data )
2013-10-21 21:54:51 -04:00
return
2013-12-01 20:24:50 -05:00
elif _packettype == CALL_MON_RPT :
2016-12-18 22:51:13 -05:00
self . call_mon_rpt ( data )
2013-10-21 21:54:51 -04:00
return
2013-12-01 20:24:50 -05:00
elif _packettype == CALL_MON_NACK :
2016-12-18 22:51:13 -05:00
self . call_mon_nack ( data )
2013-10-21 21:54:51 -04:00
return
2016-12-15 13:12:56 -05:00
2014-05-14 15:18:33 -04:00
# IPSC CONNECTION MAINTENANCE MESSAGES
2013-11-26 17:05:08 -05:00
elif _packettype == DE_REG_REQ :
2016-12-18 22:51:13 -05:00
self . de_register_peer ( _peerid )
self . _logger . warning ( ' ( %s ) Peer De-Registration Request From: %s , %s : %s ' , self . _system , int_id ( _peerid ) , host , port )
2013-10-21 21:54:51 -04:00
return
2013-11-26 17:05:08 -05:00
elif _packettype == DE_REG_REPLY :
2016-12-18 22:51:13 -05:00
self . _logger . warning ( ' ( %s ) Peer De-Registration Reply From: %s , %s : %s ' , self . _system , int_id ( _peerid ) , host , port )
2013-10-21 21:54:51 -04:00
return
2013-11-26 17:05:08 -05:00
elif _packettype == RPT_WAKE_UP :
2016-12-18 22:51:13 -05:00
self . repeater_wake_up ( data )
self . _logger . debug ( ' ( %s ) Repeater Wake-Up Packet From: %s , %s : %s ' , self . _system , int_id ( _peerid ) , host , port )
2013-10-21 21:54:51 -04:00
return
return
2013-09-10 16:28:18 -04:00
2016-12-15 13:12:56 -05:00
2014-05-14 15:18:33 -04:00
# THE FOLLOWING PACKETS ARE RECEIVED ONLY IF WE ARE OPERATING AS A PEER
# ONLY ACCEPT FROM A PREVIOUSLY VALIDATED PEER
2013-11-26 17:05:08 -05:00
if _packettype in PEER_REQUIRED :
2016-12-18 22:51:13 -05:00
if not self . valid_peer ( _peerid ) :
self . _logger . warning ( ' ( %s ) PeerError: Peer not in peer-list: %s , %s : %s ' , self . _system , int_id ( _peerid ) , host , port )
2013-07-30 12:50:29 -04:00
return
2013-10-21 21:54:51 -04:00
2014-05-14 15:18:33 -04:00
# REQUESTS FROM PEERS: WE MUST REPLY IMMEDIATELY FOR IPSC MAINTENANCE
2013-11-26 17:05:08 -05:00
if _packettype == PEER_ALIVE_REQ :
2014-09-05 14:57:47 -04:00
self . peer_alive_req ( data , _peerid , host , port )
2013-07-30 12:50:29 -04:00
return
2013-10-21 21:54:51 -04:00
2013-11-26 17:05:08 -05:00
elif _packettype == PEER_REG_REQ :
2014-09-05 14:57:47 -04:00
self . peer_reg_req ( _peerid , host , port )
2013-10-14 16:09:16 -04:00
return
2013-10-21 21:54:51 -04:00
2014-05-14 15:18:33 -04:00
# ANSWERS FROM REQUESTS WE SENT TO PEERS: WE DO NOT REPLY
2013-11-26 17:05:08 -05:00
elif _packettype == PEER_ALIVE_REPLY :
2014-09-05 14:57:47 -04:00
self . peer_alive_reply ( _peerid )
2013-11-11 15:38:27 -05:00
return
2013-10-21 21:54:51 -04:00
2013-11-26 17:05:08 -05:00
elif _packettype == PEER_REG_REPLY :
2014-09-05 14:57:47 -04:00
self . peer_reg_reply ( _peerid )
2013-10-21 21:54:51 -04:00
return
return
2014-05-14 15:18:33 -04:00
2013-09-10 16:28:18 -04:00
2014-05-14 15:18:33 -04:00
# PACKETS ONLY ACCEPTED FROM OUR MASTER
2016-12-15 13:12:56 -05:00
2014-05-14 15:18:33 -04:00
# PACKETS WE ONLY ACCEPT IF WE HAVE FINISHED REGISTERING WITH OUR MASTER
2013-11-26 17:05:08 -05:00
if _packettype in MASTER_REQUIRED :
2016-12-18 22:51:13 -05:00
if not self . valid_master ( _peerid ) :
self . _logger . warning ( ' ( %s ) MasterError: %s , %s : %s is not the master peer ' , self . _system , int_id ( _peerid ) , host , port )
2013-10-14 16:09:16 -04:00
return
2014-05-14 15:18:33 -04:00
# ANSWERS FROM REQUESTS WE SENT TO THE MASTER: WE DO NOT REPLY
2013-11-26 17:05:08 -05:00
if _packettype == MASTER_ALIVE_REPLY :
2014-09-05 14:57:47 -04:00
self . master_alive_reply ( _peerid )
2013-10-14 16:09:16 -04:00
return
2013-08-08 22:48:28 -04:00
2013-11-26 17:05:08 -05:00
elif _packettype == PEER_LIST_REPLY :
2014-09-05 14:57:47 -04:00
self . peer_list_reply ( data , _peerid )
2013-10-14 16:09:16 -04:00
return
2013-10-21 21:54:51 -04:00
return
2014-05-14 15:18:33 -04:00
# THIS MEANS WE HAVE SUCCESSFULLY REGISTERED TO OUR MASTER - RECORD MASTER INFORMATION
2013-11-26 17:05:08 -05:00
elif _packettype == MASTER_REG_REPLY :
2014-09-05 14:57:47 -04:00
self . master_reg_reply ( data , _peerid )
2013-10-21 21:54:51 -04:00
return
2014-05-14 15:18:33 -04:00
2016-12-15 13:12:56 -05:00
# THE FOLLOWING PACKETS ARE RECEIVED ONLLY IF WE ARE OPERATING AS A MASTER
2014-05-14 15:18:33 -04:00
# REQUESTS FROM PEERS: WE MUST REPLY IMMEDIATELY FOR IPSC MAINTENANCE
# REQUEST TO REGISTER TO THE IPSC
2013-11-26 17:05:08 -05:00
elif _packettype == MASTER_REG_REQ :
2014-09-05 15:13:18 -04:00
self . master_reg_req ( data , _peerid , host , port )
2014-05-12 22:18:04 -04:00
return
2014-05-14 15:18:33 -04:00
# REQUEST FOR A KEEP-ALIVE REPLY (WE KNOW THE PEER IS STILL ALIVE TOO)
2014-05-12 22:18:04 -04:00
elif _packettype == MASTER_ALIVE_REQ :
2014-09-05 15:10:51 -04:00
self . master_alive_req ( _peerid , host , port )
2014-05-12 22:18:04 -04:00
return
2014-05-14 15:18:33 -04:00
# REQUEST FOR A PEER LIST
2014-05-12 22:18:04 -04:00
elif _packettype == PEER_LIST_REQ :
2014-09-05 15:10:51 -04:00
self . peer_list_req ( _peerid )
2014-05-12 22:18:04 -04:00
return
2016-12-15 16:04:45 -05:00
2014-05-14 15:18:33 -04:00
# PACKET IS OF AN UNKNOWN TYPE. LOG IT AND IDENTTIFY IT!
2013-06-27 17:15:54 -04:00
else :
2016-12-15 16:04:45 -05:00
self . unknown_message ( self . _system , _packettype , _peerid , data )
2013-10-21 21:54:51 -04:00
return
2013-06-27 17:15:54 -04:00
2013-09-10 16:28:18 -04:00
2013-07-20 09:28:52 -04:00
2013-07-11 20:45:09 -04:00
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
#************************************************
2013-06-27 17:15:54 -04:00
if __name__ == ' __main__ ' :
2016-12-15 14:08:41 -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 = ' CFG_FILE ' , help = ' /full/path/to/config.file (usually dmrlink.cfg) ' )
cli_args = parser . parse_args ( )
if not cli_args . CFG_FILE :
cli_args . CFG_FILE = os . path . dirname ( os . path . abspath ( __file__ ) ) + ' /dmrlink.cfg '
# Call the external routine to build the configuration dictionary
CONFIG = build_config ( cli_args . CFG_FILE )
# Call the external routing to start the system logger
logger = config_logging ( CONFIG [ ' LOGGER ' ] )
2016-12-15 13:12:56 -05:00
config_reports ( CONFIG )
2016-12-15 16:04:45 -05:00
2015-03-24 12:06:42 -04:00
logger . info ( ' DMRlink \' dmrlink.py \' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING... ' )
2014-05-17 14:18:45 -04:00
2016-12-15 14:08:41 -05:00
# Shut ourselves down gracefully with the IPSC peers.
2016-12-15 16:04:45 -05:00
def sig_handler ( _signal , _frame ) :
2016-12-15 14:08:41 -05:00
logger . info ( ' *** DMRLINK IS TERMINATING WITH SIGNAL %s *** ' , str ( _signal ) )
for system in systems :
this_ipsc = systems [ system ]
logger . info ( ' De-Registering from IPSC %s ' , system )
de_reg_req_pkt = this_ipsc . hashed_packet ( this_ipsc . _local [ ' AUTH_KEY ' ] , this_ipsc . DE_REG_REQ_PKT )
this_ipsc . send_to_ipsc ( de_reg_req_pkt )
reactor . stop ( )
# Set signal handers so that we can gracefully exit if need be
for sig in [ signal . SIGTERM , signal . SIGINT , signal . SIGQUIT ] :
2016-12-15 16:04:45 -05:00
signal . signal ( sig , sig_handler )
2016-12-15 14:08:41 -05:00
2015-06-12 09:28:48 -04:00
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
2016-12-15 16:04:45 -05:00
for system in CONFIG [ ' SYSTEMS ' ] :
if CONFIG [ ' SYSTEMS ' ] [ system ] [ ' LOCAL ' ] [ ' ENABLED ' ] :
systems [ system ] = IPSC ( system , CONFIG , logger )
reactor . listenUDP ( CONFIG [ ' SYSTEMS ' ] [ system ] [ ' LOCAL ' ] [ ' PORT ' ] , systems [ system ] , interface = CONFIG [ ' SYSTEMS ' ] [ system ] [ ' LOCAL ' ] [ ' IP ' ] )
2015-06-12 09:28:48 -04:00
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
2016-11-23 17:37:37 -05:00
if CONFIG [ ' REPORTS ' ] [ ' REPORT_NETWORKS ' ] :
2016-12-18 22:51:13 -05:00
reporting_loop = config_reports ( CONFIG )
reporting = task . LoopingCall ( reporting_loop , logger )
2016-11-23 17:37:37 -05:00
reporting . start ( CONFIG [ ' REPORTS ' ] [ ' REPORT_INTERVAL ' ] )
2015-06-12 09:28:48 -04:00
2016-08-31 15:02:07 -04:00
reactor . run ( )