예제 #1
0
파일: tlv.py 프로젝트: gatekeep/dvmfne
    def generate_ipsc_voice_burst(self, _rx_slot, _burst_type, _ambe):
        if _burst_type == BURST_DATA_TYPE['SLOT1_VOICE'] and _rx_slot.slot == 2:
            _burst_type = BURST_DATA_TYPE['SLOT2_VOICE']

        length = 20
        control = 0x00
        controlData = ''
        if _rx_slot.vf == 0:
            control = VC_SYNC

        elif _rx_slot.vf == 1 or _rx_slot.vf == 2 or _rx_slot.vf == 3 or _rx_slot.vf == 5:
            control = VC_EMB | VC_EMBEDDED_LC_BITS
            length += 5

            emb = bptc.encode_emblc(self.emb_lc)
            if _rx_slot.vf == 1:
                controlData = emb[1].tobytes()
            elif _rx_slot.vf == 2:
                controlData = emb[2].tobytes()
            elif _rx_slot.vf == 3:
                controlData = emb[3].tobytes()
            elif _rx_slot.vf == 5:
                controlData = struct.pack('>I', 0)

            controlData += '\x00'

        elif _rx_slot.vf == 4:
            control = VC_EMB | VC_EMBEDDED_LC_BITS | VC_EMBEDDED_LC
            length += 14

            emb = bptc.encode_emblc(self.emb_lc)
            controlData = emb[4].tobytes()
            controlData += self.emb_lc[1:]

            controlData += '\x00'

        burst = _burst_type + struct.pack('B', length) + struct.pack(
            'B', control) + _ambe + controlData
        return burst
예제 #2
0
 def __encode_voice_header( self, _rx_slot, _sync, _dtype ):
     _src_id = _rx_slot.rf_src
     _dst_id = _rx_slot.dst_id
     _cc = _rx_slot.cc
     # create lc
     lc = '\x00\x00\x00' + _dst_id + _src_id         # PF + Reserved + FLCO + FID + Service Options + Group Address + Source Address
     # encode lc into info
     full_lc_encode = bptc.encode_header_lc(lc)
     _rx_slot.emblc = bptc.encode_emblc(lc)          # save off the emb lc for voice frames B-E
     _rx_slot.emblc[5] = bitarray(32)                # NULL message (F)
     # create slot_type
     slot_type = chr((_cc << 4) | (_dtype & 0x0f))   # data type is Header or Term
     # generate FEC for slot type
     slot_with_fec  = BitArray(uint=golay.encode_2087(slot_type), length=20)
     # construct final frame - info[0:98] + slot_type[0:10] + DMR_DATA_SYNC_MS + slot_type[10:20] + info[98:196]
     frame_bits = full_lc_encode[0:98] + slot_with_fec[0:10] + decode.to_bits(_sync) + slot_with_fec[10:20] + full_lc_encode[98:196]
     return decode.to_bytes(frame_bits)
예제 #3
0
파일: tlv.py 프로젝트: gatekeep/dvmfne
    def encode_lc(self, _rx_slot, _dtype):
        _src_id = _rx_slot.src_id
        _dst_id = _rx_slot.dst_id
        _cc = _rx_slot.cc

        _fid = FID_ETSI
        if (_rx_slot.secure == True):
            _fid = FID_DMRA

        _flco = LC_GROUP_VOICE
        if (_rx_slot.group == False):
            _flco = LC_PRIVATE_VOICE

        # create lc
        lcHeader = _flco + _fid + '\x00' + _dst_id + _src_id  # PF + Reserved + FLCO + FID + Service Options + Destination Address + Source Address

        _rx_slot.emblc = bptc.encode_emblc(
            lcHeader)  # save off the emb lc for voice frames B-E
        _rx_slot.emblc[5] = bitarray(32)  # NULL message (F)

        return lc.encode_lc_header(lcHeader, _cc, _dtype, MS_DATA_SYNC)
