Merge branch 'Version-.2-Reorganization'

This commit is contained in:
Cort Buffington 2013-07-12 16:58:05 -05:00
commit 21bf42b3e3
8 changed files with 308 additions and 333 deletions

View File

@ -31,6 +31,7 @@ The following sections of this document will include various packet types. This
CALL_CTL_3 = 0x63 | CALL_CTL_3 = 0x63 |
XCMP_XNL = 0x70 Control protocol messages XCMP_XNL = 0x70 Control protocol messages
GROUP_VOICE = 0x80 This is a group voice call GROUP_VOICE = 0x80 This is a group voice call
PVT_VOICE = 0x81 This is a private voice call
GROUP_DATA = 0x83 This is a group data call GROUP_DATA = 0x83 This is a group data call
PVT_DATA = 0x84 This is a private data call PVT_DATA = 0x84 This is a private data call
RPT_WAKE_UP = 0x85 Wakes up all repeaters on the IPSC RPT_WAKE_UP = 0x85 Wakes up all repeaters on the IPSC
@ -118,7 +119,7 @@ PEER LIST RESPONSE:
Byte 1 - 0x00 = Unknown Byte 1 - 0x00 = Unknown
Byte 2 - 0x00 = Unknown Byte 2 - 0x00 = Unknown
Byte 3 - BIT FLAGS: Byte 3 - BIT FLAGS:
x... .... = CBSK Message x... .... = CSBK Message
.x.. .... = Repeater Call Monitoring .x.. .... = Repeater Call Monitoring
..x. .... = 3rd Party "Console" Application ..x. .... = 3rd Party "Console" Application
...x xxxx = Unknown - default to 0 ...x xxxx = Unknown - default to 0

305
ipsc.py
View File

