begin implementing some basic SNDCP handling logic (this isn't complete, and this doesn't make SNDCP work *yet*), however until the implementation is complete, this should prevent channels from becoming grant locked (hopefully);

pull/61/head
Bryan Biedenkapp 2 years ago
parent 6b8e889ea6
commit e90350d350

@ -244,8 +244,8 @@ protocols:
unitToUnitAvailCheck: false
# Flag indicating explicit source ID support is enabled.
allowExplicitSourceId: true
# Flag indicating whether or not the host will respond to SNDCP data grant requests.
sndcpGrant: false
# Flag indicating whether or not the host will respond to SNDCP data requests.
sndcpSupport: false
# BER/Error threshold for silencing voice packets. (0 or 1233 disables)
silenceThreshold: 124
# Maximum number of lost frames before terminating a call.

@ -363,17 +363,122 @@ namespace p25
};
}
const uint8_t SNDCP_VERSION_1 = 0x01U; // SNDCP Version 1
const uint8_t SNDCP_MTU_510 = 2U; // 510 byte MTU
/// <summary>
/// SNDCP Type
/// SNDCP PDU Message Type
/// </summary>
namespace PDUSNDCPType {
namespace SNDCP_PDUType {
enum : uint8_t {
ACT_TDS_CTX_ACCPT = 0x00U, // Activate Context Accept
DEACT_TDS_CTX_ACCPT = 0x01U, // Deactivate Context Accept
ACT_TDS_CTX = 0x00U, // Context Activation Request (ISP) / Context Activation Accept (OSP)
DEACT_TDS_CTX_REQ = 0x02U, // Deactivate Context Request
ACT_TDS_CTX_REJECT = 0x03U, // Activate Context Reject
RF_UNCONFIRMED = 0x04U, // RF Unconfirmed
RF_CONFIRMED = 0x05U // RF Confirmed
RF_UNCONFIRMED = 0x04U, // Data Unconfirmed
RF_CONFIRMED = 0x05U // Data Confirmed
};
}
/// <summary>
/// SNDCP Activation TDS States
/// </summary.
namespace SNDCPState {
// SNDCP Activation TDS State
enum E : uint8_t {
IDLE = 0U, // Idle - Waiting for SU Registration
READY_S = 1U, // Ready* - Waiting for SU Activation
STANDBY = 2U, // Standby - SU Activated
READY = 3U, // Ready - SU Activated and Rx/Tx Data
CLOSED = 4U, // Closed - SU not yet Registered or Deregistered
ILLEGAL = 255U, // Illegal/Unknown
};
}
/// <summary>
/// SNDCP Data Subscriber Unit Type
/// </summary>
namespace SNDCP_DSUT {
enum : uint8_t {
TRUNKED_DATA_ONLY = 0U, // Trunked Data Only
ALTERNATING_TRUNKED_DATA_VOICE = 1U, // Alternating Trunked Voice & Data
CONV_DATA_ONLY = 2U, // Conventional Data Only
ALTERNATING_CONV_DATA_VOICE = 3U, // Alternating Conventional Voice & Data
TRUNKED_CONV_DATA_ONLY = 4U, // Trunked and Conventional Data Only
};
}
/// <summary>
/// SNDCP Ready Timer
/// </summary>
namespace SNDCPReadyTimer {
enum : uint8_t {
NOT_ALLOWED = 0U, // Not Allowed
ONE_SECOND = 1U, // 1 Second
TWO_SECONDS = 2U, // 2 Seconds
FOUR_SECONDS = 3U, // 4 Seconds
SIX_SECONDS = 4U, // 6 Seconds
EIGHT_SECONDS = 5U, // 8 Seconds
TEN_SECONDS = 6U, // 10 Seconds
FIFTEEN_SECONDS = 7U, // 15 Seconds
TWENTY_SECONDS = 8U, // 20 Seconds
TWENTYFIVE_SECONDS = 9U, // 25 Seconds
THIRTY_SECONDS = 10U, // 30 Seconds
SIXTY_SECONDS = 11U, // 60 Seconds
ONE_TWENTY_SECONDS = 12U, // 120 Seconds
ONE_EIGHT_SECONDS = 13U, // 180 Seconds
THREE_HUNDRED_SECONDS = 14U, // 300 Seconds
ALWAYS = 15U // Always
};
}
/// <summary>
/// SNDCP Standby Timer
/// </summary>
namespace SNDCPStandbyTimer {
enum : uint8_t {
NOT_ALLOWED = 0U, // Not Allowed
TEN_SECONDS = 1U, // 10 Seconds
THIRTY_SECONDS = 2U, // 30 Seconds
ONE_MINUTE = 3U, // 1 Minute
FIVE_MINUTES = 4U, // 5 Minutes
TEN_MINUTES = 5U, // 10 Minutes
THIRTY_MINUTES = 6U, // 30 Minutes
ONE_HOUR = 7U, // 1 Hour
TWO_HOURS = 8U, // 2 Hours
FOUR_HOURS = 9U, // 4 Hours
EIGHT_HOURS = 10U, // 8 Hours
TWELVE_HOURS = 11U, // 12 Hours
TWENTY_FOUR_HOURS = 12U, // 24 Hours
FORTY_EIGHT_HOURS = 13U, // 48 Hours
SEVENTY_TWO_HOURS = 14U, // 72 Hours
ALWAYS = 15U // Always
};
}
/// <summary>
/// SNDCP Reject Reasons
/// </summary>
namespace SNDCPRejectReason {
enum : uint8_t {
ANY_REASON = 0U, // Any Reason
SU_NOT_PROVISIONED = 1U, // Subscriber Not Provisioned
SU_DSUT_NOT_SUPPORTED = 2U, // Subscriber Data Unit Type Not Supported
MAX_TDS_CTX_EXCEEDED = 3U, // Maximum Number of TDS Contexts Exceeded
SNDCP_VER_NOT_SUPPORTED = 4U, // SNDCP Version Not Supported
PDS_NOT_SUPPORTED_SITE = 5U, // Packet Data Service Not Supported on Site
PDS_NOT_SUPPORTED_SYSTEM = 6U, // Packet Data Service Not Supported on System
STATIC_IP_NOT_CORRECT = 7U, // Static IP Address Not Correct
STATIC_IP_ALLOCATION_UNSUPPORTED = 8U, // Static IP Address Allocation Unsupported
STATIC_IP_IN_USE = 9U, // Static IP In Use
IPV4_NOT_SUPPORTED = 10U, // IPv4 Not Supported
DYN_IP_POOL_EMPTY = 11U, // Dynamic IP Address Pool Empty
DYN_IP_ALLOCATION_UNSUPPORTED = 12U // Dynamic IP Address Allocation Unsupported
};
}
@ -430,6 +535,7 @@ namespace p25
// TSBK Inbound Signalling Packet (ISP) Opcode(s)
ISP_TELE_INT_PSTN_REQ = 0x09U, // TELE INT PSTN REQ - Telephone Interconnect Request - Implicit
ISP_SNDCP_CH_REQ = 0x12U, // SNDCP CH REQ - SNDCP Data Channel Request
ISP_SNDCP_REC_REQ = 0x14U, // SNDCP REC REQ - SNDCP Reconnect Request
ISP_STS_Q_RSP = 0x19U, // STS Q RSP - Status Query Response
ISP_STS_Q_REQ = 0x1CU, // STS Q REQ - Status Query Request
ISP_CAN_SRV_REQ = 0x23U, // CAN SRV REQ - Cancel Service Request

@ -0,0 +1,103 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "p25/lc/tsbk/ISP_SNDCP_REC_REQ.h"
using namespace p25;
using namespace p25::defines;
using namespace p25::lc;
using namespace p25::lc::tsbk;
#include <cassert>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the ISP_SNDCP_REC_REQ class.
/// </summary>
ISP_SNDCP_REC_REQ::ISP_SNDCP_REC_REQ() : TSBK(),
m_dataToSend(false),
m_dataServiceOptions(0U),
m_dataAccessControl(0U)
{
m_lco = TSBKO::ISP_SNDCP_REC_REQ;
}
/// <summary>
/// Decode a trunking signalling block.
/// </summary>
/// <param name="data"></param>
/// <param name="rawTSBK"></param>
/// <returns>True, if TSBK was decoded, otherwise false.</returns>
bool ISP_SNDCP_REC_REQ::decode(const uint8_t* data, bool rawTSBK)
{
assert(data != nullptr);
uint8_t tsbk[P25_TSBK_LENGTH_BYTES + 1U];
::memset(tsbk, 0x00U, P25_TSBK_LENGTH_BYTES);
bool ret = TSBK::decode(data, tsbk, rawTSBK);
if (!ret)
return false;
ulong64_t tsbkValue = TSBK::toValue(tsbk);
m_dataToSend = (tsbk[4U] & 0x80U) == 0x80U; // Data To Send Flag
m_dataServiceOptions = (uint8_t)((tsbkValue >> 56) & 0xFFU); // Data Service Options
m_dataAccessControl = (uint32_t)((tsbkValue >> 40) & 0xFFFFFFFFU); // Data Access Control
m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address
return true;
}
/// <summary>
/// Encode a trunking signalling block.
/// </summary>
/// <param name="data"></param>
/// <param name="rawTSBK"></param>
/// <param name="noTrellis"></param>
void ISP_SNDCP_REC_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis)
{
assert(data != nullptr);
/* stub */
}
/// <summary>
/// Returns a string that represents the current TSBK.
/// </summary>
/// <param name="isp"></param>
/// <returns></returns>
std::string ISP_SNDCP_REC_REQ::toString(bool isp)
{
return std::string("TSBKO, ISP_SNDCP_REC_REQ (SNDCP Data Channel Request)");
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Internal helper to copy the the class.
/// </summary>
/// <param name="data"></param>
void ISP_SNDCP_REC_REQ::copy(const ISP_SNDCP_REC_REQ& data)
{
TSBK::copy(data);
m_dataServiceOptions = data.m_dataServiceOptions;
m_dataAccessControl = data.m_dataAccessControl;
}

@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__P25_LC_TSBK__ISP_SNDCP_REC_REQ_H__)
#define __P25_LC_TSBK__ISP_SNDCP_REC_REQ_H__
#include "common/Defines.h"
#include "common/p25/lc/TSBK.h"
namespace p25
{
namespace lc
{
namespace tsbk
{
// ---------------------------------------------------------------------------
// Class Declaration
// Implements SNDCP REC REQ - SNDCP Reconnect Request (ISP).
// ---------------------------------------------------------------------------
class HOST_SW_API ISP_SNDCP_REC_REQ : public TSBK {
public:
/// <summary>Initializes a new instance of the ISP_SNDCP_REC_REQ class.</summary>
ISP_SNDCP_REC_REQ();
/// <summary>Decode a trunking signalling block.</summary>
bool decode(const uint8_t* data, bool rawTSBK = false) override;
/// <summary>Encode a trunking signalling block.</summary>
void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false) override;
/// <summary>Returns a string that represents the current TSBK.</summary>
std::string toString(bool isp = true) override;
public:
/// <summary>Flag indicationg SU has buffered data to send</summary>
__PROPERTY(bool, dataToSend, DataToSend);
/// <summary>SNDCP Data Service Options</summary>
__PROPERTY(uint8_t, dataServiceOptions, DataServiceOptions);
/// <summary>SNDCP Data Access Control</summary>
__PROPERTY(uint32_t, dataAccessControl, DataAccessControl);
__COPY(ISP_SNDCP_REC_REQ);
};
} // namespace tsbk
} // namespace lc
} // namespace p25
#endif // __P25_LC_TSBK__ISP_SNDCP_REC_REQ_H__

