From 9690cdf98c69cda1dd047de0d32018d87b0c07f4 Mon Sep 17 00:00:00 2001
From: KF7EEL <kf7eel@qsl.net>
Date: Sun, 5 Sep 2021 19:22:10 -0700
Subject: [PATCH] working SMS generation, not correct though

---
 data_gateway-SAMPLE.cfg | 363 +++++++++++++++++++++++++++++++++++
 data_gateway.py         | 339 +++++++++++++++++++++++++++++++--
 data_gateway_config.py  | 409 ++++++++++++++++++++++++++++++++++++++++
 hblink.py               |   5 +-
 4 files changed, 1101 insertions(+), 15 deletions(-)
 create mode 100644 data_gateway-SAMPLE.cfg
 create mode 100644 data_gateway_config.py

diff --git a/data_gateway-SAMPLE.cfg b/data_gateway-SAMPLE.cfg
new file mode 100644
index 0000000..0ec13a7
--- /dev/null
+++ b/data_gateway-SAMPLE.cfg
@@ -0,0 +1,363 @@
+# PROGRAM-WIDE PARAMETERS GO HERE
+# PATH - working path for files, leave it alone unless you NEED to change it
+# PING_TIME - the interval that peers will ping the master, and re-try registraion
+#           - how often the Master maintenance loop runs
+# MAX_MISSED - how many pings are missed before we give up and re-register
+#           - number of times the master maintenance loop runs before de-registering a peer
+#
+# ACLs:
+#
+# Access Control Lists are a very powerful tool for administering your system.
+# But they consume packet processing time. Disable them if you are not using them.
+# But be aware that, as of now, the configuration stanzas still need the ACL
+# sections configured even if you're not using them.
+#
+# REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!!
+#
+# The 'action' May be PERMIT|DENY
+# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999)
+# Format:
+# 	ACL = 'action:id|start-end|,id|start-end,....'
+#		--for example--
+#	SUB_ACL: DENY:1,1000-2000,4500-60000,17
+#
+# ACL Types:
+# 	REG_ACL: peer radio IDs for registration (only used on HBP master systems)
+# 	SUB_ACL: subscriber IDs for end-users
+# 	TGID_TS1_ACL: destination talkgroup IDs on Timeslot 1
+# 	TGID_TS2_ACL: destination talkgroup IDs on Timeslot 2
+#
+# ACLs may be repeated for individual systems if needed for granularity
+# Global ACLs will be processed BEFORE the system level ACLs
+# Packets will be matched against all ACLs, GLOBAL first. If a packet 'passes'
+# All elements, processing continues. Packets are discarded at the first
+# negative match, or 'reject' from an ACL element.
+#
+# If you do not wish to use ACLs, set them to 'PERMIT:ALL'
+# TGID_TS1_ACL in the global stanza is used for OPENBRIDGE systems, since all
+# traffic is passed as TS 1 between OpenBridges
+[GLOBAL]
+PATH: ./
+PING_TIME: 5
+MAX_MISSED: 3
+USE_ACL: True
+REG_ACL: PERMIT:ALL
+SUB_ACL: DENY:1
+TGID_TS1_ACL: PERMIT:ALL
+TGID_TS2_ACL: PERMIT:ALL
+
+
+# NOT YET WORKING: NETWORK REPORTING CONFIGURATION
+#   Enabling "REPORT" will configure a socket-based reporting
+#   system that will send the configuration and other items
+#   to a another process (local or remote) that may process
+#   the information for some useful purpose, like a web dashboard.
+#
+#   REPORT - True to enable, False to disable
+#   REPORT_INTERVAL - Seconds between reports
+#   REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK
+#   REPORT_CLIENTS - comma separated list of IPs you will allow clients
+#       to connect on. Entering a * will allow all.
+#
+# ****FOR NOW MUST BE TRUE - USE THE LOOPBACK IF YOU DON'T USE THIS!!!****
+[REPORTS]
+REPORT: True
+REPORT_INTERVAL: 60
+REPORT_PORT: 4329
+REPORT_CLIENTS: 127.0.0.1
+
+
+# SYSTEM LOGGER CONFIGURAITON
+#   This allows the logger to be configured without chaning the individual
+#   python logger stuff. LOG_FILE should be a complete path/filename for *your*
+#   system -- use /dev/null for non-file handlers.
+#   LOG_HANDLERS may be any of the following, please, no spaces in the
+#   list if you use several:
+#       null
+#       console
+#       console-timed
+#       file
+#       file-timed
+#       syslog
+#   LOG_LEVEL may be any of the standard syslog logging levels, though
+#   as of now, DEBUG, INFO, WARNING and CRITICAL are the only ones
+#   used.
+#
+[LOGGER]
+LOG_FILE: /tmp/hblink.log
+LOG_HANDLERS: console-timed
+LOG_LEVEL: DEBUG
+LOG_NAME: HBlink
+
+# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES
+# Ok, not the TGID, there's no master list I know of to download
+# This is intended as a facility for other applcations built on top of
+# HBlink to use, and will NOT be used in HBlink directly.
+# STALE_DAYS is the number of days since the last download before we
+# download again. Don't be an ass and change this to less than a few days.
+[ALIASES]
+TRY_DOWNLOAD: True
+PATH: ./
+PEER_FILE: peer_ids.json
+SUBSCRIBER_FILE: subscriber_ids.json
+TGID_FILE: talkgroup_ids.json
+PEER_URL: https://www.radioid.net/static/rptrs.json
+SUBSCRIBER_URL: https://www.radioid.net/static/users.json
+STALE_DAYS: 7
+
+# USER MANAGER
+# This is where to configure the details for use with a user managment script
+[WEB_SERVICE]
+THIS_SERVER_NAME: DATA_GATEWAY
+REMOTE_CONFIG_ENABLED: False
+# URL of the user managment server
+URL: http://localhost:8080/svr
+# Integer appended to DMR ID during the generation of a passphrase
+APPEND_INT: 1
+EXTRA_INT_1: 5
+EXTRA_INT_2: 8
+EXTRA_1: TeSt
+EXTRA_2: DmR4
+# Secret used to authenticate with user managment server, before checking if user login is approved
+SHARED_SECRET: test
+# Shorten passphrases
+SHORTEN_PASSPHRASE: True
+SHORTEN_SAMPLE: 4
+SHORTEN_LENGTH: 4
+BURN_FILE: ./burn_ids.txt
+BURN_INT: 5
+
+[DATA_CONFIG]
+DATA_DMR_ID: 9099
+CALL_TYPE: both
+UNIT_SMS_TS: 2
+
+USER_APRS_SSID: 5
+USER_APRS_COMMENT: HBNet APRS Gateway
+APRS_SERVER: hbl.ink
+APRS_PORT: 14580
+APRS_LOGIN_CALL: N0CALL
+APRS_LOGIN_PASSCODE: 12345
+APRS_FILTER: r/47/-120/500 t/m
+
+# The following settings are only applicable if you are using the gps_data_beacon_igate script.
+# They do not affect the operation gps_data itself.
+# Time in minutes.
+IGATE_BEACON_TIME = 45
+IGATE_BEACON_COMMENT = HBLink3 D-APRS Gateway
+IGATE_BEACON_ICON = /I
+IGATE_LATITUDE = 4730.  N
+IGATE_LONGITUDE = 11930.  W
+
+# The following settings are for the static positions only, for hotspots or repeaters connected to MASTER stanzas.
+# Implementation by IU7IGU
+# REPORT_INTERVAL in Minute (ALLOW only > 3 Minutes)
+# MESSAGE: This message will print on APRS description together RX and TX Frequency
+APRS_STATIC_REPORT_INTERVAL: 15
+APRS_STATIC_MESSAGE:Connected to HBLink
+
+# The options below are required for operation of the dashboard and will cause errors in gps_data.py
+# if configured wrong. Leave them as default unless you know what you are doing.
+# If you do change, you must use absolute paths.
+LOCATION_FILE: /tmp/gps_data_user_loc.txt
+BULLETIN_BOARD_FILE: /tmp/gps_data_user_bb.txt
+MAILBOX_FILE: /tmp/gps_data_user_mailbox.txt
+EMERGENCY_SOS_FILE: /tmp/gps_data_user_sos.txt
+SMS_FILE: /tmp/gps_data_user_sms.txt
+
+# User settings file, MUST configure using absolute path.
+USER_SETTINGS_FILE: /tmp/user_settings.txt
+
+# API settings
+# Authorized Apps file - data used for the dashboard API
+USE_API: True
+AUTHORIZED_APPS_FILE: /tmp/authorized_apps.txt
+AUTHORIZED_TOKENS_FILE: /tmp/hblink_auth_tokens.txt
+AUTHORIZED_USERS_FILE: /home/eric/Sync/hblink3_sms_dev/authorized_users.txt
+ACCESS_SYSTEMS_FILE: /home/eric/Sync/hblink3_sms_dev/access_systems.txt
+MY_SERVER_SHORTCUT: XYZ
+SERVER_NAME: Test HBLink Network
+USE_PUBLIC_APPS: True
+PUBLIC_APPS_LIST: https://raw.githubusercontent.com/kf7eel/hblink_sms_external_apps/main/public_systems.txt
+RULES_PATH: /home/eric/Sync/hblink3_sms_dev/rules.py
+
+# The following options are used for the dashboard. The dashboard is optional.
+# Title of the Dashboard
+DASHBOARD_TITLE: HBNet D-APRS Dashboard
+# Used for API, RSS feed link, etc
+DASHBOARD_URL: http://localhost:8092
+
+# Logo used on dashboard page
+LOGO: https://raw.githubusercontent.com/kf7eel/hblink3/gps/HBlink.png
+
+# Port to run server
+DASH_PORT: 8092
+
+# IP to run server on
+DASH_HOST: 127.0.0.1
+
+#Description of dashboard to show on main page
+DESCRIPTION: Welcome to the dashboard.
+
+# Gateway contact info displayed on about page.
+CONTACT_NAME: your name
+CONTACT_CALL: N0CALL
+CONTACT_EMAIL: email@example.org
+CONTACT_WEBSITE: https://hbl.ink
+
+# Time format for display
+TIME_FORMAT: %%H:%%M:%%S - %%m/%%d/%%y
+
+# Center dashboard map over these coordinates
+MAP_CENTER_LAT: 47.00
+MAP_CENTER_LON: -120.00
+ZOOM_LEVEL: 7
+
+# List and preview of some map themes at http://leaflet-extras.github.io/leaflet-providers/preview/
+# The following are options for map themes and just work, you should use one of these: “OpenStreetMap”, “Stamen” (Terrain, Toner, and Watercolor),
+MAP_THEME: Stamen Toner
+
+
+# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS
+# OpenBridge is a protocol originall created by DMR+ for connection between an
+# IPSC2 server and Brandmeister. It has been implemented here at the suggestion
+# of the Brandmeister team as a way to legitimately connect HBlink to the
+# Brandemiester network.
+# It is recommended to name the system the ID of the Brandmeister server that
+# it connects to, but is not necessary. TARGET_IP and TARGET_PORT are of the
+# Brandmeister or IPSC2 server you are connecting to. PASSPHRASE is the password
+# that must be agreed upon between you and the operator of the server you are
+# connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that
+# will be sent to the other server to identify this connection.
+# other parameters follow the other system types.
+#
+# ACLs:
+# OpenBridge does not 'register', so registration ACL is meaningless.
+# Proper OpenBridge passes all traffic on TS1.
+# HBlink can extend OPB to use both slots for unit calls only.
+# Setting "BOTH_SLOTS" True ONLY affects unit traffic!
+# Otherwise ACLs work as described in the global stanza
+[OBP-1]
+MODE: OPENBRIDGE
+ENABLED: True
+IP:
+PORT: 62036
+NETWORK_ID: 1234
+PASSPHRASE: passw0rd
+TARGET_IP: 127.0.0.1
+TARGET_PORT: 62037
+BOTH_SLOTS: True
+USE_ACL: True
+SUB_ACL: DENY:1
+TGID_ACL: PERMIT:ALL
+USE_ENCRYPTION: False
+ENCRYPTION_KEY: 
+
+# MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS
+# HomeBrew Protocol Master instances go here.
+# IP may be left blank if there's one interface on your system.
+# Port should be the port you want this master to listen on. It must be unique
+# and unused by anything else.
+# Repeat - if True, the master repeats traffic to peers, False, it does nothing.
+#
+# MAX_PEERS -- maximun number of peers that may be connect to this master
+# at any given time. This is very handy if you're allowing hotspots to
+# connect, or using a limited computer like a Raspberry Pi.
+#
+# ACLs:
+# See comments in the GLOBAL stanza
+[MASTER-1]
+MODE: MASTER
+ENABLED: True
+
+# Use the user manager? If False, MASTER instance will operate as normal.
+USE_USER_MAN: False
+
+REPEAT: True
+MAX_PEERS: 3
+EXPORT_AMBE: False
+IP:
+PORT: 62033
+PASSPHRASE: passw0rd
+GROUP_HANGTIME: 5
+USE_ACL: True
+REG_ACL: DENY:1
+SUB_ACL: DENY:1
+TGID_TS1_ACL: PERMIT:ALL
+TGID_TS2_ACL: PERMIT:ALL
+
+# PEER INSTANCES - DUPLICATE SECTION FOR MULTIPLE PEERS
+# There are a LOT of errors in the HB Protocol specifications on this one!
+# MOST of these items are just strings and will be properly dealt with by the program
+# The TX & RX Frequencies are 9-digit numbers, and are the frequency in Hz.
+# Latitude is an 8-digit unsigned floating point number.
+# Longitude is a 9-digit signed floating point number.
+# Height is in meters
+# Setting Loose to True relaxes the validation on packets received from the master.
+# This will allow HBlink to connect to a non-compliant system such as XLXD, DMR+ etc.
+#
+# ACLs:
+# See comments in the GLOBAL stanza
+[REPEATER-1]
+MODE: PEER
+ENABLED: False
+LOOSE: False
+EXPORT_AMBE: False
+IP: 
+PORT: 54001
+MASTER_IP: 172.16.1.1
+MASTER_PORT: 54000
+PASSPHRASE: homebrew
+CALLSIGN: W1ABC
+RADIO_ID: 312000
+RX_FREQ: 449000000
+TX_FREQ: 444000000
+TX_POWER: 25
+COLORCODE: 1
+SLOTS: 1
+LATITUDE: 38.0000
+LONGITUDE: -095.0000
+HEIGHT: 75
+LOCATION: Anywhere, USA
+DESCRIPTION: This is a cool repeater
+URL: www.w1abc.org
+SOFTWARE_ID: 20170620
+PACKAGE_ID: MMDVM_HBlink
+GROUP_HANGTIME: 5
+OPTIONS:
+USE_ACL: True
+SUB_ACL: DENY:1
+TGID_TS1_ACL: PERMIT:ALL
+TGID_TS2_ACL: PERMIT:ALL
+
+[XLX-1]
+MODE: XLXPEER
+ENABLED: False
+LOOSE: True
+EXPORT_AMBE: False
+IP: 
+PORT: 54002
+MASTER_IP: 172.16.1.1
+MASTER_PORT: 62030
+PASSPHRASE: passw0rd
+CALLSIGN: W1ABC
+RADIO_ID: 312000
+RX_FREQ: 449000000
+TX_FREQ: 444000000
+TX_POWER: 25
+COLORCODE: 1
+SLOTS: 1
+LATITUDE: 38.0000
+LONGITUDE: -095.0000
+HEIGHT: 75
+LOCATION: Anywhere, USA
+DESCRIPTION: This is a cool repeater
+URL: www.w1abc.org
+SOFTWARE_ID: 20170620
+PACKAGE_ID: MMDVM_HBlink
+GROUP_HANGTIME: 5
+XLXMODULE: 4004
+USE_ACL: True
+SUB_ACL: DENY:1
+TGID_TS1_ACL: PERMIT:ALL
+TGID_TS2_ACL: PERMIT:ALL
diff --git a/data_gateway.py b/data_gateway.py
index cbc26df..ecdfffa 100644
--- a/data_gateway.py
+++ b/data_gateway.py
@@ -38,7 +38,7 @@ from twisted.internet import reactor, task
 
 # Things we import from the main hblink module
 from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases, config_reports