@ -2,141 +2,88 @@ from __future__ import print_function
from twisted.internet.protocol import DatagramProtocol from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet import task from twisted.internet import task
import sys
import argparse import argparse
import binascii import binascii
import hmac import hmac
import hashlib import hashlib
import logging #from logging.config import dictConfig
from logging.config import dictConfig #import logging
dictConfig({
'version': 1,
'disable_existing_loggers': False,
'filters': {
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'timed': {
'format': '%(levelname)s %(asctime)s %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'formatter': 'simple',
'filename': '/tmp/ipsc.log',
},
'console-timed': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'timed'
},
'file-timed': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'formatter': 'timed',
'filename': '/tmp/ipsc.log',
},
},
'loggers': {
'ipsc': {
# 'handlers': ['file-timed', 'console-timed'],
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': True,
}
}
})
logger = logging.getLogger('ipsc')
# Data structure for holding IPSC information
#************************************************
# IMPORTING OTHER FILES - '#include'
#************************************************
# 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: try:
from my_ipsc_config import NETWORK from my_ipsc_config import NETWORK
except ImportError: except ImportError:
NETWORK = { sys.exit('Configuration file not found, or not valid formatting')
'IPSC1': {
'LOCAL': { # Import IPSC message types and version information
'DESCRIPTION': 'IPSC Network #1', try:
'MODE': b'\x65', from ipsc_message_types import *
'FLAGS': b'\x00\x00\x80\xDC', except ImportError:
'PORT': 50001, sys.exit('IPSC message types file not found or invalid')
'ALIVE_TIMER': 10, # Seconds between keep-alives and registration attempts
'RADIO_ID': binascii.unhexlify('0000000A'), # Import IPSC flag mask values
'AUTH_KEY': binascii.unhexlify('0000000000000000000000000000000000000001') try:
}, from ipsc_mask import *
'MASTER': { except ImportError:
'IP': '1.2.3.4', sys.exit('IPSC mask values file not found or invalid')
'PORT': 50000,
'STATUS': {
'RADIO_ID': '',
'CONNECTED': 0,
'KEEP_ALIVES_MISSED': 0,
'MODE': b'\x00',
'FLAGS': b'\x00\x00\x00\x00',
}
},
'PEERS': []
}
}
# each list item contains {
# 'IP': '100.200.1.1',
# 'PORT': 50000,
# 'RADIO_ID': b'\x00\x00\x00\xFF',
# 'STATUS': {
# 'CONNECTED': 0,
# 'KEEP_ALIVES_MISSED': 0
# }
# },
#
# IPSC2.... etc... repeat as many times as you have resources for
# Known IPSC Message Types #************************************************
CALL_CTL_1 = b'\x61' # | # GLOBALLY SCOPED FUNCTIONS
CALL_CTL_2 = b'\x62' # | Exact meaning unknown #************************************************
CALL_CTL_3 = b'\x63' # |
XCMP_XNL = b'\x70' # XCMP/XNL control message
GROUP_VOICE = b'\x80'
GROUP_DATA = b'\x83'
PVT_DATA = b'\x84'
RPT_WAKE_UP = b'\x85' # Similar to OTA DMR "wake up"
MASTER_REG_REQ = b'\x90' # FROM peer TO master
MASTER_REG_REPLY = b'\x91' # FROM master TO peer
PEER_LIST_REQ = b'\x92'
PEER_LIST_REPLY = b'\x93'
PEER_REG_REQUEST = b'\x94' # Peer registration request
PEER_REG_REPLY = b'\x95' # Peer registration reply
MASTER_ALIVE_REQ = b'\x96' # FROM peer TO master
MASTER_ALIVE_REPLY = b'\x97' # FROM master TO peer
PEER_ALIVE_REQ = b'\x98' # Peer keep alive request
PEER_ALIVE_REPLY = b'\x99' # Peer keep alive reply
DE_REG_REQ = b'\x9A' # Request de-registration from system
DE_REG_REPLY = b'\x9B' # De-registration reply
# IPSC Version Information def hashed_packet(_key, _data):
IPSC_OP_VER = b'\x04\x03' # 0x04, 0x03 -- seems to be current version of IPSC hash = binascii.unhexlify((hmac.new(_key,_data,hashlib.sha1)).hexdigest()[:20])
IPSC_OLD_VER = b'\x04\x00' # 0x04, 0x02 -- oldest version of IPSC suppoerted return (_data + hash)
IPSC_VER = IPSC_OP_VER + IPSC_OLD_VER
def validate_auth(_key, _data):
return
def hashed_packet(key, data): def print_mode_decode(_mode):
hash = binascii.unhexlify((hmac.new(key,data,hashlib.sha1)).hexdigest()[:20]) _mode = int(_mode, 16)
return (data + hash) 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): def print_peer_list(_ipsc_network):
logger.info('%s', _ipsc_network['LOCAL']['DESCRIPTION']) logger.info('\t%s', _ipsc_network['LOCAL']['DESCRIPTION'])
for dictionary in _ipsc_network['PEERS']: for dictionary in _ipsc_network['PEERS']:
hex_address = dictionary['IP'] hex_address = dictionary['IP']
hex_port = dictionary['PORT'] hex_port = dictionary['PORT']
@ -149,8 +96,14 @@ def print_peer_list(_ipsc_network):
logger.info('\tIP Address: %s.%s.%s.%s:%s', address[0], address[1], address[2], address[3], port) logger.info('\tIP Address: %s.%s.%s.%s:%s', address[0], address[1], address[2], address[3], port)
logger.info('\tRADIO ID: %s ', radio_id) logger.info('\tRADIO ID: %s ', radio_id)
logger.info("\tIPSC Mode: %s", hex_mode) logger.info("\tIPSC Mode:")
print_mode_decode(hex_mode)
logger.info("") logger.info("")
#************************************************
# IPSC Network Engine
#************************************************
class IPSC(DatagramProtocol): class IPSC(DatagramProtocol):
@ -173,35 +126,34 @@ class IPSC(DatagramProtocol):
# logger.debug("keepAlive Routine Running in Condition %s", _master_connected) # logger.debug("keepAlive Routine Running in Condition %s", _master_connected)
if (_master_connected == 0): if (_master_connected == 0):
logger.info("*** Starting up IPSC Client and Registering to the Master ***")
reg_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.MASTER_REG_REQ_PKT) 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'])) self.transport.write(reg_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT']))
logger.info("->> Sending Registration to Master:%s:%sFrom:%s\n", self._config['MASTER']['IP'], self._config['MASTER']['PORT'], binascii.b2a_hex(self._config['LOCAL']['RADIO_ID'])) 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)): elif (_master_connected in (1,2)):
if (_master_connected == 1): if (_master_connected == 1):
peer_list_req_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.PEER_LIST_REQ_PKT) 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'])) self.transport.write(peer_list_req_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT']))
logger.info("->> Peer List Reqested from Master:%s:%s\n", 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) 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'])) self.transport.write(master_alive_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT']))
logger.info("->> Master Keep Alive Sent To:%s:%s\n", 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'])
elif (_master_connected == 2): elif (_master_connected == 2):
master_alive_packet = hashed_packet(self._config['LOCAL']['AUTH_KEY'], self.MASTER_ALIVE_PKT) 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'])) self.transport.write(master_alive_packet, (self._config['MASTER']['IP'], self._config['MASTER']['PORT']))
logger.info("->> Master Keep Alive Sent To:%s:%s\n", 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: else:
logger.error("->> Resetting Masteter in UNKOWN STATE:%s:%s\n", self._config['MASTER']['IP'], self._config['MASTER']['PORT']) logger.error("->> Master in UNKOWN STATE:%s:%s", self._config['MASTER']['IP'], self._config['MASTER']['PORT'])
# logger.debug("keepAlive Routine ending at Condition %s", _master_connected) # logger.debug("keepAlive Routine ending at Condition %s", _master_connected)
def startProtocol(self): def startProtocol(self):
logger.debug("*** config: %s", self._config) #logger.debug("*** config: %s", self._config)
logger.info("") #logger.info("")
self._call = task.LoopingCall(self.keepAlive) self._call = task.LoopingCall(self.keepAlive)
self._loop = self._call.start(self._config['LOCAL']['ALIVE_TIMER']) self._loop = self._call.start(self._config['LOCAL']['ALIVE_TIMER'])
@ -212,40 +164,41 @@ class IPSC(DatagramProtocol):
#logger.info("received %r from %s:%d", binascii.b2a_hex(data), host, port) #logger.info("received %r from %s:%d", binascii.b2a_hex(data), host, port)
_packettype = (data[0:1]) _packettype = (data[0:1])
_peerid = (data[1:5])
if (_packettype == MASTER_REG_REQ): if (_packettype == PEER_ALIVE_REQ):
logger.info("<<- Registration Packet Recieved\n") 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)
#logger.info(binascii.b2a_hex(peer_alive_req_packet))
elif (_packettype == MASTER_ALIVE_REPLY):
logger.info("<<- Master Keep-alive Reply From:%s:%s", host, port)
elif (_packettype == PEER_ALIVE_REPLY):
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): elif (_packettype == MASTER_REG_REPLY):
self._config['MASTER']['STATUS']['CONNECTED'] = 1 self._config['MASTER']['STATUS']['CONNECTED'] = 1
logger.info("<<- Master Registration Reply From:%s:%s Setting Condition %s", host, port,self._config['MASTER']['STATUS']['CONNECTED']) logger.info("<<- Master Registration Reply From:%s:%s Setting Condition %s", host, port,self._config['MASTER']['STATUS']['CONNECTED'])
elif (_packettype == PEER_REG_REQUEST): elif (_packettype == PEER_REG_REQUEST):
logger.info("<<- Peer Registration Request From:%s:%s", host, port) 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) 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)) self.transport.write(peer_reg_reply_packet, (host, port))
logger.info("->> Peer Registration Reply Sent To:%s:%s\n", host, port) logger.info("->> Peer Registration Reply Sent To:%s:%s", host, port)
#logger.info("%s:%s", host, port) #logger.info("%s:%s", host, port)
#logger.info(binascii.b2a_hex(peer_reg_reply_packet)) #logger.info(binascii.b2a_hex(peer_reg_reply_packet))
elif (_packettype == PEER_ALIVE_REQ):
logger.info("<<- Received Peer Keep Alive From:%s:%s", 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("->> Sent Peer Keep Alive Reply To:%s:%s", host, port)
self.transport.write(peer_alive_req_packet, (host, port))
logger.info("->> Sent Peer Keep Alive Request To:%s:%s\n", host, port)
#logger.info(binascii.b2a_hex(peer_alive_req_packet))
elif (_packettype == MASTER_ALIVE_REPLY):
logger.info("<<- Keep Alive Received from Master:%s:%s\n", host, port)
elif (_packettype == PEER_ALIVE_REPLY):
logger.info("<<- Keep Alive Received from Peer:%s:%s\n", host, port)
elif (_packettype == XCMP_XNL): elif (_packettype == XCMP_XNL):
logger.info("<<- XCMP_XNL and/or Control Packet From:%s:%s\n", host, port) logger.warning("<<- XCMP_XNL Packet From:%s:%s - We did not indicate XCMP capable!", host, port)
elif (_packettype == PEER_LIST_REPLY): elif (_packettype == PEER_LIST_REPLY):
logger.info("<<- The Peer List has been Received from Master:%s:%s Setting Condition 2", host, port) logger.info("<<- The Peer List has been Received from Master:%s:%s Setting Condition 2", host, port)
@ -263,28 +216,40 @@ class IPSC(DatagramProtocol):
print_peer_list(self._config) print_peer_list(self._config)
logger.info("") logger.info("")
elif (_packettype == GROUP_VOICE):
logger.info("<<- Group Voice Packet From:%s:%s", host, port)
elif (_packettype == PVT_VOICE):
logger.info("<<- Voice Packet From:%s:%s", host, port)
elif (_packettype == GROUP_DATA):
logger.info("<<- Group Data Packet From:%s:%s", host, port)
elif (_packettype == PVT_DATA):
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)
elif (_packettype == DE_REG_REQ):
logger.info("<<- 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)
elif (_packettype in (CALL_CTL_1, CALL_CTL_2, CALL_CTL_3)):
logger.info("<<- Call Control Packet From:%s:%s", host, port)
else: else:
packet_type = binascii.b2a_hex(_packettype) packet_type = binascii.b2a_hex(_packettype)
# logger.error("<<- Received Unprocessed Type %s From:%s:%s\n", packet_type, host, port) logger.error("<<- Received Unprocessed Type %s From:%s:%s", packet_type, host, port)
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
#************************************************
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Start an IPSC client.") logger.info('SYSTEM STARTING UP')
parser.add_argument('-n', '--network', required=False) for ipsc_network in NETWORK:
args = parser.parse_args() reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], IPSC(NETWORK[ipsc_network]))
reactor.run()
if args.network is not None:
if args.network in NETWORK:
logger.info("Connecting to %s", args.network)
reactor.listenUDP(NETWORK[args.network]['LOCAL']['PORT'], IPSC(NETWORK[args.network]))
else:
logger.info("%s is not a configured ISPC network.", args.network)
exit()
else: # connect to all
logger.info("No network supplied, connecting to all networks.")
for ipsc_network in NETWORK:
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], IPSC(NETWORK[ipsc_network]))
reactor.run()

