@ -7,7 +7,7 @@
*
*/
/*
* Copyright ( C ) 2023 by Bryan Biedenkapp N2PLL
* Copyright ( C ) 2023 - 2024 by Bryan Biedenkapp N2PLL
*
* 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
@ -24,6 +24,8 @@
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include "Defines.h"
# include "common/p25/lc/tsbk/TSBKFactory.h"
# include "common/p25/Sync.h"
# include "common/Clock.h"
# include "common/Log.h"
# include "common/StopWatch.h"
@ -79,11 +81,16 @@ TagP25Data::~TagP25Data()
/// <param name="peerId">Peer ID</param>
/// <param name="pktSeq"></param>
/// <param name="streamId">Stream ID</param>
/// <param name="fromPeer">Flag indicating whether this traffic is from a peer connection or not.</param>
/// <returns></returns>
bool TagP25Data : : processFrame ( const uint8_t * data , uint32_t len , uint32_t peerId , uint16_t pktSeq , uint32_t streamId )
bool TagP25Data : : processFrame ( const uint8_t * data , uint32_t len , uint32_t peerId , uint16_t pktSeq , uint32_t streamId , bool fromPeer )
{
hrc : : hrc_t pktTime = hrc : : now ( ) ;
uint8_t buffer [ len ] ;
: : memset ( buffer , 0x00U , len ) ;
: : memcpy ( buffer , data , len ) ;
uint8_t lco = data [ 4U ] ;
uint32_t srcId = __GET_UINT16 ( data , 5U ) ;
@ -95,34 +102,34 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
uint8_t lsd2 = data [ 21U ] ;
uint8_t duid = data [ 22U ] ;
uint8_t frameType = p25: : P25_FT_DATA_UNIT;
uint8_t frameType = P25_FT_DATA_UNIT;
p25: : lc: : LC control ;
p25: : data: : LowSpeedData lsd ;
lc: : LC control ;
data: : LowSpeedData lsd ;
// is this a LDU1, is this the first of a call?
if ( duid = = p25: : P25_DUID_LDU1) {
if ( duid = = P25_DUID_LDU1) {
frameType = data [ 180U ] ;
if ( m_debug ) {
LogDebug ( LOG_NET , " P25, frameType = $%02X " , frameType ) ;
}
if ( frameType = = p25: : P25_FT_HDU_VALID) {
if ( frameType = = P25_FT_HDU_VALID) {
uint8_t algId = data [ 181U ] ;
uint32_t kid = ( data [ 182U ] < < 8 ) | ( data [ 183U ] < < 0 ) ;
// copy MI data
uint8_t mi [ p25: : P25_MI_LENGTH_BYTES] ;
: : memset ( mi , 0x00U , p25: : P25_MI_LENGTH_BYTES) ;
uint8_t mi [ P25_MI_LENGTH_BYTES] ;
: : memset ( mi , 0x00U , P25_MI_LENGTH_BYTES) ;
for ( uint8_t i = 0 ; i < p25: : P25_MI_LENGTH_BYTES; i + + ) {
for ( uint8_t i = 0 ; i < P25_MI_LENGTH_BYTES; i + + ) {
mi [ i ] = data [ 184U + i ] ;
}
if ( m_debug ) {
LogDebug ( LOG_NET , " P25, HDU algId = $%02X, kId = $%02X " , algId , kid ) ;
Utils : : dump ( 1U , " P25 HDU Network MI " , mi , p25: : P25_MI_LENGTH_BYTES) ;
Utils : : dump ( 1U , " P25 HDU Network MI " , mi , P25_MI_LENGTH_BYTES) ;
}
control . setAlgId ( algId ) ;
@ -139,6 +146,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
lsd . setLSD1 ( lsd1 ) ;
lsd . setLSD2 ( lsd2 ) ;
// is this data from a peer connection?
if ( fromPeer ) {
// perform TGID mutations if configured
mutateBuffer ( buffer , peerId , duid , dstId , false ) ;
}
// is the stream valid?
if ( validate ( peerId , control , duid , streamId ) ) {
// is this peer ignored?
@ -147,9 +160,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
}
// specifically only check the following logic for end of call or voice frames
if ( duid ! = p25: : P25_DUID_TSDU & & duid ! = p25 : : P25_DUID_PDU ) {
if ( duid ! = P25_DUID_TSDU & & duid ! = P25_DUID_PDU ) {
// is this the end of the call stream?
if ( ( duid = = p25: : P25_DUID_TDU) | | ( duid = = p25 : : P25_DUID_TDULC ) ) {
if ( ( duid = = P25_DUID_TDU) | | ( duid = = P25_DUID_TDULC ) ) {
RxStatus status = m_status [ dstId ] ;
uint64_t duration = hrc : : diff ( pktTime , status . callStartTime ) ;
@ -174,11 +187,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
}
// is this a new call stream?
if ( ( duid ! = p25: : P25_DUID_TDU) & & ( duid ! = p25 : : P25_DUID_TDULC ) ) {
if ( ( duid ! = P25_DUID_TDU) & & ( duid ! = P25_DUID_TDULC ) ) {
auto it = std : : find_if ( m_status . begin ( ) , m_status . end ( ) , [ & ] ( StatusMapPair x ) { return x . second . dstId = = dstId ; } ) ;
if ( it ! = m_status . end ( ) ) {
RxStatus status = m_status [ dstId ] ;
if ( streamId ! = status . streamId & & ( ( duid ! = p25: : P25_DUID_TDU) & & ( duid ! = p25 : : P25_DUID_TDULC ) ) ) {
if ( streamId ! = status . streamId & & ( ( duid ! = P25_DUID_TDU) & & ( duid ! = P25_DUID_TDULC ) ) ) {
if ( status . srcId ! = 0U & & status . srcId ! = srcId ) {
LogWarning ( LOG_NET , " P25, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u " , peerId , srcId , dstId , streamId ) ;
return false ;
@ -217,7 +230,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
lookups : : TalkgroupRuleGroupVoice tg = m_network - > m_tidLookup - > find ( dstId ) ;
if ( tg . config ( ) . parrot ( ) ) {
uint8_t * copy = new uint8_t [ len ] ;
: : memcpy ( copy , data , len ) ;
: : memcpy ( copy , buffer , len ) ;
m_parrotFrames . push_back ( std : : make_tuple ( copy , len , pktSeq , streamId , srcId , dstId ) ) ;
}
@ -229,7 +242,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
continue ;
}
m_network - > writePeer ( peer . first , { NET_FUNC_PROTOCOL , NET_PROTOCOL_SUBFUNC_P25 } , data , len , pktSeq , streamId , true ) ;
m_network - > writePeer ( peer . first , { NET_FUNC_PROTOCOL , NET_PROTOCOL_SUBFUNC_P25 } , buffer , len , pktSeq , streamId , true ) ;
if ( m_network - > m_debug ) {
LogDebug ( LOG_NET , " P25, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u " ,
peerId , peer . first , duid , lco , MFId , srcId , dstId , len , pktSeq , streamId ) ;
@ -241,7 +254,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
}
// repeat traffic to upstream peers
if ( m_network - > m_host - > m_peerNetworks . size ( ) > 0 ) {
if ( m_network - > m_host - > m_peerNetworks . size ( ) > 0 & & ! tg . config ( ) . parrot ( ) ) {
for ( auto peer : m_network - > m_host - > m_peerNetworks ) {
uint32_t peerId = peer . second - > getPeerId ( ) ;
@ -250,7 +263,14 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
continue ;
}
peer . second - > writeMaster ( { NET_FUNC_PROTOCOL , NET_PROTOCOL_SUBFUNC_P25 } , data , len , pktSeq , streamId ) ;
uint8_t outboundPeerBuffer [ len ] ;
: : memset ( outboundPeerBuffer , 0x00U , len ) ;
: : memcpy ( outboundPeerBuffer , buffer , len ) ;
// perform TGID mutations if configured
mutateBuffer ( outboundPeerBuffer , peerId , duid , dstId ) ;
peer . second - > writeMaster ( { NET_FUNC_PROTOCOL , NET_PROTOCOL_SUBFUNC_P25 } , outboundPeerBuffer , len , pktSeq , streamId ) ;
}
}
@ -323,6 +343,108 @@ void TagP25Data::playbackParrot()
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Helper to mutate the network data buffer.
/// </summary>
/// <param name="buffer"></param>
/// <param name="peerId">Peer ID</param>
/// <param name="duid"></param>
/// <param name="dstId"></param>
/// <param name="outbound"></param>
void TagP25Data : : mutateBuffer ( uint8_t * buffer , uint32_t peerId , uint8_t duid , uint32_t dstId , bool outbound )
{
uint32_t srcId = __GET_UINT16 ( buffer , 5U ) ;
uint32_t frameLength = buffer [ 23U ] ;
uint32_t mutatedDstId = dstId ;
// does the data require mutation?
if ( peerMutate ( peerId , mutatedDstId , outbound ) ) {
// rewrite destination TGID in the frame
__SET_UINT16 ( mutatedDstId , buffer , 8U ) ;
UInt8Array data = std : : unique_ptr < uint8_t [ ] > ( new uint8_t [ frameLength ] ) ;
: : memset ( data . get ( ) , 0x00U , frameLength ) ;
: : memcpy ( data . get ( ) , buffer + 24U , frameLength ) ;
// are we receiving a TSDU?
if ( duid = = P25_DUID_TSDU ) {
std : : unique_ptr < lc : : TSBK > tsbk = lc : : tsbk : : TSBKFactory : : createTSBK ( data . get ( ) ) ;
if ( tsbk ! = nullptr ) {
// handle standard P25 reference opcodes
switch ( tsbk - > getLCO ( ) ) {
case TSBK_IOSP_GRP_VCH :
{
LogMessage ( LOG_NET , P25_TSDU_STR " , %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u " ,
tsbk - > toString ( true ) . c_str ( ) , tsbk - > getEmergency ( ) , tsbk - > getEncrypted ( ) , tsbk - > getPriority ( ) , tsbk - > getGrpVchNo ( ) , srcId , dstId ) ;
tsbk - > setDstId ( dstId ) ;
}
break ;
}
// regenerate TSDU
uint8_t data [ P25_TSDU_FRAME_LENGTH_BYTES + 2U ] ;
: : memset ( data + 2U , 0x00U , P25_TSDU_FRAME_LENGTH_BYTES ) ;
// Generate Sync
Sync : : addP25Sync ( data + 2U ) ;
// Generate TSBK block
tsbk - > setLastBlock ( true ) ; // always set last block -- this a Single Block TSDU
tsbk - > encode ( data + 2U ) ;
if ( m_debug ) {
LogDebug ( LOG_RF , P25_TSDU_STR " , lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X " ,
tsbk - > getLCO ( ) , tsbk - > getMFId ( ) , tsbk - > getLastBlock ( ) , tsbk - > getAIV ( ) , tsbk - > getEX ( ) , tsbk - > getSrcId ( ) , tsbk - > getDstId ( ) ,
tsbk - > getSysId ( ) , tsbk - > getNetId ( ) ) ;
Utils : : dump ( 1U , " !!! *TSDU (SBF) TSBK Block Data " , data + P25_PREAMBLE_LENGTH_BYTES + 2U , P25_TSBK_FEC_LENGTH_BYTES ) ;
}
: : memcpy ( buffer + 24U , data + 2U , p25 : : P25_TSDU_FRAME_LENGTH_BYTES ) ;
}
}
}
}
/// <summary>
/// Helper to mutate destination ID.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="dstId"></param>
/// <param name="outbound"></param>
bool TagP25Data : : peerMutate ( uint32_t peerId , uint32_t & dstId , bool outbound )
{
lookups : : TalkgroupRuleGroupVoice tg ;
if ( outbound ) {
tg = m_network - > m_tidLookup - > find ( dstId ) ;
}
else {
tg = m_network - > m_tidLookup - > findByMutation ( peerId , dstId ) ;
}
std : : vector < lookups : : TalkgroupRuleMutation > mutations = tg . config ( ) . mutation ( ) ;
bool mutated = false ;
if ( mutations . size ( ) > 0 ) {
for ( auto entry : mutations ) {
if ( entry . peerId ( ) = = peerId ) {
if ( outbound ) {
dstId = tg . source ( ) . tgId ( ) ;
}
else {
dstId = entry . tgId ( ) ;
}
mutated = true ;
break ;
}
}
}
return mutated ;
}
/// <summary>
/// Helper to determine if the peer is permitted for traffic.
/// </summary>
@ -331,19 +453,19 @@ void TagP25Data::playbackParrot()
/// <param name="duid"></param>
/// <param name="streamId">Stream ID</param>
/// <returns></returns>
bool TagP25Data : : isPeerPermitted ( uint32_t peerId , p25: : lc: : LC & control , uint8_t duid , uint32_t streamId )
bool TagP25Data : : isPeerPermitted ( uint32_t peerId , lc: : LC & control , uint8_t duid , uint32_t streamId )
{
// private calls are always permitted
if ( control . getLCO ( ) = = p25: : LC_PRIVATE) {
if ( control . getLCO ( ) = = LC_PRIVATE) {
return true ;
}
// always permit a TSDU or PDU
if ( duid = = p25: : P25_DUID_TSDU | | duid = = p25 : : P25_DUID_PDU )
if ( duid = = P25_DUID_TSDU | | duid = = P25_DUID_PDU )
return true ;
// always permit a terminator
if ( duid = = p25: : P25_DUID_TDU | | duid = = p25 : : P25_DUID_TDULC )
if ( duid = = P25_DUID_TDU | | duid = = P25_DUID_TDULC )
return true ;
// is this a group call?
@ -379,7 +501,7 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t
/// <param name="duid"></param>
/// <param name="streamId">Stream ID</param>
/// <returns></returns>
bool TagP25Data : : validate ( uint32_t peerId , p25: : lc: : LC & control , uint8_t duid , uint32_t streamId )
bool TagP25Data : : validate ( uint32_t peerId , lc: : LC & control , uint8_t duid , uint32_t streamId )
{
// is the source ID a blacklisted ID?
lookups : : RadioId rid = m_network - > m_ridLookup - > find ( control . getSrcId ( ) ) ;
@ -390,15 +512,15 @@ bool TagP25Data::validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, u
}
// always validate a TSDU or PDU if the source is valid
if ( duid = = p25: : P25_DUID_TSDU | | duid = = p25 : : P25_DUID_PDU )
if ( duid = = P25_DUID_TSDU | | duid = = P25_DUID_PDU )
return true ;
// always validate a terminator if the source is valid
if ( duid = = p25: : P25_DUID_TDU | | duid = = p25 : : P25_DUID_TDULC )
if ( duid = = P25_DUID_TDU | | duid = = P25_DUID_TDULC )
return true ;
// is this a private call?
if ( control . getLCO ( ) = = p25: : LC_PRIVATE) {
if ( control . getLCO ( ) = = LC_PRIVATE) {
// is the destination ID a blacklisted ID?
lookups : : RadioId rid = m_network - > m_ridLookup - > find ( control . getDstId ( ) ) ;
if ( ! rid . radioDefault ( ) ) {