From e90350d350a46c2e226026f93f4f7654c45e5159 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 25 Jun 2024 15:54:16 -0400 Subject: [PATCH] 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); --- configs/config.example.yml | 4 +- src/common/p25/P25Defines.h | 118 ++++++++++- src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.cpp | 103 +++++++++ src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.h | 57 +++++ src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp | 14 +- src/common/p25/lc/tsbk/TSBKFactory.cpp | 4 +- src/common/p25/lc/tsbk/TSBKFactory.h | 1 + src/host/network/RESTAPI.cpp | 9 +- src/host/p25/Control.cpp | 13 +- src/host/p25/Control.h | 3 +- src/host/p25/packet/ControlSignaling.cpp | 121 +++++++---- src/host/p25/packet/ControlSignaling.h | 3 +- src/host/p25/packet/Data.cpp | 211 ++++++++++++++++++- src/host/p25/packet/Data.h | 15 +- 14 files changed, 596 insertions(+), 80 deletions(-) create mode 100644 src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.cpp create mode 100644 src/common/p25/lc/tsbk/ISP_SNDCP_REC_REQ.h 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);