@ -44,19 +44,7 @@ bool OSP_SNDCP_CH_GNT::decode(const uint8_t* data, bool rawTSBK)
{
assert(data != nullptr);
uint8_t tsbk[P25_TSBK_LENGTH_BYTES + 1U];
::memset(tsbk, 0x00U, P25_TSBK_LENGTH_BYTES);
bool ret = TSBK::decode(data, tsbk, rawTSBK);
if (!ret)
return false;
ulong64_t tsbkValue = TSBK::toValue(tsbk);
m_dataServiceOptions = (uint8_t)((tsbkValue >> 56) & 0xFFU); // Data Service Options
m_grpVchId = (uint8_t)((tsbkValue >> 52) & 0x0FU); // Channel (T) ID
m_dataChannelNo = ((tsbkValue >> 40) & 0xFFFU); // Channel (T) Number
m_dstId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Target Radio Address
/* stub */
return true;
}

@ -186,6 +186,8 @@ std::unique_ptr<TSBK> TSBKFactory::createTSBK(const uint8_t* data, bool rawTSBK)
return decode(new IOSP_UU_ANS(), data, rawTSBK);
case TSBKO::ISP_SNDCP_CH_REQ:
return decode(new ISP_SNDCP_CH_REQ(), data, rawTSBK);
case TSBKO::ISP_SNDCP_REC_REQ:
return decode(new ISP_SNDCP_REC_REQ(), data, rawTSBK);
case TSBKO::IOSP_STS_UPDT:
return decode(new IOSP_STS_UPDT(), data, rawTSBK);
case TSBKO::IOSP_MSG_UPDT:
@ -224,8 +226,6 @@ std::unique_ptr<TSBK> TSBKFactory::createTSBK(const uint8_t* data, bool rawTSBK)
return decode(new ISP_AUTH_SU_DMD(), data, rawTSBK);
case TSBKO::OSP_ADJ_STS_BCAST:
return decode(new OSP_ADJ_STS_BCAST(), data, rawTSBK);
case TSBKO::OSP_SNDCP_CH_GNT:
return decode(new OSP_SNDCP_CH_GNT(), data, rawTSBK);
default:
LogError(LOG_P25, "TSBKFactory::create(), unknown TSBK LCO value, mfId = $%02X, lco = $%02X", mfId, lco);
break;

