diff --git a/ipsc.py b/ipsc.py index 5328689..3ba7b37 100644 --- a/ipsc.py +++ b/ipsc.py @@ -17,24 +17,28 @@ import socket #************************************************ # Import system logger configuration +# try: from ipsc_logger import logger except ImportError: sys.exit('System logger configuraiton not found or invalid') # Import configuration and informational data structures +# try: from my_ipsc_config import NETWORK except ImportError: sys.exit('Configuration file not found, or not valid formatting') # Import IPSC message types and version information +# try: from ipsc_message_types import * except ImportError: sys.exit('IPSC message types file not found or invalid') # Import IPSC flag mask values +# try: from ipsc_mask import * except ImportError: @@ -46,13 +50,22 @@ except ImportError: # GLOBALLY SCOPED FUNCTIONS #************************************************ + +# Take a packet to be SENT, calcualte auth hash and return the whole thing +# def hashed_packet(_key, _data): hash = binascii.unhexlify((hmac.new(_key,_data,hashlib.sha1)).hexdigest()[:20]) return (_data + hash) - + + +# Take a RECEIVED packet, calculate the auth hash and verify authenticity +# def validate_auth(_key, _data): return +# Decide the Mode bit flags and print them - later, use this for more +# than just informational purposes, for now, it's FYI/Debug info. +# def print_mode_decode(_mode): _mode = int(binascii.b2a_hex(_mode), 16) link_op = _mode & PEER_OP_MSK @@ -81,64 +94,42 @@ def print_mode_decode(_mode): if ts2 == 0b00000010: logger.info('\t\tIPSC Enabled on TS2') - -def mode_decode(_mode): - _mode = int(binascii.b2a_hex(_mode), 16) - link_op = _mode & PEER_OP_MSK - link_mode = _mode & PEER_MODE_MSK - ts1 = _mode & IPSC_TS1_MSK - ts2 = _mode & IPSC_TS2_MSK - - if link_op == 0b01000000: - logger.info('\t\tPeer Operational') - elif link_op == 0b00000000: - logger.info('\t\tPeer Not Operational') - else: - logger.warning('\t\tPeer Mode Invalid') - - if link_mode == 0b00000000: - logger.info('\t\tNo RF Interface') - elif link_mode == 0b00010000: - logger.info('\t\tRadio in Analog Mode') - elif link_mode == 0b00100000: - logger.info('\t\tRadio in Digital Mode') - else: - logger.warning('\t\tRadio Mode Invalid') - - if ts1 == 0b00001000: - logger.info('\t\tIPSC Enabled on TS1') - - if ts2 == 0b00000010: - logger.info('\t\tIPSC Enabled on TS2') - -def print_peer_list(_ipsc_network): - logger.info('\t%s', _ipsc_network['LOCAL']['DESCRIPTION']) - for dictionary in _ipsc_network['PEERS']: - address = dictionary['IP'] - port = dictionary['PORT'] - radio_id = int(binascii.b2a_hex(dictionary['RADIO_ID']), 16) - mode = dictionary['RAW_MODE'] - int_connected = dictionary['STATUS']['CONNECTED'] - int_missed = dictionary['STATUS']['KEEP_ALIVES_MISSED'] - logger.info('\tIP Address: %s:%s', address, port) - logger.info('\tRADIO ID: %s ', radio_id) + +# Gratuituous print-out of the peer list.. Pretty much debug stuff. +# +def print_peer_list(_network_name): + logger.info('\t%s', _network_name) + for dictionary in NETWORK[_network_name]['PEERS']: + logger.info('\tIP Address: %s:%s', dictionary['IP'], dictionary['PORT']) + logger.info('\tRADIO ID: %s ', int(binascii.b2a_hex(dictionary['RADIO_ID']), 16)) logger.info("\tIPSC Mode:") - print_mode_decode(mode) - logger.info('\tConnection Status: %s', int_connected) - logger.info('\tKeepAlives Missed: %s', int_missed) + print_mode_decode(dictionary['RAW_MODE']) + logger.info('\tConnection Status: %s', dictionary['STATUS']['CONNECTED']) + logger.info('\tKeepAlives Missed: %s', dictionary['STATUS']['KEEP_ALIVES_MISSED']) logger.info("") + #************************************************ -# IPSC Network Engine +#******** *********** +#******** IPSC Network "Engine" *********** +#******** *********** +#************************************************ + +#************************************************ +# INITIAL SETUP of IPSC INSTANCE #************************************************ class IPSC(DatagramProtocol): + # Modify the initializer to set up our environment and build the packets + # we need to maitain connections + # def __init__(self, *args, **kwargs): if len(args) == 1: - self._config = args[0] + self._network_name = args[0] + self._config = NETWORK[self._network_name] args = () self.TS_FLAGS = (self._config['LOCAL']['MODE'] + self._config['LOCAL']['FLAGS']) self.MASTER_REG_REQ_PKT = (MASTER_REG_REQ + self._config['LOCAL']['RADIO_ID'] + self.TS_FLAGS + IPSC_VER) @@ -147,58 +138,37 @@ class IPSC(DatagramProtocol): self.PEER_REG_REQ_PKT = (PEER_REG_REQ + self._config['LOCAL']['RADIO_ID'] + IPSC_VER) self.PEER_REG_REPLY_PKT = (PEER_REG_REPLY + self._config['LOCAL']['RADIO_ID'] + IPSC_VER) self.PEER_ALIVE_REQ_PKT = (PEER_ALIVE_REQ + self._config['LOCAL']['RADIO_ID'] + self.TS_FLAGS) - self.PEER_ALIVE_REPLY_PKT = (PEER_ALIVE_REPLY + self._config['LOCAL']['RADIO_ID'] + self.TS_FLAGS) + self.PEER_ALIVE_REPLY_PKT = (PEER_ALIVE_REPLY + self._config['LOCAL']['RADIO_ID'] + self.TS_FLAGS) + self._peer_list_new = False else: logger.error("Unexpected arguments found.") - - def timed_loop(self): - _master_connected = self._config['MASTER']['STATUS']['CONNECTED'] - _master_alives_missed = self._config['MASTER']['STATUS']['KEEP_ALIVES_MISSED'] - - if (_master_connected == 0): - reg_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.MASTER_REG_REQ_PKT) - self.transport.write(reg_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT'])) - logger.info("->> Master Registration Request To:%s:%s From:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT'], binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])) - - elif (_master_connected in (1,2)): - if (_master_connected == 1): - peer_list_req_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_LIST_REQ_PKT) - self.transport.write(peer_list_req_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT'])) - logger.info("->> List Reqested from Master:%s:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT']) - - master_alive_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.MASTER_ALIVE_PKT) - self.transport.write(master_alive_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT'])) - logger.info("->> Master Keep-alive Sent To:%s:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT']) - - else: - logger.error("->> Master in UNKOWN STATE:%s:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT']) - - for peer in (self._config['PEERS']): - if (peer['RADIO_ID'] == binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])): - continue - if peer['STATUS']['CONNECTED'] == 0: - peer_reg_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_REG_REQ_PKT) - self.transport.write(peer_reg_packet, (peer['IP'], peer['PORT'])) - logger.info('->> Peer Registration Request To:%s:%s From:%s', peer['IP'], peer['PORT'], binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])) - elif peer['STATUS']['CONNECTED'] == 1: - peer_alive_req_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_ALIVE_REQ_PKT) - self.transport.write(peer_reg_packet, (peer['IP'], peer['PORT'])) - logger.info('->> Peer Keep-Alive Request To:%s:%s From:%s', peer['IP'], peer['PORT'], binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])) - - + # This is called by REACTOR when it starts, We use it to set up the timed + # loop for each instance of the IPSC engine + # def startProtocol(self): #logger.debug("*** config: %s", self._config) #logger.info("") - self._call = task.LoopingCall(self.timed_loop) self._loop = self._call.start(self._config['LOCAL']['ALIVE_TIMER']) + + +#************************************************ +# FUNCTIONS FOR IPSC Network Engine +#************************************************ + + # Process a received peer list: + # Flag we have a list + # Flag the list is new (needed elsewhere) + # Populate the peer information from the list + # def peer_list_received(self, _data, (_host, _port)): - logger.info("<<- The Peer List has been Received from Master:%s:%s Setting Condition 2", _host, _port) + self._config['MASTER']['STATUS']['PEER-LIST'] = True + self._peer_list_new = True + logger.info("<<- The Peer List has been Received from Master:%s:%s ", _host, _port) _num_peers = int(str(int(binascii.b2a_hex(_data[5:7]), 16))[1:]) self._config['LOCAL']['NUM_PEERS'] = _num_peers - self._config['MASTER']['STATUS']['CONNECTED'] = 2 logger.info(' There are %s peers in this IPSC Network', _num_peers) del self._config['PEERS'][:] for i in range(7, (_num_peers*11)+7, 11): @@ -208,12 +178,78 @@ class IPSC(DatagramProtocol): 'IP': socket.inet_ntoa(hex_address), 'PORT': int(binascii.b2a_hex(_data[i+8:i+10]), 16), 'RAW_MODE': _data[i+10:i+11], - 'MODE': mode_decode(_data[i+10:i+11]), 'STATUS': {'CONNECTED': 0, 'KEEP_ALIVES_MISSED': 0} }) - print_peer_list(self._config) + print_peer_list(self._network_name) + + + +#************************************************ +# TIMED LOOP - MY CONNECTION MAINTENANCE +#************************************************ + + def timed_loop(self): + print('timed loop started') # temporary debugging to make sure this part runs + _master_connected = self._config['MASTER']['STATUS']['CONNECTED'] + _master_alives_missed = self._config['MASTER']['STATUS']['KEEP_ALIVES_MISSED'] + _peer_list_rx = self._config['MASTER']['STATUS']['PEER-LIST'] + + if (_master_connected == False): + reg_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.MASTER_REG_REQ_PKT) + self.transport.write(reg_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT'])) + logger.info("->> Master Registration Request To:%s:%s From:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT'], binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])) + + elif (_master_connected == True): + master_alive_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.MASTER_ALIVE_PKT) + self.transport.write(master_alive_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT'])) + logger.info("->> Master Keep-alive Sent To:%s:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT']) + self._config['MASTER']['STATUS']['KEEP_ALIVES_SENT'] += 1 + + if (self._config['MASTER']['STATUS']['KEEP_ALIVES_OUTSTANDING']) > 0: + self._config['MASTER']['STATUS']['KEEP_ALIVES_MISSED'] += 1 + + if self._config['MASTER']['STATUS']['KEEP_ALIVES_OUTSTANDING'] >= self._config['LOCAL']['MAX_MISSED']: + pass + self._config['MASTER']['STATUS']['CONNECTED'] = False + logger.error('Maximum Master Keep-Alives Missed -- De-registering the Master') + + else: + logger.error("->> Master in UNKOWN STATE:%s:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT']) + + if ((_master_connected == True) and (_peer_list_rx == False)): + peer_list_req_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_LIST_REQ_PKT) + self.transport.write(peer_list_req_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT'])) + logger.info("->> List Reqested from Master:%s:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT']) + +# Logic problems in the next if.... bad ones. Fix them. + if (self._peer_list_new == True): + self._peer_list_new = False + logger.info('*** NEW PEER LIST RECEIEVED - PROCESSING!') + for peer in (self._config['PEERS']): + if (peer['RADIO_ID'] == binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])): + continue + if peer['STATUS']['CONNECTED'] == 0: + peer_reg_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_REG_REQ_PKT) + self.transport.write(peer_reg_packet, (peer['IP'], peer['PORT'])) + logger.info('->> Peer Registration Request To:%s:%s From:%s', peer['IP'], peer['PORT'], binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])) + elif peer['STATUS']['CONNECTED'] == 1: + peer_alive_req_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_ALIVE_REQ_PKT) + self.transport.write(peer_reg_packet, (peer['IP'], peer['PORT'])) + logger.info('->> Peer Keep-Alive Request To:%s:%s From:%s', peer['IP'], peer['PORT'], binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])) + + print('timed loop finished') # temporary debugging to make sure this part runs + + +#************************************************ +# RECEIVED DATAGRAM - ACT IMMEDIATELY!!! +#************************************************ + + # Work in progress -- at the very least, notify we have the packet. Ultimately + # call a function or process immediately if only a few actions + # def datagramReceived(self, data, (host, port)): + print('datagram received') # temporary debugging to make sure this part runs dest_ip = self._config['MASTER']['IP'] dest_port = self._config['MASTER']['PORT'] #logger.info("received %r from %s:%d", binascii.b2a_hex(data), host, port) @@ -223,38 +259,37 @@ class IPSC(DatagramProtocol): if (_packettype == PEER_ALIVE_REQ): logger.info("<<- Peer Keep-alive Request From Peer ID %s at:%s:%s", int(binascii.b2a_hex(_peerid), 16), host, port) - peer_alive_req_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_ALIVE_REQ_PKT) peer_alive_reply_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_ALIVE_REPLY_PKT) self.transport.write(peer_alive_reply_packet, (host, port)) logger.info("->> Peer Keep-alive Reply sent To:%s:%s", host, port) - self.transport.write(peer_alive_req_packet, (host, port)) - logger.info("->> Peer Keep-alive Request sent To:%s:%s", host, port) elif (_packettype == MASTER_ALIVE_REPLY): logger.info("<<- Master Keep-alive Reply From:%s:%s", host, port) elif (_packettype == PEER_ALIVE_REPLY): + self.config['MASTER']['STATUS']['KEEP_ALIVES_OUTSTANDING'] = 0 logger.info("<<- Peer Keep-alive Reply From:%s:%s", host, port) elif (_packettype == MASTER_REG_REQ): logger.info("<<- Registration Packet Recieved") elif (_packettype == MASTER_REG_REPLY): - self._config['MASTER']['STATUS']['CONNECTED'] = 1 - logger.info("<<- Master Registration Reply From:%s:%s Setting Condition %s", host, port,self._config['MASTER']['STATUS']['CONNECTED']) + self._config['MASTER']['STATUS']['CONNECTED'] = True + self._config['MASTER']['STATUS']['KEEP_ALIVES_OUTSTANDING'] = 0 + logger.info("<<- Master Registration Reply From:%s:%s ", host, port) elif (_packettype == PEER_REG_REQ): logger.info("<<- Peer Registration Request From Peer ID %s at:%s:%s", int(binascii.b2a_hex(_peerid), 16), host, port) peer_reg_reply_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_REG_REPLY_PKT) self.transport.write(peer_reg_reply_packet, (host, port)) - logger.info("->> Peer Registration Reply Sent To:%s:%s", host, port) + logger.warning("->> Peer Registration Reply Sent To:%s:%s", host, port) elif (_packettype == PEER_REG_REPLY): - logger.info('<<- Peer Registration Reply From: %s', int(binascii.b2a_hex(_peerid), 16)) + logger.warning('<<- Peer Registration Reply From: %s', int(binascii.b2a_hex(_peerid), 16)) elif (_packettype == XCMP_XNL): - logger.warning("<<- XCMP_XNL From:%s:%s, but we did not indicate XCMP capable!", host, port) + logger.info("<<- XCMP_XNL From:%s:%s, but we did not indicate XCMP capable!", host, port) elif (_packettype == PEER_LIST_REPLY): self.peer_list_received(data, (host, port)) @@ -272,10 +307,10 @@ class IPSC(DatagramProtocol): logger.info("<<- Private Data Packet From From:%s:%s", host, port) elif (_packettype == RPT_WAKE_UP): - logger.info("<<- Repeater Wake-Up Packet From:%s:%s", host, port) + logger.warning("<<- Repeater Wake-Up Packet From:%s:%s", host, port) elif (_packettype == DE_REG_REQ): - logger.info("<<- Peer De-Registration Request From:%s:%s", host, port) + logger.warning("<<- Peer De-Registration Request From:%s:%s", host, port) elif (_packettype == DE_REG_REPLY): logger.info("<<- Peer De-Registration Reply From:%s:%s", host, port) @@ -287,6 +322,8 @@ class IPSC(DatagramProtocol): packet_type = binascii.b2a_hex(_packettype) logger.error("<<- Received Unprocessed Type %s From:%s:%s", packet_type, host, port) + + #************************************************ # MAIN PROGRAM LOOP STARTS HERE #************************************************ @@ -294,5 +331,5 @@ class IPSC(DatagramProtocol): if __name__ == '__main__': logger.info('SYSTEM STARTING UP') for ipsc_network in NETWORK: - reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], IPSC(NETWORK[ipsc_network])) + reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], IPSC(ipsc_network)) reactor.run() \ No newline at end of file diff --git a/my_ipsc_config_SAMPLE.py b/my_ipsc_config_SAMPLE.py index 7d351aa..8e35619 100644 --- a/my_ipsc_config_SAMPLE.py +++ b/my_ipsc_config_SAMPLE.py @@ -2,34 +2,46 @@ NETWORK = { 'IPSC1': { 'LOCAL': { - 'DESCRIPTION': 'IPSC Network name', 'MODE': b'\x6A', 'FLAGS': b'\x00\x00\x00\x14', 'PORT': 50001, + 'NUM_PEERS': 0 'ALIVE_TIMER': 5, # Seconds between keep-alives and registration attempts + 'MAX_MISSED': 5, # Maximum number of keep-alives missed before de-registration 'RADIO_ID': b'\x00\x00\x00\x0A', 'AUTH_KEY': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' + 'ENABLED': True, + 'STATUS': { + 'ACTIVE': False + } }, 'MASTER': { 'IP': '1.2.3.4', 'PORT': 50000, + 'RADIO_ID': b'\x00\x00\x00\x00', + 'MODE': b'\x00', + 'FLAGS': b'\x00\x00\x00\x00', 'STATUS': { - 'RADIO_ID': b'\x00\x00\x00\x00', - 'CONNECTED': 0, + 'CONNECTED': False, + 'PEER-LIST': False, + 'KEEP_ALIVES_SENT': 0, 'KEEP_ALIVES_MISSED': 0, - 'MODE': b'\x00', - 'FLAGS': b'\x00\x00\x00\x00', + 'KEEP_ALIVES_OUTSTANDING': 0 } }, 'PEERS': [] # each list item contains { # 'IP': '100.200.1.1', # 'PORT': 50000, -# 'RADIO_ID': b'\x00\x00\x00\xFF', +# 'RADIO_ID': b'\x00\x00\x00\x00', +# 'MODE': b'\x00, +# 'FLAGS': b'\x00\x00\x00\x00', # 'STATUS': { -# 'CONNECTED': 0, -# 'KEEP_ALIVES_MISSED': 0 +# 'CONNECTED': False, +# 'KEEP_ALIVES_SENT': 0, +# 'KEEP_ALIVES_MISSED': 0, +# 'KEEP_ALIVES_OUTSTANDING': 0 # } # } } -} \ No newline at end of file +}