View File

@ -1,25 +0,0 @@
import binascii
import hmac
import hashlib
PAYLOAD = binascii.unhexlify('9000c832686a0000e03c04030402')
AUTH_KEY = binascii.unhexlify('0000000000000000000000000000000000012345')
print type(PAYLOAD)
print type(AUTH_KEY)
print ""
HASH = (hmac.new(AUTH_KEY,PAYLOAD,hashlib.sha1)).hexdigest()[:20]
#PAY_HASH = HASH.hexdigest()[:20]
print binascii.b2a_hex(PAYLOAD)
print binascii.b2a_hex(AUTH_KEY)
print HASH
PACKET = binascii.b2a_hex(PAYLOAD) + HASH
print PACKET
# 27 86 3f 89 d5 a7 15 a8 31 55

55
ipsc_logger.py Normal file
View File

@ -0,0 +1,55 @@
# Logging system configuration
from logging.config import dictConfig
import logging
dictConfig({
'version': 1,
'disable_existing_loggers': False,
'filters': {
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'timed': {
'format': '%(levelname)s %(asctime)s %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'formatter': 'simple',
'filename': '/tmp/ipsc.log',
},
'console-timed': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'timed'
},
'file-timed': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'formatter': 'timed',
'filename': '/tmp/ipsc.log',
},
},
'loggers': {
'ipsc': {
# 'handlers': ['file-timed', 'console-timed'],
'handlers': ['file-timed', 'console-timed'],
'level': 'DEBUG',
'propagate': True,
}
}
})
logger = logging.getLogger('ipsc')