@ -38,6 +38,7 @@
#include "common/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.h"
#include "common/p25/lc/tsbk/ISP_LOC_REG_REQ.h"
#include "common/p25/lc/tsbk/ISP_SNDCP_CH_REQ.h"
#include "common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.h"
#include "common/p25/lc/tsbk/ISP_U_DEREG_REQ.h"
#include "common/p25/lc/tsbk/OSP_ADJ_STS_BCAST.h"
#include "common/p25/lc/tsbk/OSP_AUTH_FNE_RESP.h"

@ -838,7 +838,14 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPPayload& request, HTTPPayload& reply
case STATE_P25:
{
if (m_p25 != nullptr) {
m_p25->permittedTG(dstId);
bool dataPermit = false;
// validate destination ID is a integer within the JSON blob
if (req["dataPermit"].is<bool>()) {
dataPermit = (bool)req["dataPermit"].get<bool>();
}
m_p25->permittedTG(dstId, dataPermit);
}
else {
errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);

@ -92,6 +92,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
m_disableNetworkHDU(false),
m_allowExplicitSourceId(true),
m_convNetGrantDemand(false),
m_sndcpSupport(false),
m_idenTable(idenTable),
m_ridLookup(ridLookup),
m_tidLookup(tidLookup),
@ -281,7 +282,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
m_control->m_noMessageAck = p25Protocol["noMessageAck"].as<bool>(true);
m_control->m_unitToUnitAvailCheck = p25Protocol["unitToUnitAvailCheck"].as<bool>(true);
m_control->m_sndcpChGrant = p25Protocol["sndcpGrant"].as<bool>(false);
m_sndcpSupport = p25Protocol["sndcpSupport"].as<bool>(false);
yaml::Node control = p25Protocol["control"];
m_enableControl = control["enable"].as<bool>(false);
@ -488,7 +489,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
LogInfo(" Verify Registration: %s", m_control->m_verifyReg ? "yes" : "no");
LogInfo(" Require LLA for Registration: %s", m_control->m_requireLLAForReg ? "yes" : "no");
LogInfo(" SNDCP Channel Grant: %s", m_control->m_sndcpChGrant ? "yes" : "no");
LogInfo(" SNDCP Support: %s", m_sndcpSupport ? "yes" : "no");
LogInfo(" No Status ACK: %s", m_control->m_noStatusAck ? "yes" : "no");
LogInfo(" No Message ACK: %s", m_control->m_noMessageAck ? "yes" : "no");
@ -1019,7 +1020,8 @@ void Control::clockSiteData(uint32_t ms)
/// Permits a TGID on a non-authoritative host.
/// </summary>
/// <param name="dstId"></param>
void Control::permittedTG(uint32_t dstId)
/// <param name="dataPermit"></param>
void Control::permittedTG(uint32_t dstId, bool dataPermit)
{
if (m_authoritative) {
return;
@ -1030,6 +1032,11 @@ void Control::permittedTG(uint32_t dstId)
}
m_permittedDstId = dstId;
// is this a data permit?
if (dataPermit && m_sndcpSupport) {
m_data->sndcpInitialize(dstId);
}
}
/// <summary>

@ -98,7 +98,7 @@ namespace p25
/// <summary>Sets a flag indicating whether P25 has supervisory functions and can send permit TG to voice channels.</summary>
void setSupervisor(bool supervisor) { m_supervisor = supervisor; }
/// <summary>Permits a TGID on a non-authoritative host.</summary>
void permittedTG(uint32_t dstId);
void permittedTG(uint32_t dstId, bool dataPermit = false);
/// <summary>Grants a TGID on a non-authoritative host.</summary>
void grantTG(uint32_t srcId, uint32_t dstId, bool grp);
/// <summary>Releases a granted TG.</summary>
@ -161,6 +161,7 @@ namespace p25
bool m_disableNetworkHDU;
bool m_allowExplicitSourceId;
bool m_convNetGrantDemand;
bool m_sndcpSupport;
::lookups::IdenTableLookup* m_idenTable;
::lookups::RadioIdLookup* m_ridLookup;

@ -341,14 +341,36 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
tsbk->toString(true).c_str(), isp->getDataServiceOptions(), isp->getDataAccessControl(), srcId);
}
if (m_sndcpChGrant) {
writeRF_TSDU_SNDCP_Grant(false, false);
if (m_p25->m_sndcpSupport) {
writeRF_TSDU_SNDCP_Grant(srcId, false);
}
else {
writeRF_TSDU_Deny(srcId, WUID_FNE, ReasonCode::DENY_SYS_UNSUPPORTED_SVC, TSBKO::ISP_SNDCP_CH_REQ);
}
}
break;
case TSBKO::ISP_SNDCP_REC_REQ:
{
// make sure control data is supported
IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBKO::ISP_SNDCP_CH_REQ, srcId);
// validate the source RID
VALID_SRCID(tsbk->toString(true), TSBKO::ISP_SNDCP_CH_REQ, srcId);
ISP_SNDCP_CH_REQ* isp = static_cast<ISP_SNDCP_CH_REQ*>(tsbk.get());
if (m_verbose) {
LogMessage(LOG_RF, P25_TSDU_STR ", %s, dataServiceOptions = $%02X, dataAccessControl = %u, srcId = %u",
tsbk->toString(true).c_str(), isp->getDataServiceOptions(), isp->getDataAccessControl(), srcId);
}
if (m_p25->m_sndcpSupport) {
writeRF_TSDU_SNDCP_Grant(srcId, false);
}
else {
writeRF_TSDU_Deny(srcId, WUID_FNE, ReasonCode::DENY_SYS_UNSUPPORTED_SVC, TSBKO::ISP_SNDCP_REC_REQ);
}
}
break;
case TSBKO::IOSP_STS_UPDT:
{
// validate the source RID
@ -1225,7 +1247,6 @@ ControlSignaling::ControlSignaling(Control* p25, bool dumpTSBKData, bool debug,
m_microslotCount(0U),
m_ctrlTimeDateAnn(false),
m_ctrlTSDUMBF(true),
m_sndcpChGrant(false),
m_disableGrantSrcIdCheck(false),
m_redundantImmediate(true),
m_redundantGrant(false),
@ -1661,7 +1682,7 @@ void ControlSignaling::writeRF_TSDU_AMBT(lc::AMBT* ambt)
Utils::dump(1U, "!!! *PDU (AMBT) TSBK Block Data", pduUserData, P25_PDU_UNCONFIRMED_LENGTH_BYTES * header.getBlocksToFollow());
}
m_p25->m_data->writeRF_PDU_User(header, pduUserData);
m_p25->m_data->writeRF_PDU_User(header, header, false, pduUserData);
}
/*
@ -2446,50 +2467,43 @@ void ControlSignaling::writeRF_TSDU_Grant_Update()
/// Helper to write a SNDCP grant packet.
/// </summary>
/// <param name="srcId"></param>
/// <param name="dstId"></param>
/// <param name="skip"></param>
/// <param name="net"></param>
/// <param name="chNo"></param>
/// <returns></returns>
bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId, bool skip, bool net)
bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint32_t chNo)
{
if (!m_p25->m_sndcpSupport)
return false;
std::unique_ptr<OSP_SNDCP_CH_GNT> osp = std::make_unique<OSP_SNDCP_CH_GNT>();
osp->setMFId(m_lastMFID);
osp->setSrcId(srcId);
osp->setDstId(dstId);
if (dstId == TGID_ALL) {
return true; // do not generate grant packets for $FFFF (All Call) TGID
}
osp->setDstId(srcId);
// are we skipping checking?
if (!skip) {
if (m_p25->m_rfState != RS_RF_LISTENING && m_p25->m_rfState != RS_RF_DATA) {
if (!net) {
LogWarning(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request) denied, traffic in progress, srcId = %u", srcId);
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_COLLIDE, TSBKO::ISP_SNDCP_CH_REQ, false, true);
writeRF_TSDU_Deny(WUID_FNE, srcId, ReasonCode::DENY_PTT_COLLIDE, TSBKO::ISP_SNDCP_CH_REQ, false, true);
::ActivityLog("P25", true, "SNDCP grant request from %u denied", srcId);
m_p25->m_rfState = RS_RF_REJECTED;
}
return false;
}
if (!m_p25->m_affiliations.isGranted(srcId)) {
if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) {
if (!net) {
LogWarning(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request) denied, no channels available, srcId = %u", srcId);
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_NO_RF_RSRC_AVAIL, TSBKO::ISP_SNDCP_CH_REQ, false, true);
writeRF_TSDU_Deny(WUID_FNE, srcId, ReasonCode::DENY_NO_RF_RSRC_AVAIL, TSBKO::ISP_SNDCP_CH_REQ, false, true);
::ActivityLog("P25", true, "SNDCP grant request from %u denied", srcId);
m_p25->m_rfState = RS_RF_REJECTED;
}
return false;
}
else {
if (m_p25->m_affiliations.grantCh(srcId, srcId, GRANT_TIMER_TIMEOUT, false, net)) {
uint32_t chNo = m_p25->m_affiliations.getGrantedCh(srcId);
if (m_p25->m_affiliations.grantCh(srcId, srcId, GRANT_TIMER_TIMEOUT, false, false)) {
chNo = m_p25->m_affiliations.getGrantedCh(srcId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
osp->setGrpVchId(voiceChData.chId());
@ -2500,7 +2514,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId,
}
}
else {
uint32_t chNo = m_p25->m_affiliations.getGrantedCh(srcId);
chNo = m_p25->m_affiliations.getGrantedCh(srcId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
osp->setGrpVchId(voiceChData.chId());
@ -2511,20 +2525,49 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId,
}
}
if (!net) {
::ActivityLog("P25", true, "SNDCP grant request from %u", srcId);
if (chNo > 0U) {
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
// callback REST API to permit the granted TG on the specified voice channel
if (m_p25->m_authoritative && m_p25->m_supervisor) {
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_p25->m_siteData.channelNo()) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_P25;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(srcId);
bool dataCh = true;
req["dataPermit"].set<bool>(dataCh);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_p25->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo);
m_p25->m_affiliations.releaseGrant(srcId, false);
writeRF_TSDU_Deny(srcId, srcId, ReasonCode::DENY_PTT_BONK, TSBKO::ISP_SNDCP_CH_REQ, false, true);
m_p25->m_rfState = RS_RF_REJECTED;
return false;
}
}
else {
::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo);
}
}
::ActivityLog("P25", true, "SNDCP grant request from %u", srcId);
if (m_verbose) {
LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, chNo = %u, dstId = %u",
LogMessage(LOG_RF, P25_TSDU_STR ", %s, chNo = %u, srcId = %u",
osp->toString().c_str(), osp->getDataChnNo(), osp->getSrcId());
}
// transmit group grant
writeRF_TSDU_SBF_Imm(osp.get(), true/*net*/);
writeRF_TSDU_SBF_Imm(osp.get(), true);
if (m_redundantGrant) {
for (int i = 0; i < 3; i++)
writeRF_TSDU_SBF(osp.get(), true/*net*/);
writeRF_TSDU_SBF(osp.get(), true);
}
}
return true;