예제 #4
0
    def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot,
                      _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
        pkt_time = time()
        dmrpkt = _data[20:53]
        _bits = int_id(_data[15])

        if _call_type == 'group':

            # Check for ACL match, and return if the subscriber is not allowed
            if allow_sub(_rf_src) == False:
                self._logger.warning(
                    '(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s',
                    self._system, int_id(_rf_src), int_id(_peer_id),
                    int_id(_dst_id))
                return

            # Is this a new call stream?
            if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
                if (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM
                    ) and (pkt_time <
                           (self.STATUS[_slot]['RX_TIME'] + hb_const.STREAM_TO)
                           ) and (_rf_src != self.STATUS[_slot]['RX_RFS']):
                    self._logger.warning(
                        '(%s) Packet received with STREAM ID: %s <FROM> SUB: %s PEER: %s <TO> TGID %s, SLOT %s collided with existing call',
                        self._system, int_id(_stream_id), int_id(_rf_src),
                        int_id(_peer_id), int_id(_dst_id), _slot)
                    return

                # This is a new call stream
                self.STATUS['RX_START'] = pkt_time
                self._logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \
                        self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot)

                # If we can, use the LC from the voice header as to keep all options intact
                if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                    decoded = decode.voice_head_term(dmrpkt)
                    self.STATUS[_slot]['RX_LC'] = decoded['LC']

                # If we don't have a voice header then don't wait to decode it from the Embedded LC
                # just make a new one from the HBP header. This is good enough, and it saves lots of time
                else:
                    self.STATUS[_slot][
                        'RX_LC'] = const.LC_OPT + _dst_id + _rf_src

            for rule in RULES[self._system]['GROUP_VOICE']:
                _target = rule['DST_NET']
                _target_status = systems[_target].STATUS

                if (rule['SRC_GROUP'] == _dst_id and rule['SRC_TS'] == _slot
                        and rule['ACTIVE'] == True):

                    # BEGIN CONTENTION HANDLING
                    #
                    # The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
                    #   From a different group than last RX from this HBSystem, but it has been less than Group Hangtime
                    #   From a different group than last TX to this HBSystem, but it has been less than Group Hangtime
                    #   From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout
                    #   From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout
                    # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
                    #
                    if ((rule['DST_GROUP'] !=
                         _target_status[rule['DST_TS']]['RX_TGID']) and
                        ((pkt_time - _target_status[rule['DST_TS']]['RX_TIME'])
                         < RULES[_target]['GROUP_HANGTIME'])):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info(
                                '(%s) Call not routed to TGID%s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s',
                                self._system, int_id(rule['DST_GROUP']),
                                _target, rule['DST_TS'],
                                int_id(
                                    _target_status[rule['DST_TS']]['RX_TGID']))
                        continue
                    if ((rule['DST_GROUP'] !=
                         _target_status[rule['DST_TS']]['TX_TGID']) and
                        ((pkt_time - _target_status[rule['DST_TS']]['TX_TIME'])
                         < RULES[_target]['GROUP_HANGTIME'])):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info(
                                '(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s',
                                self._system, int_id(rule['DST_GROUP']),
                                _target, rule['DST_TS'],
                                int_id(
                                    _target_status[rule['DST_TS']]['TX_TGID']))
                        continue
                    if (rule['DST_GROUP']
                            == _target_status[rule['DST_TS']]['RX_TGID']) and (
                                (pkt_time -
                                 _target_status[rule['DST_TS']]['RX_TIME']) <
                                hb_const.STREAM_TO):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info(
                                '(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s',
                                self._system, int_id(rule['DST_GROUP']),
                                _target, rule['DST_TS'],
                                int_id(
                                    _target_status[rule['DST_TS']]['RX_TGID']))
                        continue
                    if (rule['DST_GROUP']
                            == _target_status[rule['DST_TS']]['TX_TGID']) and (
                                _rf_src !=
                                _target_status[rule['DST_TS']]['TX_RFS']) and (
                                    (pkt_time -
                                     _target_status[rule['DST_TS']]['TX_TIME'])
                                    < hb_const.STREAM_TO):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info(
                                '(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s',
                                self._system, int_id(_rf_src), _target,
                                rule['DST_TS'],
                                int_id(
                                    _target_status[rule['DST_TS']]['TX_TGID']),
                                _target_status[rule['DST_TS']]['TX_RFS'])
                        continue

                    # Set values for the contention handler to test next time there is a frame to forward
                    _target_status[rule['DST_TS']]['TX_TIME'] = pkt_time

                    if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (
                            _target_status[rule['DST_TS']]['TX_RFS'] != _rf_src
                    ) or (_target_status[rule['DST_TS']]['TX_TGID'] !=
                          rule['DST_GROUP']):
                        # Record the DST TGID and Stream ID
                        _target_status[
                            rule['DST_TS']]['TX_TGID'] = rule['DST_GROUP']
                        _target_status[
                            rule['DST_TS']]['TX_STREAM_ID'] = _stream_id
                        _target_status[rule['DST_TS']]['TX_RFS'] = _rf_src
                        # Generate LCs (full and EMB) for the TX stream
                        # if _dst_id != rule['DST_GROUP']:
                        dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + rule[
                            'DST_GROUP'] + _rf_src
                        _target_status[rule['DST_TS']][
                            'TX_H_LC'] = bptc.encode_header_lc(dst_lc)
                        _target_status[rule['DST_TS']][
                            'TX_T_LC'] = bptc.encode_terminator_lc(dst_lc)
                        _target_status[rule['DST_TS']][
                            'TX_EMB_LC'] = bptc.encode_emblc(dst_lc)
                        self._logger.debug(
                            '(%s) Packet DST TGID (%s) does not match SRC TGID(%s) - Generating FULL and EMB LCs',
                            self._system, int_id(rule['DST_GROUP']),
                            int_id(_dst_id))
                        self._logger.info(
                            '(%s) Call routed to: System: %s TS: %s, TGID: %s',
                            self._system, _target, rule['DST_TS'],
                            int_id(rule['DST_GROUP']))

                    # Handle any necessary re-writes for the destination
                    if rule['SRC_TS'] != rule['DST_TS']:
                        _tmp_bits = _bits ^ 1 << 7
                    else:
                        _tmp_bits = _bits

                    # Assemble transmit HBP packet header
                    _tmp_data = _data[:8] + rule['DST_GROUP'] + _data[
                        11:15] + chr(_tmp_bits) + _data[16:20]

                    # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET
                    # MUST RE-WRITE DESTINATION TGID IF DIFFERENT
                    # if _dst_id != rule['DST_GROUP']:
                    dmrbits = bitarray(endian='big')
                    dmrbits.frombytes(dmrpkt)
                    # Create a voice header packet (FULL LC)
                    if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                        dmrbits = _target_status[rule['DST_TS']]['TX_H_LC'][
                            0:98] + dmrbits[98:166] + _target_status[
                                rule['DST_TS']]['TX_H_LC'][98:197]
                    # Create a voice terminator packet (FULL LC)
                    elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM:
                        dmrbits = _target_status[rule['DST_TS']]['TX_T_LC'][
                            0:98] + dmrbits[98:166] + _target_status[
                                rule['DST_TS']]['TX_T_LC'][98:197]
                    # Create a Burst B-E packet (Embedded LC)
                    elif _dtype_vseq in [1, 2, 3, 4]:
                        dmrbits = dmrbits[0:116] + _target_status[
                            rule['DST_TS']]['TX_EMB_LC'][
                                _dtype_vseq] + dmrbits[148:264]
                    dmrpkt = dmrbits.tobytes()
                    _tmp_data = _tmp_data + dmrpkt + _data[53:55]

                    # Transmit the packet to the destination system
                    systems[_target].send_system(_tmp_data)
                    self._logger.debug(
                        '(%s) Packet routed by rule: %s to %s system: %s',
                        self._system, rule['NAME'],
                        self._CONFIG['SYSTEMS'][_target]['MODE'], _target)

            # Final actions - Is this a voice terminator?
            if (_frame_type == hb_const.HBPF_DATA_SYNC
                ) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (
                    self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM):
                call_duration = pkt_time - self.STATUS['RX_START']
                self._logger.info('(%s) *CALL END*   STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \
                        self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration)

                #
                # Begin in-band signalling for call end. This has nothign to do with routing traffic directly.
                #

                # Iterate the rules dictionary
                for rule in RULES[self._system]['GROUP_VOICE']:
                    _target = rule['DST_NET']

                    # TGID matches a rule source, reset its timer
                    if _slot == rule['SRC_TS'] and _dst_id == rule[
                            'SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and
                                               (rule['ACTIVE'] == True)) or
                                              (rule['TO_TYPE'] == 'OFF'
                                               and rule['ACTIVE'] == False)):
                        rule['TIMER'] = pkt_time + rule['TIMEOUT']
                        self._logger.info(
                            '(%s) Source group transmission match for rule \"%s\". Reset timeout to %s',
                            self._system, rule['NAME'], rule['TIMER'])

                        # Scan for reciprocal rules and reset their timers as well.
                        for target_rule in RULES[_target]['GROUP_VOICE']:
                            if target_rule['NAME'] == rule['NAME']:
                                target_rule['TIMER'] = pkt_time + target_rule[
                                    'TIMEOUT']
                                self._logger.info(
                                    '(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s',
                                    self._system, target_rule['NAME'], _target,
                                    rule['TIMER'])

                    # TGID matches an ACTIVATION trigger
                    if _dst_id in rule['ON']:
                        # Set the matching rule as ACTIVE
                        rule['ACTIVE'] = True
                        rule['TIMER'] = pkt_time + rule['TIMEOUT']
                        self._logger.info(
                            '(%s) Primary routing Rule \"%s\" changed to state: %s',
                            self._system, rule['NAME'], rule['ACTIVE'])

                        # Set reciprocal rules for other IPSCs as ACTIVE
                        for target_rule in RULES[_target]['GROUP_VOICE']:
                            if target_rule['NAME'] == rule['NAME']:
                                target_rule['ACTIVE'] = True
                                target_rule['TIMER'] = pkt_time + target_rule[
                                    'TIMEOUT']
                                self._logger.info(
                                    '(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s',
                                    self._system, target_rule['NAME'], _target,
                                    rule['ACTIVE'])

                    # TGID matches an DE-ACTIVATION trigger
                    if _dst_id in rule['OFF']:
                        # Set the matching rule as ACTIVE
                        rule['ACTIVE'] = False
                        self._logger.info(
                            '(%s) Routing Rule \"%s\" changed to state: %s',
                            self._system, rule['NAME'], rule['ACTIVE'])

                        # Set reciprocal rules for other IPSCs as ACTIVE
                        _target = rule['DST_NET']
                        for target_rule in RULES[_target]['GROUP_VOICE']:
                            if target_rule['NAME'] == rule['NAME']:
                                target_rule['ACTIVE'] = False
                                self._logger.info(
                                    '(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s',
                                    self._system, target_rule['NAME'], _target,
                                    rule['ACTIVE'])
            #
            # END IN-BAND SIGNALLING
            #

            # Mark status variables for use later
            self.STATUS[_slot]['RX_RFS'] = _rf_src
            self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq
            self.STATUS[_slot]['RX_TGID'] = _dst_id
            self.STATUS[_slot]['RX_TIME'] = pkt_time
            self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id