-from dmr_utils3.utils import bytes_3, int_id, get_alias
+from dmr_utils3.utils import bytes_3, int_id, get_alias, bytes_4
 from dmr_utils3 import decode, bptc, const
 import data_gateway_config
 import log
@@ -89,6 +89,8 @@ import threading
 import libscrc
 import random
 from bitarray.util import hex2ba as hex2bits
+import traceback
+
 #################################
 
 
@@ -567,22 +569,326 @@ def process_sms(_rf_src, sms, call_type):
             if call_type == 'vcsbk':
                 send_sms(False, 9, 0000, 0000, 'group',  'APRS Messaging must be enabled. Send command "@APRS ON" or use dashboard to enable.')
 
-    try:
-        if sms in cmd_list:
-            logger.info('Executing command/script.')
-            os.popen(cmd_list[sms]).read()
-            packet_assembly = ''
-    except Exception as error_exception:
-        logger.info('Exception. Command possibly not in list, or other error.')
-        logger.info(error_exception)
-        logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
-        packet_assembly = ''
+##    try:
+##        if sms in cmd_list:
+##            logger.info('Executing command/script.')
+##            os.popen(cmd_list[sms]).read()
+##            packet_assembly = ''
+##    except Exception as error_exception:
+##        logger.info('Exception. Command possibly not in list, or other error.')
+##        logger.info(error_exception)
+##        logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
+##        packet_assembly = ''
     else:
         pass
 