@ -132,7 +132,6 @@ namespace p25
bool m_ctrlTSDUMBF;
bool m_sndcpChGrant;
bool m_disableGrantSrcIdCheck;
bool m_redundantImmediate;
bool m_redundantGrant;
@ -190,7 +189,7 @@ namespace p25
/// <summary>Helper to write a grant update packet.</summary>
void writeRF_TSDU_Grant_Update();
/// <summary>Helper to write a SNDCP grant packet.</summary>
bool writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId, bool skip = false, bool net = false);
bool writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip = false, uint32_t chNo = 0U);
/// <summary>Helper to write a unit to unit answer request packet.</summary>
void writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId);
/// <summary>Helper to write a acknowledge packet.</summary>

@ -15,6 +15,7 @@
#include "Defines.h"
#include "common/p25/P25Defines.h"
#include "common/p25/acl/AccessControl.h"
#include "common/p25/lc/tdulc/TDULCFactory.h"
#include "common/p25/P25Utils.h"
#include "common/p25/Sync.h"
#include "common/edac/CRC.h"
@ -36,6 +37,7 @@ using namespace p25;
// ---------------------------------------------------------------------------
const uint32_t CONN_WAIT_TIMEOUT = 1U;
const uint32_t SNDCP_READY_TIMEOUT = 10U;
// ---------------------------------------------------------------------------
// Public Class Members
@ -407,6 +409,16 @@ bool Data::process(uint8_t* data, uint32_t len)
}
}
break;
case PDUSAP::SNDCP_CTRL_DATA:
{
if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), lco = $%02X, blocksToFollow = %u",
m_rfDataHeader.getAMBTOpcode(), m_rfDataHeader.getBlocksToFollow());
}
processSNDCP();
}
break;
case PDUSAP::TRUNK_CTRL:
{
if (m_verbose) {
@ -713,8 +725,10 @@ bool Data::hasLLIdFNEReg(uint32_t llId) const
/// Helper to write user data as a P25 PDU packet.
/// </summary>
/// <param name="dataHeader"></param>
/// <param name="secondHeader"></param>
/// <param name="useSecondHeader"></param>
/// <param name="pduUserData"></param>
void Data::writeRF_PDU_User(data::DataHeader& dataHeader, const uint8_t* pduUserData)
void Data::writeRF_PDU_User(data::DataHeader& dataHeader, data::DataHeader& secondHeader, bool useSecondHeader, uint8_t* pduUserData)
{
assert(pduUserData != nullptr);
@ -726,24 +740,90 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, const uint8_t* pduUser
uint8_t block[P25_PDU_FEC_LENGTH_BYTES];
::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
// Generate the PDU header and 1/2 rate Trellis
uint32_t blocksToFollow = dataHeader.getBlocksToFollow();
if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u",
dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(),
dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(),
dataHeader.getHeaderOffset(), dataHeader.getLLId());
}
// generate the PDU header and 1/2 rate Trellis
dataHeader.encode(block);
Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS);
offset += P25_PDU_FEC_LENGTH_BITS;
// Generate the PDU data
DataBlock rspBlock = DataBlock();
uint32_t dataOffset = 0U;
for (uint8_t i = 0; i < dataHeader.getBlocksToFollow(); i++) {
rspBlock.setFormat(PDUFormatType::UNCONFIRMED);
rspBlock.setSerialNo(0U);
rspBlock.setData(pduUserData + dataOffset);
// generate the second PDU header
if (useSecondHeader) {
secondHeader.encode(m_pduUserData, true);
::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
rspBlock.encode(block);
secondHeader.encode(block);
Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS);
bitLength += P25_PDU_FEC_LENGTH_BITS;
offset += P25_PDU_FEC_LENGTH_BITS;
dataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES;
dataOffset += P25_PDU_HEADER_LENGTH_BYTES;
blocksToFollow--;
if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", OSP, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u",
secondHeader.getFormat(), secondHeader.getMFId(), secondHeader.getSAP(), secondHeader.getFullMessage(),
secondHeader.getBlocksToFollow(), secondHeader.getPadLength(), secondHeader.getNs(), secondHeader.getFSN(), secondHeader.getLastFragment(),
secondHeader.getHeaderOffset(), secondHeader.getLLId());
}
}
if (dataHeader.getFormat() != PDUFormatType::AMBT) {
edac::CRC::addCRC32(pduUserData, m_pduUserDataLength);
}
// generate the PDU data
for (uint32_t i = 0U; i < blocksToFollow; i++) {
DataBlock dataBlock = DataBlock();
dataBlock.setFormat((useSecondHeader) ? secondHeader : dataHeader);
dataBlock.setSerialNo(i);
dataBlock.setData(pduUserData + dataOffset);
// are we processing extended address data from the first block?
if (dataHeader.getSAP() == PDUSAP::EXT_ADDR && dataHeader.getFormat() == PDUFormatType::CONFIRMED &&
dataBlock.getSerialNo() == 0U) {
if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u, sap = $%02X, llId = %u",
dataBlock.getSerialNo(), dataBlock.getFormat(), dataBlock.getLastBlock(), dataBlock.getSAP(), dataBlock.getLLId());
if (m_dumpPDUData) {
uint8_t rawDataBlock[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES];
::memset(rawDataBlock, 0xAAU, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES);
dataBlock.getData(rawDataBlock);
Utils::dump(2U, "Data Block", rawDataBlock, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES);
}
}
}
else {
if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u",
(dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlock.getSerialNo() : i, dataBlock.getFormat(),
dataBlock.getLastBlock());
if (m_dumpPDUData) {
uint8_t rawDataBlock[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES];
::memset(rawDataBlock, 0xAAU, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES);
dataBlock.getData(rawDataBlock);
Utils::dump(2U, "Data Block", rawDataBlock, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES);
}
}
}
::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
dataBlock.encode(block);
Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS);
offset += P25_PDU_FEC_LENGTH_BITS;
dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES;
}
writeRF_PDU(data, bitLength);
@ -790,6 +870,96 @@ void Data::clock(uint32_t ms)
m_connQueueTable.erase(llId);
}
if (m_p25->m_sndcpSupport) {
// clock all the SNDCP ready timers
std::vector<uint32_t> sndcpReadyExpired = std::vector<uint32_t>();
for (auto entry : m_sndcpReadyTimers) {
uint32_t llId = entry.first;
m_sndcpReadyTimers[llId].clock(ms);
if (m_sndcpReadyTimers[llId].isRunning() && m_sndcpReadyTimers[llId].hasExpired()) {
sndcpReadyExpired.push_back(llId);
}
}
// process and SNDCP enabled LLIDs
for (auto entry : m_sndcpStateTable) {
uint32_t llId = entry.first;
SNDCPState::E state = entry.second;
switch (state) {
case SNDCPState::CLOSED:
break;
case SNDCPState::IDLE:
{
if (m_p25->m_permittedDstId == llId) {
m_sndcpReadyTimers[llId].start();
m_sndcpStateTable[llId] = SNDCPState::READY_S;
if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", SNDCP, llId = %u, state = %u", llId, (uint8_t)state);
}
}
}
break;
case SNDCPState::READY_S:
{
// has the LLID reached ready state expiration?
if (std::find(sndcpReadyExpired.begin(), sndcpReadyExpired.end(), llId) != sndcpReadyExpired.end()) {
m_sndcpStateTable[llId] = SNDCPState::IDLE;
if (m_verbose) {
LogMessage(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), llId = %u", llId);
}
std::unique_ptr<lc::TDULC> lc = std::make_unique<lc::tdulc::LC_CALL_TERM>();
m_p25->m_control->writeRF_TDULC(lc.get(), true);
if (m_p25->m_notifyCC) {
m_p25->notifyCC_ReleaseGrant(llId);
}
}
}
break;
case SNDCPState::READY:
break;
default:
break;
}
}
}
}
/// <summary>
/// Helper to initialize the SNDCP state for a logical link ID.
/// </summary>
/// <param name="llId"></param>
void Data::sndcpInitialize(uint32_t llId)
{
if (!isSNDCPInitialized(llId)) {
m_sndcpStateTable[llId] = SNDCPState::IDLE;
m_sndcpReadyTimers[llId] = Timer(1000U, SNDCP_READY_TIMEOUT);
m_sndcpStandyTimers[llId] = Timer(1000U, 1000U);
if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", SNDCP, first initialize, llId = %u, state = %u", llId, (uint8_t)SNDCPState::IDLE);
}
}
}
/// <summary>
/// Helper to determine if the logical link ID has been SNDCP initialized.
/// </summary>
/// <param name="llId"></param>
/// <returns></returns>
bool Data::isSNDCPInitialized(uint32_t llId) const
{
// lookup dynamic affiliation table entry
if (m_sndcpStateTable.find(llId) != m_sndcpStateTable.end()) {
return true;
}
return false;
}
// ---------------------------------------------------------------------------
@ -830,6 +1000,9 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb
m_fneRegTable(),
m_connQueueTable(),
m_connTimerTable(),
m_sndcpStateTable(),
m_sndcpReadyTimers(),
m_sndcpStandyTimers(),
m_dumpPDUData(dumpPDUData),
m_repeatPDU(repeatPDU),
m_verbose(verbose),
@ -851,6 +1024,10 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb
m_fneRegTable.clear();
m_connQueueTable.clear();
m_connTimerTable.clear();
m_sndcpStateTable.clear();
m_sndcpReadyTimers.clear();
m_sndcpStandyTimers.clear();
}
/// <summary>
@ -865,6 +1042,20 @@ Data::~Data()
delete[] m_pduUserData;
}
/// <summary>
/// Helper used to process SNDCP control data from PDU data.
/// </summary>
/// <param name="dataHeader"></param>
/// <param name="dataBlock"></param>
bool Data::processSNDCP()
{
if (!m_p25->m_sndcpSupport) {
return false;
}
return true;
}
/// <summary>
/// Write data processed from RF to the network.
/// </summary>