예제 #5
0
    def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
        pkt_time = time()
        dmrpkt = _data[20:53]
        _bits = int_id(_data[15])

        if _call_type == 'group':
            
            # Check for ACL match, and return if the subscriber is not allowed
            if allow_sub(_rf_src) == False:
                self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, HBP Peer %s, Destination TGID %s', self._system, int_id(_rf_src), int_id(_peer_id), int_id(_dst_id))
                return
            
            # Is this a new call stream?   
            if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
                if (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + hb_const.STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']):
                    self._logger.warning('(%s) Packet received with STREAM ID: %s <FROM> SUB: %s PEER: %s <TO> TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot)
                    return
                
                # This is a new call stream
                self.STATUS['RX_START'] = pkt_time
                self._logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \
                        self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot)
                
                # If we can, use the LC from the voice header as to keep all options intact
                if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                    decoded = decode.voice_head_term(dmrpkt)
                    self.STATUS[_slot]['RX_LC'] = decoded['LC']
                
                # If we don't have a voice header then don't wait to decode it from the Embedded LC
                # just make a new one from the HBP header. This is good enough, and it saves lots of time
                else:
                    self.STATUS[_slot]['RX_LC'] = const.LC_OPT + _dst_id + _rf_src


            for rule in RULES[self._system]['GROUP_VOICE']:
                _target = rule['DST_NET']
                _target_status = systems[_target].STATUS
                
                if (rule['SRC_GROUP'] == _dst_id and rule['SRC_TS'] == _slot and rule['ACTIVE'] == True):
                    
                    # BEGIN CONTENTION HANDLING
                    #
                    # The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
                    #   From a different group than last RX from this HBSystem, but it has been less than Group Hangtime
                    #   From a different group than last TX to this HBSystem, but it has been less than Group Hangtime
                    #   From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout
                    #   From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout
                    # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
                    #
                    if ((rule['DST_GROUP'] != _target_status[rule['DST_TS']]['RX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['RX_TIME']) < RULES[_target]['GROUP_HANGTIME'])):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info('(%s) Call not routed to TGID%s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['RX_TGID']))
                        continue    
                    if ((rule['DST_GROUP'] != _target_status[rule['DST_TS']]['TX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['TX_TIME']) < RULES[_target]['GROUP_HANGTIME'])):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['TX_TGID']))
                        continue
                    if (rule['DST_GROUP'] == _target_status[rule['DST_TS']]['RX_TGID']) and ((pkt_time - _target_status[rule['DST_TS']]['RX_TIME']) < hb_const.STREAM_TO):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['RX_TGID']))
                        continue
                    if (rule['DST_GROUP'] == _target_status[rule['DST_TS']]['TX_TGID']) and (_rf_src != _target_status[rule['DST_TS']]['TX_RFS']) and ((pkt_time - _target_status[rule['DST_TS']]['TX_TIME']) < hb_const.STREAM_TO):
                        if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                            self._logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, rule['DST_TS'], int_id(_target_status[rule['DST_TS']]['TX_TGID']), _target_status[rule['DST_TS']]['TX_RFS'])
                        continue

                    # Set values for the contention handler to test next time there is a frame to forward
                    _target_status[rule['DST_TS']]['TX_TIME'] = pkt_time
                    
                    if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']) or (_target_status[rule['DST_TS']]['TX_RFS'] != _rf_src) or (_target_status[rule['DST_TS']]['TX_TGID'] != rule['DST_GROUP']):       
                        # Record the DST TGID and Stream ID
                        _target_status[rule['DST_TS']]['TX_TGID'] = rule['DST_GROUP']
                        _target_status[rule['DST_TS']]['TX_STREAM_ID'] = _stream_id
                        _target_status[rule['DST_TS']]['TX_RFS'] = _rf_src
                        # Generate LCs (full and EMB) for the TX stream
                        # if _dst_id != rule['DST_GROUP']:
                        dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + rule['DST_GROUP'] + _rf_src
                        _target_status[rule['DST_TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc)
                        _target_status[rule['DST_TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc)
                        _target_status[rule['DST_TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc)
                        self._logger.debug('(%s) Packet DST TGID (%s) does not match SRC TGID(%s) - Generating FULL and EMB LCs', self._system, int_id(rule['DST_GROUP']), int_id(_dst_id))
                        self._logger.info('(%s) Call routed to: System: %s TS: %s, TGID: %s', self._system, _target, rule['DST_TS'], int_id(rule['DST_GROUP']))
                    
                    # Handle any necessary re-writes for the destination
                    if rule['SRC_TS'] != rule['DST_TS']:
                        _tmp_bits = _bits ^ 1 << 7
                    else:
                        _tmp_bits = _bits
                    
                    # Assemble transmit HBP packet header
                    _tmp_data = _data[:8] + rule['DST_GROUP'] + _data[11:15] + chr(_tmp_bits) + _data[16:20]
                    
                    # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET
                    # MUST RE-WRITE DESTINATION TGID IF DIFFERENT
                    # if _dst_id != rule['DST_GROUP']:
                    dmrbits = bitarray(endian='big')
                    dmrbits.frombytes(dmrpkt)
                    # Create a voice header packet (FULL LC)
                    if _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VHEAD:
                        dmrbits = _target_status[rule['DST_TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[rule['DST_TS']]['TX_H_LC'][98:197]
                    # Create a voice terminator packet (FULL LC)
                    elif _frame_type == hb_const.HBPF_DATA_SYNC and _dtype_vseq == hb_const.HBPF_SLT_VTERM:
                        dmrbits = _target_status[rule['DST_TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[rule['DST_TS']]['TX_T_LC'][98:197]
                    # Create a Burst B-E packet (Embedded LC)
                    elif _dtype_vseq in [1,2,3,4]:
                        dmrbits = dmrbits[0:116] + _target_status[rule['DST_TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264]
                    dmrpkt = dmrbits.tobytes()
                    _tmp_data = _tmp_data + dmrpkt + _data[53:55]
                    
                    # Transmit the packet to the destination system
                    systems[_target].send_system(_tmp_data)
                    self._logger.debug('(%s) Packet routed by rule: %s to %s system: %s', self._system, rule['NAME'], self._CONFIG['SYSTEMS'][_target]['MODE'], _target)
            
            
            
            # Final actions - Is this a voice terminator?
            if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != hb_const.HBPF_SLT_VTERM):
                call_duration = pkt_time - self.STATUS['RX_START']
                self._logger.info('(%s) *CALL END*   STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %s', \
                        self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration)
                
                #
                # Begin in-band signalling for call end. This has nothign to do with routing traffic directly.
                #
                
                # Iterate the rules dictionary
                for rule in RULES[self._system]['GROUP_VOICE']:
                    _target = rule['DST_NET']
            
                    # TGID matches a rule source, reset its timer
                    if _slot == rule['SRC_TS'] and _dst_id == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)):
                        rule['TIMER'] = pkt_time + rule['TIMEOUT']
                        self._logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', self._system, rule['NAME'], rule['TIMER'])
                
                        # Scan for reciprocal rules and reset their timers as well.
                        for target_rule in RULES[_target]['GROUP_VOICE']:
                            if target_rule['NAME'] == rule['NAME']:
                                target_rule['TIMER'] = pkt_time + target_rule['TIMEOUT']
                                self._logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', self._system, target_rule['NAME'], _target, rule['TIMER'])
            
                    # TGID matches an ACTIVATION trigger
                    if _dst_id in rule['ON']:
                        # Set the matching rule as ACTIVE
                        rule['ACTIVE'] = True
                        rule['TIMER'] = pkt_time + rule['TIMEOUT']
                        self._logger.info('(%s) Primary routing Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
                
                        # Set reciprocal rules for other IPSCs as ACTIVE
                        for target_rule in RULES[_target]['GROUP_VOICE']:
                            if target_rule['NAME'] == rule['NAME']:
                                target_rule['ACTIVE'] = True
                                target_rule['TIMER'] = pkt_time + target_rule['TIMEOUT']
                                self._logger.info('(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
                        
                    # TGID matches an DE-ACTIVATION trigger
                    if _dst_id in rule['OFF']:
                        # Set the matching rule as ACTIVE
                        rule['ACTIVE'] = False
                        self._logger.info('(%s) Routing Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
                
                        # Set reciprocal rules for other IPSCs as ACTIVE
                        _target = rule['DST_NET']
                        for target_rule in RULES[_target]['GROUP_VOICE']:
                            if target_rule['NAME'] == rule['NAME']:
                                target_rule['ACTIVE'] = False
                                self._logger.info('(%s) Reciprocal routing Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
            #                    
            # END IN-BAND SIGNALLING
            #
                
                
            # Mark status variables for use later
            self.STATUS[_slot]['RX_RFS']       = _rf_src
            self.STATUS[_slot]['RX_TYPE']      = _dtype_vseq
            self.STATUS[_slot]['RX_TGID']      = _dst_id
            self.STATUS[_slot]['RX_TIME']      = pkt_time
            self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id