43
ipsc_mask.py Normal file
View File

@ -0,0 +1,43 @@
# LINKING STATUS:
# Byte 1 - BIT FLAGS:
# xx.. .... = Peer Operational (01 only known valid value)
# ..xx .... = Peer MODE: 00 - No Radio, 01 - Analog, 10 - Digital
# .... xx.. = IPSC Slot 1: 10 on, 01 off
# .... ..xx = IPSC Slot 2: 10 on, 01 off
# MASK VALUES:
PEER_OP_MSK = 0b11000000
PEER_MODE_MSK = 0b00110000
IPSC_TS1_MSK = 0b00001100
IPSC_TS2_MSK = 0b00000011
#SERVICE FLAGS:
# Byte 1 - 0x00 = Unknown
# Byte 2 - 0x00 = Unknown
# Byte 3 - BIT FLAGS:
# x... .... = CBSK Message
# .x.. .... = Repeater Call Monitoring
# ..x. .... = 3rd Party "Console" Application
# ...x xxxx = Unknown - default to 0
# MASK VALUES:
CSBK_MSK = 0b10000000
RPT_MON_MSK = 0b01000000
CON_APP_MSK = 0b00100000
# Byte 4 = BIT FLAGS:
# x... .... = XNL Connected (1=true)
# .x.. .... = XNL Master Device
# ..x. .... = XNL Slave Device
# ...x .... = Set if packets are authenticated
# .... x... = Set if data calls are supported
# .... .x.. = Set if voice calls are supported
# .... ..x. = Unknown - default to 0
# .... ...x = Set if master
# MASK VALUES:
XNL_STAT_MSK = 0b10000000
XNL_MSTR_MSK = 0b01000000
XNL_SLAVE_MSK = 0b00100000
PKT_AUTH_MSK = 0b00010000
DATA_CALL_MSK = 0b00001000
VOICE_CALL_MSK = 0b00000100
MSTR_PEER_MSK = 0b00000001

