diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c3d26ac8..f5e59bc2 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -28,6 +28,7 @@ file(GLOB common_SRC "src/common/p25/lc/tdulc/*.cpp" "src/common/p25/lc/tsbk/*.cpp" "src/common/p25/lc/tsbk/mbt/*.cpp" + "src/common/p25/sndcp/*.cpp" "src/common/p25/lookups/*.cpp" # NXDN module @@ -68,6 +69,7 @@ file(GLOB common_INCLUDE "src/common/p25/lc/tdulc/*.h" "src/common/p25/lc/tsbk/*.h" "src/common/p25/lc/tsbk/mbt/*.h" + "src/common/p25/sndcp/*.h" "src/common/p25/lookups/*.h" # NXDN module diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index d674efe5..b4488624 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -356,8 +356,8 @@ namespace p25 /// namespace PDURegType { enum : uint8_t { - CNCT = 0x00U, // Connect - DISCNCT = 0x01U, // Disconnect + CONNECT = 0x00U, // Connect + DISCONNECT = 0x01U, // Disconnect ACCPT = 0x04U, // Accept DENY = 0x05U // Deny }; @@ -393,7 +393,18 @@ namespace p25 READY = 3U, // Ready - SU Activated and Rx/Tx Data CLOSED = 4U, // Closed - SU not yet Registered or Deregistered - ILLEGAL = 255U, // Illegal/Unknown + ILLEGAL = 255U // Illegal/Unknown + }; + } + + /// + /// SNDCP Network Address Type + /// + namespace SNDCPNAT { + enum : uint8_t { + IPV4_STATIC_ADDR = 0U, // IPv4 Static Address + IPV4_DYN_ADDR = 1U, // IPv4 Dynamic Address + IPV4_NO_ADDRESS = 15U // No Address }; } @@ -407,6 +418,7 @@ namespace p25 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 + ALT_T_AND_C_DATA_VOICE = 5U // Alternating Trunked and Conventional Voice & Data }; } @@ -482,6 +494,16 @@ namespace p25 }; } + /// + /// SNDCP Deactivation Types + /// + namespace SNDCPDeactivationType { + enum : uint8_t { + DEACT_ALL = 0U, // Deactivate all NSAPIs + DEACT_THIS_PDU = 1U // Deactivate NSAPI in this PDU + }; + } + const uint8_t LC_SVC_OPT_EMERGENCY = 0x80U; const uint8_t LC_SVC_OPT_ENCRYPTION = 0x40U; diff --git a/src/common/p25/sndcp/SNDCPCtxActAccept.cpp b/src/common/p25/sndcp/SNDCPCtxActAccept.cpp new file mode 100644 index 00000000..417dacea --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxActAccept.cpp @@ -0,0 +1,110 @@ +// 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/P25Defines.h" +#include "p25/sndcp/SNDCPCtxActAccept.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::sndcp; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the SNDCPCtxActAccept class. +/// +SNDCPCtxActAccept::SNDCPCtxActAccept() : SNDCPPacket(), + m_priority(4U), + m_readyTimer(SNDCPReadyTimer::TEN_SECONDS), + m_standbyTimer(SNDCPStandbyTimer::ONE_MINUTE), + m_nat(SNDCPNAT::IPV4_STATIC_ADDR), + m_ipAddress(0U), + m_mtu(SNDCP_MTU_510) +{ + m_pduType = SNDCP_PDUType::ACT_TDS_CTX; +} + +/// +/// Decode a SNDCP context activation response. +/// +/// +/// True, if packet was decoded, otherwise false. +bool SNDCPCtxActAccept::decode(const uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::decodeHeader(data, false); + + m_priority = (uint8_t)((data[1U] >> 4) & 0x0FU); // Priority + m_readyTimer = (uint8_t)(data[1U] & 0x0FU); // Ready Timer + m_standbyTimer = (uint8_t)((data[2U] >> 4) & 0x0FU); // Standby Timer + m_nat = (uint8_t)(data[2U] & 0x0FU); // NAT + + m_ipAddress = 0U; // IP Address + m_ipAddress = data[3U]; + m_ipAddress = (m_ipAddress << 8) + data[4U]; + m_ipAddress = (m_ipAddress << 8) + data[5U]; + m_ipAddress = (m_ipAddress << 8) + data[6U]; + + m_mtu = (uint8_t)((data[9U] >> 4) & 0x0FU); // MTU + + return true; +} + +/// +/// Encode a SNDCP context activation response. +/// +/// +void SNDCPCtxActAccept::encode(uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::encodeHeader(data, true); + + data[1U] = ((m_priority << 4U) & 0xF0U) + // Priority + (m_readyTimer & 0x0FU); // Ready Timer + data[2U] = ((m_standbyTimer << 4U) & 0xF0U) + // Standby Timer + (m_nat & 0x0FU); // NAT + + data[3U] = (uint8_t)((m_ipAddress >> 24) & 0xFFU); // IP Address + data[4U] = (uint8_t)((m_ipAddress >> 16) & 0xFFU); + data[5U] = (uint8_t)((m_ipAddress >> 8) & 0xFFU); + data[6U] = (uint8_t)((m_ipAddress >> 0) & 0xFFU); + + data[9U] = ((m_mtu << 4U) & 0xF0U); // MTU +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/// +/// Internal helper to copy the the class. +/// +/// +void SNDCPCtxActAccept::copy(const SNDCPCtxActAccept& data) +{ + m_priority = data.m_priority; + m_readyTimer = data.m_readyTimer; + m_standbyTimer = data.m_standbyTimer; + m_nat = data.m_nat; + + m_ipAddress = data.m_ipAddress; + + m_mtu = data.m_mtu; +} diff --git a/src/common/p25/sndcp/SNDCPCtxActAccept.h b/src/common/p25/sndcp/SNDCPCtxActAccept.h new file mode 100644 index 00000000..21d073de --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxActAccept.h @@ -0,0 +1,62 @@ +// 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_SNDCP__SNDCPCTXACTACCEPT_H__) +#define __P25_SNDCP__SNDCPCTXACTACCEPT_H__ + +#include "common/Defines.h" +#include "common/p25/sndcp/SNDCPPacket.h" +#include "common/Utils.h" + +#include + +namespace p25 +{ + namespace sndcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a SNDCP PDU context accept response. + // --------------------------------------------------------------------------- + + class HOST_SW_API SNDCPCtxActAccept : public SNDCPPacket { + public: + /// Initializes a new instance of the SNDCPCtxActAccept class. + SNDCPCtxActAccept(); + + /// Decode a SNDCP context activation response. + bool decode(const uint8_t* data); + /// Encode a SNDCP context activation response. + void encode(uint8_t* data); + + public: + /// Priority + __PROPERTY(uint8_t, priority, Priority); + /// Ready Timer + __PROPERTY(uint8_t, readyTimer, ReadyTimer); + /// Ready Timer + __PROPERTY(uint8_t, standbyTimer, StandbyTimer); + /// Network Address Type + __PROPERTY(uint8_t, nat, NAT); + + /// IP Address + __PROPERTY(ulong64_t, ipAddress, IPAddress); + + /// MTU + __PROPERTY(uint8_t, mtu, MTU); + + __COPY(SNDCPCtxActAccept); + }; + } // namespace sndcp +} // namespace p25 + +#endif // __P25_SNDCP__SNDCPCTXACTACCEPT_H__ diff --git a/src/common/p25/sndcp/SNDCPCtxActReject.cpp b/src/common/p25/sndcp/SNDCPCtxActReject.cpp new file mode 100644 index 00000000..cd40d04d --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxActReject.cpp @@ -0,0 +1,73 @@ +// 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/P25Defines.h" +#include "p25/sndcp/SNDCPCtxActReject.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::sndcp; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the SNDCPCtxActReject class. +/// +SNDCPCtxActReject::SNDCPCtxActReject() : SNDCPPacket(), + m_rejectCode(SNDCPRejectReason::ANY_REASON) +{ + m_pduType = SNDCP_PDUType::ACT_TDS_CTX_REJECT; +} + +/// +/// Decode a SNDCP context activation reject packet. +/// +/// +/// True, if packet was decoded, otherwise false. +bool SNDCPCtxActReject::decode(const uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::decodeHeader(data, false); + + m_rejectCode = data[1U]; // Reject Code + + return true; +} + +/// +/// Encode a SNDCP context activation reject packet. +/// +/// +void SNDCPCtxActReject::encode(uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::encodeHeader(data, true); + + data[1U] = m_rejectCode; // Reject Code +} + +/// +/// Internal helper to copy the the class. +/// +/// +void SNDCPCtxActReject::copy(const SNDCPCtxActReject& data) +{ + m_rejectCode = data.m_rejectCode; +} diff --git a/src/common/p25/sndcp/SNDCPCtxActReject.h b/src/common/p25/sndcp/SNDCPCtxActReject.h new file mode 100644 index 00000000..60b2dbd6 --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxActReject.h @@ -0,0 +1,50 @@ +// 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_SNDCP__SNDCPCTXACTREJECT_H__) +#define __P25_SNDCP__SNDCPCTXACTREJECT_H__ + +#include "common/Defines.h" +#include "common/p25/sndcp/SNDCPPacket.h" +#include "common/Utils.h" + +#include + +namespace p25 +{ + namespace sndcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a SNDCP PDU context activation reject response. + // --------------------------------------------------------------------------- + + class HOST_SW_API SNDCPCtxActReject : public SNDCPPacket { + public: + /// Initializes a new instance of the SNDCPCtxActReject class. + SNDCPCtxActReject(); + + /// Decode a SNDCP context activation reject packet. + bool decode(const uint8_t* data); + /// Encode a SNDCP context activation reject packet. + void encode(uint8_t* data); + + public: + /// Reject Code + __PROPERTY(uint8_t, rejectCode, RejectCode); + + __COPY(SNDCPCtxActReject); + }; + } // namespace sndcp +} // namespace p25 + +#endif // __P25_SNDCP__SNDCPCTXACTREJECT_H__ diff --git a/src/common/p25/sndcp/SNDCPCtxActRequest.cpp b/src/common/p25/sndcp/SNDCPCtxActRequest.cpp new file mode 100644 index 00000000..4e50022f --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxActRequest.cpp @@ -0,0 +1,97 @@ +// 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/P25Defines.h" +#include "p25/sndcp/SNDCPCtxActRequest.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::sndcp; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the SNDCPCtxActRequest class. +/// +SNDCPCtxActRequest::SNDCPCtxActRequest() : SNDCPPacket(), + m_nat(SNDCPNAT::IPV4_NO_ADDRESS), + m_ipAddress(0U), + m_dsut(SNDCP_DSUT::ALT_T_AND_C_DATA_VOICE) +{ + m_pduType = SNDCP_PDUType::ACT_TDS_CTX; +} + +/// +/// Decode a SNDCP context activation request. +/// +/// +/// True, if packet was decoded, otherwise false. +bool SNDCPCtxActRequest::decode(const uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::decodeHeader(data, false); + + m_nsapi = (uint8_t)((data[1U] >> 4) & 0x0FU); // NSAPI + m_nat = (uint8_t)((data[1U]) & 0x0FU); // NAT + + m_ipAddress = 0U; // IP Address + m_ipAddress = data[2U]; + m_ipAddress = (m_ipAddress << 8) + data[3U]; + m_ipAddress = (m_ipAddress << 8) + data[4U]; + m_ipAddress = (m_ipAddress << 8) + data[5U]; + + m_dsut = (uint8_t)((data[6U] >> 4) & 0x0FU); // Data Subscriber Unit Type + + return true; +} + +/// +/// Encode a SNDCP context activation request. +/// +/// +void SNDCPCtxActRequest::encode(uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::encodeHeader(data, true); + + data[1U] = ((m_nsapi << 4U) & 0xF0U) + // NSAPI + (m_nat & 0x0FU); // NAT + + data[2U] = (uint8_t)((m_ipAddress >> 24) & 0xFFU); // IP Address + data[3U] = (uint8_t)((m_ipAddress >> 16) & 0xFFU); + data[4U] = (uint8_t)((m_ipAddress >> 8) & 0xFFU); + data[5U] = (uint8_t)((m_ipAddress >> 0) & 0xFFU); + + data[6U] = ((m_dsut << 4U) & 0xF0U); // Data Subscriber Unit Type +} + +/// +/// Internal helper to copy the the class. +/// +/// +void SNDCPCtxActRequest::copy(const SNDCPCtxActRequest& data) +{ + m_nsapi = data.m_nsapi; + m_nat = data.m_nat; + + m_ipAddress = data.m_ipAddress; + + m_dsut = data.m_dsut; +} diff --git a/src/common/p25/sndcp/SNDCPCtxActRequest.h b/src/common/p25/sndcp/SNDCPCtxActRequest.h new file mode 100644 index 00000000..148836de --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxActRequest.h @@ -0,0 +1,56 @@ +// 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_SNDCP__SNDCPCTXACTREQUEST_H__) +#define __P25_SNDCP__SNDCPCTXACTREQUEST_H__ + +#include "common/Defines.h" +#include "common/p25/sndcp/SNDCPPacket.h" +#include "common/Utils.h" + +#include + +namespace p25 +{ + namespace sndcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a SNDCP PDU context activation request. + // --------------------------------------------------------------------------- + + class HOST_SW_API SNDCPCtxActRequest : public SNDCPPacket { + public: + /// Initializes a new instance of the SNDCPCtxActRequest class. + SNDCPCtxActRequest(); + + /// Decode a SNDCP context activation request. + bool decode(const uint8_t* data); + /// Encode a SNDCP context activation request. + void encode(uint8_t* data); + + public: + /// Network Address Type + __PROPERTY(uint8_t, nat, NAT); + + /// IP Address + __PROPERTY(ulong64_t, ipAddress, IPAddress); + + /// Data Subscriber Unit Type + __PROPERTY(uint8_t, dsut, DSUT); + + __COPY(SNDCPCtxActRequest); + }; + } // namespace sndcp +} // namespace p25 + +#endif // __P25_SNDCP__SNDCPCTXACTREQUEST_H__ diff --git a/src/common/p25/sndcp/SNDCPCtxDeactivation.cpp b/src/common/p25/sndcp/SNDCPCtxDeactivation.cpp new file mode 100644 index 00000000..d2831f6e --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxDeactivation.cpp @@ -0,0 +1,73 @@ +// 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/P25Defines.h" +#include "p25/sndcp/SNDCPCtxDeactivation.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::sndcp; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the SNDCPCtxDeactivation class. +/// +SNDCPCtxDeactivation::SNDCPCtxDeactivation() : SNDCPPacket(), + m_deactType(SNDCPDeactivationType::DEACT_ALL) +{ + m_pduType = SNDCP_PDUType::DEACT_TDS_CTX_REQ; +} + +/// +/// Decode a SNDCP context deactivation packet. +/// +/// +/// True, if packet was decoded, otherwise false. +bool SNDCPCtxDeactivation::decode(const uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::decodeHeader(data, false); + + m_deactType = data[1U]; // Deactivation Type + + return true; +} + +/// +/// Encode a SNDCP context deactivation packet. +/// +/// +void SNDCPCtxDeactivation::encode(uint8_t* data) +{ + assert(data != nullptr); + + SNDCPPacket::encodeHeader(data, true); + + data[1U] = m_deactType; // Deactivation Type +} + +/// +/// Internal helper to copy the the class. +/// +/// +void SNDCPCtxDeactivation::copy(const SNDCPCtxDeactivation& data) +{ + m_deactType = data.m_deactType; +} diff --git a/src/common/p25/sndcp/SNDCPCtxDeactivation.h b/src/common/p25/sndcp/SNDCPCtxDeactivation.h new file mode 100644 index 00000000..5315f986 --- /dev/null +++ b/src/common/p25/sndcp/SNDCPCtxDeactivation.h @@ -0,0 +1,51 @@ +// 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_SNDCP__SNDCPCTXDEACTIVATION_H__) +#define __P25_SNDCP__SNDCPCTXDEACTIVATION_H__ + +#include "common/Defines.h" +#include "common/p25/sndcp/SNDCPPacket.h" +#include "common/Utils.h" + +#include + +namespace p25 +{ + namespace sndcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a SNDCP PDU context deactivation. + // --------------------------------------------------------------------------- + + class HOST_SW_API SNDCPCtxDeactivation : public SNDCPPacket { + public: + /// Initializes a new instance of the SNDCPCtxDeactivation class. + SNDCPCtxDeactivation(); + + /// Decode a SNDCP context deactivation packet. + bool decode(const uint8_t* data); + /// Encode a SNDCP context deactivation packet. + void encode(uint8_t* data); + + public: + /** Common Data */ + /// Deactivation Type + __PROPERTY(uint8_t, deactType, DeactType); + + __COPY(SNDCPCtxDeactivation); + }; + } // namespace sndcp +} // namespace p25 + +#endif // __P25_SNDCP__SNDCPCTXDEACTIVATION_H__ diff --git a/src/common/p25/sndcp/SNDCPFactory.cpp b/src/common/p25/sndcp/SNDCPFactory.cpp new file mode 100644 index 00000000..9aca8f79 --- /dev/null +++ b/src/common/p25/sndcp/SNDCPFactory.cpp @@ -0,0 +1,90 @@ +// 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/P25Defines.h" +#include "p25/sndcp/SNDCPFactory.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::sndcp; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the SNDCPFactory class. +/// +SNDCPFactory::SNDCPFactory() = default; + +/// +/// Finalizes a instance of SNDCPFactory class. +/// +SNDCPFactory::~SNDCPFactory() = default; + +/// +/// Create an instance of a SNDCPPacket. +/// +/// +/// True, if packet was decoded, otherwise false. +std::unique_ptr SNDCPFactory::create(const uint8_t* data) +{ + assert(data != nullptr); + + uint8_t pduType = (uint8_t)((data[0U] >> 4) & 0x0FU); // SNDCP PDU Message Type + + switch (pduType) { + case SNDCP_PDUType::ACT_TDS_CTX: + return decode(new SNDCPCtxActRequest(), data); + break; + case SNDCP_PDUType::DEACT_TDS_CTX_REQ: + return decode(new SNDCPCtxDeactivation(), data); + break; + case SNDCP_PDUType::RF_CONFIRMED: + break; + case SNDCP_PDUType::RF_UNCONFIRMED: + break; + default: + LogError(LOG_P25, "SNDCPFactory::create(), unknown SNDCP PDU value, pduType = $%02X", pduType); + break; + } + + return nullptr; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// +/// +/// +/// +/// +/// +std::unique_ptr SNDCPFactory::decode(SNDCPPacket* packet, const uint8_t* data) +{ + assert(packet != nullptr); + assert(data != nullptr); + + if (!packet->decode(data)) { + return nullptr; + } + + return std::unique_ptr(packet); +} diff --git a/src/common/p25/sndcp/SNDCPFactory.h b/src/common/p25/sndcp/SNDCPFactory.h new file mode 100644 index 00000000..e6847b8d --- /dev/null +++ b/src/common/p25/sndcp/SNDCPFactory.h @@ -0,0 +1,50 @@ +// 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_SNDCP__SNDCP_FACTORY_H__) +#define __P25_SNDCP__SNDCP_FACTORY_H__ + +#include "common/Defines.h" + +#include "common/p25/sndcp/SNDCPPacket.h" +#include "common/p25/sndcp/SNDCPCtxActAccept.h" +#include "common/p25/sndcp/SNDCPCtxActReject.h" +#include "common/p25/sndcp/SNDCPCtxActRequest.h" +#include "common/p25/sndcp/SNDCPCtxDeactivation.h" + +namespace p25 +{ + namespace sndcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Helper class to instantiate an instance of a SNDCP packet. + // --------------------------------------------------------------------------- + + class HOST_SW_API SNDCPFactory { + public: + /// Initializes a new instance of the SNDCPFactory class. + SNDCPFactory(); + /// Finalizes a instance of the SNDCPFactory class. + ~SNDCPFactory(); + + /// Create an instance of a SNDCPPacket. + static std::unique_ptr create(const uint8_t* data); + + private: + /// + static std::unique_ptr decode(SNDCPPacket* packet, const uint8_t* data); + }; + } // namespace sndcp +} // namespace p25 + +#endif // __P25_SNDCP__SNDCP_FACTORY_H__ diff --git a/src/common/p25/sndcp/SNDCPPacket.cpp b/src/common/p25/sndcp/SNDCPPacket.cpp new file mode 100644 index 00000000..6a049313 --- /dev/null +++ b/src/common/p25/sndcp/SNDCPPacket.cpp @@ -0,0 +1,104 @@ +// 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/P25Defines.h" +#include "p25/sndcp/SNDCPPacket.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::sndcp; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a copy instance of the SNDCPPacket class. +/// +/// +SNDCPPacket::SNDCPPacket(const SNDCPPacket& data) : SNDCPPacket() +{ + copy(data); +} + +/// +/// Initializes a new instance of the SNDCPPacket class. +/// +SNDCPPacket::SNDCPPacket() : + m_pduType(SNDCP_PDUType::ACT_TDS_CTX), + m_sndcpVersion(SNDCP_VERSION_1), + m_nsapi(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of SNDCPPacket class. +/// +SNDCPPacket::~SNDCPPacket() = default; + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/// +/// Internal helper to decode a SNDCP header. +/// +/// +/// +/// True, if header was decoded, otherwise false. +bool SNDCPPacket::decodeHeader(const uint8_t* data, bool outbound) +{ + assert(data != nullptr); + + m_pduType = (uint8_t)((data[0U] >> 4) & 0x0FU); // SNDCP PDU Message Type + + if (m_pduType == SNDCP_PDUType::ACT_TDS_CTX && !outbound) { + m_sndcpVersion = (uint8_t)((data[0U]) & 0x0FU); // SNDCP Version + } else { + m_nsapi = (uint8_t)((data[0U]) & 0x0FU); // NSAPI + } + + return true; +} + +/// +/// Internal helper to encode a SNDCP header. +/// +/// +/// +void SNDCPPacket::encodeHeader(uint8_t* data, bool outbound) +{ + assert(data != nullptr); + + data[0U] = ((m_pduType << 4U) & 0xF0U); // SNDCP PDU Message Type + + if (m_pduType == SNDCP_PDUType::ACT_TDS_CTX && !outbound) { + data[0U] += (m_sndcpVersion & 0x0FU); // SNDCP Version + } else { + data[0U] += (m_nsapi & 0x0FU); // NSAPI + } +} + +/// +/// Internal helper to copy the the class. +/// +/// +void SNDCPPacket::copy(const SNDCPPacket& data) +{ + m_pduType = data.m_pduType; + m_sndcpVersion = data.m_sndcpVersion; +} diff --git a/src/common/p25/sndcp/SNDCPPacket.h b/src/common/p25/sndcp/SNDCPPacket.h new file mode 100644 index 00000000..0b78fbf7 --- /dev/null +++ b/src/common/p25/sndcp/SNDCPPacket.h @@ -0,0 +1,64 @@ +// 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_SNDCP__SNDCPHEADER_H__) +#define __P25_SNDCP__SNDCPHEADER_H__ + +#include "common/Defines.h" +#include "common/Utils.h" + +#include + +namespace p25 +{ + namespace sndcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a SNDCP PDU packet header. + // --------------------------------------------------------------------------- + + class HOST_SW_API SNDCPPacket { + public: + /// Initializes a copy instance of the SNDCPPacket class. + SNDCPPacket(const SNDCPPacket& data); + /// Initializes a new instance of the SNDCPPacket class. + SNDCPPacket(); + /// Finalizes a instance of the SNDCPPacket class. + ~SNDCPPacket(); + + /// Decode a SNDCP packet. + virtual bool decode(const uint8_t* data) = 0; + /// Encode a SNDCP packet. + virtual void encode(uint8_t* data) = 0; + + public: + /** Common Data */ + /// SNDCP PDU Type. + __PROTECTED_PROPERTY(uint8_t, pduType, PDUType); + /// Link control opcode. + __READONLY_PROPERTY(uint8_t, sndcpVersion, SNDCPVersion); + /// Network Service Access Point Identifier + __PROTECTED_PROPERTY(uint8_t, nsapi, NSAPI); + + protected: + /// Internal helper to decode a SNDCP header. + bool decodeHeader(const uint8_t* data, bool outbound = false); + /// Internal helper to encode a SNDCP header. + void encodeHeader(uint8_t* data, bool outbound = false); + + __PROTECTED_COPY(SNDCPPacket); + }; + } // namespace sndcp +} // namespace p25 + +#endif // __P25_SNDCP__SNDCPHEADER_H__ diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index 1cec7dfd..1ffb843d 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -16,6 +16,7 @@ #include "common/p25/P25Defines.h" #include "common/p25/acl/AccessControl.h" #include "common/p25/lc/tdulc/TDULCFactory.h" +#include "common/p25/sndcp/SNDCPFactory.h" #include "common/p25/P25Utils.h" #include "common/p25/Sync.h" #include "common/edac/CRC.h" @@ -24,10 +25,11 @@ #include "p25/packet/Data.h" #include "ActivityLog.h" -using namespace p25::packet; -using namespace p25::data; -using namespace p25::defines; using namespace p25; +using namespace p25::defines; +using namespace p25::data; +using namespace p25::sndcp; +using namespace p25::packet; #include #include @@ -38,6 +40,7 @@ using namespace p25; const uint32_t CONN_WAIT_TIMEOUT = 1U; const uint32_t SNDCP_READY_TIMEOUT = 10U; +const uint32_t SNDCP_STANDBY_TIMEOUT = 60U; // --------------------------------------------------------------------------- // Public Class Members @@ -224,6 +227,12 @@ bool Data::process(uint8_t* data, uint32_t len) // process all blocks in the data stream uint32_t dataOffset = 0U; + // if the primary header has a header offset ensure data if offset by that amount + if (m_rfDataHeader.getHeaderOffset() > 0U) { + offset += m_rfDataHeader.getHeaderOffset() * 8; + m_pduUserDataLength -= m_rfDataHeader.getHeaderOffset(); + } + // if we are using a secondary header place it in the PDU user data buffer if (m_rfUseSecondHeader) { m_rfSecondHeader.getData(m_pduUserData + dataOffset); @@ -349,6 +358,36 @@ bool Data::process(uint8_t* data, uint32_t len) LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", m_rfDataHeader.getFormat(), m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); + + if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK && m_rfDataHeader.getResponseType() == PDUAckType::ACK) { + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u", + m_rfDataHeader.getLLId()); + } else { + if (m_rfDataHeader.getResponseClass() == PDUAckClass::NACK) { + switch (m_rfDataHeader.getResponseType()) { + case PDUAckType::NACK_ILLEGAL: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", + m_rfDataHeader.getLLId()); + break; + case PDUAckType::NACK_PACKET_CRC: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u", + m_rfDataHeader.getLLId()); + break; + case PDUAckType::NACK_SEQ: + case PDUAckType::NACK_OUT_OF_SEQ: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u", + m_rfDataHeader.getLLId()); + break; + case PDUAckType::NACK_UNDELIVERABLE: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u", + m_rfDataHeader.getLLId()); + break; + + default: + break; + } + } + } } if (m_repeatPDU) { @@ -367,14 +406,14 @@ bool Data::process(uint8_t* data, uint32_t len) { uint8_t regType = (m_pduUserData[0] >> 4) & 0x0F; switch (regType) { - case PDURegType::CNCT: + case PDURegType::CONNECT: { uint32_t llId = (m_pduUserData[1U] << 16) + (m_pduUserData[2U] << 8) + m_pduUserData[3U]; ulong64_t ipAddr = (m_pduUserData[8U] << 24) + (m_pduUserData[9U] << 16) + (m_pduUserData[10U] << 8) + m_pduUserData[11U]; if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", CNCT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_ULONG(ipAddr).c_str()); + LogMessage(LOG_RF, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_ULONG(ipAddr).c_str()); } m_connQueueTable[llId] = std::make_tuple(m_rfDataHeader.getMFId(), ipAddr); @@ -383,12 +422,12 @@ bool Data::process(uint8_t* data, uint32_t len) m_connTimerTable[llId].start(); } break; - case PDURegType::DISCNCT: + case PDURegType::DISCONNECT: { uint32_t llId = (m_pduUserData[1U] << 16) + (m_pduUserData[2U] << 8) + m_pduUserData[3U]; if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", DISCNCT (Registration Request Disconnect), llId = %u", llId); + LogMessage(LOG_RF, P25_PDU_STR ", DISCONNECT (Registration Request Disconnect), llId = %u", llId); } if (hasLLIdFNEReg(llId)) { @@ -416,7 +455,7 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfDataHeader.getAMBTOpcode(), m_rfDataHeader.getBlocksToFollow()); } - processSNDCP(); + processSNDCPControl(); } break; case PDUSAP::TRUNK_CTRL: @@ -939,7 +978,7 @@ 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); + m_sndcpStandbyTimers[llId] = Timer(1000U, SNDCP_STANDBY_TIMEOUT); if (m_verbose) { LogMessage(LOG_RF, P25_PDU_STR ", SNDCP, first initialize, llId = %u, state = %u", llId, (uint8_t)SNDCPState::IDLE); @@ -962,6 +1001,37 @@ bool Data::isSNDCPInitialized(uint32_t llId) const return false; } +/// +/// Helper to reset the SNDCP state for a logical link ID. +/// +/// +/// +void Data::sndcpReset(uint32_t llId, bool callTerm) +{ + if (isSNDCPInitialized(llId)) { + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", SNDCP, reset, llId = %u, state = %u", llId, (uint8_t)m_sndcpStateTable[llId]); + } + + m_sndcpStateTable[llId] = SNDCPState::CLOSED; + m_sndcpReadyTimers[llId].stop(); + m_sndcpStandbyTimers[llId].stop(); + + if (callTerm) { + 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); + } + } + } +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- @@ -1002,7 +1072,7 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb m_connTimerTable(), m_sndcpStateTable(), m_sndcpReadyTimers(), - m_sndcpStandyTimers(), + m_sndcpStandbyTimers(), m_dumpPDUData(dumpPDUData), m_repeatPDU(repeatPDU), m_verbose(verbose), @@ -1027,7 +1097,7 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb m_sndcpStateTable.clear(); m_sndcpReadyTimers.clear(); - m_sndcpStandyTimers.clear(); + m_sndcpStandbyTimers.clear(); } /// @@ -1045,14 +1115,110 @@ Data::~Data() /// /// Helper used to process SNDCP control data from PDU data. /// -/// -/// -bool Data::processSNDCP() +bool Data::processSNDCPControl() { if (!m_p25->m_sndcpSupport) { return false; } + uint8_t data[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; + ::memset(data, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + std::unique_ptr packet = SNDCPFactory::create(m_pduUserData); + if (packet == nullptr) { + LogWarning(LOG_RF, P25_PDU_STR ", undecodable SNDCP packet"); + return false; + } + + uint32_t llId = m_rfDataHeader.getLLId(); + + switch (packet->getPDUType()) { + case SNDCP_PDUType::ACT_TDS_CTX: + { + SNDCPCtxActRequest* isp = static_cast(packet.get()); + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", SNDCP context activation request, llId = %u, ipAddr = %08lX, nat = $%02X, dsut = $%02X", llId, + isp->getIPAddress(), isp->getNAT(), isp->getDSUT()); + } + + DataHeader rspHeader = DataHeader(); + rspHeader.setFormat(PDUFormatType::CONFIRMED); + rspHeader.setMFId(MFG_STANDARD); + rspHeader.setAckNeeded(true); + rspHeader.setOutbound(true); + rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA); + rspHeader.setLLId(llId); + rspHeader.setBlocksToFollow(1U); + + // which network address type is this? + switch (isp->getNAT()) { + case SNDCPNAT::IPV4_STATIC_ADDR: + { + std::unique_ptr osp = std::make_unique(); + osp->setNSAPI(packet->getNSAPI()); + osp->setRejectCode(SNDCPRejectReason::DYN_IP_ALLOCATION_UNSUPPORTED); + + osp->encode(data); + writeRF_PDU_User(rspHeader, rspHeader, false, data); + + sndcpReset(llId, true); + + // TODO TODO TODO + } + break; + + case SNDCPNAT::IPV4_DYN_ADDR: + { + std::unique_ptr osp = std::make_unique(); + osp->setNSAPI(packet->getNSAPI()); + osp->setRejectCode(SNDCPRejectReason::STATIC_IP_ALLOCATION_UNSUPPORTED); + + osp->encode(data); + writeRF_PDU_User(rspHeader, rspHeader, false, data); + + sndcpReset(llId, true); + + // TODO TODO TODO + } + break; + + default: + { + std::unique_ptr osp = std::make_unique(); + osp->setNSAPI(packet->getNSAPI()); + osp->setRejectCode(SNDCPRejectReason::ANY_REASON); + + osp->encode(data); + writeRF_PDU_User(rspHeader, rspHeader, false, data); + + sndcpReset(llId, true); + } + break; + } + } + break; + + case SNDCP_PDUType::DEACT_TDS_CTX_REQ: + { + SNDCPCtxDeactivation* isp = static_cast(packet.get()); + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", SNDCP context deactivation request, llId = %u, deactType = %02X", llId, + isp->getDeactType()); + } + + writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, 0U, llId); + sndcpReset(llId, true); + } + break; + + default: + { + LogError(LOG_RF, P25_PDU_STR ", unhandled SNDCP PDU Type, pduType = $%02X", packet->getPDUType()); + sndcpReset(llId, true); + } + break; + } // switch (packet->getPDUType()) + return true; } diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h index 6e389d6e..61e74474 100644 --- a/src/host/p25/packet/Data.h +++ b/src/host/p25/packet/Data.h @@ -64,9 +64,11 @@ namespace p25 /** SNDCP */ /// Helper to initialize the SNDCP state for a logical link ID. - virtual void sndcpInitialize(uint32_t srcId); + void sndcpInitialize(uint32_t llId); /// Helper to determine if the logical link ID has been SNDCP initialized. - virtual bool isSNDCPInitialized(uint32_t srcId) const; + bool isSNDCPInitialized(uint32_t llId) const; + /// Helper to reset the SNDCP state for a logical link ID. + void sndcpReset(uint32_t llId, bool callTerm = false); private: friend class p25::Control; @@ -104,7 +106,7 @@ namespace p25 std::unordered_map m_sndcpStateTable; std::unordered_map m_sndcpReadyTimers; - std::unordered_map m_sndcpStandyTimers; + std::unordered_map m_sndcpStandbyTimers; bool m_dumpPDUData; bool m_repeatPDU; @@ -118,7 +120,7 @@ namespace p25 ~Data(); /// Helper used to process SNDCP control data from PDU data. - bool processSNDCP(); + bool processSNDCPControl(); /// Write data processed from RF to the network. void writeNetwork(const uint8_t currentBlock, const uint8_t* data, uint32_t len, bool lastBlock); diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 2dbdee10..6b8fe4ed 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -26,10 +26,10 @@ #include "p25/packet/Voice.h" #include "ActivityLog.h" -using namespace p25::packet; -using namespace p25::dfsi::defines; -using namespace p25::defines; using namespace p25; +using namespace p25::defines; +using namespace p25::dfsi::defines; +using namespace p25::packet; #include #include