diff --git a/configs/config.example.yml b/configs/config.example.yml
index d1046423..155c67c4 100644
--- a/configs/config.example.yml
+++ b/configs/config.example.yml
@@ -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.
diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h
index 7b085dfa..d674efe5 100644
--- a/src/common/p25/P25Defines.h
+++ b/src/common/p25/P25Defines.h
@@ -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
+
///
- /// SNDCP Type
+ /// SNDCP PDU Message Type
///
- 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
+ };
+ }
+
+ ///
+ /// SNDCP Activation TDS States
+ ///
+ /// SNDCP Data Subscriber Unit Type
+ ///
+ 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
+ };
+ }
+
+ ///
+ /// SNDCP Ready Timer
+ ///
+ 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
+ };
+ }
+
+ ///
+ /// SNDCP Standby Timer
+ ///
+ 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
+ };
+ }
+
+ ///
+ /// SNDCP Reject Reasons
+ ///
+ 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
diff --git a/src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.cpp b/src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.cpp
new file mode 100644
index 00000000..db5d9609
--- /dev/null
+++ b/src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.cpp
@@ -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
+
+// ---------------------------------------------------------------------------
+// Public Class Members
+// ---------------------------------------------------------------------------
+
+///
+/// Initializes a new instance of the ISP_SNDCP_REC_REQ class.
+///
+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;
+}
+
+///
+/// Decode a trunking signalling block.
+///
+///
+///
+/// True, if TSBK was decoded, otherwise false.
+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;
+}
+
+///
+/// Encode a trunking signalling block.
+///
+///
+///
+///
+void ISP_SNDCP_REC_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis)
+{
+ assert(data != nullptr);
+
+ /* stub */
+}
+
+///
+/// Returns a string that represents the current TSBK.
+///
+///
+///
+std::string ISP_SNDCP_REC_REQ::toString(bool isp)
+{
+ return std::string("TSBKO, ISP_SNDCP_REC_REQ (SNDCP Data Channel Request)");
+}
+
+// ---------------------------------------------------------------------------
+// Private Class Members
+// ---------------------------------------------------------------------------
+
+///
+/// Internal helper to copy the the class.
+///
+///
+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;
+}
diff --git a/src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.h b/src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.h
new file mode 100644
index 00000000..40da66da
--- /dev/null
+++ b/src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.h
@@ -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:
+ /// Initializes a new instance of the ISP_SNDCP_REC_REQ class.
+ ISP_SNDCP_REC_REQ();
+
+ /// Decode a trunking signalling block.
+ bool decode(const uint8_t* data, bool rawTSBK = false) override;
+ /// Encode a trunking signalling block.
+ void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false) override;
+
+ /// Returns a string that represents the current TSBK.
+ std::string toString(bool isp = true) override;
+
+ public:
+ /// Flag indicationg SU has buffered data to send
+ __PROPERTY(bool, dataToSend, DataToSend);
+ /// SNDCP Data Service Options
+ __PROPERTY(uint8_t, dataServiceOptions, DataServiceOptions);
+ /// SNDCP Data Access Control
+ __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__
diff --git a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp
index 77915c03..63766722 100644
--- a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp
+++ b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp
@@ -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;
}
diff --git a/src/common/p25/lc/tsbk/TSBKFactory.cpp b/src/common/p25/lc/tsbk/TSBKFactory.cpp
index 9a16ff97..4854e191 100644
--- a/src/common/p25/lc/tsbk/TSBKFactory.cpp
+++ b/src/common/p25/lc/tsbk/TSBKFactory.cpp
@@ -186,6 +186,8 @@ std::unique_ptr 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 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;
diff --git a/src/common/p25/lc/tsbk/TSBKFactory.h b/src/common/p25/lc/tsbk/TSBKFactory.h
index 0b34e246..5a6aed33 100644
--- a/src/common/p25/lc/tsbk/TSBKFactory.h
+++ b/src/common/p25/lc/tsbk/TSBKFactory.h
@@ -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"
diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp
index 9d08b665..e4557183 100644
--- a/src/host/network/RESTAPI.cpp
+++ b/src/host/network/RESTAPI.cpp
@@ -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()) {
+ dataPermit = (bool)req["dataPermit"].get();
+ }
+
+ m_p25->permittedTG(dstId, dataPermit);
}
else {
errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp
index 7ab97c69..75a7c5b1 100644
--- a/src/host/p25/Control.cpp
+++ b/src/host/p25/Control.cpp
@@ -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(true);
m_control->m_unitToUnitAvailCheck = p25Protocol["unitToUnitAvailCheck"].as(true);
- m_control->m_sndcpChGrant = p25Protocol["sndcpGrant"].as(false);
+ m_sndcpSupport = p25Protocol["sndcpSupport"].as(false);
yaml::Node control = p25Protocol["control"];
m_enableControl = control["enable"].as(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.
///
///
-void Control::permittedTG(uint32_t dstId)
+///
+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);
+ }
}
///
diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h
index 8856ee2d..40365b2b 100644
--- a/src/host/p25/Control.h
+++ b/src/host/p25/Control.h
@@ -98,7 +98,7 @@ namespace p25
/// Sets a flag indicating whether P25 has supervisory functions and can send permit TG to voice channels.
void setSupervisor(bool supervisor) { m_supervisor = supervisor; }
/// Permits a TGID on a non-authoritative host.
- void permittedTG(uint32_t dstId);
+ void permittedTG(uint32_t dstId, bool dataPermit = false);
/// Grants a TGID on a non-authoritative host.
void grantTG(uint32_t srcId, uint32_t dstId, bool grp);
/// Releases a granted TG.
@@ -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;
diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp
index ab23f51c..4b8660c6 100644
--- a/src/host/p25/packet/ControlSignaling.cpp
+++ b/src/host/p25/packet/ControlSignaling.cpp
@@ -341,14 +341,36 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(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(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.
///
///
-///
///
-///
+///
///
-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 = std::make_unique();
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);
+ 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(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;
- }
+ ::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);
-
- ::ActivityLog("P25", true, "SNDCP grant request from %u denied", srcId);
- m_p25->m_rfState = RS_RF_REJECTED;
- }
+ 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(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) {
+ 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(state);
+ req["dstId"].set(srcId);
+ bool dataCh = true;
+ req["dataPermit"].set(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",
- osp->toString().c_str(), osp->getDataChnNo(), osp->getSrcId());
- }
+ if (m_verbose) {
+ 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*/);
- if (m_redundantGrant) {
- for (int i = 0; i < 3; i++)
- writeRF_TSDU_SBF(osp.get(), true/*net*/);
+ // transmit group grant
+ writeRF_TSDU_SBF_Imm(osp.get(), true);
+ if (m_redundantGrant) {
+ for (int i = 0; i < 3; i++)
+ writeRF_TSDU_SBF(osp.get(), true);
+ }
}
return true;
diff --git a/src/host/p25/packet/ControlSignaling.h b/src/host/p25/packet/ControlSignaling.h
index faad840d..9cea83e1 100644
--- a/src/host/p25/packet/ControlSignaling.h
+++ b/src/host/p25/packet/ControlSignaling.h
@@ -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
/// Helper to write a grant update packet.
void writeRF_TSDU_Grant_Update();
/// Helper to write a SNDCP grant packet.
- 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);
/// Helper to write a unit to unit answer request packet.
void writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId);
/// Helper to write a acknowledge packet.
diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp
index 15f8450c..1cec7dfd 100644
--- a/src/host/p25/packet/Data.cpp
+++ b/src/host/p25/packet/Data.cpp
@@ -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.
///
///
+///
+///
///
-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);
+ 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_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);
- rspBlock.encode(block);
+ dataBlock.encode(block);
Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS);
+
offset += P25_PDU_FEC_LENGTH_BITS;
- dataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES;
+ 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 sndcpReadyExpired = std::vector();
+ 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 = std::make_unique();
+ 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;
+ }
+ }
+ }
+}
+
+///
+/// Helper to initialize the SNDCP state for a logical link ID.
+///
+///
+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);
+ }
+ }
+}
+
+///
+/// Helper to determine if the logical link ID has been SNDCP initialized.
+///
+///
+///
+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();
}
///
@@ -865,6 +1042,20 @@ Data::~Data()
delete[] m_pduUserData;
}
+///
+/// Helper used to process SNDCP control data from PDU data.
+///
+///
+///
+bool Data::processSNDCP()
+{
+ if (!m_p25->m_sndcpSupport) {
+ return false;
+ }
+
+ return true;
+}
+
///
/// Write data processed from RF to the network.
///
diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h
index 9736f103..6e389d6e 100644
--- a/src/host/p25/packet/Data.h
+++ b/src/host/p25/packet/Data.h
@@ -57,11 +57,17 @@ namespace p25
bool hasLLIdFNEReg(uint32_t llId) const;
/// Helper to write user data as a P25 PDU packet.
- 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);
/// Updates the processor by the passed number of milliseconds.
void clock(uint32_t ms);
+ /** SNDCP */
+ /// Helper to initialize the SNDCP state for a logical link ID.
+ virtual void sndcpInitialize(uint32_t srcId);
+ /// Helper to determine if the logical link ID has been SNDCP initialized.
+ virtual bool isSNDCPInitialized(uint32_t srcId) const;
+
private:
friend class p25::Control;
Control* m_p25;
@@ -96,6 +102,10 @@ namespace p25
std::unordered_map> m_connQueueTable;
std::unordered_map m_connTimerTable;
+ std::unordered_map m_sndcpStateTable;
+ std::unordered_map m_sndcpReadyTimers;
+ std::unordered_map m_sndcpStandyTimers;
+
bool m_dumpPDUData;
bool m_repeatPDU;
@@ -107,6 +117,9 @@ namespace p25
/// Finalizes a instance of the Data class.
~Data();
+ /// Helper used to process SNDCP control data from PDU data.
+ bool processSNDCP();
+
/// Write data processed from RF to the network.
void writeNetwork(const uint8_t currentBlock, const uint8_t* data, uint32_t len, bool lastBlock);