38
ipsc_message_types.py Normal file
View File

@ -0,0 +1,38 @@
# Known IPSC Message Types
CALL_CTL_1 = b'\x61' # |
CALL_CTL_2 = b'\x62' # | Exact meaning unknown
CALL_CTL_3 = b'\x63' # |
XCMP_XNL = b'\x70' # XCMP/XNL control message
GROUP_VOICE = b'\x80'
PVT_VOICE = b'\x81'
GROUP_DATA = b'\x83'
PVT_DATA = b'\x84'
RPT_WAKE_UP = b'\x85' # Similar to OTA DMR "wake up"
MASTER_REG_REQ = b'\x90' # FROM peer TO master
MASTER_REG_REPLY = b'\x91' # FROM master TO peer
PEER_LIST_REQ = b'\x92'
PEER_LIST_REPLY = b'\x93'
PEER_REG_REQUEST = b'\x94' # Peer registration request
PEER_REG_REPLY = b'\x95' # Peer registration reply
MASTER_ALIVE_REQ = b'\x96' # FROM peer TO master
MASTER_ALIVE_REPLY = b'\x97' # FROM master TO peer
PEER_ALIVE_REQ = b'\x98' # Peer keep alive request
PEER_ALIVE_REPLY = b'\x99' # Peer keep alive reply
DE_REG_REQ = b'\x9A' # Request de-registration from system
DE_REG_REPLY = b'\x9B' # De-registration reply
# IPSC Version Information
IPSC_VER_14 = b'\x00'
IPSC_VER_15 = b'\x00'
IPSC_VER_15A = b'\x00'
IPSC_VER_16 = b'\x01'
IPSC_VER_17 = b'\x02'
IPSC_VER_18 = b'\x02'
IPSC_VER_19 = b'\x03'
IPSC_VER_22 = b'\x04'
# Link Type Values - assumed that cap+, etc. are different, this is all I can confirm
LINK_TYPE_IPSC = b'\x04'
# IPSC Version and Link Type are Used for a 4-byte version field in registration packets
IPSC_VER = LINK_TYPE_IPSC + IPSC_VER_19 + LINK_TYPE_IPSC + IPSC_VER_17