-# Module gobal varaibles
+##### SMS encode #########
+############## SMS Que and functions ###########
+def create_crc16(fragment_input):
+    crc16 = libscrc.gsm16(bytearray.fromhex(fragment_input))
+    return fragment_input + re.sub('x', '0', str(hex(crc16 ^ 0xcccc))[-4:])
+
+def create_crc32(fragment_input):
+    # Create and append CRC32 to data
+    # Create list of hex
+    word_list = []
+    count_index = 0
+    while count_index < len(fragment_input):
+        word_list.append((fragment_input[count_index:count_index + 2]))
+        count_index = count_index + 2
+    # Create string of rearranged word_list to match ETSI 102 361-1 pg 141
+    lst_index = 0
+    crc_string = ''
+    for i in (word_list):
+        #print(lst_index)
+        if lst_index % 2 == 0:
+            crc_string =  crc_string + word_list[lst_index + 1]
+            #print(crc_string)
+        if lst_index % 2 == 1:
+            crc_string = crc_string + word_list[lst_index - 1]
+            #print(crc_string)
+        lst_index = lst_index + 1
+    # Create bytearray of word_list_string
+   # print(crc_string)
+    word_array = libscrc.posix(bytearray.fromhex(crc_string))
+    # XOR to get almost final CRC
+    pre_crc = str(hex(word_array ^ 0xffffffff))[2:]
+    # Rearrange pre_crc for transmission
+    crc = ''
+    c = 8
+    while c > 0:
+        crc = crc + pre_crc[c-2:c]
+        c = c - 2
+    #crc = crc.zfill(8)
+    crc = crc.ljust(8, '0')
+    # Return original data and append CRC32
+    print('Output: ' + fragment_input + crc)
+    return fragment_input + crc
+
+def create_crc16_csbk(fragment_input):
+    crc16_csbk = libscrc.gsm16(bytearray.fromhex(fragment_input))
+    return fragment_input + re.sub('x', '0', str(hex(crc16_csbk ^ 0xa5a5))[-4:])
+def csbk_gen(to_id, from_id):
+    csbk_lst = ['BD00801a', 'BD008019', 'BD008018', 'BD008017', 'BD008016']
+
+    send_seq_list = ''
+    for block in csbk_lst:
+        block = block + to_id + from_id
+        block  = create_crc16_csbk(block)
+        print(block)
+        send_seq_list = send_seq_list + block
+        print(send_seq_list)
+    return send_seq_list
+
+def mmdvm_encapsulate(dst_id, src_id, peer_id, _seq, _slot, _call_type, _dtype_vseq, _stream_id, _dmr_data):
+    signature = 'DMRD'
+    # needs to be in bytes
+    frame_type = 0x10 #bytes_2(int(10))
+    #print((frame_type))
+    dest_id = bytes_3(int(dst_id, 16))
+    #print(ahex(dest_id))
+    source_id = bytes_3(int(src_id, 16))
+    via_id = bytes_4(int(peer_id, 16))
+    #print(ahex(via_id))
+    seq = int(_seq).to_bytes(1, 'big')
+    #print(ahex(seq))
+    # Binary, 0 for 1, 1 for 2
+    slot = bitarray(str(_slot))
+    #print(slot)
+    # binary, 0 for group, 1 for unit, bin(1)
+    call_type = bitarray(str(_call_type))
+    #print(call_type)
+    #0x00 for voice, 0x01 for voice sync, 0x10 for data 
+    #frame_type = int(16).to_bytes(1, 'big')
+    frame_type = bitarray('10')
+    #print(frame_type)
+    # Observed to be always 7, int. Will be 6 for header
+    #dtype_vseq = hex(int(_dtype_vseq)).encode()
+    if _dtype_vseq == 6:
+        dtype_vseq = bitarray('0110')
+    if _dtype_vseq == 7:
+        dtype_vseq = bitarray('0111')
+    if _dtype_vseq == 3:
+        dtype_vseq = bitarray('0011')
+    # 9 digit integer in hex
+    stream_id = bytes_4(_stream_id)
+    #print(ahex(stream_id))
+
+    middle_guts = slot + call_type + frame_type + dtype_vseq
+    #print(middle_guts)
+    dmr_data = str(_dmr_data)[2:-1] #str(re.sub("b'|'", '', str(_dmr_data)))
+    complete_packet = signature.encode() + seq + dest_id + source_id + via_id + middle_guts.tobytes() + stream_id + bytes.fromhex((dmr_data)) + bitarray('0000000000101111').tobytes()#bytes.fromhex(dmr_data)
+    #print('Complete: ' + str(ahex(complete_packet)))
+    return complete_packet
+
+
+# Break long string into block sequence
+def block_sequence(input_string):
+    seq_blocks = len(input_string)/24
+    n = 0
+    block_seq = []
+    while n < seq_blocks:
+        if n == 0:
+            block_seq.append(bytes.fromhex(input_string[:24].ljust(24,'0')))
+            n = n + 1
+        else:
+            block_seq.append(bytes.fromhex(input_string[n*24:n*24+24].ljust(24,'0')))
+            n = n + 1
+    return block_seq
+
+# Takes list of DMR packets, 12 bytes, then encodes them
+def dmr_encode(packet_list, _slot):
+    send_seq = []
+    for i in packet_list:
+        stitched_pkt = bptc.interleave_19696(bptc.encode_19696(i))
+        l_slot = bitarray('0111011100')
+        r_slot = bitarray('1101110001')
+        #Mobile Station
+        #sync_data = bitarray('110101011101011111110111011111111101011101010111')
+        if _slot == 0:
+            # TS1 - F7FDD5DDFD55
+            sync_data = bitarray('111101111111110111010101110111011111110101010101')
+        if _slot == 1:
+            #TS2 - D7557F5FF7F5
+            sync_data = bitarray('110101110101010101111111010111111111011111110101')
+            
+        # Data sync? 110101011101011111110111011111111101011101010111 - D5D7F77FD757
+        new_pkt = ahex(stitched_pkt[:98] + l_slot + sync_data + r_slot + stitched_pkt[98:])
+        send_seq.append(new_pkt)
+    return send_seq
+
+
+def create_sms_seq(dst_id, src_id, peer_id, _slot, _call_type, dmr_string):
+    rand_seq = random.randint(1, 999999)
+    block_seq = block_sequence(dmr_string)
+    dmr_list = dmr_encode(block_seq, _slot)
+    cap_in = 0
+    mmdvm_send_seq = []
+    for i in dmr_list:
+        if use_csbk == True:
+            if cap_in < 5:
+                the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 3, rand_seq, i)
+                #print(block_seq[cap_in])
+                #print(3)
+            if cap_in == 5:
+                #print(block_seq[cap_in])
+                #print(6)
+                the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 6, rand_seq, i) #(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4])))))
+            if cap_in > 5:
+                #print(block_seq[cap_in])
+                #print(7)
+                the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 7, rand_seq, i)#(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4])))))
+            mmdvm_send_seq.append(ahex(the_mmdvm_pkt))
+            cap_in = cap_in + 1
+        if use_csbk == False:
+            if cap_in == 0:
+                the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 6, rand_seq, i) #(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4])))))
+            else:
+                the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 7, rand_seq, i)#(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4])))))
+            mmdvm_send_seq.append(ahex(the_mmdvm_pkt))
+            cap_in = cap_in + 1
+            print(ahex(the_mmdvm_pkt))
+            systems['OBP-2'].send_system(the_mmdvm_pkt)
+            
+    with open('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/' + str(random.randint(1000, 9999)) + '.mmdvm_seq', "w") as packet_write_file:
+        packet_write_file.write(str(mmdvm_send_seq))
+
+    return mmdvm_send_seq
+
+# Built for max length msg, will improve later
+def sms_headers(to_id, from_id):
+##    #ETSI 102 361-2 uncompressed ipv4
+##    # UDP header, src and dest ports are 4007, 0fa7
+##    udp_ports = '0fa70fa7'
+##    # Length, of what?
+##    udp_length = '00da'
+##    # Checksum
+##    udp_checksum = '4b37'
+##
+##    # IPV4
+##    #IPV4 version and header length, always 45
+##    ipv4_v_l = '45'
+##    #Type of service, always 00
+##    ipv4_svc = '00'
+##    #length, always 00ee
+##    ipv4_len = '00ee'
+##    #ID always 000d
+##    ipv4_id = '000d'
+##    #Flags and offset always0
+##    ipv4_flag_off = '0000'
+##    #TTL and Protocol always 4011, no matter what
+##    ipv4_ttl_proto = '4011'
+    #ipv4 = '450000ee000d0000401100000c' + from_id + '0c' + to_id
+    ipv4 = '450000ee00000000401100000c' + from_id + '0c' + to_id
+    count_index = 0
+    hdr_lst = []
+    while count_index < len(ipv4):
+        hdr_lst.append((ipv4[count_index:count_index + 4]))
+        count_index = count_index + 4
+    sum = 0
+    for i in hdr_lst:
+        sum = sum + int(i, 16)
+    flipped = ''
+    for i in str(bin(sum))[2:]:
+        if i == '1':
+            flipped = flipped + '0'
+        if i == '0':
+            flipped = flipped + '1'
+    ipv4_chk_sum = str(hex(int(flipped, 2)))[2:]
+    # UDP checksum is optional per ETSI, zero for now as Anytone is not affected.
+    header = ipv4[:20] + ipv4_chk_sum + ipv4[24:] + '0fa70fa700da000000d0a00081040d000a'
+    return header
+
+def format_sms(msg, to_id, from_id):
+    msg_bytes = str.encode(msg)
+    encoded = "".join([str('00' + x) for x in re.findall('..',bytes.hex(msg_bytes))] )
+    final = encoded
+    while len(final) < 400:
+        final = final + '002e'
+    final = final + '0000000000000000000000'
+    headers = sms_headers(to_id, from_id)
+    return headers + final
+
+def gen_header(to_id, from_id, call_type):
+    if call_type == 1:
+        seq_header = '024A' + to_id + from_id + '9550'
+    if call_type == 0:
+        seq_header = '824A' + to_id + from_id + '9550'
+    return seq_header
+
+def send_sms(csbk, to_id, from_id, peer_id, call_type, msg):
+    global use_csbk
+    use_csbk = csbk
+    to_id = str(hex(to_id))[2:].zfill(6)
+    from_id = str(hex(from_id))[2:].zfill(6)
+    peer_id = str(hex(peer_id))[2:].zfill(8)
+    if call_type == 'unit':
+        call_type = 1
+        # Try to find slot from UNIT_MAP
+        try:
+            #Slot 2
+            if UNIT_MAP[bytes.fromhex(to_id)][2] == 2:
+                slot = 1
+            # Slot 1
+            if UNIT_MAP[bytes.fromhex(to_id)][2] == 1:
+                slot = 0
+        except Exception as e:
+            logger.info(e)
+            # Change to config value later
+            slot = 1
+    if call_type == 'group':
+        call_type = 0
+        # Send all Group data to TS 2, need to fix later.
+        slot = 1
+    if csbk == 'yes':
+        use_csbk = True
+        create_sms_seq(to_id, from_id, peer_id, int(slot), new_call_type, csbk_gen(to_id, from_id) + create_crc16(gen_header(to_id, from_id, new_call_type)) + create_crc32(format_sms(msg, to_id, from_id)))
+    else:
+        create_sms_seq(to_id, from_id, peer_id, int(slot), call_type, create_crc16(gen_header(to_id, from_id, call_type)) + create_crc32(format_sms(str(msg), to_id, from_id)))
+
+def data_que_check():
+    l=task.LoopingCall(data_que_send)
+    l.start(1)
+def data_que_send():
+    #logger.info('Check SMS que')
+    try:
+        #logger.info(UNIT_MAP)
+        for packet_file in os.listdir('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/'):
+            logger.info('Sending SMS')
+            logger.info(os.listdir('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/'))
+            snd_seq = ast.literal_eval(os.popen('cat /tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/' + packet_file).read())
+            for data in snd_seq:
+                # Get dest id
+                dst_id = bytes.fromhex(str(data[10:16])[2:-1])
+                call_type = hex2bits(data)[121:122]
+                # Handle UNIT calls
+                if call_type[0] == True:
+                # If destination ID in map, route call only there
+                    if dst_id in UNIT_MAP:
+                        data_target = UNIT_MAP[dst_id][0]
+                        reactor.callFromThread(systems[data_target].send_system,bytes.fromhex(re.sub("b'|'", '', str(data))))
+                        logger.info('Sending data to ' + str(data[10:16])[2:-1] + ' on system ' + data_target)
+                    # Flood all systems
+                    elif dst_id not in UNIT_MAP:
+                        for i in UNIT:
+                            reactor.callFromThread(systems[i].send_system,bytes.fromhex(re.sub("b'|'", '', str(data))))
+                            logger.info('Sending data to ' + str(data[10:16])[2:-1] + ' on system ' + i)
+                # Handle group calls
+                elif call_type[0] == False:
+                    for i in BRIDGES.items():
+                        for d in i[1]:
+                            if dst_id == d['TGID']:
+                                data_target = d['SYSTEM']
+                                reactor.callFromThread(systems[data_target].send_system,bytes.fromhex(re.sub("b'|'", '', str(data))))
+                                logger.info('Sending data to ' + str(data[10:16])[2:-1] + ' on system ' + data_target)
+      
+            os.system('rm /tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/' + packet_file)
+
+                    #routerHBP.send_peer('MASTER-2', bytes.fromhex(re.sub("b'|'", '', str(data))))
+    ##            os.system('rm /tmp/.hblink_data_que/' + packet_file)
+    except Exception as e:
+        logger.info(e)
 