@ -57,11 +57,17 @@ namespace p25
bool hasLLIdFNEReg(uint32_t llId) const;
/// <summary>Helper to write user data as a P25 PDU packet.</summary>
void writeRF_PDU_User(data::DataHeader& dataHeader, const uint8_t* pduUserData);
void writeRF_PDU_User(data::DataHeader& dataHeader, data::DataHeader& secondHeader, bool useSecondHeader, uint8_t* pduUserData);
/// <summary>Updates the processor by the passed number of milliseconds.</summary>
void clock(uint32_t ms);
/** SNDCP */
/// <summary>Helper to initialize the SNDCP state for a logical link ID.</summary>
virtual void sndcpInitialize(uint32_t srcId);
/// <summary>Helper to determine if the logical link ID has been SNDCP initialized.</summary>
virtual bool isSNDCPInitialized(uint32_t srcId) const;
private:
friend class p25::Control;
Control* m_p25;
@ -96,6 +102,10 @@ namespace p25
std::unordered_map<uint32_t, std::tuple<uint8_t, ulong64_t>> m_connQueueTable;
std::unordered_map<uint32_t, Timer> m_connTimerTable;
std::unordered_map<uint32_t, defines::SNDCPState::E> m_sndcpStateTable;
std::unordered_map<uint32_t, Timer> m_sndcpReadyTimers;
std::unordered_map<uint32_t, Timer> m_sndcpStandyTimers;
bool m_dumpPDUData;
bool m_repeatPDU;
@ -107,6 +117,9 @@ namespace p25
/// <summary>Finalizes a instance of the Data class.</summary>
~Data();
/// <summary>Helper used to process SNDCP control data from PDU data.</summary>
bool processSNDCP();
/// <summary>Write data processed from RF to the network.</summary>
void writeNetwork(const uint8_t currentBlock, const uint8_t* data, uint32_t len, bool lastBlock);

Loading…
Cancel
Save

Powered by TurnKey Linux.