137
list.py
View File

@ -1,137 +0,0 @@
import socket
import binascii
import hmac
import hashlib
# Data structure for holding IPSC information
NETWORK = {
'IPSC1': {
'LOCAL': {
'DESCRIPTION': 'K0USY Lecompton, KS - Master',
'MODE': b'\x6A',
'PORT': 50001,
'RADIO_ID': binascii.unhexlify('00000001'),
'AUTH_KEY': binascii.unhexlify('0000000000000000000000000000000000012345')
},
'MASTER': {
'IP': '24.143.49.121',
'MODE': b'\x6A',
'PORT': 50000,
'RADIO_ID': '',
},
'PEERS': [ # each list entry will be a dictionary for IP, RADIO ID and PORT
#{'IP': '100.200.1.1', 'PORT': 50000, 'RADIO_ID': b'\x00\x00\x00\xFF'},
]
}
}
# Known IPSC Message Types
RDAC_CTL = b'\x70'
GROUP_VOICE = b'\x80'
GROUP_DATA = b'\x83'
PVT_DATA = b'\x84'
REG_REQ = b'\x90'
REG_REPLY = b'\x91'
PEER_LIST_REQ = b'\x92'
PEER_LIST_REPLY = b'\x93'
PEER_KEEP_ALIVE_REQ = b'\x94'
PEER_KEEP_ALIVE_REPLY = b'\x95'
KEEP_ALIVE_REQ = b'\x96'
KEEP_ALIVE_REPLY = b'\x97'
# IPSC information
IPSC_TS_BOTH = b'\x6A' # Both Timeslots IPSC enabled
IPSC_OP_VER = b'\x04\x03' # 0x04, 0x03 -- seems to be current version of IPSC
IPSC_OLD_VER = b'\x04\x00' # 0x04, 0x02 -- oldest version of IPSC suppoerted
IPSC_FLAGS = b'\x00\x00\x80\xDC' # Just accept this... it works, we know some of the pieces
#********** FUNCTIONS THAT WE WILL USE
# function to send a payload to a defined socket
def send_auth_packet (_dest_addr, _dest_port, _socket, _data, _key):
_hash = binascii.unhexlify((hmac.new(_key,_data,hashlib.sha1)).hexdigest()[:20])
print("==> Sending Authenticated Packet")
print(" Destination IP:", _dest_addr)
print(" Destination UDP Port:", _dest_port)
print(" Raw Packet:", binascii.b2a_hex(_data + _hash))
_socket.sendto((_data+_hash), (_dest_addr, _dest_port))
return
# Note: This function ignores authentiation information!!!
def receive_packet(_socket):
_data = (_socket.recv(1024))
_peer_id = str(int(binascii.b2a_hex(_data[2:5]), 16))
_mode = binascii.b2a_hex(_data[5:6])
print('<== Response Recieved from Radio ID:', _peer_id)
print(' Raw Packet:', binascii.b2a_hex(_data))
# Parse returned information
_packettype = (_data[0:1])
_sock = 'IPSC1'
if (_packettype == REG_REQ):
print(" >> This is a registration packet")
elif (_packettype == REG_REPLY):
print(" >> This is a registration reply packet")
elif (_packettype == PEER_LIST_REPLY):
print(">> This packet is a peer list from the master")
_num_peers = int(str(int(binascii.b2a_hex(_data[5:7]), 16))[1:])
# print('>>There are', binascii.b2a_hex(_data[5:7]), 'peers in this IPSC (RAW)')
print('>> There are', _num_peers, 'peers in this IPSC')
for i in range(7, (_num_peers*11)+7, 11):
NETWORK[_sock]['PEERS'].append({
'RADIO_ID': binascii.b2a_hex(_data[i:i+4]),
'IP': binascii.b2a_hex(_data[i+4:i+8]),
'PORT': binascii.b2a_hex(_data[i+8:i+10]),
'MODE': binascii.b2a_hex(_data[i+10:i+11])
})
print_peer_list('IPSC1')
return _data, _packettype
def print_peer_list(_ipsc_network):
print(NETWORK[_ipsc_network]['LOCAL']['DESCRIPTION'])
for dictionary in NETWORK[_ipsc_network]['PEERS']:
hex_address = dictionary['IP']
hex_port = dictionary['PORT']
hex_radio_id = dictionary['RADIO_ID']
hex_mode = dictionary['MODE']
address = [int(hex_address[0:2], 16), int(hex_address[2:4], 16), int(hex_address[4:6], 16), int(hex_address[6:8], 16)]
port = int(hex_port, 16)
radio_id = int(hex_radio_id, 16)
print(address[0],".",address[1],".",address[2],".",address[3],"\t", sep='', end='')
print(port, radio_id, hex_mode, sep='\t')
return
#********** THE ACTUAL MEAT
# Create a socket to conetact IPSC Network #1
ipsc1_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ipsc1_sock.bind(('', NETWORK['IPSC1']['LOCAL']['PORT']))
ipsc1_sock.setblocking(0)
ipsc1_sock.settimeout(60)
CTL_SUFFIX = (IPSC_TS_BOTH + IPSC_FLAGS + IPSC_OP_VER + IPSC_OLD_VER)
REG_REQ_PACKET = (REG_REQ + NETWORK['IPSC1']['LOCAL']['RADIO_ID'] + CTL_SUFFIX)
KEEP_ALIVE_PACKET = (KEEP_ALIVE_REQ + NETWORK['IPSC1']['LOCAL']['RADIO_ID'] + CTL_SUFFIX)
PEER_LIST_REQ_PACKET = (PEER_LIST_REQ + NETWORK['IPSC1']['LOCAL']['RADIO_ID'])
# Send registration packet
send_auth_packet(NETWORK['IPSC1']['MASTER']['IP'], NETWORK['IPSC1']['MASTER']['PORT'], ipsc1_sock, REG_REQ_PACKET, NETWORK['IPSC1']['LOCAL']['AUTH_KEY'])
receive_packet(ipsc1_sock)
# Send keep alive packet
send_auth_packet(NETWORK['IPSC1']['MASTER']['IP'], NETWORK['IPSC1']['MASTER']['PORT'], ipsc1_sock, KEEP_ALIVE_PACKET, NETWORK['IPSC1']['LOCAL']['AUTH_KEY'])
receive_packet(ipsc1_sock)
# Request peer list from master
send_auth_packet(NETWORK['IPSC1']['MASTER']['IP'], NETWORK['IPSC1']['MASTER']['PORT'], ipsc1_sock, PEER_LIST_REQ_PACKET, NETWORK['IPSC1']['LOCAL']['AUTH_KEY'])
receive_packet(ipsc1_sock)
ipsc1_sock.close

35
my_ipsc_config_SAMPLE.py Normal file
View File

@ -0,0 +1,35 @@
# Configuration file for IPSC.py -- each network has several parts, some of this is muted by the script once it runs, thus there are placeholders
NETWORK = {
'IPSC1': {
'LOCAL': {
'DESCRIPTION': 'IPSC Network name',
'MODE': b'\x6A',
'FLAGS': b'\x00\x00\x00\x14',
'PORT': 50001,
'ALIVE_TIMER': 5, # Seconds between keep-alives and registration attempts
'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'
},
'MASTER': {
'IP': '1.2.3.4',
'PORT': 50000,
'STATUS': {
'RADIO_ID': b'\x00\x00\x00\x00',
'CONNECTED': 0,
'KEEP_ALIVES_MISSED': 0,
'MODE': b'\x00',
'FLAGS': b'\x00\x00\x00\x00',
}
},
'PEERS': []
# each list item contains {
# 'IP': '100.200.1.1',
# 'PORT': 50000,
# 'RADIO_ID': b'\x00\x00\x00\xFF',
# 'STATUS': {
# 'CONNECTED': 0,
# 'KEEP_ALIVES_MISSED': 0
# }
# }
}
}