-##class DATA():
 ##### DMR data function ####
 def data_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
     # Capture data headers
@@ -951,7 +1257,12 @@ if __name__ == '__main__':
         with open(sms_file, 'w') as user_sms_file:
             user_sms_file.write("[]")
             user_sms_file.close()
-    
+    try:
+        Path('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/').mkdir(parents=True, exist_ok=True)
+        logger.info('Created que directory')
+    except:
+        logger.info('Unable to create data que directory')
+        pass    
 
     # Start the system logger
     if cli_args.LOG_LEVEL:
diff --git a/data_gateway_config.py b/data_gateway_config.py
new file mode 100644
index 0000000..9a5f6e4
--- /dev/null
+++ b/data_gateway_config.py
@@ -0,0 +1,409 @@
+#!/usr/bin/env python
+#
+###############################################################################
+#   Copyright (C) 2016-2018 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
+###############################################################################
+
+'''
+This module generates the configuration data structure for hblink.py and
+assoicated programs that use it. It has been seaparated into a different
+module so as to keep hblink.py easeier to navigate. This file only needs
+updated if the items in the main configuraiton file (usually hblink.cfg)
+change.
+'''
+
+import configparser
+import sys
+import const
+
+from socket import gethostbyname
+
+# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
+__author__     = 'Cortney T. Buffington, N0MJS'
+__copyright__  = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group'
+__credits__    = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT'
+__license__    = 'GNU GPLv3'
+__maintainer__ = 'Cort Buffington, N0MJS'
+__email__      = 'n0mjs@me.com'
+
+# Processing of ALS goes here. It's separated from the acl_build function because this
+# code is hblink config-file format specific, and acl_build is abstracted
+def process_acls(_config):
+    # Global registration ACL
+    _config['GLOBAL']['REG_ACL'] = acl_build(_config['GLOBAL']['REG_ACL'], const.PEER_MAX)
+
+    # Global subscriber and TGID ACLs
+    for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']:
+        _config['GLOBAL'][acl] = acl_build(_config['GLOBAL'][acl], const.ID_MAX)
+
+    # System level ACLs
+    for system in _config['SYSTEMS']:
+        # Registration ACLs (which make no sense for peer systems)
+        if _config['SYSTEMS'][system]['MODE'] == 'MASTER':
+            _config['SYSTEMS'][system]['REG_ACL'] = acl_build(_config['SYSTEMS'][system]['REG_ACL'], const.PEER_MAX)
+
+        # Subscriber and TGID ACLs (valid for all system types)
+        for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']:
+            _config['SYSTEMS'][system][acl] = acl_build(_config['SYSTEMS'][system][acl], const.ID_MAX)
+
+# Create an access control list that is programatically useable from human readable:
+# ORIGINAL:  'DENY:1-5,3120101,3120124'
+# PROCESSED: (False, set([(1, 5), (3120124, 3120124), (3120101, 3120101)]))
+def acl_build(_acl, _max):
+    if not _acl:
+        return(True, set((const.ID_MIN, _max)))
+
+    acl = [] #set()
+    sections = _acl.split(':')
+
+    if sections[0] == 'PERMIT':
+        action = True
+    else:
+        action = False
+
+    for entry in sections[1].split(','):
+        if entry == 'ALL':
+            acl.append((const.ID_MIN, _max))
+            break
+
+        elif '-' in entry:
+            start,end = entry.split('-')
+            start,end = int(start), int(end)
+            if (const.ID_MIN <= start <= _max) or (const.ID_MIN <= end <= _max):
+                acl.append((start, end))
+            else:
+                sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE ({} - {})IN RANGE-BASED ENTRY: {}'.format(const.ID_MIN, _max, entry))
+        else:
+            id = int(entry)
+            if (const.ID_MIN <= id <= _max):
+                acl.append((id, id))
+            else:
+                 sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE ({} - {}) IN SINGLE ID ENTRY: {}'.format(const.ID_MIN, _max, entry))
+
+    return (action, acl)
+
+def build_config(_config_file):
+    config = configparser.ConfigParser()
+
+    if not config.read(_config_file):
+        sys.exit('Configuration file \''+_config_file+'\' is not a valid configuration file! Exiting...')        
+
+    CONFIG = {}
+    CONFIG['GLOBAL'] = {}
+    CONFIG['REPORTS'] = {}
+    CONFIG['LOGGER'] = {}
+    CONFIG['ALIASES'] = {}
+    CONFIG['WEB_SERVICE'] = {}
+    CONFIG['DATA_CONFIG'] = {}
+    CONFIG['SYSTEMS'] = {}
+
+    try:
+        for section in config.sections():
+            if section == 'GLOBAL':
+                CONFIG['GLOBAL'].update({
+                    'PATH': config.get(section, 'PATH'),
+                    'PING_TIME': config.getint(section, 'PING_TIME'),
+                    'MAX_MISSED': config.getint(section, 'MAX_MISSED'),
+                    'USE_ACL': config.get(section, 'USE_ACL'),
+                    'REG_ACL': config.get(section, 'REG_ACL'),
+                    'SUB_ACL': config.get(section, 'SUB_ACL'),
+                    'TG1_ACL': config.get(section, 'TGID_TS1_ACL'),
+                    'TG2_ACL': config.get(section, 'TGID_TS2_ACL')
+                })
+
+            elif section == 'REPORTS':
+                CONFIG['REPORTS'].update({
+                    'REPORT': config.getboolean(section, 'REPORT'),
+                    'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
+                    'REPORT_PORT': config.getint(section, 'REPORT_PORT'),
+                    'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(',')
+                })
+
+            elif section == 'LOGGER':
+                CONFIG['LOGGER'].update({
+                    'LOG_FILE': config.get(section, 'LOG_FILE'),
+                    'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'),
+                    'LOG_LEVEL': config.get(section, 'LOG_LEVEL'),
+                    'LOG_NAME': config.get(section, 'LOG_NAME')
+                })
+                if not CONFIG['LOGGER']['LOG_FILE']:
+                    CONFIG['LOGGER']['LOG_FILE'] = '/dev/null'
+
+            elif section == 'ALIASES':
+                CONFIG['ALIASES'].update({
+                    'TRY_DOWNLOAD': config.getboolean(section, 'TRY_DOWNLOAD'),
+                    'PATH': config.get(section, 'PATH'),
+                    'PEER_FILE': config.get(section, 'PEER_FILE'),
+                    'SUBSCRIBER_FILE': config.get(section, 'SUBSCRIBER_FILE'),
+                    'TGID_FILE': config.get(section, 'TGID_FILE'),
+                    'PEER_URL': config.get(section, 'PEER_URL'),
+                    'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'),
+                    'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400,
+                })
+
+            elif section == 'WEB_SERVICE':
+                CONFIG['WEB_SERVICE'].update({
+                    'THIS_SERVER_NAME': config.get(section, 'THIS_SERVER_NAME'),
+                    'URL': config.get(section, 'URL'),
+                    'REMOTE_CONFIG_ENABLED': config.getboolean(section, 'REMOTE_CONFIG_ENABLED'),
+                    'APPEND_INT': config.getint(section, 'APPEND_INT'),
+                    'EXTRA_INT_1': config.getint(section, 'EXTRA_INT_1'),
+                    'EXTRA_INT_2': config.getint(section, 'EXTRA_INT_2'),
+                    'EXTRA_1': config.get(section, 'EXTRA_1'),
+                    'EXTRA_2': config.get(section, 'EXTRA_2'),
+                    'SHARED_SECRET': config.get(section, 'SHARED_SECRET'),
+                    'SHORTEN_PASSPHRASE': config.getboolean(section, 'SHORTEN_PASSPHRASE'),
+                    'SHORTEN_SAMPLE': config.get(section, 'SHORTEN_SAMPLE'),
+                    'SHORTEN_LENGTH': config.get(section, 'SHORTEN_LENGTH'),
+                    'BURN_FILE': config.get(section, 'BURN_FILE'),
+                    'BURN_INT': config.getint(section, 'BURN_INT'),
+
+
+                })
+
+            elif section == 'DATA_CONFIG':
+                CONFIG['DATA_CONFIG'].update({
+                    'DATA_DMR_ID': config.get(section, 'DATA_DMR_ID'),
+                    'USER_APRS_SSID': config.get(section, 'USER_APRS_SSID'),
+                    'CALL_TYPE': config.get(section, 'CALL_TYPE'),
+##                    'UNIT_SMS_TS': config.get(section, 'UNIT_SMS_TS'),
+                    'USER_APRS_COMMENT': config.get(section, 'USER_APRS_COMMENT'),
+                    'APRS_LOGIN_CALL': config.get(section, 'APRS_LOGIN_CALL'),
+                    'APRS_LOGIN_PASSCODE': config.get(section, 'APRS_LOGIN_PASSCODE'),
+                    'APRS_SERVER': config.get(section, 'APRS_SERVER'),
+                    'APRS_PORT': config.get(section, 'APRS_PORT'),
+                    'APRS_FILTER': config.get(section, 'APRS_FILTER'),
+                    'IGATE_BEACON_TIME': config.get(section, 'IGATE_BEACON_TIME'),
+                    'IGATE_BEACON_ICON': config.get(section, 'IGATE_BEACON_ICON'),
+                    'IGATE_BEACON_COMMENT': config.get(section, 'IGATE_BEACON_COMMENT'),
+                    'IGATE_LATITUDE': config.get(section, 'IGATE_LATITUDE'),
+                    'IGATE_LONGITUDE': config.get(section, 'IGATE_LONGITUDE'),
+                    'APRS_STATIC_REPORT_INTERVAL': config.get(section, 'APRS_STATIC_REPORT_INTERVAL'),
+                    'APRS_STATIC_MESSAGE': config.get(section, 'APRS_STATIC_MESSAGE'),
+##                    'EMAIL_SENDER': config.get(section, 'EMAIL_SENDER'),
+##                    'EMAIL_PASSWORD': config.get(section, 'EMAIL_PASSWORD'),
+##                    'SMTP_SERVER': config.get(section, 'SMTP_SERVER'),
+##                    'SMTP_PORT': config.get(section, 'SMTP_PORT'),
+                    'LOCATION_FILE': config.get(section, 'LOCATION_FILE'),
+                    'BULLETIN_BOARD_FILE': config.get(section, 'BULLETIN_BOARD_FILE'),
+                    'MAILBOX_FILE': config.get(section, 'MAILBOX_FILE'),
+                    'SMS_FILE': config.get(section, 'SMS_FILE'),
+                    'EMERGENCY_SOS_FILE': config.get(section, 'EMERGENCY_SOS_FILE'),
+                    'USER_SETTINGS_FILE': config.get(section, 'USER_SETTINGS_FILE'),
+##                    'USE_API': config.getboolean(section, 'USE_API'),
+##                    'AUTHORIZED_TOKENS_FILE': config.get(section, 'AUTHORIZED_TOKENS_FILE'),
+##                    'USE_PUBLIC_APPS': config.getboolean(section, 'USE_PUBLIC_APPS'),
+##                    'PUBLIC_APPS_LIST': config.get(section, 'PUBLIC_APPS_LIST'),
+##                    'MY_SERVER_SHORTCUT': config.get(section, 'MY_SERVER_SHORTCUT'),
+##                    'DASHBOARD_URL': config.get(section, 'DASHBOARD_URL'),
+##                    'SERVER_NAME': config.get(section, 'SERVER_NAME'),
+##                    'RULES_PATH': config.get(section, 'RULES_PATH'),
+
+
+                })
+
+
+            elif config.getboolean(section, 'ENABLED'):
+                if config.get(section, 'MODE') == 'PEER':
+                    CONFIG['SYSTEMS'].update({section: {
+                        'MODE': config.get(section, 'MODE'),
+                        'ENABLED': config.getboolean(section, 'ENABLED'),
+                        'LOOSE': config.getboolean(section, 'LOOSE'),
+                        'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')),
+                        'IP': gethostbyname(config.get(section, 'IP')),
+                        'PORT': config.getint(section, 'PORT'),
+                        'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')),
+                        'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')),
+                        'MASTER_PORT': config.getint(section, 'MASTER_PORT'),
+                        'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'),
+                        'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'),
+                        'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'),
+                        'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'),
+                        'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'),
+                        'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'),
+                        'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'),
+                        'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'),
+                        'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'),
+                        'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'),
+                        'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'),
+                        'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'),
+                        'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'),
+                        'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'),
+                        'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'),
+                        'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'),
+                        'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'),
+                        'OPTIONS': b''.join([b'Type=HBlink;', bytes(config.get(section, 'OPTIONS'), 'utf-8')]),
+                        'USE_ACL': config.getboolean(section, 'USE_ACL'),
+                        'SUB_ACL': config.get(section, 'SUB_ACL'),
+                        'TG1_ACL': config.get(section, 'TGID_TS1_ACL'),
+                        'TG2_ACL': config.get(section, 'TGID_TS2_ACL')
+                    }})
+                    CONFIG['SYSTEMS'][section].update({'STATS': {
+                        'CONNECTION': 'NO',             # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES 
+                        'CONNECTED': None,
+                        'PINGS_SENT': 0,
+                        'PINGS_ACKD': 0,
+                        'NUM_OUTSTANDING': 0,
+                        'PING_OUTSTANDING': False,
+                        'LAST_PING_TX_TIME': 0,
+                        'LAST_PING_ACK_TIME': 0,
+                    }})
+
+                if config.get(section, 'MODE') == 'XLXPEER':
+                    CONFIG['SYSTEMS'].update({section: {
+                        'MODE': config.get(section, 'MODE'),
+                        'ENABLED': config.getboolean(section, 'ENABLED'),
+                        'LOOSE': config.getboolean(section, 'LOOSE'),
+                        'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')),
+                        'IP': gethostbyname(config.get(section, 'IP')),
+                        'PORT': config.getint(section, 'PORT'),
+                        'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')),
+                        'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')),
+                        'MASTER_PORT': config.getint(section, 'MASTER_PORT'),
+                        'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'),
+                        'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'),
+                        'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'),
+                        'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'),
+                        'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'),
+                        'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'),
+                        'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'),
+                        'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'),
+                        'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'),
+                        'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'),
+                        'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'),
+                        'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'),
+                        'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'),
+                        'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'),
+                        'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'),
+                        'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'),
+                        'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'),
+                        'XLXMODULE': config.getint(section, 'XLXMODULE'),
+                        'OPTIONS': '',
+                        'USE_ACL': config.getboolean(section, 'USE_ACL'),
+                        'SUB_ACL': config.get(section, 'SUB_ACL'),
+                        'TG1_ACL': config.get(section, 'TGID_TS1_ACL'),
+                        'TG2_ACL': config.get(section, 'TGID_TS2_ACL')
+                    }})
+                    CONFIG['SYSTEMS'][section].update({'XLXSTATS': {
+                        'CONNECTION': 'NO',             # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES 
+                        'CONNECTED': None,
+                        'PINGS_SENT': 0,
+                        'PINGS_ACKD': 0,
+                        'NUM_OUTSTANDING': 0,
+                        'PING_OUTSTANDING': False,
+                        'LAST_PING_TX_TIME': 0,
+                        'LAST_PING_ACK_TIME': 0,
+                    }})
+
+                elif config.get(section, 'MODE') == 'MASTER':
+                    CONFIG['SYSTEMS'].update({section: {
+                        'MODE': config.get(section, 'MODE'),
+                        'ENABLED': config.getboolean(section, 'ENABLED'),
+                        'USE_USER_MAN': config.getboolean(section, 'USE_USER_MAN'),
+                        'REPEAT': config.getboolean(section, 'REPEAT'),
+                        'MAX_PEERS': config.getint(section, 'MAX_PEERS'),
+                        'IP': gethostbyname(config.get(section, 'IP')),
+                        'PORT': config.getint(section, 'PORT'),
+                        'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'),
+                        'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'),
+                        'USE_ACL': config.getboolean(section, 'USE_ACL'),
+                        'REG_ACL': config.get(section, 'REG_ACL'),
+                        'SUB_ACL': config.get(section, 'SUB_ACL'),
+                        'TG1_ACL': config.get(section, 'TGID_TS1_ACL'),
+                        'TG2_ACL': config.get(section, 'TGID_TS2_ACL')
+                    }})
+                    CONFIG['SYSTEMS'][section].update({'PEERS': {}})
+                    
+                elif config.get(section, 'MODE') == 'OPENBRIDGE':
+                    CONFIG['SYSTEMS'].update({section: {
+                        'MODE': config.get(section, 'MODE'),
+                        'ENABLED': config.getboolean(section, 'ENABLED'),
+                        'NETWORK_ID': config.getint(section, 'NETWORK_ID').to_bytes(4, 'big'),
+                        'IP': gethostbyname(config.get(section, 'IP')),
+                        'PORT': config.getint(section, 'PORT'),
+                        'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], 'utf-8'),
+                        'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')),
+                        'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')),
+                        'TARGET_PORT': config.getint(section, 'TARGET_PORT'),
+                        'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'),
+                        'USE_ACL': config.getboolean(section, 'USE_ACL'),
+                        'SUB_ACL': config.get(section, 'SUB_ACL'),
+                        'TG1_ACL': config.get(section, 'TGID_ACL'),
+                        'TG2_ACL': 'PERMIT:ALL',
+                        'USE_ENCRYPTION': config.getboolean(section, 'USE_ENCRYPTION'),
+                        'ENCRYPTION_KEY': bytes(config.get(section, 'ENCRYPTION_KEY'), 'utf-8'),
+                    }})
+                elif config.get(section, 'MODE') == 'PROXY':
+                    CONFIG['SYSTEMS'].update({section: {
+                        'MODE': config.get(section, 'MODE'),
+                        'ENABLED': config.getboolean(section, 'ENABLED'),
+                        'EXTERNAL_PROXY_SCRIPT': config.getboolean(section, 'EXTERNAL_PROXY_SCRIPT'),
+                        'STATIC_APRS_POSITION_ENABLED': config.getboolean(section, 'STATIC_APRS_POSITION_ENABLED'),
+                        'REPEAT': config.getboolean(section, 'REPEAT'),
+                        'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'),
+                        'EXTERNAL_PORT': config.getint(section, 'EXTERNAL_PORT'),
+                        'INTERNAL_PORT_START': config.getint(section, 'INTERNAL_PORT_START'),
+                        'INTERNAL_PORT_STOP': config.getint(section, 'INTERNAL_PORT_STOP'),
+                        'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'),
+                        'USE_ACL': config.getboolean(section, 'USE_ACL'),
+                        'REG_ACL': config.get(section, 'REG_ACL'),
+                        'SUB_ACL': config.get(section, 'SUB_ACL'),
+                        'TG1_ACL': config.get(section, 'TG1_ACL'),
+                        'TG2_ACL': config.get(section, 'TG2_ACL'),
+                    }})
+                    CONFIG['SYSTEMS'][section].update({'PEERS': {}})
+    
+    except configparser.Error as err:
+        sys.exit('Error processing configuration file -- {}'.format(err))
+        
+    process_acls(CONFIG)
+    
+    return CONFIG
+
+# Used to run this file direclty and print the config,
+# which might be useful for debugging
+if __name__ == '__main__':
+    import sys
+    import os
+    import argparse
+    from pprint import pprint
+    from dmr_utils3.utils import int_id
+    
+    # Change the current directory to the location of the application
+    os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
+
+    # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)')
+    cli_args = parser.parse_args()
+
+
+    # Ensure we have a path for the config file, if one wasn't specified, then use the execution directory
+    if not cli_args.CONFIG_FILE:
+        cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg'
+    
+    CONFIG = build_config(cli_args.CONFIG_FILE)
+    pprint(CONFIG)
+    
+    def acl_check(_id, _acl):
+        id = int_id(_id)
+        for entry in _acl[1]:
+            if entry[0] <= id <= entry[1]:
+                return _acl[0]
+        return not _acl[0]
+        
+    print(acl_check(b'\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL']))
diff --git a/hblink.py b/hblink.py
index fe23ea8..bad3c85 100755
--- a/hblink.py
+++ b/hblink.py
@@ -196,9 +196,12 @@ class OPENBRIDGE(DatagramProtocol):
                 _data = _packet[:53]
                 _hash = _packet[53:]
                 _ckhs = hmac_new(self._config['PASSPHRASE'],_data,sha1).digest()
+                print(ahex(_ckhs))
+                print(ahex(_hash))
+
+                print(compare_digest(_hash, _ckhs))
 
                 if compare_digest(_hash, _ckhs) and _sockaddr == self._config['TARGET_SOCK']:
-                    print('good data')
                     _peer_id = _data[11:15]
                     _seq = _data[4]
                     _rf_src = _data[5:8]