From 297911042171389ef6a5d193886d0d6fd24dbda1 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 11 Nov 2024 20:30:16 -0500 Subject: [PATCH 01/68] add some mutex locking for affiliation lookups; --- src/common/lookups/AffiliationLookup.cpp | 24 +++++++++++++++++-- src/common/lookups/AffiliationLookup.h | 6 ++++- src/host/dmr/lookups/DMRAffiliationLookup.cpp | 12 +++++++++- src/host/dmr/lookups/DMRAffiliationLookup.h | 3 ++- src/host/p25/lookups/P25AffiliationLookup.cpp | 4 ++-- src/host/p25/lookups/P25AffiliationLookup.h | 3 ++- 6 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 168196aa..e4dde41c 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -20,6 +20,12 @@ using namespace lookups; const uint32_t UNIT_REG_TIMEOUT = 43200U; // 12 hours +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::mutex AffiliationLookup::m_mutex; + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -297,6 +303,7 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi return false; } + std::lock_guard lock(m_mutex); uint32_t chNo = m_chLookup->getFirstRFChannel(); m_chLookup->removeRFCh(chNo); @@ -326,6 +333,7 @@ void AffiliationLookup::touchGrant(uint32_t dstId) return; } + std::lock_guard lock(m_mutex); if (isGranted(dstId)) { m_grantTimers[dstId].start(); } @@ -333,12 +341,15 @@ void AffiliationLookup::touchGrant(uint32_t dstId) /* Helper to release the channel grant for the destination ID. */ -bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) +bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock) { if (dstId == 0U && !releaseAll) { return false; } + if (!noLock) + m_mutex.lock(); + // are we trying to release all grants? if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); @@ -354,6 +365,8 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) releaseGrant(dstId, false); } + if (!noLock) + m_mutex.unlock(); return true; } @@ -383,9 +396,14 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) } m_grantTimers[dstId].stop(); + + if (!noLock) + m_mutex.unlock(); return true; } + if (!noLock) + m_mutex.unlock(); return false; } @@ -527,6 +545,8 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId) void AffiliationLookup::clock(uint32_t ms) { + std::lock_guard lock(m_mutex); + // clock all the grant timers std::vector gntsToRel = std::vector(); for (auto entry : m_grantChTable) { @@ -540,7 +560,7 @@ void AffiliationLookup::clock(uint32_t ms) // release grants that have timed out for (uint32_t dstId : gntsToRel) { - releaseGrant(dstId, false); + releaseGrant(dstId, false, true); } if (!m_disableUnitRegTimeout) { diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index 1edc4a39..2c7ac586 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/lookups/AffiliationLookup.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace lookups { @@ -182,9 +183,10 @@ namespace lookups * @brief Helper to release the channel grant for the destination ID. * @param dstId Destination Address. * @param releaseAll Flag indicating all channel grants should be released. + * @param noLock Flag indicating no mutex lock operation should be performed while releasing. * @returns bool True, if channel grant was released, otherwise false. */ - virtual bool releaseGrant(uint32_t dstId, bool releaseAll); + virtual bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false); /** * @brief Helper to determine if the channel number is busy. * @param chNo Channel Number. @@ -298,6 +300,8 @@ namespace lookups bool m_disableUnitRegTimeout; bool m_verbose; + + static std::mutex m_mutex; }; } // namespace lookups diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.cpp b/src/host/dmr/lookups/DMRAffiliationLookup.cpp index 9791fa61..37b1c540 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.cpp +++ b/src/host/dmr/lookups/DMRAffiliationLookup.cpp @@ -86,12 +86,15 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s /* Helper to release the channel grant for the destination ID. */ -bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) +bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock) { if (dstId == 0U && !releaseAll) { return false; } + if (!noLock) + m_mutex.lock(); + // are we trying to release all grants? if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); @@ -107,6 +110,8 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) releaseGrant(dstId, false); } + if (!noLock) + m_mutex.unlock(); return true; } @@ -139,9 +144,14 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) } m_grantTimers[dstId].stop(); + + if (!noLock) + m_mutex.unlock(); return true; } + if (!noLock) + m_mutex.unlock(); return false; } diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.h b/src/host/dmr/lookups/DMRAffiliationLookup.h index 90c85125..0e394f12 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.h +++ b/src/host/dmr/lookups/DMRAffiliationLookup.h @@ -76,9 +76,10 @@ namespace dmr * @brief Helper to release the channel grant for the destination ID. * @param dstId Destination Address. * @param releaseAll Flag indicating all channel grants should be released. + * @param noLock Flag indicating no mutex lock operation should be performed while releasing. * @returns bool True, if channel grant was released, otherwise false. */ - bool releaseGrant(uint32_t dstId, bool releaseAll) override; + bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false) override; /** * @brief Helper to determine if the channel number is busy. * @param chNo Channel Number. diff --git a/src/host/p25/lookups/P25AffiliationLookup.cpp b/src/host/p25/lookups/P25AffiliationLookup.cpp index 72dda5e5..10bdbedf 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.cpp +++ b/src/host/p25/lookups/P25AffiliationLookup.cpp @@ -46,9 +46,9 @@ std::vector P25AffiliationLookup::clearGroupAff(uint32_t dstId, bool r /* Helper to release the channel grant for the destination ID. */ -bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) +bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock) { - bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll); + bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll, noLock); if (ret) { if (m_rfGrantChCnt > 0U) { m_p25->m_siteData.setChCnt(m_chLookup->rfChSize() + m_rfGrantChCnt); diff --git a/src/host/p25/lookups/P25AffiliationLookup.h b/src/host/p25/lookups/P25AffiliationLookup.h index 56540b6a..875f524f 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.h +++ b/src/host/p25/lookups/P25AffiliationLookup.h @@ -72,9 +72,10 @@ namespace p25 * @brief Helper to release the channel grant for the destination ID. * @param dstId Destination Address. * @param releaseAll Flag indicating all channel grants should be released. + * @param noLock Flag indicating no mutex lock operation should be performed while releasing. * @returns bool True, if channel grant was released, otherwise false. */ - bool releaseGrant(uint32_t dstId, bool releaseAll) override; + bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false) override; /** @} */ protected: From dc5e156a6aeb8eeae7d956bb34f2c515671ef3df Mon Sep 17 00:00:00 2001 From: ilyacodes <33097525+ilyacodes@users.noreply.github.com> Date: Wed, 13 Nov 2024 17:29:35 -0500 Subject: [PATCH 02/68] Update documentation for Golay (23,12,7) FEC (#75) --- src/common/edac/Golay24128.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/edac/Golay24128.h b/src/common/edac/Golay24128.h index 92f52f38..e9562792 100644 --- a/src/common/edac/Golay24128.h +++ b/src/common/edac/Golay24128.h @@ -35,12 +35,12 @@ namespace edac /** * @brief Decode Golay (23,12,7) FEC. * @param code - * @returns uint8_t Number of errors detected. + * @returns uint32_t Data decoded with Golay FEC */ static uint32_t decode23127(uint32_t code); /** * @brief Decode Golay (24,12,8) FEC. - * @param code + * @param code * @param out * @returns bool */ From 973fa5974007a7b56d5e837020563b9f9dbed11c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Nov 2024 11:46:01 -0500 Subject: [PATCH 03/68] add extra error handling and check if the lookup tables are available before attempting to process; --- src/fne/network/PeerNetwork.cpp | 15 +++++++++++++++ src/sysview/network/PeerNetwork.cpp | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index b66446ff..0339105a 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -193,6 +193,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // check that we got the appropriate data if (decompressedLen == m_tgidSize) { + if (m_tidLookup == nullptr) { + LogError(LOG_NET, "Talkgroup ID lookup not available yet."); + goto tid_lookup_cleanup; // yes - I hate myself; but this is quick + } + // store to file std::unique_ptr __str = std::make_unique(decompressedLen + 1U); char* str = __str.get(); @@ -325,6 +330,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // check that we got the appropriate data if (decompressedLen == m_ridSize) { + if (m_ridLookup == nullptr) { + LogError(LOG_NET, "Radio ID lookup not available yet."); + goto rid_lookup_cleanup; // yes - I hate myself; but this is quick + } + // store to file std::unique_ptr __str = std::make_unique(decompressedLen + 1U); char* str = __str.get(); @@ -457,6 +467,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // check that we got the appropriate data if (decompressedLen == m_pidSize) { + if (m_pidLookup == nullptr) { + LogError(LOG_NET, "Peer ID lookup not available yet."); + goto pid_lookup_cleanup; // yes - I hate myself; but this is quick + } + // store to file std::unique_ptr __str = std::make_unique(decompressedLen + 1U); char* str = __str.get(); diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index 29f73048..1de24f5d 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -209,6 +209,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // check that we got the appropriate data if (decompressedLen == m_tgidSize) { + if (m_tidLookup == nullptr) { + LogError(LOG_NET, "Talkgroup ID lookups not available yet."); + goto tid_lookup_cleanup; // yes - I hate myself; but this is quick + } + // store to file std::unique_ptr __str = std::make_unique(decompressedLen + 1U); char* str = __str.get(); @@ -341,6 +346,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // check that we got the appropriate data if (decompressedLen == m_ridSize) { + if (m_ridLookup == nullptr) { + LogError(LOG_NET, "Radio ID lookups not available yet."); + goto rid_lookup_cleanup; // yes - I hate myself; but this is quick + } + // store to file std::unique_ptr __str = std::make_unique(decompressedLen + 1U); char* str = __str.get(); From aadfbd4393c2d0c7819278b29538bf79f8c92e15 Mon Sep 17 00:00:00 2001 From: Jim <25770089+faultywarrior@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:58:54 -0600 Subject: [PATCH 04/68] Fixed typo (#76) Co-authored-by: faulty --- src/bridge/HostBridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 89faa792..fe6fef7f 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -925,7 +925,7 @@ bool HostBridge::createNetwork() LogInfo(" PCM over UDP Audio: %s", m_udpAudio ? "yes" : "no"); if (m_udpAudio) { LogInfo(" UDP Audio Metadata: %s", m_udpMetadata ? "yes" : "no"); - LogInfo(" UDP Audio end Address: %s", m_udpSendAddress.c_str()); + LogInfo(" UDP Audio Send Address: %s", m_udpSendAddress.c_str()); LogInfo(" UDP Audio Send Port: %u", m_udpSendPort); LogInfo(" UDP Audio Receive Address: %s", m_udpReceiveAddress.c_str()); LogInfo(" UDP Audio Receive Port: %u", m_udpReceivePort); From 1528894099738b32f29bb9abf5f6a614a11f654d Mon Sep 17 00:00:00 2001 From: W3AXL Date: Tue, 3 Dec 2024 19:31:04 -0500 Subject: [PATCH 05/68] Implement override of default RTS/DTR behavior for DVM-V24-V2 boards with hardware boot control. (#77) * initial work to support DVM-V24 RTS/DTR boot modes * whoops forgot to remove this debug print --------- Co-authored-by: W3AXL <29879554+W3AXL@users.noreply.github.com> --- src/host/Host.Config.cpp | 9 ++++-- src/host/modem/port/PseudoPTYPort.cpp | 2 +- src/host/modem/port/UARTPort.cpp | 40 +++++++++++++++++++++++++-- src/host/modem/port/UARTPort.h | 5 ++-- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index eaab7480..9ad5d41b 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -552,12 +552,17 @@ bool Host::createModem() } if (portType == PTY_PORT) { - modemPort = new port::UARTPort(uartPort, serialSpeed, false); + modemPort = new port::UARTPort(uartPort, serialSpeed, false, false); LogInfo(" PTY Port: %s", uartPort.c_str()); LogInfo(" PTY Speed: %u", uartSpeed); } else { - modemPort = new port::UARTPort(uartPort, serialSpeed, true); + if (modemMode == MODEM_MODE_DFSI) { + modemPort = new port::UARTPort(uartPort, serialSpeed, false, true); + LogInfo(" RTS/DTR boot flags enabled"); + } else { + modemPort = new port::UARTPort(uartPort, serialSpeed, true, false); + } LogInfo(" UART Port: %s", uartPort.c_str()); LogInfo(" UART Speed: %u", uartSpeed); } diff --git a/src/host/modem/port/PseudoPTYPort.cpp b/src/host/modem/port/PseudoPTYPort.cpp index 3e7948dd..cf17279f 100644 --- a/src/host/modem/port/PseudoPTYPort.cpp +++ b/src/host/modem/port/PseudoPTYPort.cpp @@ -30,7 +30,7 @@ using namespace modem::port; /* Initializes a new instance of the PseudoPTYPort class. */ -PseudoPTYPort::PseudoPTYPort(const std::string& symlink, SERIAL_SPEED speed, bool assertRTS) : UARTPort(speed, assertRTS), +PseudoPTYPort::PseudoPTYPort(const std::string& symlink, SERIAL_SPEED speed, bool assertRTS) : UARTPort(speed, assertRTS, false), m_symlink(symlink) { /* stub */ diff --git a/src/host/modem/port/UARTPort.cpp b/src/host/modem/port/UARTPort.cpp index 1f12a2df..cbd4592e 100644 --- a/src/host/modem/port/UARTPort.cpp +++ b/src/host/modem/port/UARTPort.cpp @@ -37,11 +37,12 @@ using namespace modem::port; /* Initializes a new instance of the UARTPort class. */ -UARTPort::UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : +UARTPort::UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS, bool rtsBoot) : m_isOpen(false), m_device(device), m_speed(speed), m_assertRTS(assertRTS), + m_rtsBoot(rtsBoot), #if defined(_WIN32) m_fd(INVALID_HANDLE_VALUE) #else @@ -334,10 +335,11 @@ int UARTPort::setNonblock(bool nonblock) /* Initializes a new instance of the UARTPort class. */ -UARTPort::UARTPort(SERIAL_SPEED speed, bool assertRTS) : +UARTPort::UARTPort(SERIAL_SPEED speed, bool assertRTS, bool rtsBoot) : m_isOpen(false), m_speed(speed), m_assertRTS(assertRTS), + m_rtsBoot(rtsBoot), #if defined(_WIN32) m_fd(INVALID_HANDLE_VALUE) #else @@ -500,6 +502,40 @@ bool UARTPort::setTermios() } } + // Special setting of RTS/DTR for DVM-V24 boards with onboard DTR/RTS boot connections + if (m_rtsBoot) { + ::LogInfoEx(LOG_MODEM, "RTS/DTR boot flag enabled, forcing board reset"); + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + // Force RTS bit off + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + // Toggle DTR to force second reset + y &= ~TIOCM_DTR; + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + y |= TIOCM_DTR; + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + } + #if defined(__APPLE__) setNonblock(false); #endif diff --git a/src/host/modem/port/UARTPort.h b/src/host/modem/port/UARTPort.h index 4aed835f..b5a4a1a7 100644 --- a/src/host/modem/port/UARTPort.h +++ b/src/host/modem/port/UARTPort.h @@ -76,7 +76,7 @@ namespace modem * @param speed Serial port speed. * @param assertRTS */ - UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS = false); + UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS = false, bool rtsBoot = false); /** * @brief Finalizes a instance of the UARTPort class. */ @@ -122,13 +122,14 @@ namespace modem * @param speed Serial port speed. * @param assertRTS */ - UARTPort(SERIAL_SPEED speed, bool assertRTS = false); + UARTPort(SERIAL_SPEED speed, bool assertRTS = false, bool rtsBoot = false); bool m_isOpen; std::string m_device; SERIAL_SPEED m_speed; bool m_assertRTS; + bool m_rtsBoot; #if defined(_WIN32) HANDLE m_fd; #else From 1b4ea4d8c7cd6e30e73613348f0203e90cbd5996 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 13 Dec 2024 10:24:22 -0500 Subject: [PATCH 06/68] refactor status bit handling slightly; --- src/common/p25/P25Utils.cpp | 55 ++++++++++++--- src/common/p25/P25Utils.h | 41 +++++++---- src/fne/network/callhandler/TagP25Data.cpp | 12 ++-- src/host/modem/ModemV24.cpp | 54 +++++++------- src/host/p25/Control.cpp | 8 +-- src/host/p25/packet/ControlSignaling.cpp | 78 ++++++++++---------- src/host/p25/packet/Data.cpp | 31 ++++---- src/host/p25/packet/Data.h | 6 +- src/host/p25/packet/Voice.cpp | 82 +++++++++++----------- src/host/setup/SetupApplication.h | 8 +-- src/sysview/TransmitWndBase.h | 12 ++-- 11 files changed, 207 insertions(+), 180 deletions(-) diff --git a/src/common/p25/P25Utils.cpp b/src/common/p25/P25Utils.cpp index f4efa990..548c98d2 100644 --- a/src/common/p25/P25Utils.cpp +++ b/src/common/p25/P25Utils.cpp @@ -31,13 +31,37 @@ void P25Utils::setStatusBits(uint8_t* data, uint32_t ssOffset, bool b1, bool b2) WRITE_BIT(data, ssOffset + 1U, b2); } +/* Helper to set the starting status bits on P25 frame data to 1,1 for idle. */ + +void P25Utils::setStatusBitsStartIdle(uint8_t* data) +{ + assert(data != nullptr); + + // set "1,1" (Start of Inbound Slot/Idle) status bits [TIA-102.BAAA] + P25Utils::setStatusBits(data, P25_SS0_START, true, true); +} + +/* Helper to set all status bits on a P25 frame data to 1,1 for idle. */ + +void P25Utils::setStatusBitsAllIdle(uint8_t* data, uint32_t length) +{ + assert(data != nullptr); + + // set "1,1" (Idle) status bits [TIA-102.BAAA] + for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += P25_SS_INCREMENT) { + uint32_t ss1Pos = ss0Pos + 1U; + WRITE_BIT(data, ss0Pos, true); // 1 + WRITE_BIT(data, ss1Pos, true); // 1 + } +} + /* Helper to add the status bits on P25 frame data. */ -void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool inbound, bool control) +void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool busy, bool unknown) { assert(data != nullptr); - // insert the "10" (Unknown, use for inbound or outbound) status bits + // set "1,0" (Unknown) status bits [TIA-102.BAAA] for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += P25_SS_INCREMENT) { uint32_t ss1Pos = ss0Pos + 1U; WRITE_BIT(data, ss0Pos, true); // 1 @@ -47,14 +71,17 @@ void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool inbound, bool // interleave the requested status bits (every other) for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * 2U)) { uint32_t ss1Pos = ss0Pos + 1U; - if (inbound) { + if (busy) { + // set "0,1" (Busy) status bits [TIA-102.BAAA] WRITE_BIT(data, ss0Pos, false); // 0 WRITE_BIT(data, ss1Pos, true); // 1 } else { - if (control) { + if (unknown) { + // set "1,0" (Unknown) status bits [TIA-102.BAAA] WRITE_BIT(data, ss0Pos, true); // 1 WRITE_BIT(data, ss1Pos, false); // 0 } else { + // set "1,1" (Start of Inbound Slot/Idle) status bits [TIA-102.BAAA] WRITE_BIT(data, ss0Pos, true); // 1 WRITE_BIT(data, ss1Pos, true); // 1 } @@ -62,27 +89,35 @@ void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool inbound, bool } } -/* Helper to add the idle status bits on P25 frame data. */ +/* Helper to add the unknown (1,0) status bits on P25 frame data. */ -void P25Utils::addIdleStatusBits(uint8_t* data, uint32_t length) +void P25Utils::addUnknownStatusBits(uint8_t* data, uint32_t length, uint8_t interval) { assert(data != nullptr); - for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * 5U)) { + if (interval == 0U) + interval = 1U; + + for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * interval)) { uint32_t ss1Pos = ss0Pos + 1U; + // set "1,0" (Unknown) status bits [TIA-102.BAAA] WRITE_BIT(data, ss0Pos, true); // 1 WRITE_BIT(data, ss1Pos, false); // 0 } } -/* Helper to add the trunk start slot status bits on P25 frame data. */ +/* Helper to add the idle (1,1) status bits on P25 frame data. */ -void P25Utils::addTrunkSlotStatusBits(uint8_t* data, uint32_t length) +void P25Utils::addIdleStatusBits(uint8_t* data, uint32_t length, uint8_t interval) { assert(data != nullptr); - for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * 5U)) { + if (interval == 0U) + interval = 1U; + + for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * interval)) { uint32_t ss1Pos = ss0Pos + 1U; + // set "1,1" (Start of Inbound Slot/Idle) status bits [TIA-102.BAAA] WRITE_BIT(data, ss0Pos, true); // 1 WRITE_BIT(data, ss1Pos, true); // 1 } diff --git a/src/common/p25/P25Utils.h b/src/common/p25/P25Utils.h index bfad54d1..bba98a28 100644 --- a/src/common/p25/P25Utils.h +++ b/src/common/p25/P25Utils.h @@ -120,35 +120,48 @@ namespace p25 /** * @brief Helper to set the status bits on P25 frame data. * @param data P25 frame data buffer. - * @param ssOffset + * @param ssOffset Status symbol offset (bit offset). * @param b1 Status Bit 1 * @param b2 Status Bit 2 */ static void setStatusBits(uint8_t* data, uint32_t ssOffset, bool b1, bool b2); + /** + * @brief Helper to set the starting status bits on P25 frame data to 1,1 for idle. + * @param data P25 frame data buffer. + */ + static void setStatusBitsStartIdle(uint8_t* data); + /** + * @brief Helper to set all status bits on a P25 frame data to 1,1 for idle. + * @param data P25 frame data buffer. + * @param length Lenght of P25 frame in bits. + */ + static void setStatusBitsAllIdle(uint8_t* data, uint32_t length); /** * @brief Helper to add the status bits on P25 frame data. * This appropriately sets the status bits for the P25 frame, starting with 1,0 and then - * properly setting 0,1 for inbound traffic, or 1,1 for idle (or 1,0 for control channels). + * properly setting 0,1 for inbound traffic, or 1,1 for idle (or 1,0 for unknown). * @param data P25 frame data buffer. - * @param length - * @param inbound Flag indicating inbound channel is busy. - * @param control Flag indicating the channel is a control channel. + * @param length Lenght of P25 frame in bits. + * @param busy Flag indicating inbound channel is busy. + * @param unknown Flag indicating unknown slot state. */ - static void addStatusBits(uint8_t *data, uint32_t length, bool inbound, bool control = false); + static void addStatusBits(uint8_t *data, uint32_t length, bool busy, bool unknown); /** - * @brief Helper to add the idle status bits on P25 frame data. - * This sets the status bits to 1,0 interleaved every 5th status bit pair. + * @brief Helper to add the unknown (1,0) status bits on P25 frame data. + * This sets the status bits to 1,0 interleaved every variable status bit pair. * @param data P25 frame data buffer. - * @param length + * @param length Lenght of P25 frame in bits. + * @param interval Status bit pair interval. */ - static void addIdleStatusBits(uint8_t* data, uint32_t length); + static void addUnknownStatusBits(uint8_t* data, uint32_t length, uint8_t interval = 5U); /** - * @brief Helper to add the trunk start slot status bits on P25 frame data. - * This sets the status bits to 1,1 interleaved every 5th status bit pair. + * @brief Helper to add the idle (1,1) status bits on P25 frame data. + * This sets the status bits to 1,1 interleaved every variable status bit pair. * @param data P25 frame data buffer. - * @param length + * @param length Lenght of P25 frame in bits. + * @param interval Status bit pair interval. */ - static void addTrunkSlotStatusBits(uint8_t* data, uint32_t length); + static void addIdleStatusBits(uint8_t* data, uint32_t length, uint8_t interval = 5U); /** * @brief Decode bit interleaving. diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 6e22416e..754da483 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1247,20 +1247,18 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES]; ::memset(data, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data); // network bursts have no NID - // Generate TSBK block + // generate TSBK block tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU tsbk->encode(data); - // Add busy bits - P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(data, P25_SS0_START, true, true); + // add status bits + P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false, true); + P25Utils::setStatusBitsStartIdle(data); if (m_debug) { LogDebug(LOG_RF, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X", diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index a96a6cad..36cf66a5 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -542,14 +542,14 @@ void ModemV24::create_TDU(uint8_t* buffer) uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data + 2U); - // Generate NID + // generate NID m_nid->encode(data + 2U, DUID::TDU); - // Add busy bits - P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_EOT; buffer[1U] = 0x01U; @@ -672,17 +672,17 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) lc.setKId(m_rxCall->kId); lc.setMI(m_rxCall->MI); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_nid->encode(buffer + 2U, DUID::HDU); - // Generate HDU + // generate HDU lc.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; @@ -753,24 +753,22 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - // Add the data + // add the data uint32_t newBitLength = P25Utils::encode(data, buffer + 2U, bitLength); uint32_t newByteLength = newBitLength / 8U; if ((newBitLength % 8U) > 0U) newByteLength++; - // Regenerate Sync + // regenerate Sync Sync::addP25Sync(buffer + 2U); - // Regenerate NID + // regenerate NID m_nid->encode(buffer + 2U, DUID::PDU); - // Add status bits - P25Utils::addStatusBits(buffer + 2U, newBitLength, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, newBitLength, false, false); P25Utils::addIdleStatusBits(buffer + 2U, newBitLength); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(buffer + 2U, P25_SS0_START, true, true); + P25Utils::setStatusBitsStartIdle(buffer + 2U); storeConvertedRx(buffer, P25_PDU_FRAME_LENGTH_BYTES + 2U); } @@ -790,22 +788,20 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_nid->encode(buffer + 2U, DUID::TSDU); - // Regenerate TSDU Data + // regenerate TSDU Data tsbk.setLastBlock(true); // always set last block -- this a Single Block TSDU tsbk.encode(buffer + 2U); - // Add busy bits + // add status bits P25Utils::addStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES, false, true); - P25Utils::addTrunkSlotStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(buffer + 2U, P25_SS0_START, true, true); + P25Utils::addIdleStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES); + P25Utils::setStatusBitsStartIdle(buffer + 2U); storeConvertedRx(buffer, P25_TSDU_FRAME_LENGTH_BYTES + 2U); } @@ -1028,8 +1024,8 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 204U, 8U); - // add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; @@ -1070,8 +1066,8 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 204U, 8U); - // add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 106caf49..4efa443f 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1725,14 +1725,14 @@ void Control::writeRF_TDU(bool noNetwork, bool imm) uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data + 2U); - // Generate NID + // generate NID m_nid.encode(data + 2U, DUID::TDU); - // Add busy bits - P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::setStatusBitsAllIdle(data + 2U, P25_TDU_FRAME_LENGTH_BITS); if (!noNetwork) m_voice->writeNetwork(data + 2U, DUID::TDU); diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index b645ec12..07214407 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -1357,17 +1357,17 @@ void ControlSignaling::writeRF_TDULC(lc::TDULC* lc, bool noNetwork) uint8_t data[P25_TDULC_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(data + 2U, DUID::TDULC); - // Generate TDULC Data + // generate TDULC Data lc->encode(data + 2U); - // Add busy bits - P25Utils::addStatusBits(data + 2U, P25_TDULC_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_TDULC_FRAME_LENGTH_BITS, false, false); m_p25->m_rfTimeout.stop(); @@ -1396,17 +1396,17 @@ void ControlSignaling::writeNet_TDULC(lc::TDULC* lc) buffer[0U] = modem::TAG_EOT; buffer[1U] = 0x00U; - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::TDULC); - // Regenerate TDULC Data + // regenerate TDULC Data lc->encode(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BITS, false, false); m_p25->addFrame(buffer, P25_TDULC_FRAME_LENGTH_BYTES + 2U, true); @@ -1443,13 +1443,13 @@ void ControlSignaling::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool for uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(data + 2U, DUID::TSDU); - // Generate TSBK block + // generate TSBK block tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU tsbk->encode(data + 2U); @@ -1461,12 +1461,10 @@ void ControlSignaling::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool for Utils::dump(1U, "!!! *TSDU (SBF) TSBK Block Data", data + P25_PREAMBLE_LENGTH_BYTES + 2U, P25_TSBK_FEC_LENGTH_BYTES); } - // Add busy bits + // add status bits P25Utils::addStatusBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS, m_inbound, true); - P25Utils::addTrunkSlotStatusBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(data + 2U, P25_SS0_START, true, true); + P25Utils::addIdleStatusBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS); + P25Utils::setStatusBitsStartIdle(data + 2U); if (!noNetwork) writeNetworkRF(tsbk, data + 2U, true); @@ -1513,22 +1511,20 @@ void ControlSignaling::writeNet_TSDU(lc::TSBK* tsbk) buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::TSDU); - // Regenerate TSDU Data + // regenerate TSDU Data tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU tsbk->encode(buffer + 2U); - // Add busy bits + // add status bits P25Utils::addStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES, false, true); - P25Utils::addTrunkSlotStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(buffer + 2U, P25_SS0_START, true, true); + P25Utils::addIdleStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES); + P25Utils::setStatusBitsStartIdle(buffer + 2U); m_p25->addFrame(buffer, P25_TSDU_FRAME_LENGTH_BYTES + 2U, true); @@ -1566,7 +1562,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk) // trigger encoding of last block and write to queue if (m_mbfCnt + 1U == TSBK_MBF_CNT) { - // Generate TSBK block + // generate TSBK block tsbk->setLastBlock(true); // set last block tsbk->encode(frame, true); @@ -1580,7 +1576,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk) Utils::setBitRange(frame, m_rfMBF, (m_mbfCnt * P25_TSBK_FEC_LENGTH_BITS), P25_TSBK_FEC_LENGTH_BITS); - // Generate TSDU frame + // generate TSDU frame uint8_t tsdu[P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES]; ::memset(tsdu, 0x00U, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES); @@ -1597,7 +1593,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk) Utils::dump(1U, "!!! *TSDU (MBF) TSBK Block", frame, P25_TSBK_FEC_LENGTH_BYTES); } - // Add TSBK data + // add TSBK data Utils::setBitRange(frame, tsdu, offset, P25_TSBK_FEC_LENGTH_BITS); offset += P25_TSBK_FEC_LENGTH_BITS; @@ -1608,18 +1604,18 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk) uint8_t data[P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(data + 2U, DUID::TSDU); // interleave P25Utils::encode(tsdu, data + 2U, 114U, 720U); - // Add busy bits + // add busy bits P25Utils::addStatusBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS, m_inbound, true); - P25Utils::addTrunkSlotStatusBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS); + P25Utils::addIdleStatusBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS); data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -1631,7 +1627,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk) return; } - // Generate TSBK block + // generate TSBK block tsbk->setLastBlock(false); // clear last block tsbk->encode(frame, true); @@ -2938,21 +2934,19 @@ void ControlSignaling::writeNet_TSDU_From_RF(lc::TSBK* tsbk, uint8_t* data) ::memset(data, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data); - // Generate NID + // generate NID m_p25->m_nid.encode(data, DUID::TSDU); - // Regenerate TSDU Data + // regenerate TSDU Data tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU tsbk->encode(data); - // Add busy bits - P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(data, P25_SS0_START, true, true); + // add status bits + P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false, false); + P25Utils::setStatusBitsStartIdle(data); } /* Helper to automatically inhibit a source ID on a denial. */ diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index de8c6e00..f91902ad 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -368,7 +368,7 @@ bool Data::process(uint8_t* data, uint32_t len) if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { if (m_retryCount < MAX_PDU_RETRY_CNT) { m_p25->writeRF_Preamble(); - writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, false, true); + writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, true); m_retryCount++; } else { @@ -610,6 +610,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } if (m_p25->m_netState == RS_NET_DATA) { + m_inbound = false; // forcibly set inbound to false ::memcpy(m_netPDU + m_netDataOffset, data + 24U, blockLength); m_netDataOffset += blockLength; m_netPDUCount++; @@ -898,7 +899,7 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, } } - writeRF_PDU(data, bitLength, false, imm); + writeRF_PDU(data, bitLength, imm); } /* Updates the processor by the passed number of milliseconds. */ @@ -1386,7 +1387,7 @@ void Data::writeNetwork(const uint8_t currentBlock, const uint8_t *data, uint32_ /* Helper to write a P25 PDU packet. */ -void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, bool imm, bool ackRetry) +void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ackRetry) { assert(pdu != nullptr); assert(bitLength > 0U); @@ -1414,24 +1415,21 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, boo uint8_t data[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - // Add the data + // add the data uint32_t newBitLength = P25Utils::encode(pdu, data + 2U, bitLength); uint32_t newByteLength = newBitLength / 8U; if ((newBitLength % 8U) > 0U) newByteLength++; - // Regenerate Sync + // regenerate Sync Sync::addP25Sync(data + 2U); - // Regenerate NID + // regenerate NID m_p25->m_nid.encode(data + 2U, DUID::PDU); - // Add status bits - P25Utils::addStatusBits(data + 2U, newBitLength, false); - P25Utils::addTrunkSlotStatusBits(data + 2U, newBitLength); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(data + 2U, P25_SS0_START, true, true); + // add status bits + P25Utils::addStatusBits(data + 2U, newBitLength, m_inbound, true); + P25Utils::setStatusBitsStartIdle(data + 2U); if (m_p25->m_duplex) { data[0U] = modem::TAG_DATA; @@ -1440,10 +1438,7 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, boo m_p25->addFrame(data, newByteLength + 2U, false, imm); } - // add trailing null pad; only if control data isn't being transmitted - if (!m_p25->m_ccRunning && !noNulls) { - m_p25->writeRF_Nulls(); - } + m_p25->writeRF_TDU(true, imm); } /* Helper to write a network P25 PDU packet. */ @@ -1663,7 +1658,7 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA /* Helper to write a PDU acknowledge response. */ -void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId, bool noNulls) +void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId) { if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK) return; @@ -1702,5 +1697,5 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a rspHeader.getResponseClass(), rspHeader.getResponseType(), rspHeader.getResponseStatus(), rspHeader.getLLId(), rspHeader.getSrcLLId()); } - writeRF_PDU(data, bitLength, noNulls); + writeRF_PDU(data, bitLength); } diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h index 111bcb20..7ff8fc58 100644 --- a/src/host/p25/packet/Data.h +++ b/src/host/p25/packet/Data.h @@ -202,11 +202,10 @@ namespace p25 * @brief Helper to write a P25 PDU packet. * @param[in] pdu Constructed PDU to transmit. * @param bitlength Length of PDU in bits. - * @param noNulls Flag indicating no trailing nulls should be transmitted. * @param imm Flag indicating the PDU should be written to the immediate queue. * @param ackRetry Flag indicating the PDU is being sent as an acknowledged retry. */ - void writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls = false, bool imm = false, bool ackRetry = false); + void writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm = false, bool ackRetry = false); /** * @brief Helper to write a network P25 PDU packet. * This will take buffered network PDU data and repeat it over the air. @@ -231,9 +230,8 @@ namespace p25 * @param ackStatus * @param llId Logical Link ID. * @param srcLlId Source Logical Link ID. - * @param noNulls Flag indicating no trailing nulls should be transmitted. */ - void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U, bool noNulls = false); + void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U); }; } // namespace packet } // namespace p25 diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 8a8f530f..cd1ea29d 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -508,17 +508,17 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::HDU); - // Generate HDU + // generate HDU m_rfLC.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(buffer, DUID::HDU); @@ -740,8 +740,8 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfErrs += errors; m_rfFrames++; - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::LDU1, frameType); @@ -857,8 +857,8 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfErrs += errors; m_rfFrames++; - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::LDU2); @@ -937,17 +937,17 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::HDU); - // Generate HDU + // generate HDU m_rfLC.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(buffer, DUID::HDU); @@ -989,8 +989,8 @@ bool Voice::process(uint8_t* data, uint32_t len) // generate NID m_p25->m_nid.encode(data + 2U, DUID::VSELP1); - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::VSELP1); @@ -1031,8 +1031,8 @@ bool Voice::process(uint8_t* data, uint32_t len) // generate NID m_p25->m_nid.encode(data + 2U, DUID::VSELP2); - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::VSELP2); @@ -1507,14 +1507,14 @@ void Voice::writeNet_TDU() buffer[0U] = modem::TAG_EOT; buffer[1U] = 0x00U; - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::TDU); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS, false, false); m_p25->addFrame(buffer, P25_TDU_FRAME_LENGTH_BYTES + 2U, true); @@ -1776,17 +1776,17 @@ void Voice::writeNet_LDU1() uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::HDU); - // Generate header + // generate HDU m_netLC.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -1864,13 +1864,13 @@ void Voice::writeNet_LDU1() uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::LDU1); - // Generate LDU1 data + // generate LDU1 data if (!m_netLC.isStandardMFId()) { if (m_debug) { LogDebug(LOG_NET, "P25, LDU1 LC, non-standard payload, lco = $%02X, mfId = $%02X", m_netLC.getLCO(), m_netLC.getMFId()); @@ -1880,7 +1880,7 @@ void Voice::writeNet_LDU1() m_netLC.encodeLDU1(buffer + 2U); - // Add the Audio + // add the Audio m_audio.encode(buffer + 2U, m_netLDU1 + 10U, 0U); m_audio.encode(buffer + 2U, m_netLDU1 + 26U, 1U); m_audio.encode(buffer + 2U, m_netLDU1 + 55U, 2U); @@ -1891,13 +1891,13 @@ void Voice::writeNet_LDU1() m_audio.encode(buffer + 2U, m_netLDU1 + 180U, 7U); m_audio.encode(buffer + 2U, m_netLDU1 + 204U, 8U); - // Add the Low Speed Data + // add the Low Speed Data m_netLSD.setLSD1(lsd.getLSD1()); m_netLSD.setLSD2(lsd.getLSD2()); m_netLSD.encode(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -1962,16 +1962,16 @@ void Voice::writeNet_LDU2() uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::LDU2); - // Generate LDU2 data + // generate LDU2 data m_netLC.encodeLDU2(buffer + 2U); - // Add the Audio + // add the Audio m_audio.encode(buffer + 2U, m_netLDU2 + 10U, 0U); m_audio.encode(buffer + 2U, m_netLDU2 + 26U, 1U); m_audio.encode(buffer + 2U, m_netLDU2 + 55U, 2U); @@ -1982,13 +1982,13 @@ void Voice::writeNet_LDU2() m_audio.encode(buffer + 2U, m_netLDU2 + 180U, 7U); m_audio.encode(buffer + 2U, m_netLDU2 + 204U, 8U); - // Add the Low Speed Data + // add the Low Speed Data m_netLSD.setLSD1(lsd.getLSD1()); m_netLSD.setLSD2(lsd.getLSD2()); m_netLSD.encode(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; diff --git a/src/host/setup/SetupApplication.h b/src/host/setup/SetupApplication.h index 67b48e0e..07936da6 100644 --- a/src/host/setup/SetupApplication.h +++ b/src/host/setup/SetupApplication.h @@ -193,15 +193,15 @@ protected: uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync p25::Sync::addP25Sync(data + 2U); - // Generate NID + // generate NID std::unique_ptr nid = std::make_unique(1U); nid->encode(data + 2U, DUID::TDU); - // Add busy bits - p25::P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false); + // add status bits + p25::P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false, false); data[0U] = modem::TAG_EOT; data[1U] = 0x00U; diff --git a/src/sysview/TransmitWndBase.h b/src/sysview/TransmitWndBase.h index 9411841f..5c5394d4 100644 --- a/src/sysview/TransmitWndBase.h +++ b/src/sysview/TransmitWndBase.h @@ -338,20 +338,18 @@ protected: uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES]; ::memset(data, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync Sync::addP25Sync(data); // network bursts have no NID - // Generate TSBK block + // generate TSBK block tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU tsbk->encode(data); - // Add busy bits - P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(data, P25_SS0_START, true, true); + // add status bits + P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false, true); + P25Utils::setStatusBitsStartIdle(data); if (g_debug) { LogDebug(LOG_RF, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X", From f3fa7053b8e43b7ba8476a0a8f324d602e35454d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 13 Dec 2024 12:49:54 -0500 Subject: [PATCH 07/68] minor cleanups; ensure pad length is handled properly by PDU code; --- src/common/p25/P25Utils.cpp | 7 ++----- src/common/p25/P25Utils.h | 2 +- src/host/modem/ModemV24.cpp | 6 +++--- src/host/p25/packet/Data.cpp | 21 +++++++++++++++++---- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/common/p25/P25Utils.cpp b/src/common/p25/P25Utils.cpp index 548c98d2..a9d57b36 100644 --- a/src/common/p25/P25Utils.cpp +++ b/src/common/p25/P25Utils.cpp @@ -191,9 +191,9 @@ uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t start, uint3 return n; } -/* Encode bit interleaving. */ +/* Encode bit interleaving for a given length. */ -uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t length) +uint32_t P25Utils::encodeByLength(const uint8_t* in, uint8_t* out, uint32_t length) { assert(in != nullptr); assert(out != nullptr); @@ -207,17 +207,14 @@ uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t length) while (n < length) { if (pos == ss0Pos) { ss0Pos += P25_SS_INCREMENT; - } else if (pos == ss1Pos) { ss1Pos += P25_SS_INCREMENT; - } else { bool b = READ_BIT(in, n); WRITE_BIT(out, pos, b); n++; - } pos++; diff --git a/src/common/p25/P25Utils.h b/src/common/p25/P25Utils.h index bba98a28..bc030a43 100644 --- a/src/common/p25/P25Utils.h +++ b/src/common/p25/P25Utils.h @@ -188,7 +188,7 @@ namespace p25 * @param length * @returns uint32_t */ - static uint32_t encode(const uint8_t* in, uint8_t* out, uint32_t length); + static uint32_t encodeByLength(const uint8_t* in, uint8_t* out, uint32_t length); /** * @brief Compare two datasets for the given length. diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 36cf66a5..bea2a2fe 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -754,15 +754,15 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); // add the data - uint32_t newBitLength = P25Utils::encode(data, buffer + 2U, bitLength); + uint32_t newBitLength = P25Utils::encodeByLength(data, buffer + 2U, bitLength); uint32_t newByteLength = newBitLength / 8U; if ((newBitLength % 8U) > 0U) newByteLength++; - // regenerate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // regenerate NID + // generate NID m_nid->encode(buffer + 2U, DUID::PDU); // add status bits diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index f91902ad..30071bca 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -96,6 +96,8 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfPduUserDataLength = 0U; } + //Utils::dump(1U, "Raw PDU ISP", data, len); + uint32_t start = m_rfPDUCount * P25_PDU_FRAME_LENGTH_BITS; uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; @@ -815,6 +817,9 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, m_p25->writeRF_TDU(true, imm); uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (dataHeader.getPadLength() > 0U) + bitLength += (dataHeader.getPadLength() * 8U); + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; UInt8Array __data = std::make_unique((bitLength / 8U) + 1U); @@ -1392,7 +1397,7 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac assert(pdu != nullptr); assert(bitLength > 0U); - m_p25->writeRF_Preamble(); + m_p25->writeRF_TDU(true, imm); if (!ackRetry) { if (m_retryPDUData != nullptr) @@ -1416,21 +1421,23 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac ::memset(data, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); // add the data - uint32_t newBitLength = P25Utils::encode(pdu, data + 2U, bitLength); + uint32_t newBitLength = P25Utils::encodeByLength(pdu, data + 2U, bitLength); uint32_t newByteLength = newBitLength / 8U; if ((newBitLength % 8U) > 0U) newByteLength++; - // regenerate Sync + // generate Sync Sync::addP25Sync(data + 2U); - // regenerate NID + // generate NID m_p25->m_nid.encode(data + 2U, DUID::PDU); // add status bits P25Utils::addStatusBits(data + 2U, newBitLength, m_inbound, true); P25Utils::setStatusBitsStartIdle(data + 2U); + //Utils::dump("Raw PDU OSP", data, newByteLength + 2U); + if (m_p25->m_duplex) { data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -1446,6 +1453,9 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac void Data::writeNet_PDU_Buffered() { uint32_t bitLength = ((m_netDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (m_netDataHeader.getPadLength() > 0U) + bitLength += (m_netDataHeader.getPadLength() * 8U); + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; UInt8Array __data = std::make_unique((bitLength / 8U) + 1U); @@ -1537,6 +1547,9 @@ void Data::writeNet_PDU_Buffered() void Data::writeRF_PDU_Buffered() { uint32_t bitLength = ((m_rfDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (m_rfDataHeader.getPadLength() > 0U) + bitLength += (m_rfDataHeader.getPadLength() * 8U); + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; UInt8Array __data = std::make_unique((bitLength / 8U) + 1U); From a908a24f54636006f818ca53cbe128aad9bc5ab8 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 13 Dec 2024 12:59:07 -0500 Subject: [PATCH 08/68] update a RID's TG affiliation based on the response from a Group Affiliation Query Response; --- src/host/p25/packet/ControlSignaling.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 07214407..77123367 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -559,6 +559,14 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_affiliations.isGroupAff(srcId, dstId)) { + // update dynamic affiliation table + m_p25->m_affiliations.groupAff(srcId, dstId); + + if (m_p25->m_network != nullptr) + m_p25->m_network->announceGroupAffiliation(srcId, dstId); + } } break; case TSBKO::ISP_U_DEREG_REQ: From 5dce90b6d03dc7480446a0365224b6e82f9ca6f5 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 13 Dec 2024 13:00:00 -0500 Subject: [PATCH 09/68] perform src and dst ACL checking for group aff query responses; --- src/host/p25/packet/ControlSignaling.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 77123367..8f3c45ab 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -548,6 +548,12 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(true), TSBKO::ISP_GRP_AFF_Q_RSP, srcId); + // validate the source RID + VALID_SRCID(tsbk->toString(true), TSBKO::IOSP_ACK_RSP, srcId); + + // validate the target RID + VALID_DSTID(tsbk->toString(true), TSBKO::IOSP_ACK_RSP, srcId, dstId); + if (m_p25->m_ackTSBKRequests) { writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_GRP_AFF_Q_RSP, true, true); } From 28153fbab9b6c0260991eb5e914ffa989937aff0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 14 Dec 2024 11:50:48 -0500 Subject: [PATCH 10/68] fix cross-thread ARM shennigans (this should resolve #78); --- src/host/setup/ChannelConfigSetWnd.h | 30 ++++-------- src/host/setup/HostSetup.cpp | 72 ++++++++++++++++++---------- src/host/setup/HostSetup.h | 3 +- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/src/host/setup/ChannelConfigSetWnd.h b/src/host/setup/ChannelConfigSetWnd.h index 574f646d..e4a4af03 100644 --- a/src/host/setup/ChannelConfigSetWnd.h +++ b/src/host/setup/ChannelConfigSetWnd.h @@ -149,6 +149,9 @@ private: m_channelNo.setRange(0, 4095); m_channelNo.setShadow(false); m_channelNo.addCallback("changed", [&]() { + if (!m_radioChNo.isChecked()) + return; + m_setup->m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_channelNo.getValue()); m_setup->calculateRxTxFreq(); m_channelFreq.setValue(m_setup->m_txFrequency); @@ -167,34 +170,17 @@ private: m_channelFreq.setValue(m_setup->m_txFrequency); m_channelFreq.setShadow(false); m_channelFreq.addCallback("changed", [&]() { - entry = m_setup->m_idenTable->find(m_setup->m_channelId); - - uint32_t txFrequency = m_channelFreq.getValue(); - - uint32_t prevTxFrequency = m_setup->m_txFrequency; - m_setup->m_txFrequency = txFrequency; - uint32_t prevRxFrequency = m_setup->m_rxFrequency; - m_setup->m_rxFrequency = m_setup->m_txFrequency + (uint32_t)(entry.txOffsetMhz() * 1000000); - - float spaceHz = entry.chSpaceKhz() * 1000; + if (!m_radioChFreq.isChecked()) + return; - uint32_t rootFreq = m_setup->m_txFrequency - entry.baseFrequency(); - uint8_t prevChannelNo = m_setup->m_channelNo; - m_setup->m_channelNo = (uint32_t)(rootFreq / spaceHz); - - if (m_setup->m_channelNo < 0 || m_setup->m_channelNo > 4096) { - m_setup->m_channelNo = prevChannelNo; - m_setup->m_txFrequency = prevTxFrequency; - m_setup->m_rxFrequency = prevRxFrequency; - } - - m_setup->m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_setup->m_channelNo); - m_setup->calculateRxTxFreq(); + uint32_t txFrequency = (uint32_t)(m_channelFreq.getValue()); + m_setup->calculateRxTxFreq(false, txFrequency); m_channelNo.setValue(m_setup->m_channelNo); if (m_setup->m_isConnected) { m_setup->writeRFParams(); } }); + m_hzLabel.setGeometry(FPoint(40, 12), FSize(5, 1)); } diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 28112b1d..4a084026 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -598,7 +598,7 @@ void HostSetup::saveConfig() /* Helper to calculate the Rx/Tx frequencies. */ -bool HostSetup::calculateRxTxFreq(bool consoleDisplay) +bool HostSetup::calculateRxTxFreq(bool consoleDisplay, uint32_t txFrequency) { IdenTable entry = m_idenTable->find(m_channelId); if (entry.baseFrequency() == 0U) { @@ -610,37 +610,57 @@ bool HostSetup::calculateRxTxFreq(bool consoleDisplay) return false; } - yaml::Node systemConf = m_conf["system"]; - yaml::Node rfssConfig = systemConf["config"]; - m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); - if (m_channelNo == 0U) { // clamp to 1 - m_channelNo = 1U; - } - if (m_channelNo > 4095U) { // clamp to 4095 - m_channelNo = 4095U; - } + if (txFrequency > 0U) { + uint32_t prevTxFrequency = m_txFrequency; + m_txFrequency = txFrequency; + uint32_t prevRxFrequency = m_rxFrequency; + m_rxFrequency = m_txFrequency + (uint32_t)(entry.txOffsetMhz() * 1000000); - if (m_startupDuplex) { - if (entry.txOffsetMhz() == 0U) { - if (consoleDisplay) { - g_logDisplayLevel = 1U; - } + float spaceHz = entry.chSpaceKhz() * 1000.0; - ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); - return false; - } - - uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); - float calcTxOffset = entry.txOffsetMhz() * 1000000.0; + uint32_t rootFreq = m_txFrequency - entry.baseFrequency(); + uint8_t prevChannelNo = m_channelNo; + m_channelNo = (uint32_t)(rootFreq / spaceHz); - m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + (int32_t)calcTxOffset); - m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); + if (m_channelNo < 0 || m_channelNo > 4096) { + m_channelNo = prevChannelNo; + m_txFrequency = prevTxFrequency; + m_rxFrequency = prevRxFrequency; + } } else { - uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + yaml::Node systemConf = m_conf["system"]; + yaml::Node rfssConfig = systemConf["config"]; + m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); + if (m_channelNo == 0U) { // clamp to 1 + m_channelNo = 1U; + } + if (m_channelNo > 4095U) { // clamp to 4095 + m_channelNo = 4095U; + } + + if (m_startupDuplex) { + if (entry.txOffsetMhz() == 0U) { + if (consoleDisplay) { + g_logDisplayLevel = 1U; + } + + ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); + return false; + } - m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); - m_txFrequency = m_rxFrequency; + uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + float calcTxOffset = entry.txOffsetMhz() * 1000000.0; + + m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + (int32_t)calcTxOffset); + m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); + } + else { + uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + + m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); + m_txFrequency = m_rxFrequency; + } } if (m_isHotspot) { diff --git a/src/host/setup/HostSetup.h b/src/host/setup/HostSetup.h index 2c0d36c1..8d7c6545 100644 --- a/src/host/setup/HostSetup.h +++ b/src/host/setup/HostSetup.h @@ -218,9 +218,10 @@ protected: /** * @brief Helper to calculate the Rx/Tx frequencies. * @param consoleDisplay Flag indicating output should goto the console log. + * @param txFrequency Transmit frequency to use (this will auto calculate the Rx frequency from Tx frequency). * @returns bool True, if Rx/Tx frequencies are calculated, otherwise false. */ - bool calculateRxTxFreq(bool consoleDisplay = false); + bool calculateRxTxFreq(bool consoleDisplay = false, uint32_t txFrequency = 0U); /** * @brief Helper to log the system configuration parameters. */ From ef4e3104acd800bcd94ac9855fe5dece4526a4da Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 14 Dec 2024 11:55:06 -0500 Subject: [PATCH 11/68] you know what helps? if you actually set the channel number; --- src/host/setup/HostSetup.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 4a084026..072f488d 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -627,6 +627,8 @@ bool HostSetup::calculateRxTxFreq(bool consoleDisplay, uint32_t txFrequency) m_txFrequency = prevTxFrequency; m_rxFrequency = prevRxFrequency; } + + m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_channelNo); } else { yaml::Node systemConf = m_conf["system"]; From e1ca3bc7d6e364116b91c56e9685d90a5972b075 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 14 Dec 2024 14:38:52 -0500 Subject: [PATCH 12/68] update v24 submodule; --- src/fw/v24 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fw/v24 b/src/fw/v24 index 8269757a..a35f0d95 160000 --- a/src/fw/v24 +++ b/src/fw/v24 @@ -1 +1 @@ -Subproject commit 8269757ac2e9e77ad48da3934197c306b3184fc0 +Subproject commit a35f0d95ee782efb2097c32a621e34cc8dad60b8 From 95ab9addc03a3f739ddadcaf1d55a838f453aeee Mon Sep 17 00:00:00 2001 From: Jim <25770089+faultywarrior@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:21:23 -0600 Subject: [PATCH 13/68] Fixed typo in CMakeList causing make old_install to fail (#79) Co-authored-by: faulty --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33fef823..b4d5190f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -543,7 +543,7 @@ add_custom_target(old_install COMMAND install -m 644 ../configs/rid_acl.example.dat ${CMAKE_LEGACY_INSTALL_PREFIX}/rid_acl.dat COMMAND install -m 644 ../configs/talkgroup_rules.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/talkgroup_rules.example.yml COMMAND install -m 644 ../configs/bridge-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/bridge-config.example.yml - COMMAND install -m 644 ../configs/talkgroup_rules.yaml_schema.json ${CMAKE_LEGACY_INSTALL_PREFIX}/schema/talkgroup_rules.yaml_schema.json + COMMAND install -m 644 ../configs/schema/talkgroup_rules.yaml_schema.json ${CMAKE_LEGACY_INSTALL_PREFIX}/schema/talkgroup_rules.yaml_schema.json COMMAND install -m 755 ../tools/start-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX} COMMAND install -m 755 ../tools/stop-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX} COMMAND install -m 755 ../tools/dvm-watchdog.sh ${CMAKE_LEGACY_INSTALL_PREFIX} From 0481a1fc684849531db6561abff0292aa97ff87f Mon Sep 17 00:00:00 2001 From: W3AXL Date: Thu, 19 Dec 2024 13:24:32 -0500 Subject: [PATCH 14/68] FNE Peer ACL Rest Improvements (#80) * initial support for peer ACL aliases, reworked peer ACL REST endpoints * oops, fixed peer list saving --------- Co-authored-by: W3AXL <29879554+W3AXL@users.noreply.github.com> --- configs/peer_list.example.dat | 4 ++- src/common/lookups/PeerListLookup.cpp | 48 +++++++++++++++++---------- src/common/lookups/PeerListLookup.h | 16 +++++++-- src/fne/network/RESTAPI.cpp | 48 +++++++++++++++++++++++++-- 4 files changed, 92 insertions(+), 24 deletions(-) diff --git a/configs/peer_list.example.dat b/configs/peer_list.example.dat index c94eb702..50a77385 100644 --- a/configs/peer_list.example.dat +++ b/configs/peer_list.example.dat @@ -1,7 +1,9 @@ # # This file sets the valid peer IDs allowed on a FNE. # -# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled)" +# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional)," #1234,,0, #5678,MYSECUREPASSWORD,0, #9876,MYSECUREPASSWORD,1, +#5432,MYSECUREPASSWORD,,Peer Alias 1, +#1012,MYSECUREPASSWORD,1,Peer Alias 2, diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index fee33719..f64d131c 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -46,16 +46,16 @@ void PeerListLookup::clear() /* Adds a new entry to the list. */ -void PeerListLookup::addEntry(uint32_t id, const std::string& password, bool peerLink) +void PeerListLookup::addEntry(uint32_t id, const std::string& alias, const std::string& password, bool peerLink) { - PeerId entry = PeerId(id, password, peerLink, false); + PeerId entry = PeerId(id, alias, password, peerLink, false); std::lock_guard lock(m_mutex); try { PeerId _entry = m_table.at(id); // if either the alias or the enabled flag doesn't match, update the entry if (_entry.peerId() == id) { - _entry = PeerId(id, password, peerLink, false); + _entry = PeerId(id, alias, password, peerLink, false); m_table[id] = _entry; } } catch (...) { @@ -87,7 +87,7 @@ PeerId PeerListLookup::find(uint32_t id) try { entry = m_table.at(id); } catch (...) { - entry = PeerId(0U, "", false, true); + entry = PeerId(0U, "", "", false, true); } return entry; @@ -201,22 +201,29 @@ bool PeerListLookup::load() // parse tokenized line uint32_t id = ::atoi(parsed[0].c_str()); + + // Parse optional alias field (at end of line to avoid breaking change with existing lists) + std::string alias = ""; + if (parsed.size() >= 4) + alias = parsed[3].c_str(); + + // Parse peer link flag bool peerLink = false; if (parsed.size() >= 3) peerLink = ::atoi(parsed[2].c_str()) == 1; - // Check for an optional alias field - if (parsed.size() >= 2) { - if (!parsed[1].empty()) { - m_table[id] = PeerId(id, parsed[1], peerLink, false); - LogDebug(LOG_HOST, "Loaded peer ID %u into peer ID lookup table, using unique peer password%s", id, - (peerLink) ? ", Peer-Link Enabled" : ""); - continue; - } - } + // Parse optional password + std::string password = ""; + if (parsed.size() >= 2) + password = parsed[1].c_str(); - m_table[id] = PeerId(id, "", peerLink, false); - LogDebug(LOG_HOST, "Loaded peer ID %u into peer ID lookup table, using master password%s", id, + // Load into table + m_table[id] = PeerId(id, alias, password, peerLink, false); + + // Log depending on what was loaded + LogDebug(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s", id, + (!alias.empty() ? (" (" + alias + ")").c_str() : ""), + (!password.empty() ? "using unique peer password" : "using master password"), (peerLink) ? ", Peer-Link Enabled" : ""); } } @@ -258,20 +265,27 @@ bool PeerListLookup::save() for (auto& entry: m_table) { // Get the parameters uint32_t peerId = entry.first; + std::string alias = entry.second.peerAlias(); std::string password = entry.second.peerPassword(); // Format into a string line = std::to_string(peerId) + ","; - // Add the alias if we have one + // Add the password if we have one if (password.length() > 0) { line += password; - line += ","; } + line += ","; + // Add peerLink flag bool peerLink = entry.second.peerLink(); if (peerLink) { line += "1,"; } else { line += "0,"; } + // Add alias if we have one + if (alias.length() > 0) { + line += alias; + line += ","; + } // Add the newline line += "\n"; // Write to file diff --git a/src/common/lookups/PeerListLookup.h b/src/common/lookups/PeerListLookup.h index eed88808..b1b318ba 100644 --- a/src/common/lookups/PeerListLookup.h +++ b/src/common/lookups/PeerListLookup.h @@ -47,6 +47,7 @@ namespace lookups */ PeerId() : m_peerId(0U), + m_peerAlias(), m_peerPassword(), m_peerLink(false), m_peerDefault(false) @@ -56,12 +57,14 @@ namespace lookups /** * @brief Initializes a new instance of the PeerId class. * @param peerId Peer ID. + * @param peerAlias Peer alias * @param peerPassword Per Peer Password. * @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. */ - PeerId(uint32_t peerId, const std::string& peerPassword, bool peerLink, bool peerDefault) : + PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool peerDefault) : m_peerId(peerId), + m_peerAlias(peerAlias), m_peerPassword(peerPassword), m_peerLink(peerLink), m_peerDefault(peerDefault) @@ -77,6 +80,7 @@ namespace lookups { if (this != &data) { m_peerId = data.m_peerId; + m_peerAlias = data.m_peerAlias; m_peerPassword = data.m_peerPassword; m_peerLink = data.m_peerLink; m_peerDefault = data.m_peerDefault; @@ -88,13 +92,15 @@ namespace lookups /** * @brief Sets flag values. * @param peerId Peer ID. + * @param peerAlias Peer Alias * @param peerPassword Per Peer Password. * @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. */ - void set(uint32_t peerId, const std::string& peerPassword, bool peerLink, bool peerDefault) + void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool peerDefault) { m_peerId = peerId; + m_peerAlias = peerAlias; m_peerPassword = peerPassword; m_peerLink = peerLink; m_peerDefault = peerDefault; @@ -105,6 +111,10 @@ namespace lookups * @brief Peer ID. */ __READONLY_PROPERTY_PLAIN(uint32_t, peerId); + /** + * @breif Peer Alias + */ + __READONLY_PROPERTY_PLAIN(std::string, peerAlias); /** * @brief Per Peer Password. */ @@ -157,7 +167,7 @@ namespace lookups * @param password Per Peer Password. * @param peerLink Flag indicating this peer will participate in peer link and should be sent configuration. */ - void addEntry(uint32_t id, const std::string& password = "", bool peerLink = false); + void addEntry(uint32_t id, const std::string& alias = "", const std::string& password = "", bool peerLink = false); /** * @brief Removes an existing entry from the list. * @param peerId Unique peer ID to remove. diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 46e39825..3b30f09d 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -1177,8 +1177,13 @@ void RESTAPI::restAPI_GetPeerList(const HTTPPayload& request, HTTPPayload& reply json::object peerObj = json::object(); uint32_t peerId = entry.first; + std::string peerAlias = entry.second.peerAlias(); + bool peerLink = entry.second.peerLink(); + bool peerPassword = !entry.second.peerPassword().empty(); // True if password is not empty, otherwise false peerObj["peerId"].set(peerId); - + peerObj["peerAlias"].set(peerAlias); + peerObj["peerLink"].set(peerLink); + peerObj["peerPassword"].set(peerPassword); peers.push_back(json::value(peerObj)); } } @@ -1203,14 +1208,51 @@ void RESTAPI::restAPI_PutPeerAdd(const HTTPPayload& request, HTTPPayload& reply, errorPayload(reply, "OK", HTTPPayload::OK); + // Validate peer ID (required) if (!req["peerId"].is()) { errorPayload(reply, "peerId was not a valid integer"); return; } - + // Get uint32_t peerId = req["peerId"].get(); - m_peerListLookup->addEntry(peerId); + // Get peer alias (optional) + std::string peerAlias = ""; + if (req.find("peerAlias") != req.end()) { + // Validate + if (!req["peerAlias"].is()) { + errorPayload(reply, "peerAlias was not a valid string"); + return; + } + // Get + peerAlias = req["peerAlias"].get(); + } + + // Get peer link setting (optional) + bool peerLink = false; + if (req.find("peerLink") != req.end()) { + // Validate + if (!req["peerLink"].is()) { + errorPayload(reply, "peerLink was not a valid boolean"); + return; + } + // Get + peerLink = req["peerLink"].get(); + } + + // Get peer password (optional) + std::string peerPassword = ""; + if (req.find("peerPassword") != req.end()) { + // Validate + if (!req["peerPassword"].is()) { + errorPayload(reply, "peerPassword was not a valid string"); + return; + } + // Get + peerPassword = req["peerPassword"].get(); + } + + m_peerListLookup->addEntry(peerId, peerAlias, peerPassword, peerLink); } /* REST API endpoint; implements put peer delete request. */ From bd5de23a4ebc34e27eaa9bb8bdaa6a985a3baa13 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 22 Dec 2024 13:17:56 -0500 Subject: [PATCH 15/68] add filename display to main window; --- src/tged/TGListWnd.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 94315f78..8d482f1c 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -132,6 +132,7 @@ private: FButton m_addTG{"&Add", this}; FButton m_editTG{"&Edit", this}; + FLabel m_fileName{"/path/to/file.yml", this}; FButton m_deleteTG{"&Delete", this}; /** @@ -168,6 +169,9 @@ private: m_editTG.setDisable(); m_editTG.addCallback("clicked", [&]() { editEntry(); }); + m_fileName.setGeometry(FPoint(27, int(getHeight() - 4)), FSize(42, 1)); + m_fileName.setText(g_iniFile); + m_deleteTG.setGeometry(FPoint(int(getWidth()) - 13, int(getHeight() - 4)), FSize(10, 1)); m_deleteTG.setDisable(); m_deleteTG.addCallback("clicked", [&]() { deleteEntry(); }); From ff1860b2bbbb9d14c0ec389a27073284df01318b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 22 Dec 2024 13:23:30 -0500 Subject: [PATCH 16/68] don't log selecting entries for TGED; --- src/tged/TGListWnd.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 8d482f1c..0efb3e5b 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -216,8 +216,10 @@ private: auto entry = g_tidLookups->find(tgid); if (!entry.isInvalid()) { m_selected = entry; +/* if (m_selectedTgId != tgid) LogMessage(LOG_HOST, "Selected TG %s (%u) for editing", m_selected.name().c_str(), m_selected.source().tgId()); +*/ m_selectedTgId = tgid; m_editTG.setEnable(); From a9c75a31013b8b26ce9756b6ec5bc738f527630f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 23 Dec 2024 13:35:25 -0500 Subject: [PATCH 17/68] correct issue with peer list data not properly committing; --- src/tged/TGEditPeerListWnd.h | 39 +++++++++++++++++++++++------------- src/tged/TGListWnd.h | 16 +++++++-------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/tged/TGEditPeerListWnd.h b/src/tged/TGEditPeerListWnd.h index b7d984d1..6b8314b4 100644 --- a/src/tged/TGEditPeerListWnd.h +++ b/src/tged/TGEditPeerListWnd.h @@ -219,12 +219,18 @@ private: */ void addEntry() { + LogMessage(LOG_HOST, "Adding %s peer ID %s from TG %s (%u)", m_title.c_str(), m_entry.getText().c_str(), + m_rule.name().c_str(), m_rule.source().tgId()); + if (m_entry.getText() == "") { - m_listBox.insert(std::to_string(0U)); + peerList.push_back(0U); } else { - m_listBox.insert(m_entry.getText()); + uint32_t peerId = ::atoi(m_entry.getText().c_str()); + peerList.push_back(peerId); } + loadList(); + //setFocusWidget(&m_listBox); redraw(); } @@ -238,9 +244,20 @@ private: size_t curItem = m_listBox.currentItem(); auto item = m_listBox.getItem(curItem); - LogMessage(LOG_HOST, "Removing %s peer ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), - m_rule.name().c_str(), m_rule.source().tgId()); - m_listBox.remove(curItem); + if (item.getText() != "") { + uint32_t peerId = ::atoi(item.getText().c_str()); + for (std::vector::iterator it = peerList.begin(); it != peerList.end(); it++) { + auto entry = *it; + if (entry == peerId) { + LogMessage(LOG_HOST, "Removing %s peer ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), + m_rule.name().c_str(), m_rule.source().tgId()); + peerList.erase(it); + break; + } + } + } + + loadList(); //setFocusWidget(&m_listBox); redraw(); @@ -283,15 +300,9 @@ private: return; } - peerList.clear(); - for (uint32_t i = 0U; i < m_listBox.getCount(); i++) { - auto item = m_listBox.getItem(i + 1U); - if (item.getText() != "") { - uint32_t peerId = ::atoi(item.getText().c_str()); - LogMessage(LOG_HOST, "%s peer ID %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), - m_rule.name().c_str(), m_rule.source().tgId()); - peerList.push_back(peerId); - } + for (auto entry : peerList) { + LogMessage(LOG_HOST, "%s peer ID %u for TG %s (%u)", m_title.c_str(), entry, + m_rule.name().c_str(), m_rule.source().tgId()); } CloseWndBase::onClose(e); diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 0efb3e5b..3a315cdc 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -322,14 +322,14 @@ private: // Use box-drawing characters to draw a border constexpr std::array box_char {{ - static_cast(0x2554), - static_cast(0x2550), - static_cast(0x2557), - static_cast(0x2551), - static_cast(0x2551), - static_cast(0x255A), - static_cast(0x2550), - static_cast(0x255D) + static_cast(0x2554), // ╔ + static_cast(0x2550), // ═ + static_cast(0x2557), // ╗ + static_cast(0x2551), // ║ + static_cast(0x2551), // ║ + static_cast(0x255A), // ╚ + static_cast(0x2550), // ═ + static_cast(0x255D) // ╝ }}; drawGenericBox(this, box, box_char); From b6191bde5683bcd279bf3f6e8f0956bff4f581a0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 23 Dec 2024 15:00:20 -0500 Subject: [PATCH 18/68] enhance tged somewhat to attempt to main the scrolled listview position; --- src/tged/TGEdMain.h | 38 ++++++++++++++++++++++++++++++++++++++ src/tged/TGListWnd.h | 24 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/tged/TGEdMain.h b/src/tged/TGEdMain.h index 7f3c5db0..93ad4295 100644 --- a/src/tged/TGEdMain.h +++ b/src/tged/TGEdMain.h @@ -35,6 +35,44 @@ // Externs // --------------------------------------------------------------------------- +/** + * bryanb: This is some low-down, dirty, C++ hack-o-ramma. + */ + +/** + * @brief Implements RTTI type defining. + * @typedef Tag + */ +template +struct RTTIResult { + typedef typename Tag::type type; + static type ptr; +}; + +template +typename RTTIResult::type RTTIResult::ptr; + +/** + * @brief Implements nasty hack to access private members of a class. + * @typedef Tag + * @typedef TypePtr + */ +template +struct HackTheGibson : RTTIResult { + /* fill it ... */ + struct filler { + filler() { RTTIResult::ptr = TypePtr; } + }; + static filler fillerObj; +}; + +template +typename HackTheGibson::filler HackTheGibson::fillerObj; + +// --------------------------------------------------------------------------- +// Externs +// --------------------------------------------------------------------------- + /** @brief */ extern std::string g_progExe; /** @brief */ diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 3a315cdc..00670fc8 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -23,6 +23,11 @@ #include using namespace finalcut; +struct PrivateFListViewScrollToY { typedef void(FListView::*type)(int); }; +template class HackTheGibson; +struct PrivateFListViewIteratorFirst { typedef FListViewIterator FListView::*type; }; +template class HackTheGibson; + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- @@ -94,6 +99,15 @@ public: auto entry = g_tidLookups->groupVoice()[0U]; m_selected = entry; + // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + int firstScrollLinePos = 0; + if (m_listView.getCount() > 0) { + firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); + } + m_listView.clear(); for (auto entry : g_tidLookups->groupVoice()) { // pad TGs properly @@ -114,6 +128,16 @@ public: m_listView.insert(line); } + // bryanb: HACK -- use HackTheGibson to access the private set scroll Y to set the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + if (firstScrollLinePos > m_listView.getCount()) + firstScrollLinePos = 0; + if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { + (m_listView.*RTTIResult::ptr)(firstScrollLinePos); + } + // generate dialog title uint32_t len = g_tidLookups->groupVoice().size(); std::stringstream ss; From a28eaa46d4f13a2991979bea28f16187e470f56e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 23 Dec 2024 15:01:27 -0500 Subject: [PATCH 19/68] silence warning; --- src/tged/TGListWnd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 00670fc8..73387254 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -132,7 +132,7 @@ public: /* * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. */ - if (firstScrollLinePos > m_listView.getCount()) + if ((size_t)firstScrollLinePos > m_listView.getCount()) firstScrollLinePos = 0; if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { (m_listView.*RTTIResult::ptr)(firstScrollLinePos); From a11c0c7543589c06f8f4cb29b720fde3d21ded87 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 23 Dec 2024 15:02:35 -0500 Subject: [PATCH 20/68] fix header documentation; --- src/tged/TGEdMain.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tged/TGEdMain.h b/src/tged/TGEdMain.h index 93ad4295..128a0817 100644 --- a/src/tged/TGEdMain.h +++ b/src/tged/TGEdMain.h @@ -32,7 +32,7 @@ #define __EXE_NAME__ "tged" // --------------------------------------------------------------------------- -// Externs +// Class Declaration // --------------------------------------------------------------------------- /** From 115c677bcec83c83aa182ce74636da2a7ca1c809 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 23 Dec 2024 15:15:12 -0500 Subject: [PATCH 21/68] ensure the FListView vertical scrollbar is updated appropriately when we scroll the list view; --- src/tged/TGListWnd.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 73387254..c459e11b 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -27,6 +27,8 @@ struct PrivateFListViewScrollToY { typedef void(FListView::*type)(int); }; template class HackTheGibson; struct PrivateFListViewIteratorFirst { typedef FListViewIterator FListView::*type; }; template class HackTheGibson; +struct PrivateFListViewVBarPtr { typedef FScrollbarPtr FListView::*type; }; +template class HackTheGibson; // --------------------------------------------------------------------------- // Constants @@ -136,6 +138,7 @@ public: firstScrollLinePos = 0; if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { (m_listView.*RTTIResult::ptr)(firstScrollLinePos); + (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); } // generate dialog title From 80542e246a656d3f427f548b0e9138deebde15c6 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 5 Jan 2025 08:06:35 -0500 Subject: [PATCH 22/68] use *DESTINATION ID* not *SOURCE ID* for outgoing U_DEREG_ACK frames; --- src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp index ba06e082..5cd92136 100644 --- a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp +++ b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp @@ -60,7 +60,7 @@ void OSP_U_DEREG_ACK::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 8) + m_siteData.netId(); // Network ID tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID - tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + tsbkValue = (tsbkValue << 24) + m_dstId; // Destination Radio Address std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); From 14b749fff1b2a6cfa49947f3d617d9258bf6aeb6 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 7 Jan 2025 15:27:34 -0500 Subject: [PATCH 23/68] correct issue where DFSI FSC control port wasn't living at the voice conveyance port + 1; refactor the proof of concept V24UDPport class to handle voice conveyance frames in their own thread (this is so the main clock doesn't get locked up); correct bad offsetting of data for V24UDPort causing weird overflow, underflow and buffer corruption; --- configs/config.example.yml | 2 + src/host/Host.Config.cpp | 5 +- .../modem/port/specialized/V24UDPPort.cpp | 173 ++++++++++++------ src/host/modem/port/specialized/V24UDPPort.h | 15 ++ 4 files changed, 139 insertions(+), 56 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index de7b01fe..e366f611 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -590,6 +590,8 @@ system: jitter: 200 # Timer which will reset local/remote call flags if frames aren't received longer than this time in ms callTimeout: 200 + # Flag indicating when operating in V.24 UDP mode should the FSC protocol be used to negotiate connection. + useFSC: false # Sets received the signal offset from DC. rxDCOffset: 0 # Valid values between -128 and 128 diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 9ad5d41b..67a5a536 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -594,7 +594,10 @@ bool Host::createModem() if (modemMode == MODEM_MODE_DFSI) { yaml::Node networkConf = m_conf["network"]; uint32_t id = networkConf["id"].as(1000U); - modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, g_remotePort, useFSCForUDP, debug); + modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, useFSCForUDP, debug); + if (useFSCForUDP) { + modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort + 1U, g_remotePort, useFSCForUDP, debug); + } m_udpDSFIRemotePort = modemPort; } else { modemPort = new port::UDPPort(g_remoteAddress, g_remotePort); diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 25f11586..fbfea833 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -38,6 +38,12 @@ const uint32_t BUFFER_LENGTH = 2000U; const char* V24_UDP_HARDWARE = "V.24 UDP Modem Controller"; const uint8_t V24_UDP_PROTOCOL_VERSION = 4U; +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::mutex V24UDPPort::m_bufferMutex; + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -139,60 +145,7 @@ void V24UDPPort::clock(uint32_t ms) processCtrlNetwork(); } - // if we have a RTP voice socket - if (m_socket != nullptr) { - uint8_t data[BUFFER_LENGTH]; - ::memset(data, 0x00U, BUFFER_LENGTH); - - sockaddr_storage addr; - uint32_t addrLen; - int ret = m_socket->read(data, BUFFER_LENGTH, addr, addrLen); - if (ret != 0) { - // An error occurred on the socket - if (ret < 0) - return; - - // Add new data to the ring buffer - if (ret > 0) { - RTPHeader rtpHeader = RTPHeader(); - rtpHeader.decode(data); - - // ensure payload type is correct - if (rtpHeader.getPayloadType() != DFSI_RTP_PAYLOAD_TYPE) - { - LogError(LOG_MODEM, "Invalid RTP header received from network"); - return; - } - - // copy message - uint32_t messageLength = ret - RTP_HEADER_LENGTH_BYTES; - UInt8Array __message = std::make_unique(messageLength); - uint8_t* message = __message.get(); - ::memset(message, 0x00U, messageLength); - - ::memcpy(message, data + RTP_HEADER_LENGTH_BYTES, messageLength); - - if (udp::Socket::match(addr, m_addr)) { - UInt8Array __reply = std::make_unique(messageLength + 4U); - uint8_t* reply = __reply.get(); - - reply[0U] = DVM_SHORT_FRAME_START; - reply[1U] = messageLength & 0xFFU; - reply[2U] = CMD_P25_DATA; - - reply[3U] = 0x00U; - - ::memcpy(reply + 4U, message, messageLength); - - m_buffer.addData(reply, messageLength + 4U); - } - else { - std::string addrStr = udp::Socket::address(addr); - LogWarning(LOG_HOST, "SECURITY: Remote modem mode encountered invalid IP address; %s", addrStr.c_str()); - } - } - } - } + processVCNetwork(); } /* Resets the RTP packet sequence and stream ID. */ @@ -231,6 +184,8 @@ int V24UDPPort::read(uint8_t* buffer, uint32_t length) assert(buffer != nullptr); assert(length > 0U); + std::lock_guard lock(m_bufferMutex); + // Get required data from the ring buffer uint32_t avail = m_buffer.dataSize(); if (avail < length) @@ -264,7 +219,10 @@ int V24UDPPort::write(const uint8_t* buffer, uint32_t length) { if (m_socket != nullptr) { uint32_t messageLen = 0U; - uint8_t* message = generateMessage(buffer + 3U, length - 3U, m_streamId, m_peerId, m_pktSeq, &messageLen); + uint8_t* message = generateMessage(buffer + 4U, length - 4U, m_streamId, m_peerId, m_pktSeq, &messageLen); + + if (m_debug) + Utils::dump(1U, "!!! Tx Outgoing DFSI UDP", buffer + 4U, length - 4U); bool written = m_socket->write(message, messageLen, m_addr, m_addrLen); if (written) @@ -458,6 +416,107 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) return nullptr; } +/* Process voice conveyance frames from the network. */ + +void V24UDPPort::processVCNetwork() +{ + // if we have a RTP voice socket + if (m_socket != nullptr) { + uint8_t data[BUFFER_LENGTH]; + ::memset(data, 0x00U, BUFFER_LENGTH); + + sockaddr_storage addr; + uint32_t addrLen; + int ret = m_socket->read(data, BUFFER_LENGTH, addr, addrLen); + if (ret != 0) { + // An error occurred on the socket + if (ret < 0) + return; + + // Add new data to the ring buffer + if (ret > 0) { + if (m_debug) + Utils::dump("!!! Rx Incoming DFSI UDP", data, ret); + + V24PacketRequest* req = new V24PacketRequest(); + req->address = addr; + req->addrLen = addrLen; + + req->rtpHeader = RTPHeader(); + req->rtpHeader.decode(data); + + // ensure payload type is correct + if (req->rtpHeader.getPayloadType() != DFSI_RTP_PAYLOAD_TYPE) { + LogError(LOG_MODEM, "Invalid RTP header received from network"); + delete req; + return; + } + + // copy message + req->length = ret - RTP_HEADER_LENGTH_BYTES; + req->buffer = new uint8_t[req->length]; + ::memset(req->buffer, 0x00U, req->length); + + ::memcpy(req->buffer, data + RTP_HEADER_LENGTH_BYTES, req->length); + + if (!Thread::runAsThread(this, threadedVCNetworkRx, req)) { + delete[] req->buffer; + delete req; + return; + } + } + } + } +} + +/* Process a data frames from the network. */ + +void* V24UDPPort::threadedVCNetworkRx(void* arg) +{ + V24PacketRequest* req = (V24PacketRequest*)arg; + if (req != nullptr) { +#if defined(_WIN32) + ::CloseHandle(req->thread); +#else + ::pthread_detach(req->thread); +#endif // defined(_WIN32) + + V24UDPPort* network = static_cast(req->obj); + if (network == nullptr) { + delete req; + return nullptr; + } + + if (req->length > 0) { + if (udp::Socket::match(req->address, network->m_addr)) { + UInt8Array __reply = std::make_unique(req->length + 4U); + uint8_t* reply = __reply.get(); + + reply[0U] = DVM_SHORT_FRAME_START; + reply[1U] = (req->length + 4U) & 0xFFU; + reply[2U] = CMD_P25_DATA; + + reply[3U] = 0x00U; + + ::memcpy(reply + 4U, req->buffer, req->length); + + std::lock_guard lock(m_bufferMutex); + network->m_buffer.addData(reply, req->length + 4U); + } + else { + std::string addrStr = udp::Socket::address(req->address); + LogWarning(LOG_HOST, "SECURITY: Remote modem mode encountered invalid IP address; %s", addrStr.c_str()); + } + } + + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } + + return nullptr; +} + /* Internal helper to setup the voice channel port. */ void V24UDPPort::createVCPort(uint16_t port) @@ -580,6 +639,7 @@ void V24UDPPort::getVersion() reply[1U] = count; + std::lock_guard lock(m_bufferMutex); m_buffer.addData(reply, count); } @@ -612,6 +672,7 @@ void V24UDPPort::getStatus() reply[11U] = 0U; + std::lock_guard lock(m_bufferMutex); m_buffer.addData(reply, 12U); } @@ -626,6 +687,7 @@ void V24UDPPort::writeAck(uint8_t type) reply[2U] = CMD_ACK; reply[3U] = type; + std::lock_guard lock(m_bufferMutex); m_buffer.addData(reply, 4U); } @@ -641,5 +703,6 @@ void V24UDPPort::writeNAK(uint8_t opcode, uint8_t err) reply[3U] = opcode; reply[4U] = err; + std::lock_guard lock(m_bufferMutex); m_buffer.addData(reply, 5U); } \ No newline at end of file diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index ba114168..28401c5a 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -31,6 +31,7 @@ #include #include +#include namespace modem { @@ -150,6 +151,8 @@ namespace modem bool m_debug; + static std::mutex m_bufferMutex; + /** * @brief Process FSC control frames from the network. */ @@ -162,6 +165,18 @@ namespace modem */ static void* threadedCtrlNetworkRx(void* arg); + /** + * @brief Process voice conveyance frames from the network. + */ + void processVCNetwork(); + + /** + * @brief Entry point to process a given network packet. + * @param arg Instance of the NetPacketRequest structure. + * @returns void* (Ignore) + */ + static void* threadedVCNetworkRx(void* arg); + /** * @brief Internal helper to setup the voice channel port. * @param port Port number. From 88dcb0bc96b44a7775d1a31c4af606646491b7ab Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 7 Jan 2025 15:47:42 -0500 Subject: [PATCH 24/68] update copyright dates; --- src/bridge/BridgeMain.cpp | 4 ++-- src/bridge/HostBridge.cpp | 2 +- src/fne/FNEMain.cpp | 4 ++-- src/fne/HostFNE.cpp | 2 +- src/host/Host.cpp | 2 +- src/host/HostMain.cpp | 4 ++-- .../modem/port/specialized/V24UDPPort.cpp | 2 +- src/host/modem/port/specialized/V24UDPPort.h | 2 +- src/monitor/MonitorMain.cpp | 6 +++--- src/monitor/MonitorMainWnd.h | 2 +- src/remote/RESTClientMain.cpp | 2 +- src/sysview/SysViewMain.cpp | 20 ++++++------------- src/sysview/SysViewMainWnd.h | 2 +- src/tged/TGEdMain.cpp | 6 +++--- src/tged/TGEdMainWnd.h | 2 +- 15 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/bridge/BridgeMain.cpp b/src/bridge/BridgeMain.cpp index 07063d1f..0ca43790 100644 --- a/src/bridge/BridgeMain.cpp +++ b/src/bridge/BridgeMain.cpp @@ -92,7 +92,7 @@ void fatal(const char* msg, ...) void usage(const char* message, const char* arg) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (message != nullptr) { ::fprintf(stderr, "%s: ", g_progExe.c_str()); @@ -209,7 +209,7 @@ int checkArgs(int argc, char* argv[]) #endif else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (argc == 2) exit(EXIT_SUCCESS); diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index fe6fef7f..6fe4e54c 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -318,7 +318,7 @@ int HostBridge::run() #endif // !defined(_WIN32) ::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ + "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ ">> Audio Bridge\r\n"); diff --git a/src/fne/FNEMain.cpp b/src/fne/FNEMain.cpp index ec63f18c..765c6cbb 100644 --- a/src/fne/FNEMain.cpp +++ b/src/fne/FNEMain.cpp @@ -79,7 +79,7 @@ void fatal(const char* msg, ...) void usage(const char* message, const char* arg) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (message != nullptr) { ::fprintf(stderr, "%s: ", g_progExe.c_str()); @@ -143,7 +143,7 @@ int checkArgs(int argc, char* argv[]) } else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n"); if (argc == 2) exit(EXIT_SUCCESS); diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 266238eb..d1292106 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -151,7 +151,7 @@ int HostFNE::run() #endif // !defined(_WIN32) ::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ + "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ ">> Fixed Network Equipment\r\n"); diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 383551ea..012bb016 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -239,7 +239,7 @@ int Host::run() #endif // !defined(_WIN32) ::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ + "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ ">> Modem Controller\r\n"); diff --git a/src/host/HostMain.cpp b/src/host/HostMain.cpp index b9fefec5..f5bd93fa 100644 --- a/src/host/HostMain.cpp +++ b/src/host/HostMain.cpp @@ -99,7 +99,7 @@ void fatal(const char* msg, ...) void usage(const char* message, const char* arg) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (message != nullptr) { ::fprintf(stderr, "%s: ", g_progExe.c_str()); @@ -216,7 +216,7 @@ int checkArgs(int argc, char* argv[]) } else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n"); if (argc == 2) exit(EXIT_SUCCESS); diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index fbfea833..2a875319 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 28401c5a..808c32ab 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -8,7 +8,7 @@ * @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** diff --git a/src/monitor/MonitorMain.cpp b/src/monitor/MonitorMain.cpp index ba6aea8c..36cb43ae 100644 --- a/src/monitor/MonitorMain.cpp +++ b/src/monitor/MonitorMain.cpp @@ -67,7 +67,7 @@ void fatal(const char* msg, ...) void usage(const char* message, const char* arg) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (message != nullptr) { ::fprintf(stderr, "%s: ", g_progExe.c_str()); @@ -134,7 +134,7 @@ int checkArgs(int argc, char* argv[]) } else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (argc == 2) exit(EXIT_SUCCESS); @@ -186,7 +186,7 @@ int main(int argc, char** argv) } ::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ + "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ ">> Host Monitor\r\n"); diff --git a/src/monitor/MonitorMainWnd.h b/src/monitor/MonitorMainWnd.h index b71824c3..d72a0ef0 100644 --- a/src/monitor/MonitorMainWnd.h +++ b/src/monitor/MonitorMainWnd.h @@ -102,7 +102,7 @@ public: FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n" L"" + __BANNER__ + L"\n" L"Version " + __VER__ + L"\n\n" - L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" + L"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others", FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); info.setCenterText(); diff --git a/src/remote/RESTClientMain.cpp b/src/remote/RESTClientMain.cpp index b7b1a8bb..d3d2b88a 100644 --- a/src/remote/RESTClientMain.cpp +++ b/src/remote/RESTClientMain.cpp @@ -346,7 +346,7 @@ int checkArgs(int argc, char* argv[]) } else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (argc == 2) exit(EXIT_SUCCESS); diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index 30b57362..5ed79fd0 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.cpp @@ -997,7 +997,7 @@ void* threadNetworkPump(void* arg) void usage(const char* message, const char* arg) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (message != nullptr) { ::fprintf(stderr, "%s: ", g_progExe.c_str()); @@ -1082,7 +1082,7 @@ int checkArgs(int argc, char* argv[]) } else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (argc == 2) exit(EXIT_SUCCESS); @@ -1133,18 +1133,10 @@ int main(int argc, char** argv) return 1; } - if (!g_webSocketMode) { - ::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ - "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ - ">> FNE System View\r\n"); - } else { - ::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2024 DVMProject (https://github.com/dvmproject) Authors.\r\n" \ - "This program is non-free software; redistribution is strictly prohibited.\r\n" \ - "RESTRICTED CONFIDENTIAL PROPRIETARY. DO NOT DISTRIBUTE.\r\n" \ - ">> FNE System View\r\n"); - } + ::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ + "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ + "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ + ">> FNE System View\r\n"); try { ret = yaml::Parse(g_conf, g_iniFile.c_str()); diff --git a/src/sysview/SysViewMainWnd.h b/src/sysview/SysViewMainWnd.h index 2b3ac015..cc420b19 100644 --- a/src/sysview/SysViewMainWnd.h +++ b/src/sysview/SysViewMainWnd.h @@ -180,7 +180,7 @@ public: FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n" L"" + __BANNER__ + L"\n" L"Version " + __VER__ + L"\n\n" - L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" + L"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others", FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); info.setCenterText(); diff --git a/src/tged/TGEdMain.cpp b/src/tged/TGEdMain.cpp index 589f16d1..da216e83 100644 --- a/src/tged/TGEdMain.cpp +++ b/src/tged/TGEdMain.cpp @@ -67,7 +67,7 @@ void fatal(const char* msg, ...) void usage(const char* message, const char* arg) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (message != nullptr) { ::fprintf(stderr, "%s: ", g_progExe.c_str()); @@ -134,7 +134,7 @@ int checkArgs(int argc, char* argv[]) } else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); - ::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); if (argc == 2) exit(EXIT_SUCCESS); @@ -186,7 +186,7 @@ int main(int argc, char** argv) } ::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ + "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ ">> Talkgroup Rules Editor\r\n"); diff --git a/src/tged/TGEdMainWnd.h b/src/tged/TGEdMainWnd.h index bf61ffc1..2182f720 100644 --- a/src/tged/TGEdMainWnd.h +++ b/src/tged/TGEdMainWnd.h @@ -84,7 +84,7 @@ public: FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n" L"" + __BANNER__ + L"\n" L"Version " + __VER__ + L"\n\n" - L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" + L"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others", FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); info.setCenterText(); From 19d8f2237f1aa8d4564dbe723aa8f65f2d56831d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 7 Jan 2025 17:15:35 -0500 Subject: [PATCH 25/68] refactor FSC (it was entirely just broken); --- configs/config.example.yml | 6 +- src/common/p25/dfsi/frames/fsc/FSCACK.cpp | 2 +- src/common/p25/dfsi/frames/fsc/FSCConnect.cpp | 2 +- .../dfsi/frames/fsc/FSCConnectResponse.cpp | 2 +- .../p25/dfsi/frames/fsc/FSCDisconnect.cpp | 4 +- .../p25/dfsi/frames/fsc/FSCHeartbeat.cpp | 4 +- src/common/p25/dfsi/frames/fsc/FSCMessage.h | 2 +- src/host/Host.Config.cpp | 11 +- .../modem/port/specialized/V24UDPPort.cpp | 174 ++++++++++-------- src/host/modem/port/specialized/V24UDPPort.h | 7 +- 10 files changed, 125 insertions(+), 89 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index e366f611..ebb3b421 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -590,8 +590,10 @@ system: jitter: 200 # Timer which will reset local/remote call flags if frames aren't received longer than this time in ms callTimeout: 200 - # Flag indicating when operating in V.24 UDP mode should the FSC protocol be used to negotiate connection. - useFSC: false + # Flag indicating when operating in V.24 UDP mode, the FSC protocol should be used to negotiate connection. + fsc: false + # Flag indicating when operating in V.24 UDP mode, this instance should initiate the FSC protocol handshake. + initiator: false # Sets received the signal offset from DC. rxDCOffset: 0 # Valid values between -128 and 128 diff --git a/src/common/p25/dfsi/frames/fsc/FSCACK.cpp b/src/common/p25/dfsi/frames/fsc/FSCACK.cpp index 2fa01505..10abc999 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCACK.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCACK.cpp @@ -44,7 +44,7 @@ FSCACK::FSCACK(const uint8_t* data) : FSCMessage(data), m_responseCode(FSCAckResponseCode::CONTROL_ACK), m_respLength(0U) { - decode(data); + FSCACK::decode(data); } /* Decode a FSC ACK frame. */ diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp b/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp index ce7d3666..9d3f6fdd 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp @@ -42,7 +42,7 @@ FSCConnect::FSCConnect(const uint8_t* data) : FSCMessage(data), m_fsHeartbeatPeriod(5U), m_hostHeartbeatPeriod(5U) { - decode(data); + FSCConnect::decode(data); } /* Decode a FSC connect frame. */ diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp b/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp index 6af0a65e..d93332ed 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp @@ -36,7 +36,7 @@ FSCConnectResponse::FSCConnectResponse() : FSCResponse(), FSCConnectResponse::FSCConnectResponse(const uint8_t* data) : FSCResponse(data), m_vcBasePort(0U) { - decode(data); + FSCConnectResponse::decode(data); } /* Decode a FSC connect frame. */ diff --git a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp index f6f79c01..03255b49 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/fsc/FSCDisconnect.h" @@ -34,5 +34,5 @@ FSCDisconnect::FSCDisconnect() : FSCMessage() FSCDisconnect::FSCDisconnect(const uint8_t* data) : FSCMessage(data) { - decode(data); + FSCMessage::decode(data); } diff --git a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp index 43aef62e..a37613a2 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/fsc/FSCHeartbeat.h" @@ -34,5 +34,5 @@ FSCHeartbeat::FSCHeartbeat() : FSCMessage() FSCHeartbeat::FSCHeartbeat(const uint8_t* data) : FSCMessage(data) { - decode(data); + /* stub */ } diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.h b/src/common/p25/dfsi/frames/fsc/FSCMessage.h index 1772c86c..2b5b6252 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.h +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.h @@ -82,7 +82,7 @@ namespace p25 /** * @brief */ - __PROTECTED_READONLY_PROPERTY(uint8_t, correlationTag, CorrelationTag); + __PROPERTY(uint8_t, correlationTag, CorrelationTag); }; } // namespace fsc } // namespace frames diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 67a5a536..4f8318ad 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -7,7 +7,7 @@ * @package DVM / Modem Host Software * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -461,8 +461,9 @@ bool Host::createModem() bool rtrt = dfsiParams["rtrt"].as(true); bool diu = dfsiParams["diu"].as(true); uint16_t jitter = dfsiParams["jitter"].as(200U); - bool useFSCForUDP = dfsiParams["useFSC"].as(false); uint16_t dfsiCallTimeout = dfsiParams["callTimeout"].as(200U); + bool useFSCForUDP = dfsiParams["fsc"].as(false); + bool fscInitiator = dfsiParams["initiator"].as(false); // clamp fifo sizes if (dmrFifoLength < DMR_TX_BUFFER_LEN) { @@ -580,6 +581,7 @@ bool Host::createModem() LogInfo(" DFSI Jitter Size: %u ms", jitter); if (g_remoteModemMode) { LogInfo(" DFSI Use FSC: %s", useFSCForUDP ? "yes" : "no"); + LogInfo(" DFSI FSC Initiator: %s", fscInitiator ? "yes" : "no"); } } @@ -594,9 +596,10 @@ bool Host::createModem() if (modemMode == MODEM_MODE_DFSI) { yaml::Node networkConf = m_conf["network"]; uint32_t id = networkConf["id"].as(1000U); - modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, useFSCForUDP, debug); if (useFSCForUDP) { - modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort + 1U, g_remotePort, useFSCForUDP, debug); + modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort + 1U, g_remotePort, true, fscInitiator, debug); + } else { + modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, false, false, debug); } m_udpDSFIRemotePort = modemPort; } else { diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 2a875319..941fb529 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -50,7 +50,7 @@ std::mutex V24UDPPort::m_bufferMutex; /* Initializes a new instance of the V24UDPPort class. */ -V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t modemPort, uint16_t controlPort, bool useFSC, bool debug) : +V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t modemPort, uint16_t controlPort, bool useFSC, bool fscInitiator, bool debug) : m_socket(nullptr), m_localPort(modemPort), m_controlSocket(nullptr), @@ -61,6 +61,8 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod m_addrLen(0U), m_ctrlAddrLen(0U), m_buffer(2000U, "UDP Port Ring Buffer"), + m_fscInitiator(fscInitiator), + m_timeoutTimer(1000U, 30U), m_reqConnectionTimer(1000U, 30U), m_heartbeatTimer(1000U, 5U), m_reqConnectionToPeer(true), @@ -120,7 +122,7 @@ void V24UDPPort::clock(uint32_t ms) { // if we have a FSC control socket if (m_controlSocket != nullptr) { - if (m_reqConnectionToPeer) { + if (!m_establishedConnection && m_fscInitiator) { if (!m_reqConnectionTimer.isRunning()) { // make initial request writeConnect(); @@ -130,6 +132,7 @@ void V24UDPPort::clock(uint32_t ms) if (m_reqConnectionTimer.isRunning() && m_reqConnectionTimer.hasExpired()) { // make another request writeConnect(); + m_reqConnectionTimer.start(); } } } @@ -142,6 +145,16 @@ void V24UDPPort::clock(uint32_t ms) } } + m_timeoutTimer.clock(ms); + if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { + LogError(LOG_NET, "PEER %u connection to the DFSI endpoint has timed out, disconnected"); + m_reqConnectionTimer.stop(); + m_reqConnectionToPeer = true; + m_establishedConnection = false; + m_heartbeatTimer.stop(); + m_timeoutTimer.stop(); + } + processCtrlNetwork(); } @@ -268,7 +281,7 @@ void V24UDPPort::processCtrlNetwork() // read message UInt8Array buffer = m_ctrlFrameQueue->read(length, address, addrLen); if (length > 0) { - if (m_debug) + //if (m_debug) Utils::dump(1U, "FSC Control Network Message", buffer.get(), length); V24PacketRequest* req = new V24PacketRequest(); @@ -299,6 +312,8 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) ::pthread_detach(req->thread); #endif // defined(_WIN32) + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + V24UDPPort* network = static_cast(req->obj); if (network == nullptr) { delete req; @@ -306,7 +321,7 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) } if (req->length > 0) { - if (network->m_reqConnectionToPeer) { + if (network->m_reqConnectionToPeer && !network->m_establishedConnection) { // FSC_CONNECT response -- is ... strange if (req->buffer[0] == 1U) { network->m_reqConnectionToPeer = false; @@ -319,91 +334,100 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) network->m_localPort = vcBasePort; network->createVCPort(vcBasePort); network->m_heartbeatTimer.start(); + network->m_timeoutTimer.start(); - uint8_t buffer[FSCConnectResponse::LENGTH]; - ::memset(buffer, 0x00U, FSCConnectResponse::LENGTH); - - resp.setVCBasePort(network->m_localPort); - resp.encode(buffer); + LogMessage(LOG_MODEM, "Established DFSI FSC Connection, vcBasePort = %u", vcBasePort); - network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen); + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + return nullptr; } } - else - { - std::unique_ptr message = FSCMessage::createMessage(req->buffer); - if (message != nullptr) { - switch (message->getMessageId()) - { - case FSCMessageType::FSC_ACK: + + std::unique_ptr message = FSCMessage::createMessage(req->buffer); + if (message != nullptr) { + switch (message->getMessageId()) + { + case FSCMessageType::FSC_ACK: + { + FSCACK* ackMessage = static_cast(message.get()); + switch (ackMessage->getResponseCode()) { - FSCACK* ackMessage = static_cast(message.get()); - switch (ackMessage->getResponseCode()) - { - case FSCAckResponseCode::CONTROL_NAK: - case FSCAckResponseCode::CONTROL_NAK_CONNECTED: - case FSCAckResponseCode::CONTROL_NAK_M_UNSUPP: - case FSCAckResponseCode::CONTROL_NAK_V_UNSUPP: - case FSCAckResponseCode::CONTROL_NAK_F_UNSUPP: - case FSCAckResponseCode::CONTROL_NAK_PARMS: - case FSCAckResponseCode::CONTROL_NAK_BUSY: - LogError(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X", ackMessage->getAckMessageId(), ackMessage->getResponseCode()); - break; - - case FSCAckResponseCode::CONTROL_ACK: - { - if (ackMessage->getAckMessageId() == FSCMessageType::FSC_DISCONNECT) { - network->m_reqConnectionTimer.stop(); - network->m_reqConnectionToPeer = false; - network->m_establishedConnection = false; - network->m_heartbeatTimer.stop(); - } + case FSCAckResponseCode::CONTROL_NAK: + case FSCAckResponseCode::CONTROL_NAK_CONNECTED: + case FSCAckResponseCode::CONTROL_NAK_M_UNSUPP: + case FSCAckResponseCode::CONTROL_NAK_V_UNSUPP: + case FSCAckResponseCode::CONTROL_NAK_F_UNSUPP: + case FSCAckResponseCode::CONTROL_NAK_PARMS: + case FSCAckResponseCode::CONTROL_NAK_BUSY: + LogError(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X", ackMessage->getAckMessageId(), ackMessage->getResponseCode()); + break; + + case FSCAckResponseCode::CONTROL_ACK: + { + if (ackMessage->getAckMessageId() == FSCMessageType::FSC_DISCONNECT) { + network->m_reqConnectionTimer.stop(); + network->m_reqConnectionToPeer = true; + network->m_establishedConnection = false; + network->m_heartbeatTimer.stop(); + network->m_timeoutTimer.stop(); } - break; + } + break; - default: - LogError(LOG_MODEM, "V.24 UDP, unknown ACK opcode, ackMessageId = $%02X", ackMessage->getAckMessageId()); - break; - } + default: + LogError(LOG_MODEM, "V.24 UDP, unknown ACK opcode, ackMessageId = $%02X", ackMessage->getAckMessageId()); + break; + } + } + break; + + case FSCMessageType::FSC_CONNECT: + { + if (network->m_socket != nullptr) { + network->m_socket->close(); + delete network->m_socket; } - break; - case FSCMessageType::FSC_CONNECT: - { - network->createVCPort(network->m_localPort); - network->m_heartbeatTimer.start(); + network->createVCPort(network->m_localPort); - uint8_t buffer[FSCConnectResponse::LENGTH]; - ::memset(buffer, 0x00U, FSCConnectResponse::LENGTH); + network->m_reqConnectionToPeer = false; + network->m_reqConnectionTimer.stop(); + network->m_establishedConnection = true; + network->m_heartbeatTimer.start(); + network->m_timeoutTimer.start(); - FSCConnectResponse resp = FSCConnectResponse(req->buffer); - resp.setVCBasePort(network->m_localPort); - resp.encode(buffer); + LogMessage(LOG_MODEM, "Incoming DFSI FSC Connection, vcBasePort = %u", network->m_localPort); - network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen); - } - break; + uint8_t buffer[FSCConnectResponse::LENGTH]; + ::memset(buffer, 0x00U, FSCConnectResponse::LENGTH); - case FSCMessageType::FSC_DISCONNECT: - { - network->m_reqConnectionTimer.stop(); - network->m_reqConnectionToPeer = false; - network->m_establishedConnection = false; - network->m_heartbeatTimer.stop(); - } - break; + FSCConnectResponse resp = FSCConnectResponse(); + resp.setVCBasePort(network->m_localPort); + resp.encode(buffer); - case FSCMessageType::FSC_HEARTBEAT: - { - if (network->m_establishedConnection) { - network->writeHeartbeat(); - } - } - break; + network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen); + } + break; + + case FSCMessageType::FSC_DISCONNECT: + { + LogMessage(LOG_MODEM, "DFSI FSC Disconnect"); + network->m_reqConnectionTimer.stop(); + network->m_reqConnectionToPeer = true; + network->m_establishedConnection = false; + network->m_heartbeatTimer.stop(); + network->m_timeoutTimer.stop(); + } + break; - default: - break; - } + case FSCMessageType::FSC_HEARTBEAT: + network->m_timeoutTimer.start(); + break; + + default: + break; } } } @@ -536,6 +560,8 @@ void V24UDPPort::createVCPort(uint16_t port) void V24UDPPort::writeConnect() { + LogMessage(LOG_MODEM, "Attempting DFSI FSC Connection, peerId = %u, vcBasePort = %u", m_peerId, m_localPort); + FSCConnect connect = FSCConnect(); connect.setFSHeartbeatPeriod(5U); // hardcoded? connect.setHostHeartbeatPeriod(5U); // hardcoded? diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 808c32ab..6b2d8392 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -72,9 +72,10 @@ namespace modem * @param modemPort Port number. * @param controlPort Control Port number. * @param useFSC Flag indicating whether or not FSC handshakes are used to setup communications. + * @param fscInitiator Flag indicating whether or not the FSC handshake should be initiated when the port is opened. * @param debug Flag indicating whether network debug is enabled. */ - V24UDPPort(uint32_t peerId, const std::string& modemAddress, uint16_t modemPort, uint16_t controlPort = 0U, bool useFSC = false, bool debug = false); + V24UDPPort(uint32_t peerId, const std::string& modemAddress, uint16_t modemPort, uint16_t controlPort = 0U, bool useFSC = false, bool fscInitiator = false, bool debug = false); /** * @brief Finalizes a instance of the V24UDPPort class. */ @@ -132,6 +133,10 @@ namespace modem RingBuffer m_buffer; + bool m_fscInitiator; + + Timer m_timeoutTimer; + Timer m_reqConnectionTimer; Timer m_heartbeatTimer; From 85dd6fce098efd4081c62c16675e471642f87464 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 7 Jan 2025 17:16:13 -0500 Subject: [PATCH 26/68] remove unused variable; --- src/host/modem/port/specialized/V24UDPPort.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 941fb529..240234cb 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -312,8 +312,6 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) ::pthread_detach(req->thread); #endif // defined(_WIN32) - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - V24UDPPort* network = static_cast(req->obj); if (network == nullptr) { delete req; From 3347035d0db3dd2a98bd52c02d19f6a61118cafd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 7 Jan 2025 17:17:37 -0500 Subject: [PATCH 27/68] reenable debug message; --- src/host/modem/port/specialized/V24UDPPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 240234cb..efcdc0bc 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -281,7 +281,7 @@ void V24UDPPort::processCtrlNetwork() // read message UInt8Array buffer = m_ctrlFrameQueue->read(length, address, addrLen); if (length > 0) { - //if (m_debug) + if (m_debug) Utils::dump(1U, "FSC Control Network Message", buffer.get(), length); V24PacketRequest* req = new V24PacketRequest(); From 1a8f3bcfb97b1c7f0d39a0e4a7b6a7d971622b85 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 7 Jan 2025 17:18:33 -0500 Subject: [PATCH 28/68] refactor message; --- src/host/modem/port/specialized/V24UDPPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index efcdc0bc..38166201 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -147,7 +147,7 @@ void V24UDPPort::clock(uint32_t ms) m_timeoutTimer.clock(ms); if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { - LogError(LOG_NET, "PEER %u connection to the DFSI endpoint has timed out, disconnected"); + LogError(LOG_NET, "DFSI connection to the remote endpoint has timed out, disconnected"); m_reqConnectionTimer.stop(); m_reqConnectionToPeer = true; m_establishedConnection = false; From ff0a5c57c7c467d2044f0a69e7e118b0e357779a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 7 Jan 2025 17:38:02 -0500 Subject: [PATCH 29/68] fix issue where ports wouldn't be opened after FSC connection establishment; --- src/host/modem/port/specialized/V24UDPPort.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 38166201..ed376d84 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -331,6 +331,7 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) network->m_localPort = vcBasePort; network->createVCPort(vcBasePort); + network->m_socket->open(network->m_addr); network->m_heartbeatTimer.start(); network->m_timeoutTimer.start(); @@ -389,6 +390,7 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) } network->createVCPort(network->m_localPort); + network->m_socket->open(network->m_addr); network->m_reqConnectionToPeer = false; network->m_reqConnectionTimer.stop(); From da3a2a1869f3ade2cb1c64dc7a941ab0ef3096c4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 8 Jan 2025 20:41:23 -0500 Subject: [PATCH 30/68] fix long standing dvmhost shutdown bug (object cleanup wasn't occuring due to bad state change handling); refactor FSC packet data classes; remove FSCResponse and FSCConnectResponse (these were ill conceived due to bad interpretation of TIA-102 specifications); correct FSCACK not properly decoding response user data after packet data; refactor the way the FSC message factory createMessage() function created instances of FSC packets; refactor how opening and closing the FSC port is handled; better implement the FSC state machine; correct bad response to an FSC_CONNECT (due to incorrect understanding of TIA-102 specifications); --- src/common/p25/dfsi/frames/Frames.h | 2 - src/common/p25/dfsi/frames/fsc/FSCACK.cpp | 16 +- src/common/p25/dfsi/frames/fsc/FSCACK.h | 9 +- src/common/p25/dfsi/frames/fsc/FSCConnect.cpp | 11 - src/common/p25/dfsi/frames/fsc/FSCConnect.h | 7 +- .../dfsi/frames/fsc/FSCConnectResponse.cpp | 62 ---- .../p25/dfsi/frames/fsc/FSCConnectResponse.h | 78 ----- .../p25/dfsi/frames/fsc/FSCDisconnect.cpp | 7 - .../p25/dfsi/frames/fsc/FSCDisconnect.h | 7 +- .../p25/dfsi/frames/fsc/FSCHeartbeat.cpp | 7 - src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h | 7 +- src/common/p25/dfsi/frames/fsc/FSCMessage.cpp | 37 ++- src/common/p25/dfsi/frames/fsc/FSCMessage.h | 5 - .../p25/dfsi/frames/fsc/FSCResponse.cpp | 60 ---- src/common/p25/dfsi/frames/fsc/FSCResponse.h | 77 ----- src/host/Host.Config.cpp | 7 +- src/host/Host.cpp | 14 +- src/host/Host.h | 2 +- .../modem/port/specialized/V24UDPPort.cpp | 273 ++++++++++++------ src/host/modem/port/specialized/V24UDPPort.h | 27 +- 20 files changed, 241 insertions(+), 474 deletions(-) delete mode 100644 src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp delete mode 100644 src/common/p25/dfsi/frames/fsc/FSCConnectResponse.h delete mode 100644 src/common/p25/dfsi/frames/fsc/FSCResponse.cpp delete mode 100644 src/common/p25/dfsi/frames/fsc/FSCResponse.h diff --git a/src/common/p25/dfsi/frames/Frames.h b/src/common/p25/dfsi/frames/Frames.h index b529d58d..3e71e77e 100644 --- a/src/common/p25/dfsi/frames/Frames.h +++ b/src/common/p25/dfsi/frames/Frames.h @@ -30,10 +30,8 @@ // FSC #include "common/p25/dfsi/frames/fsc/FSCMessage.h" -#include "common/p25/dfsi/frames/fsc/FSCResponse.h" #include "common/p25/dfsi/frames/fsc/FSCACK.h" #include "common/p25/dfsi/frames/fsc/FSCConnect.h" -#include "common/p25/dfsi/frames/fsc/FSCConnectResponse.h" #include "common/p25/dfsi/frames/fsc/FSCDisconnect.h" #include "common/p25/dfsi/frames/fsc/FSCHeartbeat.h" diff --git a/src/common/p25/dfsi/frames/fsc/FSCACK.cpp b/src/common/p25/dfsi/frames/fsc/FSCACK.cpp index 10abc999..6d5d025a 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCACK.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCACK.cpp @@ -35,18 +35,6 @@ FSCACK::FSCACK() : FSCMessage(), m_messageId = FSCMessageType::FSC_ACK; } -/* Initializes a instance of the FSCACK class. */ - -FSCACK::FSCACK(const uint8_t* data) : FSCMessage(data), - m_ackMessageId(FSCMessageType::FSC_INVALID), - m_ackVersion(1U), - m_ackCorrelationTag(0U), - m_responseCode(FSCAckResponseCode::CONTROL_ACK), - m_respLength(0U) -{ - FSCACK::decode(data); -} - /* Decode a FSC ACK frame. */ bool FSCACK::decode(const uint8_t* data) @@ -65,7 +53,7 @@ bool FSCACK::decode(const uint8_t* data) delete responseData; responseData = new uint8_t[m_respLength]; ::memset(responseData, 0x00U, m_respLength); - ::memcpy(responseData, data, m_respLength); + ::memcpy(responseData, data + 7U, m_respLength); } else { if (responseData != nullptr) @@ -90,6 +78,6 @@ void FSCACK::encode(uint8_t* data) data[6U] = m_respLength; // Response Data Length if (m_respLength > 0U && responseData != nullptr) { - ::memcpy(data, responseData, m_respLength); + ::memcpy(data + 7U, responseData, m_respLength); } } diff --git a/src/common/p25/dfsi/frames/fsc/FSCACK.h b/src/common/p25/dfsi/frames/fsc/FSCACK.h index f2df8f51..2c378a82 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCACK.h +++ b/src/common/p25/dfsi/frames/fsc/FSCACK.h @@ -41,17 +41,12 @@ namespace p25 */ class HOST_SW_API FSCACK : public FSCMessage { public: - static const uint8_t LENGTH = 6; + static const uint8_t LENGTH = 7U; /** * @brief Initializes a copy instance of the FSCACK class. */ FSCACK(); - /** - * @brief Initializes a copy instance of the FSCACK class. - * @param data Buffer to containing FSCACK to decode. - */ - FSCACK(const uint8_t* data); /** * @brief Decode a FSC ACK frame. @@ -78,7 +73,7 @@ namespace p25 /** * @brief */ - __READONLY_PROPERTY(uint8_t, ackCorrelationTag, AckCorrelationTag); + __PROPERTY(uint8_t, ackCorrelationTag, AckCorrelationTag); /** * @brief Response code. */ diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp b/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp index 9d3f6fdd..ed0f7149 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp @@ -34,17 +34,6 @@ FSCConnect::FSCConnect() : FSCMessage(), m_messageId = FSCMessageType::FSC_CONNECT; } -/* Initializes a instance of the FSCConnect class. */ - -FSCConnect::FSCConnect(const uint8_t* data) : FSCMessage(data), - m_vcBasePort(0U), - m_vcSSRC(0U), - m_fsHeartbeatPeriod(5U), - m_hostHeartbeatPeriod(5U) -{ - FSCConnect::decode(data); -} - /* Decode a FSC connect frame. */ bool FSCConnect::decode(const uint8_t* data) diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnect.h b/src/common/p25/dfsi/frames/fsc/FSCConnect.h index 067a93bd..9dc3d60e 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCConnect.h +++ b/src/common/p25/dfsi/frames/fsc/FSCConnect.h @@ -41,17 +41,12 @@ namespace p25 */ class HOST_SW_API FSCConnect : public FSCMessage { public: - static const uint8_t LENGTH = 11; + static const uint8_t LENGTH = 11U; /** * @brief Initializes a copy instance of the FSCConnect class. */ FSCConnect(); - /** - * @brief Initializes a copy instance of the FSCConnect class. - * @param data Buffer to containing FSCConnect to decode. - */ - FSCConnect(const uint8_t* data); /** * @brief Decode a FSC connect frame. diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp b/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp deleted file mode 100644 index d93332ed..00000000 --- a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// 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. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/dfsi/frames/fsc/FSCConnectResponse.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25::dfsi; -using namespace p25::dfsi::frames; -using namespace p25::dfsi::frames::fsc; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the FSCConnectResponse class. */ - -FSCConnectResponse::FSCConnectResponse() : FSCResponse(), - m_vcBasePort(0U) -{ - /* stub */ -} - -/* Initializes a instance of the FSCConnectResponse class. */ - -FSCConnectResponse::FSCConnectResponse(const uint8_t* data) : FSCResponse(data), - m_vcBasePort(0U) -{ - FSCConnectResponse::decode(data); -} - -/* Decode a FSC connect frame. */ - -bool FSCConnectResponse::decode(const uint8_t* data) -{ - assert(data != nullptr); - FSCResponse::decode(data); - - m_vcBasePort = __GET_UINT16B(data, 1U); // Voice Conveyance RTP Port - - return true; -} - -/* Encode a FSC connect frame. */ - -void FSCConnectResponse::encode(uint8_t* data) -{ - assert(data != nullptr); - FSCResponse::encode(data); - - __SET_UINT16B(m_vcBasePort, data, 1U); // Voice Conveyance RTP Port -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.h b/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.h deleted file mode 100644 index 18196c15..00000000 --- a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.h +++ /dev/null @@ -1,78 +0,0 @@ -// 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. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file FSCConnectResponse.h - * @ingroup dfsi_fsc_frames - * @file FSCConnectResponse.cpp - * @ingroup dfsi_fsc_frames - */ -#if !defined(__FSC_CONNECT_RESPONSE_H__) -#define __FSC_CONNECT_RESPONSE_H__ - -#include "Defines.h" -#include "common/Defines.h" -#include "common/Log.h" -#include "common/Utils.h" -#include "common/p25/dfsi/frames/FrameDefines.h" -#include "common/p25/dfsi/frames/fsc/FSCResponse.h" - -namespace p25 -{ - namespace dfsi - { - namespace frames - { - namespace fsc - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Implements the FSC Connect Response Message. - * @ingroup dfsi_fsc_frames - */ - class HOST_SW_API FSCConnectResponse : public FSCResponse { - public: - static const uint8_t LENGTH = 3; - - /** - * @brief Initializes a copy instance of the FSCConnectResponse class. - */ - FSCConnectResponse(); - /** - * @brief Initializes a copy instance of the FSCConnectResponse class. - * @param data Buffer to containing FSCConnectResponse to decode. - */ - FSCConnectResponse(const uint8_t* data); - - /** - * @brief Decode a FSC connect response frame. - * @param[in] data Buffer to containing FSCConnectResponse to decode. - */ - bool decode(const uint8_t* data) override; - /** - * @brief Encode a FSC connect response frame. - * @param[out] data Buffer to encode a FSCConnectResponse. - */ - void encode(uint8_t* data) override; - - public: - /** - * @brief Voice Conveyance RTP Port. - */ - __PROPERTY(uint16_t, vcBasePort, VCBasePort); - }; - } // namespace fsc - } // namespace frames - } // namespace dfsi -} // namespace p25 - -#endif // __FSC_CONNECT_RESPONSE_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp index 03255b49..2001055b 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp @@ -29,10 +29,3 @@ FSCDisconnect::FSCDisconnect() : FSCMessage() { m_messageId = FSCMessageType::FSC_DISCONNECT; } - -/* Initializes a instance of the FSCDisconnect class. */ - -FSCDisconnect::FSCDisconnect(const uint8_t* data) : FSCMessage(data) -{ - FSCMessage::decode(data); -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h index 0cea9537..1e9517b9 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h +++ b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h @@ -41,17 +41,12 @@ namespace p25 */ class HOST_SW_API FSCDisconnect : public FSCMessage { public: - static const uint8_t LENGTH = 3; + static const uint8_t LENGTH = 3U; /** * @brief Initializes a copy instance of the FSCDisconnect class. */ FSCDisconnect(); - /** - * @brief Initializes a copy instance of the FSCDisconnect class. - * @param data Buffer to containing FSCDisconnect to decode. - */ - FSCDisconnect(const uint8_t* data); }; } // namespace fsc } // namespace frames diff --git a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp index a37613a2..339d0325 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp @@ -29,10 +29,3 @@ FSCHeartbeat::FSCHeartbeat() : FSCMessage() { m_messageId = FSCMessageType::FSC_HEARTBEAT; } - -/* Initializes a instance of the FSCHeartbeat class. */ - -FSCHeartbeat::FSCHeartbeat(const uint8_t* data) : FSCMessage(data) -{ - /* stub */ -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h index 15a33d1f..ccc7e68e 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h +++ b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h @@ -41,17 +41,12 @@ namespace p25 */ class HOST_SW_API FSCHeartbeat : public FSCMessage { public: - static const uint8_t LENGTH = 3; + static const uint8_t LENGTH = 3U; /** * @brief Initializes a copy instance of the FSCHeartbeat class. */ FSCHeartbeat(); - /** - * @brief Initializes a copy instance of the FSCHeartbeat class. - * @param data Buffer to containing FSCHeartbeat to decode. - */ - FSCHeartbeat(const uint8_t* data); }; } // namespace fsc } // namespace frames diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp index cd483f35..dc3f4f54 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp @@ -29,21 +29,11 @@ using namespace p25::dfsi::frames::fsc; FSCMessage::FSCMessage() : m_messageId(FSCMessageType::FSC_INVALID), m_version(1U), - m_correlationTag(0U) + m_correlationTag(1U) { /* stub */ } -/* Initializes a instance of the FSCMessage class. */ - -FSCMessage::FSCMessage(const uint8_t* data) : - m_messageId(FSCMessageType::FSC_INVALID), - m_version(1U), - m_correlationTag(0U) -{ - decode(data); -} - /* Decode a FSC message frame. */ bool FSCMessage::decode(const uint8_t* data) @@ -78,25 +68,32 @@ std::unique_ptr FSCMessage::createMessage(const uint8_t* data) { assert(data != nullptr); - uint8_t msg[FSCMessage::LENGTH + 1U]; - ::memset(msg, 0x00U, FSCMessage::LENGTH); + uint8_t messageId = (FSCMessageType::E)(data[0U]); // Message ID - uint8_t messageId = (FSCMessageType::E)(msg[0U]); // Message ID + FSCMessage* message = nullptr; // standard P25 reference opcodes switch (messageId) { case FSCMessageType::FSC_CONNECT: - return std::unique_ptr(new FSCConnect(data)); + message = new FSCConnect(); + break; case FSCMessageType::FSC_HEARTBEAT: - return std::unique_ptr(new FSCHeartbeat(data)); + message = new FSCHeartbeat(); + break; case FSCMessageType::FSC_ACK: - return std::unique_ptr(new FSCACK(data)); + message = new FSCACK(); + break; case FSCMessageType::FSC_DISCONNECT: - return std::unique_ptr(new FSCDisconnect(data)); + message = new FSCDisconnect(); + break; default: - LogError(LOG_P25, "FSCMessage::create(), unknown message value, messageId = $%02X", messageId); + LogError(LOG_P25, "FSCMessage::createMessage(), unknown message value, messageId = $%02X", messageId); break; } - return nullptr; + if (!message->decode(data)) { + return nullptr; + } + + return std::unique_ptr(message); } diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.h b/src/common/p25/dfsi/frames/fsc/FSCMessage.h index 2b5b6252..08398c7a 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.h +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.h @@ -46,11 +46,6 @@ namespace p25 * @brief Initializes a copy instance of the FSCMessage class. */ FSCMessage(); - /** - * @brief Initializes a copy instance of the FSCMessage class. - * @param data Buffer to containing FSCMessage to decode. - */ - FSCMessage(const uint8_t* data); /** * @brief Decode a FSC message frame. diff --git a/src/common/p25/dfsi/frames/fsc/FSCResponse.cpp b/src/common/p25/dfsi/frames/fsc/FSCResponse.cpp deleted file mode 100644 index 4cf5b57d..00000000 --- a/src/common/p25/dfsi/frames/fsc/FSCResponse.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// 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. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/dfsi/frames/fsc/FSCResponse.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25::dfsi; -using namespace p25::dfsi::frames; -using namespace p25::dfsi::frames::fsc; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the FSCResponse class. */ - -FSCResponse::FSCResponse() : - m_version(1U) -{ - /* stub */ -} - -/* Initializes a instance of the FSCResponse class. */ - -FSCResponse::FSCResponse(const uint8_t* data) : - m_version(1U) -{ - decode(data); -} - -/* Decode a FSC message frame. */ - -bool FSCResponse::decode(const uint8_t* data) -{ - assert(data != nullptr); - - m_version = data[0U]; // Response Version - - return true; -} - -/* Encode a FSC message frame. */ - -void FSCResponse::encode(uint8_t* data) -{ - assert(data != nullptr); - - data[0U] = m_version; // Response Version -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCResponse.h b/src/common/p25/dfsi/frames/fsc/FSCResponse.h deleted file mode 100644 index cb4cbf18..00000000 --- a/src/common/p25/dfsi/frames/fsc/FSCResponse.h +++ /dev/null @@ -1,77 +0,0 @@ -// 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. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file FSCResponse.h - * @ingroup dfsi_fsc_frames - * @file FSCResponse.cpp - * @ingroup dfsi_fsc_frames - */ -#if !defined(__FSC_RESPONSE_H__) -#define __FSC_RESPONSE_H__ - -#include "Defines.h" -#include "common/Defines.h" -#include "common/Log.h" -#include "common/Utils.h" -#include "common/p25/dfsi/frames/FrameDefines.h" - -namespace p25 -{ - namespace dfsi - { - namespace frames - { - namespace fsc - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Base class FSC response messages derive from. - * @ingroup dfsi_fsc_frames - */ - class HOST_SW_API FSCResponse { - public: - static const uint8_t LENGTH = 1; - - /** - * @brief Initializes a copy instance of the FSCResponse class. - */ - FSCResponse(); - /** - * @brief Initializes a copy instance of the FSCResponse class. - * @param data Buffer to containing FSCResponse to decode. - */ - FSCResponse(const uint8_t* data); - - /** - * @brief Decode a FSC message frame. - * @param[in] data Buffer to containing FSCResponse to decode. - */ - virtual bool decode(const uint8_t* data); - /** - * @brief Encode a FSC message frame. - * @param[out] data Buffer to encode a FSCResponse. - */ - virtual void encode(uint8_t* data); - - public: - /** - * @brief Response Version. - */ - __PROTECTED_READONLY_PROPERTY(uint8_t, version, Version); - }; - } // namespace fsc - } // namespace frames - } // namespace dfsi -} // namespace p25 - -#endif // __FSC_RESPONSE_H__ \ No newline at end of file diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 4f8318ad..007103f2 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -601,7 +601,7 @@ bool Host::createModem() } else { modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, false, false, debug); } - m_udpDSFIRemotePort = modemPort; + m_udpDFSIRemotePort = modemPort; } else { modemPort = new port::UDPPort(g_remoteAddress, g_remotePort); } @@ -687,6 +687,11 @@ bool Host::createModem() m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(Host::rmtPortModemHandler, this)); } + if (useFSCForUDP) { + modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDFSIRemotePort); + udpPort->openFSC(); + } + bool ret = m_modem->open(); if (!ret) { delete m_modem; diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 012bb016..f1effa93 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -64,7 +64,7 @@ Host::Host(const std::string& confFile) : m_modem(nullptr), m_modemRemote(false), m_isModemDFSI(false), - m_udpDSFIRemotePort(nullptr), + m_udpDFSIRemotePort(nullptr), m_network(nullptr), m_modemRemotePort(nullptr), m_state(STATE_IDLE), @@ -945,10 +945,9 @@ int Host::run() } } - if (m_udpDSFIRemotePort != nullptr) { + if (m_udpDFSIRemotePort != nullptr) { m_mainLoopStage = 11U; // intentional magic number - modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDSFIRemotePort); - + modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDFSIRemotePort); udpPort->clock(ms); } @@ -1638,9 +1637,14 @@ void Host::setState(uint8_t state) m_modeTimer.stop(); - if (m_state == HOST_STATE_QUIT) { + if (state == HOST_STATE_QUIT) { ::LogInfoEx(LOG_HOST, "Host is shutting down"); + if (m_udpDFSIRemotePort != nullptr) { + modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDFSIRemotePort); + udpPort->closeFSC(); + } + if (m_modem != nullptr) { m_modem->close(); delete m_modem; diff --git a/src/host/Host.h b/src/host/Host.h index 50117cfa..f1aec2fb 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -99,7 +99,7 @@ private: modem::Modem* m_modem; bool m_modemRemote; bool m_isModemDFSI; - modem::port::IModemPort* m_udpDSFIRemotePort; + modem::port::IModemPort* m_udpDFSIRemotePort; network::Network* m_network; modem::port::IModemPort* m_modemRemotePort; diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index ed376d84..92cc7cec 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -65,13 +65,12 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod m_timeoutTimer(1000U, 30U), m_reqConnectionTimer(1000U, 30U), m_heartbeatTimer(1000U, 5U), - m_reqConnectionToPeer(true), - m_establishedConnection(false), m_random(), m_peerId(peerId), m_streamId(0U), m_timestamp(INVALID_TS), m_pktSeq(0U), + m_fscState(CS_NOT_CONNECTED), m_modemState(STATE_P25), m_tx(false), m_debug(debug) @@ -122,7 +121,7 @@ void V24UDPPort::clock(uint32_t ms) { // if we have a FSC control socket if (m_controlSocket != nullptr) { - if (!m_establishedConnection && m_fscInitiator) { + if ((m_fscState == CS_NOT_CONNECTED) && m_fscInitiator) { if (!m_reqConnectionTimer.isRunning()) { // make initial request writeConnect(); @@ -137,7 +136,7 @@ void V24UDPPort::clock(uint32_t ms) } } - if (m_establishedConnection) { + if (m_fscState == CS_CONNECTED) { m_heartbeatTimer.clock(ms); if (m_heartbeatTimer.isRunning() && m_heartbeatTimer.hasExpired()) { writeHeartbeat(); @@ -147,10 +146,12 @@ void V24UDPPort::clock(uint32_t ms) m_timeoutTimer.clock(ms); if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { - LogError(LOG_NET, "DFSI connection to the remote endpoint has timed out, disconnected"); - m_reqConnectionTimer.stop(); - m_reqConnectionToPeer = true; - m_establishedConnection = false; + LogError(LOG_NET, "V.24 UDP, DFSI connection to the remote endpoint has timed out, disconnected"); + m_fscState = CS_NOT_CONNECTED; + if (!m_fscInitiator) + m_reqConnectionTimer.stop(); + else + m_reqConnectionTimer.start(); m_heartbeatTimer.stop(); m_timeoutTimer.stop(); } @@ -170,18 +171,34 @@ void V24UDPPort::reset() m_streamId = createStreamId(); } -/* Opens a connection to the port. */ +/* Opens a connection to the FSC port. */ -bool V24UDPPort::open() +bool V24UDPPort::openFSC() { - if (m_addrLen == 0U && m_ctrlAddrLen == 0U) { + if (m_ctrlAddrLen == 0U) { LogError(LOG_NET, "Unable to resolve the address of the modem"); return false; } if (m_controlSocket != nullptr) { return m_controlSocket->open(m_controlAddr); + } + + return false; +} + +/* Opens a connection to the port. */ + +bool V24UDPPort::open() +{ + if (m_controlSocket != nullptr) { + return true; // FSC mode always returns that the port was opened } else { + if (m_addrLen == 0U) { + LogError(LOG_NET, "Unable to resolve the address of the modem"); + return false; + } + if (m_socket != nullptr) { return m_socket->open(m_addr); } @@ -256,12 +273,33 @@ int V24UDPPort::write(const uint8_t* buffer, uint32_t length) return -1; } +/* Closes the connection to the FSC port. */ + +void V24UDPPort::closeFSC() +{ + if (m_controlSocket != nullptr) { + if (m_fscState == CS_CONNECTED) { + LogMessage(LOG_MODEM, "V.24 UDP, Closing DFSI FSC Connection, vcBasePort = %u", m_localPort); + + FSCDisconnect discoMessage = FSCDisconnect(); + + uint8_t buffer[FSCDisconnect::LENGTH]; + ::memset(buffer, 0x00U, FSCDisconnect::LENGTH); + discoMessage.encode(buffer); + + m_ctrlFrameQueue->write(buffer, FSCDisconnect::LENGTH, m_controlAddr, m_ctrlAddrLen); + + Thread::sleep(500U); + } + + m_controlSocket->close(); + } +} + /* Closes the connection to the port. */ void V24UDPPort::close() { - if (m_controlSocket != nullptr) - m_controlSocket->close(); if (m_socket != nullptr) m_socket->close(); } @@ -319,108 +357,157 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) } if (req->length > 0) { - if (network->m_reqConnectionToPeer && !network->m_establishedConnection) { - // FSC_CONNECT response -- is ... strange - if (req->buffer[0] == 1U) { - network->m_reqConnectionToPeer = false; - network->m_reqConnectionTimer.stop(); - network->m_establishedConnection = true; - - FSCConnectResponse resp = FSCConnectResponse(req->buffer); - uint16_t vcBasePort = resp.getVCBasePort(); - - network->m_localPort = vcBasePort; - network->createVCPort(vcBasePort); - network->m_socket->open(network->m_addr); - network->m_heartbeatTimer.start(); - network->m_timeoutTimer.start(); - - LogMessage(LOG_MODEM, "Established DFSI FSC Connection, vcBasePort = %u", vcBasePort); - - if (req->buffer != nullptr) - delete[] req->buffer; - delete req; - return nullptr; - } - } - std::unique_ptr message = FSCMessage::createMessage(req->buffer); if (message != nullptr) { - switch (message->getMessageId()) - { + switch (message->getMessageId()) { case FSCMessageType::FSC_ACK: - { - FSCACK* ackMessage = static_cast(message.get()); - switch (ackMessage->getResponseCode()) + { + FSCACK* ackMessage = static_cast(message.get()); + if (network->m_debug) + LogDebug(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X, respLength = %u", ackMessage->getAckMessageId(), ackMessage->getResponseCode(), ackMessage->getResponseLength()); + + switch (ackMessage->getResponseCode()) { + case FSCAckResponseCode::CONTROL_NAK: + case FSCAckResponseCode::CONTROL_NAK_CONNECTED: + case FSCAckResponseCode::CONTROL_NAK_M_UNSUPP: + case FSCAckResponseCode::CONTROL_NAK_V_UNSUPP: + case FSCAckResponseCode::CONTROL_NAK_F_UNSUPP: + case FSCAckResponseCode::CONTROL_NAK_PARMS: + case FSCAckResponseCode::CONTROL_NAK_BUSY: + LogError(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X", ackMessage->getAckMessageId(), ackMessage->getResponseCode()); + break; + + case FSCAckResponseCode::CONTROL_ACK: { - case FSCAckResponseCode::CONTROL_NAK: - case FSCAckResponseCode::CONTROL_NAK_CONNECTED: - case FSCAckResponseCode::CONTROL_NAK_M_UNSUPP: - case FSCAckResponseCode::CONTROL_NAK_V_UNSUPP: - case FSCAckResponseCode::CONTROL_NAK_F_UNSUPP: - case FSCAckResponseCode::CONTROL_NAK_PARMS: - case FSCAckResponseCode::CONTROL_NAK_BUSY: - LogError(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X", ackMessage->getAckMessageId(), ackMessage->getResponseCode()); + switch (ackMessage->getAckMessageId()) { + case FSCMessageType::FSC_CONNECT: + { + uint16_t vcBasePort = __GET_UINT16B(ackMessage->responseData, 1U); + + if (network->m_socket != nullptr) { + network->m_socket->close(); + delete network->m_socket; + network->m_socket = nullptr; + } + + network->m_localPort = vcBasePort; + network->createVCPort(vcBasePort); + network->m_socket->open(network->m_addr); + + network->m_fscState = CS_CONNECTED; + network->m_reqConnectionTimer.stop(); + network->m_heartbeatTimer.start(); + network->m_timeoutTimer.start(); + + LogMessage(LOG_MODEM, "V.24 UDP, Established DFSI FSC Connection, vcBasePort = %u", vcBasePort); + } break; - case FSCAckResponseCode::CONTROL_ACK: + case FSCMessageType::FSC_DISCONNECT: { - if (ackMessage->getAckMessageId() == FSCMessageType::FSC_DISCONNECT) { - network->m_reqConnectionTimer.stop(); - network->m_reqConnectionToPeer = true; - network->m_establishedConnection = false; - network->m_heartbeatTimer.stop(); - network->m_timeoutTimer.stop(); + if (network->m_socket != nullptr) { + network->m_socket->close(); + delete network->m_socket; + network->m_socket = nullptr; } + + network->m_fscState = CS_NOT_CONNECTED; + if (!network->m_fscInitiator) + network->m_reqConnectionTimer.stop(); + else + network->m_reqConnectionTimer.start(); + network->m_heartbeatTimer.stop(); + network->m_timeoutTimer.stop(); } break; - default: - LogError(LOG_MODEM, "V.24 UDP, unknown ACK opcode, ackMessageId = $%02X", ackMessage->getAckMessageId()); - break; + default: + break; + } } + break; + + default: + LogError(LOG_MODEM, "V.24 UDP, unknown ACK opcode, ackMessageId = $%02X", ackMessage->getAckMessageId()); + break; } - break; + } + break; case FSCMessageType::FSC_CONNECT: - { - if (network->m_socket != nullptr) { - network->m_socket->close(); - delete network->m_socket; - } + { + FSCConnect* connMessage = static_cast(message.get()); + FSCACK ackResp = FSCACK(); + ackResp.setCorrelationTag(connMessage->getCorrelationTag()); + ackResp.setAckMessageId(FSCMessageType::FSC_CONNECT); + ackResp.setResponseCode(FSCAckResponseCode::CONTROL_ACK); + ackResp.setAckCorrelationTag(connMessage->getCorrelationTag()); + + if (connMessage->getVersion() != 1U) { + ackResp.setResponseCode(FSCAckResponseCode::CONTROL_NAK_V_UNSUPP); + + uint8_t buffer[FSCACK::LENGTH]; + ::memset(buffer, 0x00U, FSCACK::LENGTH); + ackResp.encode(buffer); + + network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen); + break; + } + + if (network->m_socket != nullptr) { + network->m_socket->close(); + delete network->m_socket; + network->m_socket = nullptr; + } - network->createVCPort(network->m_localPort); - network->m_socket->open(network->m_addr); + uint16_t vcPort = connMessage->getVCBasePort(); + network->m_localPort = vcPort; - network->m_reqConnectionToPeer = false; - network->m_reqConnectionTimer.stop(); - network->m_establishedConnection = true; - network->m_heartbeatTimer.start(); - network->m_timeoutTimer.start(); + network->createVCPort(network->m_localPort); + network->m_socket->open(network->m_addr); - LogMessage(LOG_MODEM, "Incoming DFSI FSC Connection, vcBasePort = %u", network->m_localPort); + network->m_fscState = CS_CONNECTED; + network->m_reqConnectionTimer.stop(); + network->m_heartbeatTimer.start(); + network->m_timeoutTimer.start(); - uint8_t buffer[FSCConnectResponse::LENGTH]; - ::memset(buffer, 0x00U, FSCConnectResponse::LENGTH); + LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, vcBasePort = %u", network->m_localPort); - FSCConnectResponse resp = FSCConnectResponse(); - resp.setVCBasePort(network->m_localPort); - resp.encode(buffer); + // construct connect ACK response data + uint8_t respData[3U]; + ::memset(respData, 0x00U, 3U); - network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen); - } - break; + respData[0U] = 1U; // Version 1 + __SET_UINT16B(network->m_localPort, respData, 1U); + + // pack ack + ackResp.setResponseLength(3U); + ackResp.responseData = respData; + + uint8_t buffer[FSCACK::LENGTH + 3U]; + ::memset(buffer, 0x00U, FSCACK::LENGTH + 3U); + ackResp.encode(buffer); + + network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH + 3U, network->m_controlAddr, network->m_ctrlAddrLen); + } + break; case FSCMessageType::FSC_DISCONNECT: - { - LogMessage(LOG_MODEM, "DFSI FSC Disconnect"); - network->m_reqConnectionTimer.stop(); - network->m_reqConnectionToPeer = true; - network->m_establishedConnection = false; - network->m_heartbeatTimer.stop(); - network->m_timeoutTimer.stop(); + { + LogMessage(LOG_MODEM, "V.24 UDP, DFSI FSC Disconnect, vcBasePort = %u", network->m_localPort); + + if (network->m_socket != nullptr) { + network->m_socket->close(); + delete network->m_socket; + network->m_socket = nullptr; } - break; + + network->m_fscState = CS_NOT_CONNECTED; + network->m_reqConnectionTimer.stop(); + network->m_heartbeatTimer.stop(); + network->m_timeoutTimer.stop(); + } + break; case FSCMessageType::FSC_HEARTBEAT: network->m_timeoutTimer.start(); @@ -560,7 +647,7 @@ void V24UDPPort::createVCPort(uint16_t port) void V24UDPPort::writeConnect() { - LogMessage(LOG_MODEM, "Attempting DFSI FSC Connection, peerId = %u, vcBasePort = %u", m_peerId, m_localPort); + LogMessage(LOG_MODEM, "V.24 UDP, Attempting DFSI FSC Connection, peerId = %u, vcBasePort = %u", m_peerId, m_localPort); FSCConnect connect = FSCConnect(); connect.setFSHeartbeatPeriod(5U); // hardcoded? @@ -573,6 +660,8 @@ void V24UDPPort::writeConnect() connect.encode(buffer); + m_fscState = CS_CONNECTING; + m_ctrlFrameQueue->write(buffer, FSCConnect::LENGTH, m_controlAddr, m_ctrlAddrLen); } diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 6b2d8392..750f54cf 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -93,20 +93,25 @@ namespace modem void reset(); /** - * @brief Opens a connection to the serial port. + * @brief Opens a connection to the FSC port. + * @returns bool True, if connection is opened, otherwise false. + */ + bool openFSC(); + /** + * @brief Opens a connection to the port. * @returns bool True, if connection is opened, otherwise false. */ bool open() override; /** - * @brief Reads data from the serial port. + * @brief Reads data from the port. * @param[out] buffer Buffer to read data from the port to. * @param length Length of data to read from the port. * @returns int Actual length of data read from serial port. */ int read(uint8_t* buffer, uint32_t length) override; /** - * @brief Writes data to the serial port. + * @brief Writes data to the port. * @param[in] buffer Buffer containing data to write to port. * @param length Length of data to write to port. * @returns int Actual length of data written to the port. @@ -114,7 +119,11 @@ namespace modem int write(const uint8_t* buffer, uint32_t length) override; /** - * @brief Closes the connection to the serial port. + * @brief Closes the connection to the FSC port. + */ + void closeFSC(); + /** + * @brief Closes the connection to the port. */ void close() override; @@ -140,9 +149,6 @@ namespace modem Timer m_reqConnectionTimer; Timer m_heartbeatTimer; - bool m_reqConnectionToPeer; - bool m_establishedConnection; - std::mt19937 m_random; uint32_t m_peerId; @@ -151,6 +157,13 @@ namespace modem uint32_t m_timestamp; uint16_t m_pktSeq; + enum CS_STATE : uint8_t { + CS_NOT_CONNECTED = 0, + CS_CONNECTING = 1, + CS_CONNECTED = 2 + }; + CS_STATE m_fscState; + uint8_t m_modemState; bool m_tx; From ca7149e9f45f34024b27bae21c0e47e7477f65d4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 8 Jan 2025 21:00:13 -0500 Subject: [PATCH 31/68] add FSC_REPORT_SEL_MODES; --- src/common/p25/dfsi/frames/FrameDefines.h | 2 ++ src/common/p25/dfsi/frames/Frames.h | 1 + src/common/p25/dfsi/frames/fsc/FSCMessage.cpp | 3 ++ .../modem/port/specialized/V24UDPPort.cpp | 31 +++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/src/common/p25/dfsi/frames/FrameDefines.h b/src/common/p25/dfsi/frames/FrameDefines.h index 4798cab8..1277a8fe 100644 --- a/src/common/p25/dfsi/frames/FrameDefines.h +++ b/src/common/p25/dfsi/frames/FrameDefines.h @@ -48,6 +48,8 @@ namespace p25 FSC_HEARTBEAT = 1, //! Heartbeat/Connectivity Maintenance. FSC_ACK = 2, //! Control Service Ack. + FSC_REPORT_SEL_MODES = 8, //! Report Selected Modes + FSC_DISCONNECT = 9, //! Detach Control Service. FSC_INVALID = 127, //! Invalid Control Message. diff --git a/src/common/p25/dfsi/frames/Frames.h b/src/common/p25/dfsi/frames/Frames.h index 3e71e77e..694eeeb5 100644 --- a/src/common/p25/dfsi/frames/Frames.h +++ b/src/common/p25/dfsi/frames/Frames.h @@ -32,6 +32,7 @@ #include "common/p25/dfsi/frames/fsc/FSCMessage.h" #include "common/p25/dfsi/frames/fsc/FSCACK.h" #include "common/p25/dfsi/frames/fsc/FSCConnect.h" +#include "common/p25/dfsi/frames/fsc/FSCReportSelModes.h" #include "common/p25/dfsi/frames/fsc/FSCDisconnect.h" #include "common/p25/dfsi/frames/fsc/FSCHeartbeat.h" diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp index dc3f4f54..efd47a66 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp @@ -83,6 +83,9 @@ std::unique_ptr FSCMessage::createMessage(const uint8_t* data) case FSCMessageType::FSC_ACK: message = new FSCACK(); break; + case FSCMessageType::FSC_REPORT_SEL_MODES: + message = new FSCReportSelModes(); + break; case FSCMessageType::FSC_DISCONNECT: message = new FSCDisconnect(); break; diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 92cc7cec..fe24b974 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -492,6 +492,37 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) } break; + case FSCMessageType::FSC_REPORT_SEL_MODES: + { + FSCACK ackResp = FSCACK(); + ackResp.setCorrelationTag(message->getCorrelationTag()); + ackResp.setAckMessageId(FSCMessageType::FSC_REPORT_SEL_MODES); + ackResp.setResponseCode(FSCAckResponseCode::CONTROL_ACK); + ackResp.setAckCorrelationTag(message->getCorrelationTag()); + + // construct connect ACK response data + uint8_t respData[5U]; + ::memset(respData, 0x00U, 5U); + + // bryanb: because DVM is essentially a repeater -- we hardcode these values + respData[0U] = 1U; // Version 1 + respData[1U] = 1U; // Repeat Inbound Audio + respData[2U] = 0U; // Rx Channel Selection + respData[3U] = 0U; // Tx Channel Selection + respData[4U] = 1U; // Monitor Mode + + // pack ack + ackResp.setResponseLength(5U); + ackResp.responseData = respData; + + uint8_t buffer[FSCACK::LENGTH + 5U]; + ::memset(buffer, 0x00U, FSCACK::LENGTH + 5U); + ackResp.encode(buffer); + + network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH + 5U, network->m_controlAddr, network->m_ctrlAddrLen); + } + break; + case FSCMessageType::FSC_DISCONNECT: { LogMessage(LOG_MODEM, "V.24 UDP, DFSI FSC Disconnect, vcBasePort = %u", network->m_localPort); From ddda823b5588f5205f8a9ead7bd037253f5548a7 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 8 Jan 2025 21:00:26 -0500 Subject: [PATCH 32/68] whoops forgot to add files for previous commit; --- .../p25/dfsi/frames/fsc/FSCReportSelModes.cpp | 31 ++++++++++ .../p25/dfsi/frames/fsc/FSCReportSelModes.h | 56 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/common/p25/dfsi/frames/fsc/FSCReportSelModes.cpp create mode 100644 src/common/p25/dfsi/frames/fsc/FSCReportSelModes.h diff --git a/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.cpp b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.cpp new file mode 100644 index 00000000..139a773c --- /dev/null +++ b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.cpp @@ -0,0 +1,31 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "common/p25/dfsi/frames/fsc/FSCReportSelModes.h" +#include "common/p25/dfsi/DFSIDefines.h" +#include "common/Utils.h" +#include "common/Log.h" + +#include +#include + +using namespace p25::dfsi; +using namespace p25::dfsi::frames; +using namespace p25::dfsi::frames::fsc; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a instance of the FSCReportSelModes class. */ + +FSCReportSelModes::FSCReportSelModes() : FSCMessage() +{ + m_messageId = FSCMessageType::FSC_REPORT_SEL_MODES; +} diff --git a/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.h b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.h new file mode 100644 index 00000000..e6be7042 --- /dev/null +++ b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file FSCReportSelModes.h + * @ingroup dfsi_fsc_frames + * @file FSCReportSelModes.cpp + * @ingroup dfsi_fsc_frames + */ +#if !defined(__FSC_REPORT_SEL_MODES_H__) +#define __FSC_REPORT_SEL_MODES_H__ + +#include "Defines.h" +#include "common/Defines.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "common/p25/dfsi/frames/FrameDefines.h" +#include "common/p25/dfsi/frames/fsc/FSCMessage.h" + +namespace p25 +{ + namespace dfsi + { + namespace frames + { + namespace fsc + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements the FSC Report Selected Modes Message. + * @ingroup dfsi_fsc_frames + */ + class HOST_SW_API FSCReportSelModes : public FSCMessage { + public: + static const uint8_t LENGTH = 3U; + + /** + * @brief Initializes a copy instance of the FSCReportSelModes class. + */ + FSCReportSelModes(); + }; + } // namespace fsc + } // namespace frames + } // namespace dfsi +} // namespace p25 + +#endif // __FSC_REPORT_SEL_MODES_H__ \ No newline at end of file From 2d450f64b0d54b2e90b110996aba6583e831179e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 9 Jan 2025 12:27:44 -0500 Subject: [PATCH 33/68] [EXPERIMENTAL] initial TIA-102 DFSI packet support (not enabled yet); --- src/common/p25/dfsi/frames/BlockHeader.cpp | 14 +- src/common/p25/dfsi/frames/BlockHeader.h | 6 +- src/common/p25/dfsi/frames/ControlOctet.h | 4 +- src/common/p25/dfsi/frames/FullRateVoice.cpp | 23 +- src/common/p25/dfsi/frames/FullRateVoice.h | 20 +- src/common/p25/dfsi/frames/MotFullRateVoice.h | 6 +- src/common/p25/dfsi/frames/MotPDUFrame.h | 2 +- src/common/p25/dfsi/frames/MotStartOfStream.h | 4 +- .../p25/dfsi/frames/MotStartVoiceFrame.h | 2 +- src/common/p25/dfsi/frames/MotTSBKFrame.h | 2 +- src/common/p25/dfsi/frames/MotVoiceHeader1.h | 4 +- src/common/p25/dfsi/frames/MotVoiceHeader2.h | 4 +- src/common/p25/dfsi/frames/StartOfStream.h | 4 +- src/common/p25/lc/LC.cpp | 14 +- src/common/p25/lc/LC.h | 6 +- src/host/modem/ModemV24.cpp | 1438 +++++++++++++---- src/host/modem/ModemV24.h | 39 +- 17 files changed, 1278 insertions(+), 314 deletions(-) diff --git a/src/common/p25/dfsi/frames/BlockHeader.cpp b/src/common/p25/dfsi/frames/BlockHeader.cpp index 01e56f29..5385ed37 100644 --- a/src/common/p25/dfsi/frames/BlockHeader.cpp +++ b/src/common/p25/dfsi/frames/BlockHeader.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/BlockHeader.h" @@ -26,8 +26,10 @@ using namespace p25::dfsi::frames; /* Initializes a instance of the BlockHeader class. */ BlockHeader::BlockHeader() : - m_payloadType(false), - m_blockLength(BlockType::UNDEFINED) + m_payloadType(true), + m_blockType(BlockType::UNDEFINED), + m_timestampOffset(0U), + m_blockLength(0U) { /* stub */ } @@ -35,8 +37,10 @@ BlockHeader::BlockHeader() : /* Initializes a instance of the BlockHeader class. */ BlockHeader::BlockHeader(uint8_t* data, bool verbose) : - m_payloadType(false), - m_blockLength(BlockType::UNDEFINED) + m_payloadType(true), + m_blockType(BlockType::UNDEFINED), + m_timestampOffset(0U), + m_blockLength(0U) { decode(data, verbose); } diff --git a/src/common/p25/dfsi/frames/BlockHeader.h b/src/common/p25/dfsi/frames/BlockHeader.h index b56c54e3..9d47a6e7 100644 --- a/src/common/p25/dfsi/frames/BlockHeader.h +++ b/src/common/p25/dfsi/frames/BlockHeader.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -53,8 +53,8 @@ namespace p25 */ class HOST_SW_API BlockHeader { public: - static const uint8_t LENGTH = 1; - static const uint8_t VERBOSE_LENGTH = 4; + static const uint8_t LENGTH = 1U; + static const uint8_t VERBOSE_LENGTH = 4U; /** * @brief Initializes a copy instance of the BlockHeader class. diff --git a/src/common/p25/dfsi/frames/ControlOctet.h b/src/common/p25/dfsi/frames/ControlOctet.h index 308ec2e4..3395408b 100644 --- a/src/common/p25/dfsi/frames/ControlOctet.h +++ b/src/common/p25/dfsi/frames/ControlOctet.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -45,7 +45,7 @@ namespace p25 */ class HOST_SW_API ControlOctet { public: - static const uint8_t LENGTH = 1; + static const uint8_t LENGTH = 1U; /** * @brief Initializes a copy instance of the ControlOctet class. diff --git a/src/common/p25/dfsi/frames/FullRateVoice.cpp b/src/common/p25/dfsi/frames/FullRateVoice.cpp index f8b1ed56..8ea60eab 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/FullRateVoice.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/FullRateVoice.h" @@ -95,7 +95,15 @@ bool FullRateVoice::decode(const uint8_t* data) // CAI 9 and 10 are 3 bytes of additional data not 4 ::memcpy(additionalData, data + 14U, ADDITIONAL_LENGTH - 1U); } else { - ::memcpy(additionalData, data + 14U, ADDITIONAL_LENGTH); + uint8_t buffer[ADDITIONAL_LENGTH - 1U]; + ::memset(buffer, 0x00U, ADDITIONAL_LENGTH - 1U); + ::memcpy(buffer, data + 14U, ADDITIONAL_LENGTH - 1U); + buffer[2U] &= 0xC0U; // mask low bits + + uint32_t offset = 0; + for (uint8_t i = 0; i < ADDITIONAL_LENGTH - 1U; i++, offset += 6) { + Utils::hex2Bin(additionalData[i], buffer, offset); + } } } else { if (additionalData != nullptr) @@ -128,7 +136,16 @@ void FullRateVoice::encode(uint8_t* data) // CAI 9 and 10 are 3 bytes of additional data not 4 ::memcpy(data + 14U, additionalData, ADDITIONAL_LENGTH - 1U); } else { - ::memcpy(data + 14U, additionalData, ADDITIONAL_LENGTH); + uint8_t buffer[ADDITIONAL_LENGTH - 1U]; + ::memset(buffer, 0x00U, ADDITIONAL_LENGTH - 1U); + ::memcpy(buffer, additionalData, ADDITIONAL_LENGTH - 1U); + + uint32_t offset = 0; + for (uint8_t i = 0; i < ADDITIONAL_LENGTH - 1U; i++, offset += 6) { + buffer[i] = Utils::bin2Hex(additionalData, offset); + } + + ::memcpy(data + 14U, buffer, ADDITIONAL_LENGTH); } } } diff --git a/src/common/p25/dfsi/frames/FullRateVoice.h b/src/common/p25/dfsi/frames/FullRateVoice.h index a3696bb0..afde4e83 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.h +++ b/src/common/p25/dfsi/frames/FullRateVoice.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -133,10 +133,10 @@ namespace p25 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ - * | Et | Er |M|L|E| E1 |SF | B | Link Ctrl | Link Ctrl | - * | | | | |4| | | | | | + * | Et | Er |M|L|E| E1 |SF | B | Link Ctrl | Link Ctrl | Link | + * | | | | |4| | | | | | | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ - * | Link Ctrl |R| Status | + * |Ctr|R| Status | Rsvd | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * CAI Frames 12 - 17. @@ -150,10 +150,10 @@ namespace p25 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ - * | Et | Er |M|L|E| E1 |SF | B | Enc Sync | Enc Sync | - * | | | | |4| | | | | | + * | Et | Er |M|L|E| E1 |SF | B | Enc Sync | Enc Sync | Enc | + * | | | | |4| | | | | | | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ - * | Enc Sync |R| Status | + * |Syn|R| Status | Rsvd | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * CAI Frames 9 and 10. @@ -177,9 +177,9 @@ namespace p25 */ class HOST_SW_API FullRateVoice { public: - static const uint8_t LENGTH = 18; - static const uint8_t ADDITIONAL_LENGTH = 4; - static const uint8_t IMBE_BUF_LEN = 11; + static const uint8_t LENGTH = 18U; + static const uint8_t ADDITIONAL_LENGTH = 4U; + static const uint8_t IMBE_BUF_LEN = 11U; /** * @brief Initializes a copy instance of the FullRateVoice class. diff --git a/src/common/p25/dfsi/frames/MotFullRateVoice.h b/src/common/p25/dfsi/frames/MotFullRateVoice.h index 1def57cf..111a0bbd 100644 --- a/src/common/p25/dfsi/frames/MotFullRateVoice.h +++ b/src/common/p25/dfsi/frames/MotFullRateVoice.h @@ -55,9 +55,9 @@ namespace p25 */ class HOST_SW_API MotFullRateVoice { public: - static const uint8_t LENGTH = 17; - static const uint8_t SHORTENED_LENGTH = 14; - static const uint8_t ADDITIONAL_LENGTH = 4; + static const uint8_t LENGTH = 17U; + static const uint8_t SHORTENED_LENGTH = 14U; + static const uint8_t ADDITIONAL_LENGTH = 4U; /** * @brief Initializes a copy instance of the MotFullRateVoice class. diff --git a/src/common/p25/dfsi/frames/MotPDUFrame.h b/src/common/p25/dfsi/frames/MotPDUFrame.h index 4280d15a..792143a6 100644 --- a/src/common/p25/dfsi/frames/MotPDUFrame.h +++ b/src/common/p25/dfsi/frames/MotPDUFrame.h @@ -55,7 +55,7 @@ namespace p25 */ class HOST_SW_API MotPDUFrame { public: - static const uint8_t LENGTH = 20; + static const uint8_t LENGTH = 20U; /** * @brief Initializes a copy instance of the MotPDUFrame class. diff --git a/src/common/p25/dfsi/frames/MotStartOfStream.h b/src/common/p25/dfsi/frames/MotStartOfStream.h index 73e898d2..4afcab92 100644 --- a/src/common/p25/dfsi/frames/MotStartOfStream.h +++ b/src/common/p25/dfsi/frames/MotStartOfStream.h @@ -50,8 +50,8 @@ namespace p25 */ class HOST_SW_API MotStartOfStream { public: - static const uint8_t LENGTH = 10; - static const uint8_t FIXED_MARKER = 0x02; + static const uint8_t LENGTH = 10U; + static const uint8_t FIXED_MARKER = 0x02U; /** * @brief Initializes a copy instance of the MotStartOfStream class. diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h index 0c7d3713..acaede19 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h @@ -58,7 +58,7 @@ namespace p25 */ class HOST_SW_API MotStartVoiceFrame { public: - static const uint8_t LENGTH = 22; + static const uint8_t LENGTH = 22U; /** * @brief Initializes a copy instance of the MotStartVoiceFrame class. diff --git a/src/common/p25/dfsi/frames/MotTSBKFrame.h b/src/common/p25/dfsi/frames/MotTSBKFrame.h index 15db4872..654f2640 100644 --- a/src/common/p25/dfsi/frames/MotTSBKFrame.h +++ b/src/common/p25/dfsi/frames/MotTSBKFrame.h @@ -57,7 +57,7 @@ namespace p25 */ class HOST_SW_API MotTSBKFrame { public: - static const uint8_t LENGTH = 24; + static const uint8_t LENGTH = 24U; /** * @brief Initializes a copy instance of the MotTSBKFrame class. diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.h b/src/common/p25/dfsi/frames/MotVoiceHeader1.h index 37cdc3cd..049572bd 100644 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.h +++ b/src/common/p25/dfsi/frames/MotVoiceHeader1.h @@ -61,8 +61,8 @@ namespace p25 */ class HOST_SW_API MotVoiceHeader1 { public: - static const uint8_t LENGTH = 30; - static const uint8_t HCW_LENGTH = 21; + static const uint8_t LENGTH = 30U; + static const uint8_t HCW_LENGTH = 21U; /** * @brief Initializes a copy instance of the MotVoiceHeader1 class. diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader2.h b/src/common/p25/dfsi/frames/MotVoiceHeader2.h index 1fa6615a..07af5245 100644 --- a/src/common/p25/dfsi/frames/MotVoiceHeader2.h +++ b/src/common/p25/dfsi/frames/MotVoiceHeader2.h @@ -57,8 +57,8 @@ namespace p25 */ class HOST_SW_API MotVoiceHeader2 { public: - static const uint8_t LENGTH = 22; - static const uint8_t HCW_LENGTH = 20; + static const uint8_t LENGTH = 22U; + static const uint8_t HCW_LENGTH = 20U; /** * @brief Initializes a copy instance of the MotVoiceHeader2 class. diff --git a/src/common/p25/dfsi/frames/StartOfStream.h b/src/common/p25/dfsi/frames/StartOfStream.h index 11c6fbac..06972926 100644 --- a/src/common/p25/dfsi/frames/StartOfStream.h +++ b/src/common/p25/dfsi/frames/StartOfStream.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -45,7 +45,7 @@ namespace p25 */ class HOST_SW_API StartOfStream { public: - static const uint8_t LENGTH = 4; + static const uint8_t LENGTH = 4U; /** * @brief Initializes a copy instance of the StartOfStream class. diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 98e35693..574df473 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -96,14 +96,17 @@ LC& LC::operator=(const LC& data) /* Decode a header data unit. */ -bool LC::decodeHDU(const uint8_t* data) +bool LC::decodeHDU(const uint8_t* data, bool rawOnly) { assert(data != nullptr); // deinterleave uint8_t rs[P25_HDU_LENGTH_BYTES + 1U]; uint8_t raw[P25_HDU_LENGTH_BYTES + 1U]; - P25Utils::decode(data, raw, 114U, 780U); + if (rawOnly) + ::memcpy(raw, data, P25_HDU_LENGTH_BYTES); + else + P25Utils::decode(data, raw, 114U, 780U); // decode Golay (18,6,8) FEC decodeHDUGolay(raw, rs); @@ -167,7 +170,7 @@ bool LC::decodeHDU(const uint8_t* data) /* Encode a header data unit. */ -void LC::encodeHDU(uint8_t* data) +void LC::encodeHDU(uint8_t* data, bool rawOnly) { assert(data != nullptr); assert(m_mi != nullptr); @@ -202,6 +205,11 @@ void LC::encodeHDU(uint8_t* data) // encode Golay (18,6,8) FEC encodeHDUGolay(raw, rs); + if (rawOnly) { + ::memcpy(data, raw, P25_HDU_LENGTH_BYTES); + return; + } + // interleave P25Utils::encode(raw, data, 114U, 780U); diff --git a/src/common/p25/lc/LC.h b/src/common/p25/lc/LC.h index 1b854af5..bab14773 100644 --- a/src/common/p25/lc/LC.h +++ b/src/common/p25/lc/LC.h @@ -73,14 +73,16 @@ namespace p25 /** * @brief Decode a header data unit. * @param[in] data Buffer containing the HDU to decode. + * @param rawOnly Flag indicating only the raw bytes of the LC should be decoded. * @returns True, if HDU decoded, otherwise false. */ - bool decodeHDU(const uint8_t* data); + bool decodeHDU(const uint8_t* data, bool rawOnly = false); /** * @brief Encode a header data unit. * @param[out] data Buffer to encode an HDU. + * @param rawOnly Flag indicating only the raw bytes of the LC should be encoded. */ - void encodeHDU(uint8_t* data); + void encodeHDU(uint8_t* data, bool rawOnly = false); /** * @brief Decode a logical link data unit 1. diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index bea2a2fe..f5f34744 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024 Patrick McDonnell, W3AXL * */ @@ -56,7 +56,8 @@ ModemV24::ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, u m_callTimeout(200U), m_jitter(jitter), m_lastP25Tx(0U), - m_rs() + m_rs(), + m_useTIAFormat(false) { m_v24Connected = false; // defaulted to false for V.24 modems @@ -89,6 +90,13 @@ void ModemV24::setP25NAC(uint32_t nac) m_nid = new NID(nac); } +/* Helper to set the TIA-102 format DFSI frame flag. */ + +void ModemV24::setTIAFormat(bool set) +{ + m_useTIAFormat = set; +} + /* Opens connection to the air interface modem. */ bool ModemV24::open() @@ -127,7 +135,10 @@ bool ModemV24::open() m_error = false; - LogMessage(LOG_MODEM, "Modem Ready [Direct Mode]"); + if (m_useTIAFormat) + LogMessage(LOG_MODEM, "Modem Ready [Direct Mode / TIA-102]"); + else + LogMessage(LOG_MODEM, "Modem Ready [Direct Mode / V.24]"); return true; } @@ -182,7 +193,10 @@ void ModemV24::clock(uint32_t ms) std::lock_guard lock(m_p25ReadLock); // convert data from V.24/DFSI formatting to TIA-102 air formatting - convertToAir(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + if (m_useTIAFormat) + convertToAirTIA(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + else + convertToAir(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } } break; @@ -429,7 +443,10 @@ int ModemV24::write(const uint8_t* data, uint32_t length) ::memset(buffer, 0x00U, length); ::memcpy(buffer, data + 2U, length); - convertFromAir(buffer, length); + if (m_useTIAFormat) + convertFromAirTIA(buffer, length); + else + convertFromAir(buffer, length); return length; } else { return Modem::write(data, length); @@ -943,8 +960,9 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } } break; + default: - break; + break; } // increment our voice frame counter @@ -1077,235 +1095,1155 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } } -/* Helper to add a V.24 data frame to the P25 TX queue with the proper timestamp and formatting */ +/* Internal helper to convert from TIA-102 DFSI to TIA-102 air interface. */ -void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType) +void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) { assert(data != nullptr); - assert(len > 0U); - - if (m_debug) - LogDebug(LOG_MODEM, "ModemV24::queueP25Frame() msgType = $%02X", msgType); - if (m_trace) - Utils::dump(1U, "ModemV24::queueP25Frame() data", data, len); - - // get current time in ms - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - - // timestamp for this message (in ms) - uint64_t msgTime = 0U; - - // if this is our first message, timestamp is just now + the jitter buffer offset in ms - if (m_lastP25Tx == 0U) { - msgTime = now + m_jitter; - - // if the message type requests no jitter delay -- just set the message time to now - if (msgType == STT_NON_IMBE_NO_JITTER) - msgTime = now; - } - // if we had a message before this, calculate the new timestamp dynamically - else { - // if the last message occurred longer than our jitter buffer delay, we restart the sequence and calculate the same as above - if ((int64_t)(now - m_lastP25Tx) > m_jitter) { - msgTime = now + m_jitter; - } - // otherwise, we time out messages as required by the message type - else { - if (msgType == STT_IMBE) { - // IMBEs must go out at 20ms intervals - msgTime = m_lastP25Tx + 20U; - } else { - // Otherwise we don't care, we use 5ms since that's the theoretical minimum time a 9600 baud message can take - msgTime = m_lastP25Tx + 5U; - } - } - } - - len += 4U; - - // convert 16-bit length to 2 bytes - uint8_t length[2U]; - if (len > 255U) - length[0U] = (len >> 8U) & 0xFFU; - else - length[0U] = 0x00U; - length[1U] = len & 0xFFU; - - m_txP25Queue.addData(length, 2U); - - // add the data tag - uint8_t tag = TAG_DATA; - m_txP25Queue.addData(&tag, 1U); - - // convert 64-bit timestamp to 8 bytes and add - uint8_t tsBytes[8U]; - assert(sizeof msgTime == 8U); - ::memcpy(tsBytes, &msgTime, 8U); - m_txP25Queue.addData(tsBytes, 8U); - - // add the DVM start byte, length byte, CMD byte, and padding 0 - uint8_t header[4U]; - header[0U] = DVM_SHORT_FRAME_START; - header[1U] = len & 0xFFU; - header[2U] = CMD_P25_DATA; - header[3U] = 0x00U; - m_txP25Queue.addData(header, 4U); - - // add the data - m_txP25Queue.addData(data, len - 4U); - - // update the last message time - m_lastP25Tx = msgTime; -} - -/* Send a start of stream sequence (HDU, etc) to the connected serial V.24 device */ - -void ModemV24::startOfStream(const p25::lc::LC& control) -{ - m_txCallInProgress = true; - - MotStartOfStream start = MotStartOfStream(); - start.setStartStop(StartStopFlag::START); - start.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + assert(length > 0U); - // create buffer for bytes and encode - uint8_t startBuf[start.LENGTH]; - ::memset(startBuf, 0x00U, start.LENGTH); - start.encode(startBuf); + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, MotStartOfStream::LENGTH); + // get the DFSI data (skip the 0x00 padded byte at the start) + UInt8Array __dfsiData = std::make_unique(length - 1U); + uint8_t* dfsiData = __dfsiData.get(); + ::memset(dfsiData, 0x00U, length - 1U); + ::memcpy(dfsiData, data + 1U, length - 1U); - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + if (m_debug) + Utils::dump("DFSI RX data from board", dfsiData, length - 1U); - uint8_t mi[MI_LENGTH_BYTES]; - ::memset(mi, 0x00U, MI_LENGTH_BYTES); - control.getMI(mi); + ControlOctet ctrl = ControlOctet(); + ctrl.decode(dfsiData); - uint8_t vhdr[DFSI_VHDR_LEN]; - ::memset(vhdr, 0x00U, DFSI_VHDR_LEN); + uint8_t blockCnt = ctrl.getBlockHeaderCnt(); - ::memcpy(vhdr, mi, MI_LENGTH_BYTES); + // iterate through blocks + uint8_t hdrOffs = 1U, dataOffs = blockCnt; + for (uint8_t i = 0U; i < blockCnt; i++) { + BlockHeader hdr = BlockHeader(); + hdr.decode(dfsiData + hdrOffs); - vhdr[9U] = control.getMFId(); - vhdr[10U] = control.getAlgId(); - __SET_UINT16B(control.getKId(), vhdr, 11U); - __SET_UINT16B(control.getDstId(), vhdr, 13U); + BlockType::E blockType = hdr.getBlockType(); + switch (blockType) { + case BlockType::START_OF_STREAM: + { + StartOfStream start = StartOfStream(); + start.decode(dfsiData + dataOffs); - // perform RS encoding - m_rs.encode362017(vhdr); + uint16_t nac = start.getNID() & 0xFFFU; - // convert the binary bytes to hex bytes - uint8_t raw[DFSI_VHDR_RAW_LEN]; - uint32_t offset = 0; - for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { - raw[i] = Utils::bin2Hex(vhdr, offset); - } + // bryanb: maybe compare the NACs? - // prepare VHDR1 - MotVoiceHeader1 vhdr1 = MotVoiceHeader1(); - vhdr1.startOfStream->setStartStop(StartStopFlag::START); - vhdr1.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - vhdr1.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); + dataOffs += StartOfStream::LENGTH; + } + break; + case BlockType::END_OF_STREAM: + { + dataOffs += 1U; - ::memcpy(vhdr1.header, raw, 8U); - ::memcpy(vhdr1.header + 9U, raw + 8U, 8U); - ::memcpy(vhdr1.header + 18U, raw + 16U, 2U); + // generate Sync + Sync::addP25Sync(buffer + 2U); - // encode VHDR1 and send - uint8_t vhdr1Buf[vhdr1.LENGTH]; - ::memset(vhdr1Buf, 0x00U, vhdr1.LENGTH); - vhdr1.encode(vhdr1Buf); + // generate NID + m_nid->encode(buffer + 2U, DUID::TDU); - if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, MotVoiceHeader1::LENGTH); + // add status bits + P25Utils::setStatusBitsAllIdle(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS); - queueP25Frame(vhdr1Buf, MotVoiceHeader1::LENGTH, STT_NON_IMBE); + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_TDU_FRAME_LENGTH_BITS + 2U); + } + break; - // prepare VHDR2 - MotVoiceHeader2 vhdr2 = MotVoiceHeader2(); - ::memcpy(vhdr2.header, raw + 18U, 8U); - ::memcpy(vhdr2.header + 9U, raw + 26U, 8U); - ::memcpy(vhdr2.header + 18U, raw + 34U, 2U); + case BlockType::VOICE_HEADER_P1: + { + // copy to call data VHDR1 + ::memset(m_rxCall->VHDR1, 0x00U, 18U); + ::memcpy(m_rxCall->VHDR1, dfsiData + dataOffs + 1U, 18U); - // encode VHDR2 and send - uint8_t vhdr2Buf[vhdr2.LENGTH]; - ::memset(vhdr2Buf, 0x00U, vhdr2.LENGTH); - vhdr2.encode(vhdr2Buf); + dataOffs += 19U; // 18 Golay + Block Type Marker + } + break; + case BlockType::VOICE_HEADER_P2: + { + // copy to call data VHDR2 + ::memset(m_rxCall->VHDR2, 0x00U, 18U); + ::memcpy(m_rxCall->VHDR2, dfsiData + dataOffs + 1U, 18U); - if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, MotVoiceHeader2::LENGTH); + dataOffs += 19U; // 18 Golay + Block Type Marker - queueP25Frame(vhdr2Buf, MotVoiceHeader2::LENGTH, STT_NON_IMBE); -} + // buffer for raw VHDR data + uint8_t raw[DFSI_VHDR_RAW_LEN]; + ::memset(raw, 0x00U, DFSI_VHDR_RAW_LEN); -/* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ + ::memcpy(raw, m_rxCall->VHDR1, 18U); + ::memcpy(raw + 18U, m_rxCall->VHDR2, 18U); -void ModemV24::endOfStream() -{ - MotStartOfStream end = MotStartOfStream(); - end.setStartStop(StartStopFlag::STOP); + // buffer for decoded VHDR data + uint8_t vhdr[DFSI_VHDR_RAW_LEN]; - // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); - end.encode(endBuf); + uint32_t offset = 0U; + for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) + Utils::hex2Bin(raw[i], vhdr, offset); - if (m_trace) - Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, MotStartOfStream::LENGTH); + // try to decode the RS data + try { + lc::LC lc = lc::LC(); + if (!lc.decodeHDU(raw, true)) { + LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); + } else { + // late entry? + if (!m_rxCallInProgress) { + m_rxCallInProgress = true; + m_rxCall->resetCallData(); + if (m_debug) + LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); + } - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + uint8_t mi[MI_LENGTH_BYTES]; + lc.getMI(mi); - m_txCallInProgress = false; -} + ::memcpy(m_rxCall->MI, mi, MI_LENGTH_BYTES); -/* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */ + m_rxCall->mfId = lc.getMFId(); + m_rxCall->algoId = lc.getAlgId(); + m_rxCall->kId = lc.getKId(); + m_rxCall->dstId = lc.getDstId(); -void ModemV24::convertFromAir(uint8_t* data, uint32_t length) -{ - assert(data != nullptr); - assert(length > 0U); + if (m_debug) { + LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); + } - if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() data", data, length); + // generate Sync + Sync::addP25Sync(buffer + 2U); - uint8_t ldu[9U * 25U]; - ::memset(ldu, 0x00U, 9 * 25U); + // generate NID + m_nid->encode(buffer + 2U, DUID::HDU); - // decode the NID - bool valid = m_nid->decode(data + 2U); - if (!valid) - return; + // generate HDU + lc.encodeHDU(buffer + 2U); - DUID::E duid = m_nid->getDUID(); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true, false); - // handle individual DUIDs - lc::LC lc = lc::LC(); - data::LowSpeedData lsd = data::LowSpeedData(); - switch (duid) { - case DUID::HDU: - { - bool ret = lc.decodeHDU(data + 2U); - if (!ret) { - LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_HDU_FRAME_LENGTH_BYTES + 2U); + } + } + catch (...) { + LogError(LOG_MODEM, "V.24/DFSI RX traffic got exception while trying to decode RS data for VHDR"); } - - startOfStream(lc); } break; - case DUID::LDU1: + + case BlockType::FULL_RATE_VOICE: { - bool ret = lc.decodeLDU1(data + 2U, true); - if (!ret) { - LogWarning(LOG_MODEM, P25_LDU1_STR ", undecodable LC"); - return; - } - - lsd.process(data + 2U); + FullRateVoice voice = FullRateVoice(); + voice.decode(dfsiData + dataOffs); - // late entry? - if (!m_txCallInProgress) { - startOfStream(lc); + DFSIFrameType::E frameType = voice.getFrameType(); + switch (frameType) { + case DFSIFrameType::LDU1_VOICE1: + { + ::memcpy(m_rxCall->netLDU1 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE2: + { + ::memcpy(m_rxCall->netLDU1 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE3: + { + ::memcpy(m_rxCall->netLDU1 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->lco = voice.additionalData[0U]; + m_rxCall->mfId = voice.additionalData[1U]; + m_rxCall->serviceOptions = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU1_VOICE4: + { + ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->dstId = __GET_UINT16(voice.additionalData, 0U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU1_VOICE5: + { + ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->srcId = __GET_UINT16(voice.additionalData, 0U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU1_VOICE6: + { + ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE7: + { + ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE8: + { + ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE9: + { + ::memcpy(m_rxCall->netLDU1 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->lsd1 = voice.additionalData[0U]; + m_rxCall->lsd2 = voice.additionalData[1U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC9 traffic missing metadata"); + } + } + break; + + case DFSIFrameType::LDU2_VOICE10: + { + ::memcpy(m_rxCall->netLDU2 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE11: + { + ::memcpy(m_rxCall->netLDU2 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE12: + { + ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + ::memcpy(m_rxCall->MI, voice.additionalData, 3U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE13: + { + ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE14: + { + ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE15: + { + ::memcpy(m_rxCall->netLDU2 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->algoId = voice.additionalData[0U]; + m_rxCall->kId = __GET_UINT16B(voice.additionalData, 1U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE16: + { + ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE17: + { + ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE18: + { + ::memcpy(m_rxCall->netLDU2 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->lsd1 = voice.additionalData[0U]; + m_rxCall->lsd2 = voice.additionalData[1U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC18 traffic missing metadata"); + } + } + break; + + default: + break; + } + + // increment our voice frame counter + m_rxCall->n++; + } + break; + + default: + break; + } + + hdrOffs += BlockHeader::LENGTH; + } + + m_rxLastFrameTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // encode LDU1 if ready + if (m_rxCall->n == 9U) { + lc::LC lc = lc::LC(); + lc.setLCO(m_rxCall->lco); + lc.setMFId(m_rxCall->mfId); + + if (lc.isStandardMFId()) { + lc.setSrcId(m_rxCall->srcId); + lc.setDstId(m_rxCall->dstId); + } else { + uint8_t rsBuffer[P25_LDU_LC_FEC_LENGTH_BYTES]; + ::memset(rsBuffer, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); + + rsBuffer[0U] = m_rxCall->lco; + rsBuffer[1U] = m_rxCall->mfId; + rsBuffer[2U] = m_rxCall->serviceOptions; + rsBuffer[3U] = (m_rxCall->dstId >> 16) & 0xFFU; + rsBuffer[4U] = (m_rxCall->dstId >> 8) & 0xFFU; + rsBuffer[5U] = (m_rxCall->dstId >> 0) & 0xFFU; + rsBuffer[6U] = (m_rxCall->srcId >> 16) & 0xFFU; + rsBuffer[7U] = (m_rxCall->srcId >> 8) & 0xFFU; + rsBuffer[8U] = (m_rxCall->srcId >> 0) & 0xFFU; + + // combine bytes into ulong64_t (8 byte) value + ulong64_t rsValue = 0U; + rsValue = rsBuffer[1U]; + rsValue = (rsValue << 8) + rsBuffer[2U]; + rsValue = (rsValue << 8) + rsBuffer[3U]; + rsValue = (rsValue << 8) + rsBuffer[4U]; + rsValue = (rsValue << 8) + rsBuffer[5U]; + rsValue = (rsValue << 8) + rsBuffer[6U]; + rsValue = (rsValue << 8) + rsBuffer[7U]; + rsValue = (rsValue << 8) + rsBuffer[8U]; + + lc.setRS(rsValue); + } + + bool emergency = ((m_rxCall->serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag + bool encryption = ((m_rxCall->serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag + uint8_t priority = ((m_rxCall->serviceOptions & 0xFFU) & 0x07U); // Priority + lc.setEmergency(emergency); + lc.setEncrypted(encryption); + lc.setPriority(priority); + + data::LowSpeedData lsd = data::LowSpeedData(); + lsd.setLSD1(m_rxCall->lsd1); + lsd.setLSD2(m_rxCall->lsd2); + + // generate Sync + Sync::addP25Sync(buffer + 2U); + + // generate NID + m_nid->encode(buffer + 2U, DUID::LDU1); + + // generate LDU1 Data + lc.encodeLDU1(buffer + 2U); + + // generate Low Speed Data + lsd.process(buffer + 2U); + + // generate audio + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 10U, 0U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 26U, 1U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 55U, 2U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 80U, 3U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 105U, 4U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 130U, 5U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 155U, 6U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 180U, 7U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 204U, 8U); + + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); + } + + // encode LDU2 if ready + if (m_rxCall->n == 18U) { + lc::LC lc = lc::LC(); + lc.setMI(m_rxCall->MI); + lc.setAlgId(m_rxCall->algoId); + lc.setKId(m_rxCall->kId); + + data::LowSpeedData lsd = data::LowSpeedData(); + lsd.setLSD1(m_rxCall->lsd1); + lsd.setLSD2(m_rxCall->lsd2); + + // generate Sync + Sync::addP25Sync(buffer + 2U); + + // generate NID + m_nid->encode(buffer + 2U, DUID::LDU2); + + // generate LDU2 data + lc.encodeLDU2(buffer + 2U); + + // generate Low Speed Data + lsd.process(buffer + 2U); + + // generate audio + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 10U, 0U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 26U, 1U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 55U, 2U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 80U, 3U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 105U, 4U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 130U, 5U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 155U, 6U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 180U, 7U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 204U, 8U); + + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + m_rxCall->n = 0; + } +} + +/* Helper to add a V.24 data frame to the P25 TX queue with the proper timestamp and formatting */ + +void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType) +{ + assert(data != nullptr); + assert(len > 0U); + + if (m_debug) + LogDebug(LOG_MODEM, "ModemV24::queueP25Frame() msgType = $%02X", msgType); + if (m_trace) + Utils::dump(1U, "ModemV24::queueP25Frame() data", data, len); + + // get current time in ms + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // timestamp for this message (in ms) + uint64_t msgTime = 0U; + + // if this is our first message, timestamp is just now + the jitter buffer offset in ms + if (m_lastP25Tx == 0U) { + msgTime = now + m_jitter; + + // if the message type requests no jitter delay -- just set the message time to now + if (msgType == STT_NON_IMBE_NO_JITTER) + msgTime = now; + } + // if we had a message before this, calculate the new timestamp dynamically + else { + // if the last message occurred longer than our jitter buffer delay, we restart the sequence and calculate the same as above + if ((int64_t)(now - m_lastP25Tx) > m_jitter) { + msgTime = now + m_jitter; + } + // otherwise, we time out messages as required by the message type + else { + if (msgType == STT_IMBE) { + // IMBEs must go out at 20ms intervals + msgTime = m_lastP25Tx + 20U; + } else { + // Otherwise we don't care, we use 5ms since that's the theoretical minimum time a 9600 baud message can take + msgTime = m_lastP25Tx + 5U; + } + } + } + + len += 4U; + + // convert 16-bit length to 2 bytes + uint8_t length[2U]; + if (len > 255U) + length[0U] = (len >> 8U) & 0xFFU; + else + length[0U] = 0x00U; + length[1U] = len & 0xFFU; + + m_txP25Queue.addData(length, 2U); + + // add the data tag + uint8_t tag = TAG_DATA; + m_txP25Queue.addData(&tag, 1U); + + // convert 64-bit timestamp to 8 bytes and add + uint8_t tsBytes[8U]; + assert(sizeof msgTime == 8U); + ::memcpy(tsBytes, &msgTime, 8U); + m_txP25Queue.addData(tsBytes, 8U); + + // add the DVM start byte, length byte, CMD byte, and padding 0 + uint8_t header[4U]; + header[0U] = DVM_SHORT_FRAME_START; + header[1U] = len & 0xFFU; + header[2U] = CMD_P25_DATA; + header[3U] = 0x00U; + m_txP25Queue.addData(header, 4U); + + // add the data + m_txP25Queue.addData(data, len - 4U); + + // update the last message time + m_lastP25Tx = msgTime; +} + +/* Send a start of stream sequence (HDU, etc) to the connected serial V.24 device */ + +void ModemV24::startOfStream(const p25::lc::LC& control) +{ + m_txCallInProgress = true; + + MotStartOfStream start = MotStartOfStream(); + start.setStartStop(StartStopFlag::START); + start.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + + // create buffer for bytes and encode + uint8_t startBuf[start.LENGTH]; + ::memset(startBuf, 0x00U, start.LENGTH); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, MotStartOfStream::LENGTH); + + queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + control.getMI(mi); + + uint8_t vhdr[DFSI_VHDR_LEN]; + ::memset(vhdr, 0x00U, DFSI_VHDR_LEN); + + ::memcpy(vhdr, mi, MI_LENGTH_BYTES); + + vhdr[9U] = control.getMFId(); + vhdr[10U] = control.getAlgId(); + __SET_UINT16B(control.getKId(), vhdr, 11U); + __SET_UINT16B(control.getDstId(), vhdr, 13U); + + // perform RS encoding + m_rs.encode362017(vhdr); + + // convert the binary bytes to hex bytes + uint8_t raw[DFSI_VHDR_RAW_LEN]; + uint32_t offset = 0; + for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { + raw[i] = Utils::bin2Hex(vhdr, offset); + } + + // prepare VHDR1 + MotVoiceHeader1 vhdr1 = MotVoiceHeader1(); + vhdr1.startOfStream->setStartStop(StartStopFlag::START); + vhdr1.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + vhdr1.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); + + ::memcpy(vhdr1.header, raw, 8U); + ::memcpy(vhdr1.header + 9U, raw + 8U, 8U); + ::memcpy(vhdr1.header + 18U, raw + 16U, 2U); + + // encode VHDR1 and send + uint8_t vhdr1Buf[vhdr1.LENGTH]; + ::memset(vhdr1Buf, 0x00U, vhdr1.LENGTH); + vhdr1.encode(vhdr1Buf); + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, MotVoiceHeader1::LENGTH); + + queueP25Frame(vhdr1Buf, MotVoiceHeader1::LENGTH, STT_NON_IMBE); + + // prepare VHDR2 + MotVoiceHeader2 vhdr2 = MotVoiceHeader2(); + ::memcpy(vhdr2.header, raw + 18U, 8U); + ::memcpy(vhdr2.header + 9U, raw + 26U, 8U); + ::memcpy(vhdr2.header + 18U, raw + 34U, 2U); + + // encode VHDR2 and send + uint8_t vhdr2Buf[vhdr2.LENGTH]; + ::memset(vhdr2Buf, 0x00U, vhdr2.LENGTH); + vhdr2.encode(vhdr2Buf); + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, MotVoiceHeader2::LENGTH); + + queueP25Frame(vhdr2Buf, MotVoiceHeader2::LENGTH, STT_NON_IMBE); +} + +/* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ + +void ModemV24::endOfStream() +{ + MotStartOfStream end = MotStartOfStream(); + end.setStartStop(StartStopFlag::STOP); + + // create buffer and encode + uint8_t endBuf[MotStartOfStream::LENGTH]; + ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); + end.encode(endBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, MotStartOfStream::LENGTH); + + queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + + m_txCallInProgress = false; +} + +/* Helper to generate the NID value. */ + +uint16_t ModemV24::generateNID(DUID::E duid) +{ + uint8_t nid[2U]; + ::memset(nid, 0x00U, 2U); + + nid[0U] = (m_p25NAC >> 4) & 0xFFU; + nid[1U] = (m_p25NAC << 4) & 0xF0U; + nid[1U] |= duid; + + return __GET_UINT16B(nid, 0U); +} + +/* Send a start of stream sequence (HDU, etc) to the connected UDP TIA-102 device. */ + +void ModemV24::startOfStreamTIA(const p25::lc::LC& control) +{ + m_txCallInProgress = true; + + p25::lc::LC lc = p25::lc::LC(control); + + uint16_t length = 0U; + uint8_t buffer[P25_HDU_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); + + // generate control octet + ControlOctet ctrl = ControlOctet(); + ctrl.setBlockHeaderCnt(1U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header + BlockHeader hdr = BlockHeader(); + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + + // generate start of stream + StartOfStream start = StartOfStream(); + start.setNID(generateNID()); + start.encode(buffer + 2U); + length += StartOfStream::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStreamTIA() StartOfStream", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); + + ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); + length = 0U; + + // generate control octet + ctrl.setBlockHeaderCnt(2U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header 1 + hdr.setBlockType(BlockType::VOICE_HEADER_P1); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 2U); + length += BlockHeader::LENGTH; + + // generate voice header 1 + uint8_t hdu[P25_HDU_LENGTH_BYTES]; + ::memset(hdu, 0x00U, P25_HDU_LENGTH_BYTES); + lc.encodeHDU(hdu, true); + + // convert the binary bytes to hex bytes + uint8_t raw[DFSI_VHDR_RAW_LEN]; + uint32_t offset = 0; + for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { + raw[i] = Utils::bin2Hex(hdu, offset); + } + + // prepare VHDR1 + buffer[3U] = DFSIFrameType::MOT_VHDR_1; + ::memcpy(buffer + 4U, raw, 18U); + length += 19U; // 18 Golay + Block Type Marker + + start.encode(buffer + length); + length += StartOfStream::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader1", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); + + ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); + length = 0U; + + // generate control octet + ctrl.setBlockHeaderCnt(2U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header 1 + hdr.setBlockType(BlockType::VOICE_HEADER_P2); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 2U); + length += BlockHeader::LENGTH; + + // prepare VHDR2 + buffer[3U] = DFSIFrameType::MOT_VHDR_2; + ::memcpy(buffer + 4U, raw + 18U, 18U); + length += 19U; // 18 Golay + Block Type Marker + + start.encode(buffer + length); + length += StartOfStream::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader2", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); +} + +/* Send an end of stream sequence (TDU, etc) to the connected UDP TIA-102 device. */ + +void ModemV24::endOfStreamTIA() +{ + uint16_t length = 0U; + uint8_t buffer[2U]; + ::memset(buffer, 0x00U, 2U); + + // generate control octet + ControlOctet ctrl = ControlOctet(); + ctrl.setBlockHeaderCnt(1U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header + BlockHeader hdr = BlockHeader(); + hdr.setBlockType(BlockType::END_OF_STREAM); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::endOfStreamTIA() EndOfStream", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); + + m_txCallInProgress = false; +} + +/* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */ + +void ModemV24::convertFromAir(uint8_t* data, uint32_t length) +{ + assert(data != nullptr); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAir() data", data, length); + + uint8_t ldu[9U * 25U]; + ::memset(ldu, 0x00U, 9 * 25U); + + // decode the NID + bool valid = m_nid->decode(data + 2U); + if (!valid) + return; + + DUID::E duid = m_nid->getDUID(); + + // handle individual DUIDs + lc::LC lc = lc::LC(); + data::LowSpeedData lsd = data::LowSpeedData(); + switch (duid) { + case DUID::HDU: + { + bool ret = lc.decodeHDU(data + 2U); + if (!ret) { + LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); + } + + startOfStream(lc); + } + break; + case DUID::LDU1: + { + bool ret = lc.decodeLDU1(data + 2U, true); + if (!ret) { + LogWarning(LOG_MODEM, P25_LDU1_STR ", undecodable LC"); + return; + } + + lsd.process(data + 2U); + + // late entry? + if (!m_txCallInProgress) { + startOfStream(lc); + if (m_debug) + LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); + } + + // generate audio + m_audio.decode(data + 2U, ldu + 10U, 0U); + m_audio.decode(data + 2U, ldu + 26U, 1U); + m_audio.decode(data + 2U, ldu + 55U, 2U); + m_audio.decode(data + 2U, ldu + 80U, 3U); + m_audio.decode(data + 2U, ldu + 105U, 4U); + m_audio.decode(data + 2U, ldu + 130U, 5U); + m_audio.decode(data + 2U, ldu + 155U, 6U); + m_audio.decode(data + 2U, ldu + 180U, 7U); + m_audio.decode(data + 2U, ldu + 204U, 8U); + } + break; + case DUID::LDU2: + { + bool ret = lc.decodeLDU2(data + 2U); + if (!ret) { + LogWarning(LOG_MODEM, P25_LDU2_STR ", undecodable LC"); + return; + } + + lsd.process(data + 2U); + + // generate audio + m_audio.decode(data + 2U, ldu + 10U, 0U); + m_audio.decode(data + 2U, ldu + 26U, 1U); + m_audio.decode(data + 2U, ldu + 55U, 2U); + m_audio.decode(data + 2U, ldu + 80U, 3U); + m_audio.decode(data + 2U, ldu + 105U, 4U); + m_audio.decode(data + 2U, ldu + 130U, 5U); + m_audio.decode(data + 2U, ldu + 155U, 6U); + m_audio.decode(data + 2U, ldu + 180U, 7U); + m_audio.decode(data + 2U, ldu + 204U, 8U); + } + break; + + case DUID::TDU: + case DUID::TDULC: + if (m_txCallInProgress) + endOfStream(); + break; + + case DUID::PDU: + break; + + case DUID::TSDU: + { + lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); + if (!tsbk.decode(data + 2U)) { + LogWarning(LOG_MODEM, P25_TSDU_STR ", undecodable LC"); + return; + } + + MotStartOfStream startOfStream = MotStartOfStream(); + startOfStream.setStartStop(StartStopFlag::START); + startOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + startOfStream.setStreamType(StreamTypeFlag::TSBK); + + // create buffer and encode + uint8_t startBuf[MotStartOfStream::LENGTH]; + ::memset(startBuf, 0x00U, MotStartOfStream::LENGTH); + startOfStream.encode(startBuf); + + queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + + MotTSBKFrame tf = MotTSBKFrame(); + tf.startOfStream->setStartStop(StartStopFlag::START); + tf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + tf.startOfStream->setStreamType(StreamTypeFlag::TSBK); + delete[] tf.tsbkData; + + tf.tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES]; + ::memset(tf.tsbkData, 0x00U, P25_TSBK_LENGTH_BYTES); + ::memcpy(tf.tsbkData, tsbk.getDecodedRaw(), P25_TSBK_LENGTH_BYTES); + + // create buffer and encode + uint8_t tsbkBuf[MotTSBKFrame::LENGTH]; + ::memset(tsbkBuf, 0x00U, MotTSBKFrame::LENGTH); + tf.encode(tsbkBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, MotTSBKFrame::LENGTH); + + queueP25Frame(tsbkBuf, MotTSBKFrame::LENGTH, STT_NON_IMBE_NO_JITTER); + + MotStartOfStream endOfStream = MotStartOfStream(); + endOfStream.setStartStop(StartStopFlag::STOP); + endOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + endOfStream.setStreamType(StreamTypeFlag::TSBK); + + // create buffer and encode + uint8_t endBuf[MotStartOfStream::LENGTH]; + ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); + endOfStream.encode(endBuf); + + queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + } + break; + + default: + break; + } + + if (duid == DUID::LDU1 || duid == DUID::LDU2) { + uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); + + if (duid == DUID::LDU1) { + rs[0U] = lc.getLCO(); // LCO + + // split ulong64_t (8 byte) value into bytes + rs[1U] = (uint8_t)((lc.getRS() >> 56) & 0xFFU); + rs[2U] = (uint8_t)((lc.getRS() >> 48) & 0xFFU); + rs[3U] = (uint8_t)((lc.getRS() >> 40) & 0xFFU); + rs[4U] = (uint8_t)((lc.getRS() >> 32) & 0xFFU); + rs[5U] = (uint8_t)((lc.getRS() >> 24) & 0xFFU); + rs[6U] = (uint8_t)((lc.getRS() >> 16) & 0xFFU); + rs[7U] = (uint8_t)((lc.getRS() >> 8) & 0xFFU); + rs[8U] = (uint8_t)((lc.getRS() >> 0) & 0xFFU); + + // encode RS (24,12,13) FEC + m_rs.encode241213(rs); + } else { + // generate MI data + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + lc.getMI(mi); + + for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) + rs[i] = mi[i]; // Message Indicator + + rs[9U] = lc.getAlgId(); // Algorithm ID + rs[10U] = (lc.getKId() >> 8) & 0xFFU; // Key ID + rs[11U] = (lc.getKId() >> 0) & 0xFFU; // ... + + // encode RS (24,16,9) FEC + m_rs.encode24169(rs); + } + + for (int n = 0; n < 9; n++) { + uint8_t* buffer = nullptr; + uint16_t bufferSize = 0; + MotFullRateVoice voice = MotFullRateVoice(); + + switch (n) { + case 0: // VOICE1/10 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10); + + MotStartVoiceFrame svf = MotStartVoiceFrame(); + svf.startOfStream->setStartStop(StartStopFlag::START); + svf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + svf.fullRateVoice->setFrameType(voice.getFrameType()); + svf.fullRateVoice->setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); + svf.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); + + ::memcpy(svf.fullRateVoice->imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); + + buffer = new uint8_t[MotStartVoiceFrame::LENGTH]; + ::memset(buffer, 0x00U, MotStartVoiceFrame::LENGTH); + svf.encode(buffer); + bufferSize = MotStartVoiceFrame::LENGTH; + } + break; + case 1: // VOICE2/11 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE2 : DFSIFrameType::LDU2_VOICE11); + voice.setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); + ::memcpy(voice.imbeData, ldu + 26U, RAW_IMBE_LENGTH_BYTES); + } + break; + case 2: // VOICE3/12 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE3 : DFSIFrameType::LDU2_VOICE12); + ::memcpy(voice.imbeData, ldu + 55U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + // copy additional data + voice.additionalData[0U] = rs[0U]; + voice.additionalData[1U] = rs[1U]; + voice.additionalData[2U] = rs[2U]; + } + break; + case 3: // VOICE4/13 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE4 : DFSIFrameType::LDU2_VOICE13); + ::memcpy(voice.imbeData, ldu + 80U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + // copy additional data + voice.additionalData[0U] = rs[3U]; + voice.additionalData[1U] = rs[4U]; + voice.additionalData[2U] = rs[5U]; + } + break; + case 4: // VOICE5/14 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE5 : DFSIFrameType::LDU2_VOICE14); + ::memcpy(voice.imbeData, ldu + 105U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[6U]; + voice.additionalData[1U] = rs[7U]; + voice.additionalData[2U] = rs[8U]; + } + break; + case 5: // VOICE6/15 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE6 : DFSIFrameType::LDU2_VOICE15); + ::memcpy(voice.imbeData, ldu + 130U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[9U]; + voice.additionalData[1U] = rs[10U]; + voice.additionalData[2U] = rs[11U]; + } + break; + case 6: // VOICE7/16 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE7 : DFSIFrameType::LDU2_VOICE16); + ::memcpy(voice.imbeData, ldu + 155U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[12U]; + voice.additionalData[1U] = rs[13U]; + voice.additionalData[2U] = rs[14U]; + } + break; + case 7: // VOICE8/17 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE8 : DFSIFrameType::LDU2_VOICE17); + ::memcpy(voice.imbeData, ldu + 180U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[15U]; + voice.additionalData[1U] = rs[16U]; + voice.additionalData[2U] = rs[17U]; + } + break; + case 8: // VOICE9/18 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE9 : DFSIFrameType::LDU2_VOICE18); + ::memcpy(voice.imbeData, ldu + 204U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = lsd.getLSD1(); + voice.additionalData[1U] = lsd.getLSD2(); + } + break; + } + + // For n=0 (VHDR1/10) case we create the buffer in the switch, for all other frame types we do that here + if (n != 0) { + buffer = new uint8_t[voice.size()]; + ::memset(buffer, 0x00U, voice.size()); + voice.encode(buffer); + bufferSize = voice.size(); + } + + if (buffer != nullptr) { + if (m_trace) { + Utils::dump("ModemV24::convertFromAir() Encoded V.24 Voice Frame Data", buffer, bufferSize); + } + + queueP25Frame(buffer, bufferSize, STT_IMBE); + delete[] buffer; + } + } + } +} + +/* Internal helper to convert from TIA-102 air interface to TIA-102 DFSI. */ + +void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) +{ + assert(data != nullptr); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirTIA() data", data, length); + + uint8_t ldu[9U * 25U]; + ::memset(ldu, 0x00U, 9 * 25U); + + // decode the NID + bool valid = m_nid->decode(data + 2U); + if (!valid) + return; + + DUID::E duid = m_nid->getDUID(); + + // handle individual DUIDs + lc::LC lc = lc::LC(); + data::LowSpeedData lsd = data::LowSpeedData(); + switch (duid) { + case DUID::HDU: + { + bool ret = lc.decodeHDU(data + 2U); + if (!ret) { + LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); + } + + startOfStreamTIA(lc); + } + break; + case DUID::LDU1: + { + bool ret = lc.decodeLDU1(data + 2U, true); + if (!ret) { + LogWarning(LOG_MODEM, P25_LDU1_STR ", undecodable LC"); + return; + } + + lsd.process(data + 2U); + + // late entry? + if (!m_txCallInProgress) { + startOfStreamTIA(lc); if (m_debug) LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); } @@ -1348,66 +2286,14 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) case DUID::TDU: case DUID::TDULC: if (m_txCallInProgress) - endOfStream(); + endOfStreamTIA(); break; case DUID::PDU: break; case DUID::TSDU: - { - lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); - if (!tsbk.decode(data + 2U)) { - LogWarning(LOG_MODEM, P25_TSDU_STR ", undecodable LC"); - return; - } - - MotStartOfStream startOfStream = MotStartOfStream(); - startOfStream.setStartStop(StartStopFlag::START); - startOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - startOfStream.setStreamType(StreamTypeFlag::TSBK); - - // create buffer and encode - uint8_t startBuf[MotStartOfStream::LENGTH]; - ::memset(startBuf, 0x00U, MotStartOfStream::LENGTH); - startOfStream.encode(startBuf); - - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - - MotTSBKFrame tf = MotTSBKFrame(); - tf.startOfStream->setStartStop(StartStopFlag::START); - tf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - tf.startOfStream->setStreamType(StreamTypeFlag::TSBK); - delete[] tf.tsbkData; - - tf.tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES]; - ::memset(tf.tsbkData, 0x00U, P25_TSBK_LENGTH_BYTES); - ::memcpy(tf.tsbkData, tsbk.getDecodedRaw(), P25_TSBK_LENGTH_BYTES); - - // create buffer and encode - uint8_t tsbkBuf[MotTSBKFrame::LENGTH]; - ::memset(tsbkBuf, 0x00U, MotTSBKFrame::LENGTH); - tf.encode(tsbkBuf); - - if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, MotTSBKFrame::LENGTH); - - queueP25Frame(tsbkBuf, MotTSBKFrame::LENGTH, STT_NON_IMBE_NO_JITTER); - - MotStartOfStream endOfStream = MotStartOfStream(); - endOfStream.setStartStop(StartStopFlag::STOP); - endOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - endOfStream.setStreamType(StreamTypeFlag::TSBK); - - // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); - endOfStream.encode(endBuf); - - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - } - break; + break; default: break; @@ -1452,32 +2338,18 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) for (int n = 0; n < 9; n++) { uint8_t* buffer = nullptr; uint16_t bufferSize = 0; - MotFullRateVoice voice = MotFullRateVoice(); + FullRateVoice voice = FullRateVoice(); switch (n) { case 0: // VOICE1/10 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10); - - MotStartVoiceFrame svf = MotStartVoiceFrame(); - svf.startOfStream->setStartStop(StartStopFlag::START); - svf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - svf.fullRateVoice->setFrameType(voice.getFrameType()); - svf.fullRateVoice->setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); - svf.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); - - ::memcpy(svf.fullRateVoice->imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); - - buffer = new uint8_t[MotStartVoiceFrame::LENGTH]; - ::memset(buffer, 0x00U, MotStartVoiceFrame::LENGTH); - svf.encode(buffer); - bufferSize = MotStartVoiceFrame::LENGTH; + ::memcpy(voice.imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); } break; case 1: // VOICE2/11 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE2 : DFSIFrameType::LDU2_VOICE11); - voice.setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); ::memcpy(voice.imbeData, ldu + 26U, RAW_IMBE_LENGTH_BYTES); } break; @@ -1577,15 +2449,39 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) // For n=0 (VHDR1/10) case we create the buffer in the switch, for all other frame types we do that here if (n != 0) { - buffer = new uint8_t[voice.size()]; - ::memset(buffer, 0x00U, voice.size()); - voice.encode(buffer); - bufferSize = voice.size(); + buffer = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // generate control octet + ControlOctet ctrl = ControlOctet(); + ctrl.setBlockHeaderCnt(2U); + ctrl.encode(buffer); + bufferSize += ControlOctet::LENGTH; + + // generate block header + BlockHeader hdr = BlockHeader(); + hdr.setBlockType(BlockType::FULL_RATE_VOICE); + hdr.encode(buffer + 1U); + bufferSize += BlockHeader::LENGTH; + + // generate block header + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 2U); + bufferSize += BlockHeader::LENGTH; + + voice.encode(buffer + bufferSize); + bufferSize += FullRateVoice::LENGTH; + + // generate start of stream + StartOfStream start = StartOfStream(); + start.setNID(generateNID()); + start.encode(buffer + bufferSize); + bufferSize += StartOfStream::LENGTH; } if (buffer != nullptr) { if (m_trace) { - Utils::dump("ModemV24::convertFromAir() Encoded V.24 Voice Frame Data", buffer, bufferSize); + Utils::dump("ModemV24::convertFromAirTIA() Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index 71a126ed..bd78e475 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -261,6 +261,12 @@ namespace modem */ void setP25NAC(uint32_t nac) override; + /** + * @brief Helper to set the TIA-102 format DFSI frame flag. + * @param set + */ + void setTIAFormat(bool set); + /** * @brief Opens connection to the air interface modem. * @returns bool True, if connection to modem is made, otherwise false. @@ -316,6 +322,8 @@ namespace modem edac::RS634717 m_rs; + bool m_useTIAFormat; + /** * @brief Helper to write data from the P25 Tx queue to the serial interface. * @return int Actual number of bytes written to the serial interface. @@ -340,6 +348,12 @@ namespace modem * @param length Length of buffer. */ void convertToAir(const uint8_t *data, uint32_t length); + /** + * @brief Internal helper to convert from TIA-102 DFSI to TIA-102 air interface. + * @param data Buffer containing data to convert. + * @param length Length of buffer. + */ + void convertToAirTIA(const uint8_t *data, uint32_t length); /** * @brief Helper to add a V.24 data frame to the P25 Tx queue with the proper timestamp and formatting. @@ -359,12 +373,35 @@ namespace modem */ void endOfStream(); + /** + * @brief Helper to generate the NID value. + * @param duid P25 DUID. + * @returns uint16_t P25 NID. + */ + uint16_t generateNID(P25DEF::DUID::E duid = P25DEF::DUID::LDU1); + + /** + * @brief Send a start of stream sequence (HDU, etc) to the connected UDP TIA-102 device. + * @param[in] control Instance of p25::lc::LC containing link control data. + */ + void startOfStreamTIA(const p25::lc::LC& control); + /** + * @brief Send an end of stream sequence (TDU, etc) to the connected UDP TIA-102 device. + */ + void endOfStreamTIA(); + /** * @brief Internal helper to convert from TIA-102 air interface to V.24/DFSI. * @param data Buffer containing data to convert. * @param length Length of buffer. */ void convertFromAir(uint8_t* data, uint32_t length); + /** + * @brief Internal helper to convert from TIA-102 air interface to TIA-102 DFSI. + * @param data Buffer containing data to convert. + * @param length Length of buffer. + */ + void convertFromAirTIA(uint8_t* data, uint32_t length); }; } // namespace modem From 0e5d075c62e61abfbdec8d126e6065d334f395b2 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 9 Jan 2025 13:17:48 -0500 Subject: [PATCH 34/68] expose configuring the FSC heartbeat interval; expose enabling TIA mode DFSI frames; correct FullRateVoice memory copy for additional data; --- configs/config.example.yml | 4 ++++ src/common/p25/dfsi/frames/FullRateVoice.cpp | 2 +- src/host/Host.Config.cpp | 8 ++++++- .../modem/port/specialized/V24UDPPort.cpp | 22 ++++++++++++++++--- src/host/modem/port/specialized/V24UDPPort.h | 6 +++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index ebb3b421..a7495665 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -592,8 +592,12 @@ system: callTimeout: 200 # Flag indicating when operating in V.24 UDP mode, the FSC protocol should be used to negotiate connection. fsc: false + # Sets the heartbeat interval for the FSC connection. + fscHeartbeat: 5 # Flag indicating when operating in V.24 UDP mode, this instance should initiate the FSC protocol handshake. initiator: false + # Flag indicating DFSI frames should be formatted in TIA-102 standard instead of serial standard format. + dfsiTIAMode: false # Sets received the signal offset from DC. rxDCOffset: 0 # Valid values between -128 and 128 diff --git a/src/common/p25/dfsi/frames/FullRateVoice.cpp b/src/common/p25/dfsi/frames/FullRateVoice.cpp index 8ea60eab..c3b50a63 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/FullRateVoice.cpp @@ -145,7 +145,7 @@ void FullRateVoice::encode(uint8_t* data) buffer[i] = Utils::bin2Hex(additionalData, offset); } - ::memcpy(data + 14U, buffer, ADDITIONAL_LENGTH); + ::memcpy(data + 14U, buffer, ADDITIONAL_LENGTH - 1U); } } } diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 007103f2..73141abe 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -463,7 +463,9 @@ bool Host::createModem() uint16_t jitter = dfsiParams["jitter"].as(200U); uint16_t dfsiCallTimeout = dfsiParams["callTimeout"].as(200U); bool useFSCForUDP = dfsiParams["fsc"].as(false); + uint32_t fscHeartbeat = dfsiParams["fscHeartbeat"].as(5U); bool fscInitiator = dfsiParams["initiator"].as(false); + bool dfsiTIAMode = dfsiParams["dfsiTIAMode"].as(false); // clamp fifo sizes if (dmrFifoLength < DMR_TX_BUFFER_LEN) { @@ -581,7 +583,9 @@ bool Host::createModem() LogInfo(" DFSI Jitter Size: %u ms", jitter); if (g_remoteModemMode) { LogInfo(" DFSI Use FSC: %s", useFSCForUDP ? "yes" : "no"); + LogInfo(" DFSI FSC Heartbeat: %us", fscHeartbeat); LogInfo(" DFSI FSC Initiator: %s", fscInitiator ? "yes" : "no"); + LogInfo(" DFSI FSC TIA Frames: %s", dfsiTIAMode ? "yes" : "no"); } } @@ -598,7 +602,8 @@ bool Host::createModem() uint32_t id = networkConf["id"].as(1000U); if (useFSCForUDP) { modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort + 1U, g_remotePort, true, fscInitiator, debug); - } else { + ((modem::port::specialized::V24UDPPort*)modemPort)->setHeartbeatInterval(fscHeartbeat); + } else { modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, false, false, debug); } m_udpDFSIRemotePort = modemPort; @@ -662,6 +667,7 @@ bool Host::createModem() m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, m_p25QueueSizeBytes, rtrt, diu, jitter, dumpModemStatus, trace, debug); ((ModemV24*)m_modem)->setCallTimeout(dfsiCallTimeout); + ((ModemV24*)m_modem)->setTIAFormat(dfsiTIAMode); } else { m_modem = new Modem(modemPort, m_duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, fdmaPreamble, dmrRxDelay, p25CorrCount, m_dmrQueueSizeBytes, m_p25QueueSizeBytes, m_nxdnQueueSizeBytes, disableOFlowReset, ignoreModemConfigArea, dumpModemStatus, trace, debug); diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index fe24b974..948b82e5 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -64,6 +64,7 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod m_fscInitiator(fscInitiator), m_timeoutTimer(1000U, 30U), m_reqConnectionTimer(1000U, 30U), + m_heartbeatInterval(5U), m_heartbeatTimer(1000U, 5U), m_random(), m_peerId(peerId), @@ -115,6 +116,19 @@ V24UDPPort::~V24UDPPort() delete m_socket; } +/* Helper to set and configure the heartbeat interval for FSC connections. */ + +void V24UDPPort::setHeartbeatInterval(uint32_t interval) +{ + if (interval < 5U) + interval = 5U; + if (interval > 30U) + interval = 30U; + + m_heartbeatInterval = interval; + m_heartbeatTimer = Timer(1000U, interval); +} + /* Updates the timer by the passed number of milliseconds. */ void V24UDPPort::clock(uint32_t ms) @@ -461,6 +475,7 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) } uint16_t vcPort = connMessage->getVCBasePort(); + network->m_heartbeatInterval = connMessage->getFSHeartbeatPeriod(); network->m_localPort = vcPort; network->createVCPort(network->m_localPort); @@ -468,10 +483,11 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) network->m_fscState = CS_CONNECTED; network->m_reqConnectionTimer.stop(); + network->m_heartbeatTimer = Timer(1000U, network->m_heartbeatInterval); network->m_heartbeatTimer.start(); network->m_timeoutTimer.start(); - LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, vcBasePort = %u", network->m_localPort); + LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, vcBasePort = %u, fsHBInterval = %u, hostHBInterval = %u", network->m_localPort, network->m_heartbeatInterval, connMessage->getHostHeartbeatPeriod()); // construct connect ACK response data uint8_t respData[3U]; @@ -681,8 +697,8 @@ void V24UDPPort::writeConnect() LogMessage(LOG_MODEM, "V.24 UDP, Attempting DFSI FSC Connection, peerId = %u, vcBasePort = %u", m_peerId, m_localPort); FSCConnect connect = FSCConnect(); - connect.setFSHeartbeatPeriod(5U); // hardcoded? - connect.setHostHeartbeatPeriod(5U); // hardcoded? + connect.setFSHeartbeatPeriod(m_heartbeatInterval); + connect.setHostHeartbeatPeriod(m_heartbeatInterval); connect.setVCBasePort(m_localPort); connect.setVCSSRC(m_peerId); diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 750f54cf..ac1f7b84 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -81,6 +81,11 @@ namespace modem */ ~V24UDPPort() override; + /** + * @brief Helper to set and configure the heartbeat interval for FSC connections. + */ + void setHeartbeatInterval(uint32_t interval); + /** * @brief Updates the timer by the passed number of milliseconds. * @param ms Number of milliseconds. @@ -147,6 +152,7 @@ namespace modem Timer m_timeoutTimer; Timer m_reqConnectionTimer; + uint32_t m_heartbeatInterval; Timer m_heartbeatTimer; std::mt19937 m_random; From 6b9d906f6888dd6f7891db4d62ed5b098ed6b8e3 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 9 Jan 2025 15:10:16 -0500 Subject: [PATCH 35/68] make FSC timeout just slightly more then the maximum heartbeat time; --- src/host/modem/port/specialized/V24UDPPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 948b82e5..d3ed9da7 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -62,7 +62,7 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod m_ctrlAddrLen(0U), m_buffer(2000U, "UDP Port Ring Buffer"), m_fscInitiator(fscInitiator), - m_timeoutTimer(1000U, 30U), + m_timeoutTimer(1000U, 45U), m_reqConnectionTimer(1000U, 30U), m_heartbeatInterval(5U), m_heartbeatTimer(1000U, 5U), From fea335dc1e7f6832015ae36158abc179490e5b09 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 9 Jan 2025 15:15:50 -0500 Subject: [PATCH 36/68] warn on connection attempts trying to set heartbeats larger then 30 seconds; --- src/host/modem/port/specialized/V24UDPPort.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index d3ed9da7..b81e9c67 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -475,7 +475,9 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) } uint16_t vcPort = connMessage->getVCBasePort(); - network->m_heartbeatInterval = connMessage->getFSHeartbeatPeriod(); + network->m_heartbeatInterval = connMessage->getHostHeartbeatPeriod(); + if (network->m_heartbeatInterval > 30U) + network->m_heartbeatInterval = 30U; network->m_localPort = vcPort; network->createVCPort(network->m_localPort); @@ -483,11 +485,15 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) network->m_fscState = CS_CONNECTED; network->m_reqConnectionTimer.stop(); + + if (connMessage->getHostHeartbeatPeriod() > 30U) + LogWarning(LOG_MODEM, "V.24 UDP, DFSI FSC Connection, requested heartbeat of %u, reduce to 30 seconds or less", connMessage->getHostHeartbeatPeriod()); + network->m_heartbeatTimer = Timer(1000U, network->m_heartbeatInterval); network->m_heartbeatTimer.start(); network->m_timeoutTimer.start(); - LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, vcBasePort = %u, fsHBInterval = %u, hostHBInterval = %u", network->m_localPort, network->m_heartbeatInterval, connMessage->getHostHeartbeatPeriod()); + LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, vcBasePort = %u, hostHBInterval = %u", network->m_localPort, connMessage->getHostHeartbeatPeriod()); // construct connect ACK response data uint8_t respData[3U]; From 418ffeea240edb8f488f43eb86c2cf741fabff26 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 9 Jan 2025 16:39:07 -0500 Subject: [PATCH 37/68] fix null reference; --- src/common/p25/dfsi/frames/fsc/FSCMessage.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp index efd47a66..8ea6fefb 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp @@ -94,9 +94,13 @@ std::unique_ptr FSCMessage::createMessage(const uint8_t* data) break; } - if (!message->decode(data)) { - return nullptr; + if (message != nullptr) { + if (!message->decode(data)) { + return nullptr; + } + + return std::unique_ptr(message); } - return std::unique_ptr(message); + return nullptr; } From 6d5e158808165c9c2301ca8b3a810ca7cb58ef20 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 9 Jan 2025 16:44:44 -0500 Subject: [PATCH 38/68] just ack the FSC_SEL_CHAN command when requested; fix data offset for TIA-102 DFSI data; --- src/common/p25/dfsi/frames/FrameDefines.h | 10 ++++++---- src/host/modem/ModemV24.cpp | 2 +- src/host/modem/port/specialized/V24UDPPort.cpp | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/common/p25/dfsi/frames/FrameDefines.h b/src/common/p25/dfsi/frames/FrameDefines.h index 1277a8fe..890c2671 100644 --- a/src/common/p25/dfsi/frames/FrameDefines.h +++ b/src/common/p25/dfsi/frames/FrameDefines.h @@ -44,15 +44,17 @@ namespace p25 namespace FSCMessageType { /** @brief FSC Control Service Message.*/ enum E : uint8_t { - FSC_CONNECT = 0, //! Establish connection with FSS. - FSC_HEARTBEAT = 1, //! Heartbeat/Connectivity Maintenance. + FSC_CONNECT = 0, //! Establish connection with FSS + FSC_HEARTBEAT = 1, //! Heartbeat/Connectivity Maintenance FSC_ACK = 2, //! Control Service Ack. + FSC_SEL_CHAN = 5, //! Channel Selection + FSC_REPORT_SEL_MODES = 8, //! Report Selected Modes - FSC_DISCONNECT = 9, //! Detach Control Service. + FSC_DISCONNECT = 9, //! Detach Control Service - FSC_INVALID = 127, //! Invalid Control Message. + FSC_INVALID = 127, //! Invalid Control Message }; } diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index f5f34744..ec15a7cb 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1120,7 +1120,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) uint8_t blockCnt = ctrl.getBlockHeaderCnt(); // iterate through blocks - uint8_t hdrOffs = 1U, dataOffs = blockCnt; + uint8_t hdrOffs = 1U, dataOffs = blockCnt + 1U; for (uint8_t i = 0U; i < blockCnt; i++) { BlockHeader hdr = BlockHeader(); hdr.decode(dfsiData + hdrOffs); diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index b81e9c67..44e69441 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -514,6 +514,23 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) } break; + case FSCMessageType::FSC_SEL_CHAN: + { + FSCACK ackResp = FSCACK(); + ackResp.setCorrelationTag(message->getCorrelationTag()); + ackResp.setAckMessageId(FSCMessageType::FSC_SEL_CHAN); + ackResp.setResponseCode(FSCAckResponseCode::CONTROL_ACK); + ackResp.setAckCorrelationTag(message->getCorrelationTag()); + ackResp.setResponseLength(0U); + + uint8_t buffer[FSCACK::LENGTH]; + ::memset(buffer, 0x00U, FSCACK::LENGTH); + ackResp.encode(buffer); + + network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen); + } + break; + case FSCMessageType::FSC_REPORT_SEL_MODES: { FSCACK ackResp = FSCACK(); From 164d2b6c8749c11ee93f0ac557920f4101168797 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 9 Jan 2025 16:46:47 -0500 Subject: [PATCH 39/68] whoops actually instantiate FSCMessage for FSC_SEL_CHAN; --- src/common/p25/dfsi/frames/fsc/FSCMessage.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp index 8ea6fefb..978363d1 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp @@ -89,6 +89,11 @@ std::unique_ptr FSCMessage::createMessage(const uint8_t* data) case FSCMessageType::FSC_DISCONNECT: message = new FSCDisconnect(); break; + + case FSCMessageType::FSC_SEL_CHAN: + message = new FSCMessage(); + break; + default: LogError(LOG_P25, "FSCMessage::createMessage(), unknown message value, messageId = $%02X", messageId); break; From 406943d75f80d700ee1bf493dc0fb770ca1a37fe Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 10 Jan 2025 10:09:56 -0500 Subject: [PATCH 40/68] add FSCSelChannel message decoding (not that we need it); --- src/common/p25/dfsi/frames/Frames.h | 1 + src/common/p25/dfsi/frames/fsc/FSCConnect.h | 2 +- src/common/p25/dfsi/frames/fsc/FSCMessage.cpp | 7 +- .../p25/dfsi/frames/fsc/FSCSelChannel.cpp | 57 ++++++++++++++ .../p25/dfsi/frames/fsc/FSCSelChannel.h | 77 +++++++++++++++++++ 5 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 src/common/p25/dfsi/frames/fsc/FSCSelChannel.cpp create mode 100644 src/common/p25/dfsi/frames/fsc/FSCSelChannel.h diff --git a/src/common/p25/dfsi/frames/Frames.h b/src/common/p25/dfsi/frames/Frames.h index 694eeeb5..7779b8d1 100644 --- a/src/common/p25/dfsi/frames/Frames.h +++ b/src/common/p25/dfsi/frames/Frames.h @@ -35,5 +35,6 @@ #include "common/p25/dfsi/frames/fsc/FSCReportSelModes.h" #include "common/p25/dfsi/frames/fsc/FSCDisconnect.h" #include "common/p25/dfsi/frames/fsc/FSCHeartbeat.h" +#include "common/p25/dfsi/frames/fsc/FSCSelChannel.h" #endif // __DFSI_FRAMES_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnect.h b/src/common/p25/dfsi/frames/fsc/FSCConnect.h index 9dc3d60e..f199ba05 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCConnect.h +++ b/src/common/p25/dfsi/frames/fsc/FSCConnect.h @@ -58,7 +58,7 @@ namespace p25 * @param[out] data Buffer to encode a FSCConnect. */ void encode(uint8_t* data) override; - + public: /** * @brief Voice Conveyance RTP Port. diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp index 978363d1..cc640c2f 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp @@ -86,14 +86,13 @@ std::unique_ptr FSCMessage::createMessage(const uint8_t* data) case FSCMessageType::FSC_REPORT_SEL_MODES: message = new FSCReportSelModes(); break; + case FSCMessageType::FSC_SEL_CHAN: + message = new FSCSelChannel(); + break; case FSCMessageType::FSC_DISCONNECT: message = new FSCDisconnect(); break; - case FSCMessageType::FSC_SEL_CHAN: - message = new FSCMessage(); - break; - default: LogError(LOG_P25, "FSCMessage::createMessage(), unknown message value, messageId = $%02X", messageId); break; diff --git a/src/common/p25/dfsi/frames/fsc/FSCSelChannel.cpp b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.cpp new file mode 100644 index 00000000..6b97b856 --- /dev/null +++ b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.cpp @@ -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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "common/p25/dfsi/frames/fsc/FSCSelChannel.h" +#include "common/p25/dfsi/DFSIDefines.h" +#include "common/Utils.h" +#include "common/Log.h" + +#include +#include + +using namespace p25::dfsi; +using namespace p25::dfsi::frames; +using namespace p25::dfsi::frames::fsc; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a instance of the FSCSelChannel class. */ + +FSCSelChannel::FSCSelChannel() : FSCMessage(), + m_rxChan(1U), + m_txChan(1U) +{ + m_messageId = FSCMessageType::FSC_SEL_CHAN; +} + +/* Decode a FSC select channel frame. */ + +bool FSCSelChannel::decode(const uint8_t* data) +{ + assert(data != nullptr); + FSCMessage::decode(data); + + m_rxChan = data[3U]; // Receive Channel + m_txChan = data[4U]; // Transmit Channel + + return true; +} + +/* Encode a FSC select channel frame. */ + +void FSCSelChannel::encode(uint8_t* data) +{ + assert(data != nullptr); + FSCMessage::encode(data); + + data[3U] = m_rxChan; // Receive Channel + data[4U] = m_txChan; // Transmit Channel +} diff --git a/src/common/p25/dfsi/frames/fsc/FSCSelChannel.h b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.h new file mode 100644 index 00000000..31727576 --- /dev/null +++ b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.h @@ -0,0 +1,77 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file FSCReportSelModes.h + * @ingroup dfsi_fsc_frames + * @file FSCReportSelModes.cpp + * @ingroup dfsi_fsc_frames + */ +#if !defined(__FSC_SEL_CHANNEL_H__) +#define __FSC_SEL_CHANNEL_H__ + +#include "Defines.h" +#include "common/Defines.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "common/p25/dfsi/frames/FrameDefines.h" +#include "common/p25/dfsi/frames/fsc/FSCMessage.h" + +namespace p25 +{ + namespace dfsi + { + namespace frames + { + namespace fsc + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements the FSC Select Channel Message. + * @ingroup dfsi_fsc_frames + */ + class HOST_SW_API FSCSelChannel : public FSCMessage { + public: + static const uint8_t LENGTH = 3U; + + /** + * @brief Initializes a copy instance of the FSCSelChannel class. + */ + FSCSelChannel(); + + /** + * @brief Decode a FSC select channel frame. + * @param[in] data Buffer to containing FSCSelChannel to decode. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a FSC select channel frame. + * @param[out] data Buffer to encode a FSCSelChannel. + */ + void encode(uint8_t* data) override; + + public: + /** + * @brief Receive Channel Number. + */ + __PROPERTY(uint8_t, rxChan, RxChan); + /** + * @brief Transmit Channel Number. + */ + __PROPERTY(uint8_t, txChan, TxChan); + }; + } // namespace fsc + } // namespace frames + } // namespace dfsi +} // namespace p25 + +#endif // __FSC_SEL_CHANNEL_H__ \ No newline at end of file From 2e0ffab13dc36fb12cda478f7cd5e4dc944cadfd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 10 Jan 2025 10:51:02 -0500 Subject: [PATCH 41/68] correct un-updated debug handles in LC; correct TIA mode HDU handling; --- src/common/p25/lc/LC.cpp | 20 ++++++++----- src/host/modem/ModemV24.cpp | 59 +++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 574df473..9d67e65c 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -108,6 +108,10 @@ bool LC::decodeHDU(const uint8_t* data, bool rawOnly) else P25Utils::decode(data, raw, 114U, 780U); +#if DEBUG_P25_HDU + Utils::dump(2U, "LC::decodeHDU(), HDU Raw", raw, P25_HDU_LENGTH_BYTES); +#endif + // decode Golay (18,6,8) FEC decodeHDUGolay(raw, rs); @@ -247,7 +251,7 @@ bool LC::decodeLDU1(const uint8_t* data, bool rawOnly) decodeLDUHamming(raw, rs + 15U); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::decodeLDU1(), LDU1 RS", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::decodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // decode RS (24,12,13) FEC @@ -264,7 +268,7 @@ bool LC::decodeLDU1(const uint8_t* data, bool rawOnly) } #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif return decodeLC(rs, rawOnly); @@ -282,14 +286,14 @@ void LC::encodeLDU1(uint8_t* data) encodeLC(rs); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::encodeLDU1(), LDU1 LC", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::encodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode RS (24,12,13) FEC m_rs.encode241213(rs); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode Hamming (10,6,3) FEC and interleave for LC data @@ -346,7 +350,7 @@ bool LC::decodeLDU2(const uint8_t* data) decodeLDUHamming(raw, rs + 15U); #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::decodeLDU2(), LDU2 RS", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::decodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // decode RS (24,16,9) FEC @@ -363,7 +367,7 @@ bool LC::decodeLDU2(const uint8_t* data) } #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif m_algId = rs[9U]; // Algorithm ID @@ -414,14 +418,14 @@ void LC::encodeLDU2(uint8_t* data) rs[11U] = (m_kId >> 0) & 0xFFU; // ... #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::encodeLDU2(), LDU2 LC", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::encodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode RS (24,16,9) FEC m_rs.encode24169(rs); #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_LENGTH_BYTES); + Utils::dump(2U, "LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode Hamming (10,6,3) FEC and interleave for LC data diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index ec15a7cb..0a5635c2 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1183,16 +1183,16 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(raw + 18U, m_rxCall->VHDR2, 18U); // buffer for decoded VHDR data - uint8_t vhdr[DFSI_VHDR_RAW_LEN]; + uint8_t vhdr[P25_HDU_LENGTH_BYTES]; uint32_t offset = 0U; - for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) + for (uint32_t i = 0; i < P25_HDU_LENGTH_BYTES; i++, offset += 6) Utils::hex2Bin(raw[i], vhdr, offset); // try to decode the RS data try { - lc::LC lc = lc::LC(); - if (!lc.decodeHDU(raw, true)) { + bool ret = m_rs.decode362017(vhdr); + if (!ret) { LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); } else { // late entry? @@ -1203,20 +1203,24 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } - uint8_t mi[MI_LENGTH_BYTES]; - lc.getMI(mi); - - ::memcpy(m_rxCall->MI, mi, MI_LENGTH_BYTES); + ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); - m_rxCall->mfId = lc.getMFId(); - m_rxCall->algoId = lc.getAlgId(); - m_rxCall->kId = lc.getKId(); - m_rxCall->dstId = lc.getDstId(); + m_rxCall->mfId = vhdr[9U]; + m_rxCall->algoId = vhdr[10U]; + m_rxCall->kId = __GET_UINT16B(vhdr, 11U); + m_rxCall->dstId = __GET_UINT16B(vhdr, 13U); if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); } + // generate a HDU + lc::LC lc = lc::LC(); + lc.setDstId(m_rxCall->dstId); + lc.setAlgId(m_rxCall->algoId); + lc.setKId(m_rxCall->kId); + lc.setMI(m_rxCall->MI); + // generate Sync Sync::addP25Sync(buffer + 2U); @@ -1762,6 +1766,30 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) queueP25Frame(buffer, length, STT_NON_IMBE); + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + control.getMI(mi); + + uint8_t vhdr[P25_HDU_LENGTH_BYTES]; + ::memset(vhdr, 0x00U, P25_HDU_LENGTH_BYTES); + + ::memcpy(vhdr, mi, MI_LENGTH_BYTES); + + vhdr[9U] = control.getMFId(); + vhdr[10U] = control.getAlgId(); + __SET_UINT16B(control.getKId(), vhdr, 11U); + __SET_UINT16B(control.getDstId(), vhdr, 13U); + + // perform RS encoding + m_rs.encode362017(vhdr); + + // convert the binary bytes to hex bytes + uint8_t raw[DFSI_VHDR_RAW_LEN]; + uint32_t offset = 0; + for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { + raw[i] = Utils::bin2Hex(vhdr, offset); + } + ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); length = 0U; @@ -1783,13 +1811,6 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) ::memset(hdu, 0x00U, P25_HDU_LENGTH_BYTES); lc.encodeHDU(hdu, true); - // convert the binary bytes to hex bytes - uint8_t raw[DFSI_VHDR_RAW_LEN]; - uint32_t offset = 0; - for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { - raw[i] = Utils::bin2Hex(hdu, offset); - } - // prepare VHDR1 buffer[3U] = DFSIFrameType::MOT_VHDR_1; ::memcpy(buffer + 4U, raw, 18U); From 6f61276552fed240de9daf6ab7c97e68be116bbf Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 12 Jan 2025 20:54:59 -0500 Subject: [PATCH 42/68] disable source ID check when filtering TDUs; --- src/fne/network/callhandler/TagP25Data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 754da483..62400993 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -907,7 +907,7 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, if (duid == DUID::TDU) { if (m_network->m_filterTerminators) { - if (control.getSrcId() != 0U && control.getDstId() != 0U) { + if (/*control.getSrcId() != 0U &&*/control.getDstId() != 0U) { // is this a group call? lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); if (!tg.isInvalid()) { From 95a8d269bea56c33705ecf230e2c33f88d9cb76a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 13 Jan 2025 12:59:31 -0500 Subject: [PATCH 43/68] fix null reference exception when dealing with VHDR in TIA-102 mode; --- src/host/modem/ModemV24.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 0a5635c2..07206c11 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1182,12 +1182,18 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(raw, m_rxCall->VHDR1, 18U); ::memcpy(raw + 18U, m_rxCall->VHDR2, 18U); + assert(raw != nullptr); + // buffer for decoded VHDR data uint8_t vhdr[P25_HDU_LENGTH_BYTES]; + ::memset(vhdr, 0x00U, P25_HDU_LENGTH_BYTES); + + assert(vhdr != nullptr); uint32_t offset = 0U; - for (uint32_t i = 0; i < P25_HDU_LENGTH_BYTES; i++, offset += 6) + for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { Utils::hex2Bin(raw[i], vhdr, offset); + } // try to decode the RS data try { From a82f848de45a22557f742da0fd3cb9c56fd35207 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 13 Jan 2025 16:58:28 -0500 Subject: [PATCH 44/68] correct bad handling for signal and compact fields of the TIA-102 control octet; enhance how the length of a TIA-102 full rate voice frame is determined; better handle initialization of the additional data fields for a TIA-102 full rate voice frame; correct bug using LDU2 frame 10 vs LDU2 frame 18 for LSD data on a full rate voice frame; implement a naive approach to performing superframe counting; correct bug causing the first full rate voice frames to never be transmitted for LDU1 or LDU2; remove assertion check for control port; --- src/common/p25/dfsi/frames/ControlOctet.cpp | 8 +-- src/common/p25/dfsi/frames/FullRateVoice.cpp | 58 ++++++++--------- src/common/p25/dfsi/frames/FullRateVoice.h | 26 ++++++-- src/host/Host.Config.cpp | 2 +- src/host/modem/ModemV24.cpp | 63 ++++++++++--------- src/host/modem/ModemV24.h | 2 + .../modem/port/specialized/V24UDPPort.cpp | 1 - 7 files changed, 88 insertions(+), 72 deletions(-) diff --git a/src/common/p25/dfsi/frames/ControlOctet.cpp b/src/common/p25/dfsi/frames/ControlOctet.cpp index 0308b526..6f4a6e20 100644 --- a/src/common/p25/dfsi/frames/ControlOctet.cpp +++ b/src/common/p25/dfsi/frames/ControlOctet.cpp @@ -49,8 +49,8 @@ bool ControlOctet::decode(const uint8_t* data) { assert(data != nullptr); - m_signal = (data[0U] & 0x07U) == 0x07U; // Signal Flag - m_compact = (data[0U] & 0x06U) == 0x06U; // Compact Flag + m_signal = (data[0U] & 0x80U) == 0x80U; // Signal Flag + m_compact = (data[0U] & 0x40U) == 0x40U; // Compact Flag m_blockHeaderCnt = (uint8_t)(data[0U] & 0x3FU); // Block Header Count return true; @@ -62,7 +62,7 @@ void ControlOctet::encode(uint8_t* data) { assert(data != nullptr); - data[0U] = (uint8_t)((m_signal ? 0x07U : 0x00U) + // Signal Flag - (m_compact ? 0x06U : 0x00U) + // Control Flag + data[0U] = (uint8_t)((m_signal ? 0x80U : 0x00U) + // Signal Flag + (m_compact ? 0x40U : 0x00U) + // Control Flag (m_blockHeaderCnt & 0x3F)); } diff --git a/src/common/p25/dfsi/frames/FullRateVoice.cpp b/src/common/p25/dfsi/frames/FullRateVoice.cpp index c3b50a63..d334e84f 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/FullRateVoice.cpp @@ -38,6 +38,8 @@ FullRateVoice::FullRateVoice() : { imbeData = new uint8_t[IMBE_BUF_LEN]; ::memset(imbeData, 0x00U, IMBE_BUF_LEN); + additionalData = new uint8_t[ADDITIONAL_LENGTH]; + ::memset(additionalData, 0x00U, ADDITIONAL_LENGTH); } /* Initializes a instance of the FullRateVoice class. */ @@ -65,6 +67,21 @@ FullRateVoice::~FullRateVoice() delete[] additionalData; } +/* */ + +uint8_t FullRateVoice::getLength() +{ + if (isVoice9or18()) { + return LENGTH_918; + } + + if (isVoice3thru8() || isVoice12thru17()) { + return LENGTH; + } + + return LENGTH_121011; +} + /* Decode a full rate voice frame. */ bool FullRateVoice::decode(const uint8_t* data) @@ -85,25 +102,14 @@ bool FullRateVoice::decode(const uint8_t* data) m_superframeCnt = (uint8_t)((data[13U] >> 2) & 0x03U); // Superframe Counter m_busy = (uint8_t)(data[13U] & 0x03U); - if (isVoice3thru8() || isVoice12thru17() || isVoice9or10()) { - if (additionalData != nullptr) - delete additionalData; - additionalData = new uint8_t[ADDITIONAL_LENGTH]; + if (isVoice3thru8() || isVoice12thru17() || isVoice9or18()) { ::memset(additionalData, 0x00U, ADDITIONAL_LENGTH); - if (isVoice9or10()) { - // CAI 9 and 10 are 3 bytes of additional data not 4 + if (isVoice9or18()) { + // CAI 9 and 18 are 3 bytes of additional data not 4 ::memcpy(additionalData, data + 14U, ADDITIONAL_LENGTH - 1U); } else { - uint8_t buffer[ADDITIONAL_LENGTH - 1U]; - ::memset(buffer, 0x00U, ADDITIONAL_LENGTH - 1U); - ::memcpy(buffer, data + 14U, ADDITIONAL_LENGTH - 1U); - buffer[2U] &= 0xC0U; // mask low bits - - uint32_t offset = 0; - for (uint8_t i = 0; i < ADDITIONAL_LENGTH - 1U; i++, offset += 6) { - Utils::hex2Bin(additionalData[i], buffer, offset); - } + ::memcpy(additionalData, data + 14U, ADDITIONAL_LENGTH); } } else { if (additionalData != nullptr) @@ -130,22 +136,12 @@ void FullRateVoice::encode(uint8_t* data) data[13U] = (uint8_t)(((m_superframeCnt & 0x03U) << 2) + // Superframe Count (m_busy & 0x03U)); // Busy Status - if ((isVoice3thru8() || isVoice12thru17() || isVoice9or10()) && - additionalData != nullptr) { - if (isVoice9or10()) { - // CAI 9 and 10 are 3 bytes of additional data not 4 + if (isVoice3thru8() || isVoice12thru17() || isVoice9or18()) { + if (isVoice9or18()) { + // CAI 9 and 18 are 3 bytes of additional data not 4 ::memcpy(data + 14U, additionalData, ADDITIONAL_LENGTH - 1U); } else { - uint8_t buffer[ADDITIONAL_LENGTH - 1U]; - ::memset(buffer, 0x00U, ADDITIONAL_LENGTH - 1U); - ::memcpy(buffer, additionalData, ADDITIONAL_LENGTH - 1U); - - uint32_t offset = 0; - for (uint8_t i = 0; i < ADDITIONAL_LENGTH - 1U; i++, offset += 6) { - buffer[i] = Utils::bin2Hex(additionalData, offset); - } - - ::memcpy(data + 14U, buffer, ADDITIONAL_LENGTH - 1U); + ::memcpy(data + 14U, additionalData, ADDITIONAL_LENGTH); } } } @@ -180,9 +176,9 @@ bool FullRateVoice::isVoice12thru17() /* Helper indicating if the frame is voice 9 or 10. */ -bool FullRateVoice::isVoice9or10() +bool FullRateVoice::isVoice9or18() { - if ( (m_frameType == DFSIFrameType::LDU1_VOICE9) || (m_frameType == DFSIFrameType::LDU2_VOICE10) ) { + if ( (m_frameType == DFSIFrameType::LDU1_VOICE9) || (m_frameType == DFSIFrameType::LDU2_VOICE18) ) { return true; } else { return false; diff --git a/src/common/p25/dfsi/frames/FullRateVoice.h b/src/common/p25/dfsi/frames/FullRateVoice.h index afde4e83..100fa7b9 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.h +++ b/src/common/p25/dfsi/frames/FullRateVoice.h @@ -37,6 +37,7 @@ namespace p25 * @brief Implements a P25 full rate voice packet. * \code{.unparsed} * CAI Frames 1, 2, 10 and 11. + * 14 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -52,6 +53,7 @@ namespace p25 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * CAI Frames 3 - 8. + * 18 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -69,6 +71,7 @@ namespace p25 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * CAI Frames 12 - 17. + * 18 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -86,6 +89,7 @@ namespace p25 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * CAI Frames 9 and 10. + * 17 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -108,6 +112,7 @@ namespace p25 * the layout with 8-bit aligned IMBE blocks instead of message vectors: * * CAI Frames 1, 2, 10 and 11. + * 14 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -123,6 +128,7 @@ namespace p25 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * CAI Frames 3 - 8. + * 18 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -140,6 +146,7 @@ namespace p25 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * CAI Frames 12 - 17. + * 18 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -156,7 +163,8 @@ namespace p25 * |Syn|R| Status | Rsvd | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * - * CAI Frames 9 and 10. + * CAI Frames 9 and 18. + * 17 bytes * * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -177,6 +185,9 @@ namespace p25 */ class HOST_SW_API FullRateVoice { public: + static const uint8_t LENGTH_121011 = 14U; + static const uint8_t LENGTH_918 = 17U; + static const uint8_t LENGTH = 18U; static const uint8_t ADDITIONAL_LENGTH = 4U; static const uint8_t IMBE_BUF_LEN = 11U; @@ -195,9 +206,16 @@ namespace p25 */ ~FullRateVoice(); + /** + * @brief + * @returns uint8_t + */ + uint8_t getLength(); + /** * @brief Decode a full rate voice frame. * @param[in] data Buffer to containing FullRateVoice to decode. + * @returns bool */ bool decode(const uint8_t* data); /** @@ -247,10 +265,10 @@ namespace p25 */ bool isVoice12thru17(); /** - * @brief Helper indicating if the frame is voice 9 or 10. - * @returns bool True, if frame is voice 9, or 10, otherwise false. + * @brief Helper indicating if the frame is voice 9 or 18. + * @returns bool True, if frame is voice 9, or 18, otherwise false. */ - bool isVoice9or10(); + bool isVoice9or18(); }; } // namespace frames } // namespace dfsi diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 73141abe..86410d98 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -585,7 +585,7 @@ bool Host::createModem() LogInfo(" DFSI Use FSC: %s", useFSCForUDP ? "yes" : "no"); LogInfo(" DFSI FSC Heartbeat: %us", fscHeartbeat); LogInfo(" DFSI FSC Initiator: %s", fscInitiator ? "yes" : "no"); - LogInfo(" DFSI FSC TIA Frames: %s", dfsiTIAMode ? "yes" : "no"); + LogInfo(" DFSI TIA-102 Frames: %s", dfsiTIAMode ? "yes" : "no"); } } diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 07206c11..7d7deba3 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -44,6 +44,7 @@ ModemV24::ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, u false, false, dumpModemStatus, trace, debug), m_rtrt(rtrt), m_diu(diu), + m_superFrameCnt(0U), m_audio(), m_nid(nullptr), m_txP25Queue(p25TxQueueSize, "TX P25 Queue"), @@ -1253,6 +1254,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) case BlockType::FULL_RATE_VOICE: { FullRateVoice voice = FullRateVoice(); + //m_superFrameCnt = voice.getSuperframeCnt(); voice.decode(dfsiData + dataOffs); DFSIFrameType::E frameType = voice.getFrameType(); @@ -1742,6 +1744,7 @@ uint16_t ModemV24::generateNID(DUID::E duid) void ModemV24::startOfStreamTIA(const p25::lc::LC& control) { m_txCallInProgress = true; + m_superFrameCnt = 0U; p25::lc::LC lc = p25::lc::LC(control); @@ -1864,6 +1867,8 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) void ModemV24::endOfStreamTIA() { + m_superFrameCnt = 0U; + uint16_t length = 0U; uint8_t buffer[2U]; ::memset(buffer, 0x00U, 2U); @@ -2474,37 +2479,24 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) break; } - // For n=0 (VHDR1/10) case we create the buffer in the switch, for all other frame types we do that here - if (n != 0) { - buffer = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES]; - ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); - - // generate control octet - ControlOctet ctrl = ControlOctet(); - ctrl.setBlockHeaderCnt(2U); - ctrl.encode(buffer); - bufferSize += ControlOctet::LENGTH; - - // generate block header - BlockHeader hdr = BlockHeader(); - hdr.setBlockType(BlockType::FULL_RATE_VOICE); - hdr.encode(buffer + 1U); - bufferSize += BlockHeader::LENGTH; - - // generate block header - hdr.setBlockType(BlockType::START_OF_STREAM); - hdr.encode(buffer + 2U); - bufferSize += BlockHeader::LENGTH; - - voice.encode(buffer + bufferSize); - bufferSize += FullRateVoice::LENGTH; - - // generate start of stream - StartOfStream start = StartOfStream(); - start.setNID(generateNID()); - start.encode(buffer + bufferSize); - bufferSize += StartOfStream::LENGTH; - } + buffer = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // generate control octet + ControlOctet ctrl = ControlOctet(); + ctrl.setBlockHeaderCnt(1U); + ctrl.encode(buffer); + bufferSize += ControlOctet::LENGTH; + + // generate block header + BlockHeader hdr = BlockHeader(); + hdr.setBlockType(BlockType::FULL_RATE_VOICE); + hdr.encode(buffer + 1U); + bufferSize += BlockHeader::LENGTH; + + voice.setSuperframeCnt(m_superFrameCnt); + voice.encode(buffer + bufferSize); + bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type if (buffer != nullptr) { if (m_trace) { @@ -2515,5 +2507,14 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) delete[] buffer; } } + + // bryanb: this is a naive way of incrementing the superframe counter, we basically just increment it after + // processing and LDU2 + if (duid == DUID::LDU2) { + if (m_superFrameCnt == 255U) + m_superFrameCnt = 0U; + else + m_superFrameCnt++; + } } } diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index bd78e475..cb48e31a 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -302,6 +302,8 @@ namespace modem bool m_rtrt; bool m_diu; + uint8_t m_superFrameCnt; + p25::Audio m_audio; p25::NID* m_nid; diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 44e69441..fc3f2018 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -79,7 +79,6 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod assert(peerId > 0U); assert(!address.empty()); assert(modemPort > 0U); - assert(controlPort > 0U); if (controlPort > 0U && useFSC) { m_controlSocket = new Socket(controlPort); From 3b395a99cfd259675f90cbbdadf6065a31f50c96 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 17 Jan 2025 09:03:14 -0500 Subject: [PATCH 45/68] fix issue with potentional concurrency crash when updating lookup tables in PEER_LINK; --- src/common/Thread.cpp | 2 ++ src/common/lookups/LookupTable.h | 6 ++++++ src/common/lookups/TalkgroupRulesLookup.h | 6 ++++++ src/fne/network/PeerNetwork.cpp | 3 +++ 4 files changed, 17 insertions(+) diff --git a/src/common/Thread.cpp b/src/common/Thread.cpp index 49fc0ad4..93e9cc96 100644 --- a/src/common/Thread.cpp +++ b/src/common/Thread.cpp @@ -63,6 +63,8 @@ bool Thread::run() void Thread::wait() { + if (!m_started) + return; #if defined(_WIN32) ::WaitForSingleObject(m_thread, INFINITE); ::CloseHandle(m_thread); diff --git a/src/common/lookups/LookupTable.h b/src/common/lookups/LookupTable.h index 361f6e40..6eabc41c 100644 --- a/src/common/lookups/LookupTable.h +++ b/src/common/lookups/LookupTable.h @@ -178,6 +178,12 @@ namespace lookups */ void filename(std::string filename) { m_filename = filename; }; + /** + * @brief Helper to set the reload time of this lookup table. + * @param reloadTime Lookup time in seconds. + */ + void setReloadTime(uint32_t reloadTime) { m_reloadTime = 0U; } + protected: std::string m_filename; uint32_t m_reloadTime; diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h index 0ed25811..05bbb883 100644 --- a/src/common/lookups/TalkgroupRulesLookup.h +++ b/src/common/lookups/TalkgroupRulesLookup.h @@ -599,6 +599,12 @@ namespace lookups */ void filename(std::string filename) { m_rulesFile = filename; }; + /** + * @brief Helper to set the reload time of this lookup table. + * @param reloadTime Lookup time in seconds. + */ + void setReloadTime(uint32_t reloadTime) { m_reloadTime = 0U; } + private: std::string m_rulesFile; uint32_t m_reloadTime; diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 0339105a..5054daf5 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -222,6 +222,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco file.close(); m_tidLookup->stop(true); + m_tidLookup->setReloadTime(0U); m_tidLookup->filename(filename); m_tidLookup->reload(); @@ -359,6 +360,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco file.close(); m_ridLookup->stop(true); + m_ridLookup->setReloadTime(0U); m_ridLookup->filename(filename); m_ridLookup->reload(); @@ -496,6 +498,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco file.close(); m_pidLookup->stop(true); + m_pidLookup->setReloadTime(0U); m_pidLookup->filename(filename); m_pidLookup->reload(); From 7979b6ff40e87115b4df6140a9a5f4a2a8b597f0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 17 Jan 2025 11:05:32 -0500 Subject: [PATCH 46/68] fix a thread concurrency issue when modifying the m_status unordered maps, instead of utilizing erase and checking if entries don't exist add a activeCall flag and check that, this resolves a possible dual access issue with find_if and erase that could result in an FNE crash; --- src/fne/network/callhandler/TagDMRData.cpp | 67 +++++++++++++++------ src/fne/network/callhandler/TagDMRData.h | 50 +++++++++++++++ src/fne/network/callhandler/TagNXDNData.cpp | 43 +++++++++---- src/fne/network/callhandler/TagNXDNData.h | 43 +++++++++++++ src/fne/network/callhandler/TagP25Data.cpp | 43 +++++++++---- src/fne/network/callhandler/TagP25Data.h | 45 +++++++++++++- 6 files changed, 246 insertions(+), 45 deletions(-) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 137da596..47dc7896 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -141,19 +141,33 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } RxStatus status; - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); - if (it == m_status.end()) { - LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, srcId, dstId, streamId, external); - } - else { - status = it->second; + { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId && x.second.slotNo == slotNo) { + return true; + } + return false; + }); + if (it == m_status.end()) { + LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, srcId, dstId, streamId, external); + } + else { + status = it->second; + } } uint64_t duration = hrc::diff(pktTime, status.callStartTime); - if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != m_status.end()) { - m_status.erase(dstId); + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId && x.second.slotNo == slotNo) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + m_status[dstId].reset(); // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); @@ -194,7 +208,13 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; } - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId && x.second.slotNo == slotNo) { + if (x.second.activeCall) + return true; + } + return false; + }); if (it != m_status.end()) { RxStatus status = it->second; if (streamId != status.streamId) { @@ -202,7 +222,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { LogWarning(LOG_NET, "DMR, Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_status.erase(dstId); + m_status[dstId].reset(); m_network->m_callInProgress = false; } @@ -228,14 +248,14 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // this is a new call stream - RxStatus status = RxStatus(); - status.callStartTime = pktTime; - status.srcId = srcId; - status.dstId = dstId; - status.slotNo = slotNo; - status.streamId = streamId; - status.peerId = peerId; - m_status[dstId] = status; // this *could* be an issue if a dstId appears on both slots somehow... + // bryanb: this could be problematic and is naive, if a dstId appears on both slots (which shouldn't happen) + m_status[dstId].callStartTime = pktTime; + m_status[dstId].srcId = srcId; + m_status[dstId].dstId = dstId; + m_status[dstId].slotNo = slotNo; + m_status[dstId].streamId = streamId; + m_status[dstId].peerId = peerId; + m_status[dstId].activeCall = true; LogMessage(LOG_NET, "DMR, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); @@ -367,7 +387,14 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { // if we have an Rx status for the destination deny the grant - if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId/* && x.second.slotNo == slot*/) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { return false; } diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index c49a87dd..23e7873b 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -120,13 +120,31 @@ namespace network uint8_t* buffer; uint32_t bufferLen; + /** + * @brief DMR slot number. + */ uint8_t slotNo; + /** + * @brief RTP Packet Sequence. + */ uint16_t pktSeq; + /** + * @brief Call Stream ID. + */ uint32_t streamId; + /** + * @brief Peer ID. + */ uint32_t peerId; + /** + * @brief Source ID. + */ uint32_t srcId; + /** + * @brief Destination ID. + */ uint32_t dstId; }; std::deque m_parrotFrames; @@ -139,11 +157,43 @@ namespace network public: system_clock::hrc::hrc_t callStartTime; system_clock::hrc::hrc_t lastPacket; + /** + * @brief Source ID. + */ uint32_t srcId; + /** + * @brief Destination ID. + */ uint32_t dstId; + /** + * @brief DMR slot number. + */ uint8_t slotNo; + /** + * @brief Call Stream ID. + */ uint32_t streamId; + /** + * @brief Peer ID. + */ uint32_t peerId; + /** + * @brief Flag indicating this call is active with traffic currently in progress. + */ + bool activeCall; + + /** + * @brief Helper to reset call status. + */ + void reset() + { + srcId = 0U; + dstId = 0U; + slotNo = 0U; + streamId = 0U; + peerId = 0U; + activeCall = false; + } }; typedef std::pair StatusMapPair; std::unordered_map m_status; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index ed0c51a2..b579a58f 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -113,8 +113,15 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI RxStatus status = m_status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); - if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) { - m_status.erase(dstId); + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + m_status[dstId].reset(); // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); @@ -154,7 +161,13 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI return false; } - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; if (streamId != status.streamId) { @@ -162,7 +175,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { LogWarning(LOG_NET, "NXDN, Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_status.erase(dstId); + m_status[dstId].reset(); m_network->m_callInProgress = false; } @@ -188,13 +201,12 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } // this is a new call stream - RxStatus status = RxStatus(); - status.callStartTime = pktTime; - status.srcId = srcId; - status.dstId = dstId; - status.streamId = streamId; - status.peerId = peerId; - m_status[dstId] = status; + m_status[dstId].callStartTime = pktTime; + m_status[dstId].srcId = srcId; + m_status[dstId].dstId = dstId; + m_status[dstId].streamId = streamId; + m_status[dstId].peerId = peerId; + m_status[dstId].activeCall = true; LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); @@ -320,7 +332,14 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { // if we have an Rx status for the destination deny the grant - if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { return false; } diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index 003235f1..195d2497 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -94,11 +94,26 @@ namespace network uint8_t* buffer; uint32_t bufferLen; + /** + * @brief RTP Packet Sequence. + */ uint16_t pktSeq; + /** + * @brief Call Stream ID. + */ uint32_t streamId; + /** + * @brief Peer ID. + */ uint32_t peerId; + /** + * @brief Source ID. + */ uint32_t srcId; + /** + * @brief Destination ID. + */ uint32_t dstId; }; std::deque m_parrotFrames; @@ -111,10 +126,38 @@ namespace network public: system_clock::hrc::hrc_t callStartTime; system_clock::hrc::hrc_t lastPacket; + /** + * @brief Source ID. + */ uint32_t srcId; + /** + * @brief Destination ID. + */ uint32_t dstId; + /** + * @brief Call Stream ID. + */ uint32_t streamId; + /** + * @brief Peer ID. + */ uint32_t peerId; + /** + * @brief Flag indicating this call is active with traffic currently in progress. + */ + bool activeCall; + + /** + * @brief Helper to reset call status. + */ + void reset() + { + srcId = 0U; + dstId = 0U; + streamId = 0U; + peerId = 0U; + activeCall = false; + } }; typedef std::pair StatusMapPair; std::unordered_map m_status; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 62400993..62551af3 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -178,14 +178,21 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { if (grantDemand) { LogWarning(LOG_NET, "P25, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", peerId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } else { - m_status.erase(dstId); + m_status[dstId].reset(); // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); @@ -227,7 +234,13 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; } - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; if (streamId != status.streamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { @@ -235,7 +248,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { LogWarning(LOG_NET, "P25, Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_status.erase(dstId); + m_status[dstId].reset(); m_network->m_callInProgress = false; } @@ -261,13 +274,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // this is a new call stream - RxStatus status = RxStatus(); - status.callStartTime = pktTime; - status.srcId = srcId; - status.dstId = dstId; - status.streamId = streamId; - status.peerId = peerId; - m_status[dstId] = status; + m_status[dstId].callStartTime = pktTime; + m_status[dstId].srcId = srcId; + m_status[dstId].dstId = dstId; + m_status[dstId].streamId = streamId; + m_status[dstId].peerId = peerId; + m_status[dstId].activeCall = true; LogMessage(LOG_NET, "P25, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); @@ -406,7 +418,14 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { // if we have an Rx status for the destination deny the grant - if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { return false; } diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index a1da1ad3..4ab19f33 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -142,11 +142,26 @@ namespace network uint8_t* buffer; uint32_t bufferLen; + /** + * @brief RTP Packet Sequence. + */ uint16_t pktSeq; + /** + * @brief Call Stream ID. + */ uint32_t streamId; + /** + * @brief Peer ID. + */ uint32_t peerId; + /** + * @brief Source ID. + */ uint32_t srcId; + /** + * @brief Destination ID. + */ uint32_t dstId; }; std::deque m_parrotFrames; @@ -160,16 +175,44 @@ namespace network public: system_clock::hrc::hrc_t callStartTime; system_clock::hrc::hrc_t lastPacket; + /** + * @brief Source ID. + */ uint32_t srcId; + /** + * @brief Destination ID. + */ uint32_t dstId; + /** + * @brief Call Stream ID. + */ uint32_t streamId; + /** + * @brief Peer ID. + */ uint32_t peerId; + /** + * @brief Flag indicating this call is active with traffic currently in progress. + */ + bool activeCall; + + /** + * @brief Helper to reset call status. + */ + void reset() + { + srcId = 0U; + dstId = 0U; + streamId = 0U; + peerId = 0U; + activeCall = false; + } }; typedef std::pair StatusMapPair; std::unordered_map m_status; friend class packetdata::P25PacketData; - packetdata::P25PacketData *m_packetData; + packetdata::P25PacketData* m_packetData; bool m_debug; From c2f2e8c69fcde684342cbbd7145e8e4acdbbecda Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 19 Jan 2025 15:58:21 -0500 Subject: [PATCH 47/68] add slot display to talkgroup list; --- src/tged/TGListWnd.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index c459e11b..8854a073 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -117,8 +117,8 @@ public: oss << std::setw(5) << std::setfill('0') << entry.source().tgId(); // build list view entry - const std::array columns = { - entry.name(), entry.nameAlias(), oss.str(), + const std::array columns = { + entry.name(), entry.nameAlias(), oss.str(), std::to_string(entry.source().tgSlot()), (entry.config().active()) ? "X" : "", (entry.config().affiliated()) ? "X" : "", std::to_string(entry.config().inclusionSize()), @@ -209,6 +209,7 @@ private: m_listView.addColumn("Name", 25); m_listView.addColumn("Alias", 20); m_listView.addColumn("TGID", 9); + m_listView.addColumn("Slot", 4); m_listView.addColumn("Active", 5); m_listView.addColumn("Affiliated", 5); m_listView.addColumn("Inclusions", 5); @@ -217,11 +218,12 @@ private: // set right alignment for TGID m_listView.setColumnAlignment(3, finalcut::Align::Right); - m_listView.setColumnAlignment(4, finalcut::Align::Center); + m_listView.setColumnAlignment(4, finalcut::Align::Right); m_listView.setColumnAlignment(5, finalcut::Align::Center); - m_listView.setColumnAlignment(6, finalcut::Align::Right); + m_listView.setColumnAlignment(6, finalcut::Align::Center); m_listView.setColumnAlignment(7, finalcut::Align::Right); m_listView.setColumnAlignment(8, finalcut::Align::Right); + m_listView.setColumnAlignment(9, finalcut::Align::Right); // set type of sorting m_listView.setColumnSortType(1, finalcut::SortType::Name); @@ -229,7 +231,6 @@ private: m_listView.setColumnSortType(3, finalcut::SortType::Name); // sort by TGID - m_listView.setColumnSort(2, finalcut::SortOrder::Ascending); m_listView.setColumnSort(3, finalcut::SortOrder::Ascending); m_listView.addCallback("clicked", [&]() { editEntry(); }); From 32c49b93eab52bef4179a897a9aea6fe406c8636 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 25 Jan 2025 17:22:28 -0500 Subject: [PATCH 48/68] filter out DVM CALL_TERM packets on the FNE based on TGID rules; --- src/fne/network/callhandler/TagP25Data.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 62551af3..a49fd4b5 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1111,6 +1111,26 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } break; } + + // handle validating DVM call termination packets + if (tsbk->getMFId() == MFG_DVM_OCS) { + switch (tsbk->getLCO()) { + case LCO::CALL_TERM: + { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(tsbk->getDstId()); + + // check TGID validity + if (tg.isInvalid()) { + return false; + } + + if (!tg.config().active()) { + return false; + } + } + break; + } + } } return true; From cb309fdb0f3eb4a4c32a7e3f9f1a4910b60e402c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 25 Jan 2025 17:28:54 -0500 Subject: [PATCH 49/68] reasonably this message shouldn't even appear if we don't have the channel granted; --- src/host/p25/packet/ControlSignaling.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 8f3c45ab..e377fbda 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -787,15 +787,15 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr case LCO::CALL_TERM: { if (m_p25->m_dedicatedControl) { - uint32_t chNo = tsbk->getGrpVchNo(); - - if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, chNo = %u, srcId = %u, dstId = %u", - tsbk->toString().c_str(), chNo, srcId, dstId); - } - // is the specified channel granted? if (/*m_p25->m_affiliations.isChBusy(chNo) &&*/ m_p25->m_affiliations.isGranted(dstId)) { + uint32_t chNo = tsbk->getGrpVchNo(); + + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, chNo = %u, srcId = %u, dstId = %u", + tsbk->toString().c_str(), chNo, srcId, dstId); + } + m_p25->m_affiliations.releaseGrant(dstId, false); } } From 9e68049cd8a0c526a69e9f62013baac9d1d5defc Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 25 Jan 2025 20:31:13 -0500 Subject: [PATCH 50/68] ensure first super-frame LDU1 after an HDU sets the encrypt flag; --- src/host/p25/packet/Voice.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index cd1ea29d..c3329929 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -195,6 +195,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_lastDUID = DUID::LDU1; bool alreadyDecoded = false; + bool hduEncrypt = false; FrameType::E frameType = FrameType::DATA_UNIT; ulong64_t rsValue = 0U; if (m_p25->m_rfState == RS_RF_LISTENING) { @@ -392,6 +393,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfLC = lc; m_rfLastLDU1 = m_rfLC; + hduEncrypt = encrypted; m_lastRejectId = 0U; ::ActivityLog("P25", true, "RF %svoice transmission from %u to %s%u", encrypted ? "encrypted ": "", srcId, group ? "TG " : "", dstId); @@ -660,6 +662,10 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfLastLDU1 = m_rfLC; } } + else { + // this might be the first LDU1 -- set the encryption flag if necessary + m_rfLC.setEncrypted(hduEncrypt); + } m_inbound = true; From d6cf8b115593baa2d702e5101b7ce3338aa7e58c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 26 Jan 2025 20:38:31 -0500 Subject: [PATCH 51/68] remove debug launch option for dvmmon from the launch.json contrib; --- contrib/vscode/launch.json | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/contrib/vscode/launch.json b/contrib/vscode/launch.json index 093012f2..b5eebe63 100644 --- a/contrib/vscode/launch.json +++ b/contrib/vscode/launch.json @@ -37,23 +37,6 @@ "moduleLoad": true, "trace": true } - }, - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": "Debug Monitor", - "type": "cppdbg", - "request": "launch", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/build/dvmmon", - "args": ["-c", "./monitor-config.yml"], - "cwd": "${workspaceFolder}/build", - "stopAtEntry": false, - "logging": { - "moduleLoad": true, - "trace": true - } - }, + } ] } From f3c86d2a8effecf62d20a6f8c260c565132a4fec Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 26 Jan 2025 21:42:04 -0500 Subject: [PATCH 52/68] support configuring SSL TCP sockets for non-blocking operations; --- src/common/network/tcp/SecureTcpClient.h | 40 ++++++++++++++++------ src/common/network/tcp/SecureTcpListener.h | 7 ++-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/common/network/tcp/SecureTcpClient.h b/src/common/network/tcp/SecureTcpClient.h index 8c7c5a14..40e5938c 100644 --- a/src/common/network/tcp/SecureTcpClient.h +++ b/src/common/network/tcp/SecureTcpClient.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -56,8 +56,9 @@ namespace network * @param sslCtx Instance of the OpenSSL context. * @param client Address for client. * @param clientLen Length of sockaddr_in structure. + * @param nonBlocking Flag indicating socket operations should be non-blocking. */ - SecureTcpClient(const int fd, SSL_CTX* sslCtx, sockaddr_in& client, int clientLen) : Socket(fd), + SecureTcpClient(const int fd, SSL_CTX* sslCtx, sockaddr_in& client, int clientLen, bool nonBlocking) : Socket(fd), m_sockaddr(), m_pSSL(nullptr), m_pSSLCtx(nullptr) @@ -135,23 +136,26 @@ namespace network } while (status == 1 && !SSL_is_init_finished(m_pSSL)); // reset socket blocking operations - flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) { - LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); - throw std::runtime_error("Cannot accept SSL client"); - } + if (!nonBlocking) { + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + throw std::runtime_error("Cannot accept SSL client"); + } - if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { - LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); - throw std::runtime_error("Cannot accept SSL client"); + if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + throw std::runtime_error("Cannot accept SSL client"); + } } } /** * @brief Initializes a new instance of the SecureTcpClient class. * @param address IP Address. * @param port Port. + * @param nonBlocking Flag indicating socket operations should be non-blocking. */ - SecureTcpClient(const std::string& address, const uint16_t port) : + SecureTcpClient(const std::string& address, const uint16_t port, bool nonBlocking) : m_pSSL(nullptr), m_pSSLCtx(nullptr) { @@ -180,6 +184,20 @@ namespace network LogError(LOG_NET, "Failed to connect to server, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno); throw std::runtime_error("Failed to SSL connect to server"); } + + // setup socket for non-blocking operations + if (nonBlocking) { + int flags = fcntl(m_fd, F_GETFL, 0); + if (flags < 0) { + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + throw std::runtime_error("Failed to set SSL server connection to non-blocking"); + } + + if (fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) < 0) { + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + throw std::runtime_error("Failed to set SSL server connection to non-blocking"); + } + } } /** * @brief Finalizes a instance of the SecureTcpClient class. diff --git a/src/common/network/tcp/SecureTcpListener.h b/src/common/network/tcp/SecureTcpListener.h index c1fc14c9..a94a7833 100644 --- a/src/common/network/tcp/SecureTcpListener.h +++ b/src/common/network/tcp/SecureTcpListener.h @@ -7,7 +7,7 @@ * @package DVM / Common Library * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -109,9 +109,10 @@ namespace network /** * @brief Accept a new TCP connection either secure or unsecure. + * @param nonBlocking Flag indicating accepted TCP connections should use non-blocking sockets. * @returns SecureTcpClient* Newly accepted TCP connection. */ - [[nodiscard]] SecureTcpClient* accept() + [[nodiscard]] SecureTcpClient* accept(bool nonBlocking = false) { sockaddr_in client = {}; socklen_t clientLen = sizeof(client); @@ -120,7 +121,7 @@ namespace network return nullptr; } - return new SecureTcpClient(fd, m_pSSLCtx, client, clientLen); + return new SecureTcpClient(fd, m_pSSLCtx, client, clientLen, nonBlocking); } private: From a868ff326442d6e5963c88a0735f976282871656 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 26 Jan 2025 22:12:39 -0500 Subject: [PATCH 53/68] fix idiotic bug where the activity log path wasn't being properly used; add potential fix for "tail riding" conditions with network traffic, that could cause the RF talkgroup hangtimer to be active when it shouldn't be; --- src/fne/ActivityLog.cpp | 4 ++-- src/host/ActivityLog.cpp | 4 ++-- src/host/p25/packet/Voice.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/fne/ActivityLog.cpp b/src/fne/ActivityLog.cpp index 4da91f94..d7a974f4 100644 --- a/src/fne/ActivityLog.cpp +++ b/src/fne/ActivityLog.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "ActivityLog.h" @@ -64,7 +64,7 @@ static bool ActivityLogOpen() } char filename[200U]; - ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", LogGetFilePath().c_str(), LogGetFileRoot().c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_actFilePath.c_str(), m_actFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); m_actFpLog = ::fopen(filename, "a+t"); m_actTm = *tm; diff --git a/src/host/ActivityLog.cpp b/src/host/ActivityLog.cpp index 51ca5034..6764a4c8 100644 --- a/src/host/ActivityLog.cpp +++ b/src/host/ActivityLog.cpp @@ -7,7 +7,7 @@ * @package DVM / Modem Host Software * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "ActivityLog.h" @@ -76,7 +76,7 @@ static bool ActivityLogOpen() } char filename[200U]; - ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", LogGetFilePath().c_str(), LogGetFileRoot().c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_actFilePath.c_str(), m_actFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); m_actFpLog = ::fopen(filename, "a+t"); m_actTm = *tm; diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index c3329929..5106a8f8 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1144,6 +1144,12 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L } } + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { + m_p25->m_rfTGHang.stop(); + } + // perform authoritative network TG hangtimer and traffic preemption if (m_p25->m_authoritative) { // don't process network frames if the destination ID's don't match and the network TG hang timer is running From 19bbd4d6d99e387a79fb8e51fd582b80e6543953 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 26 Jan 2025 23:35:45 -0500 Subject: [PATCH 54/68] fix crash when deleting TGID causes the TG list to scroll; --- src/tged/TGListWnd.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 8854a073..4e4fc299 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -316,6 +316,23 @@ private: LogMessage(LOG_HOST, "Deleting TG %s (%u)", m_selected.name().c_str(), m_selected.source().tgId()); g_tidLookups->eraseEntry(m_selected.source().tgId(), m_selected.source().tgSlot()); + + // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + int firstScrollLinePos = 0; + if (m_listView.getCount() > 0) { + firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); + } + if ((size_t)firstScrollLinePos > m_listView.getCount()) + firstScrollLinePos = 0; + if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { + --firstScrollLinePos; + (m_listView.*RTTIResult::ptr)(firstScrollLinePos); + (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); + } + loadListView(); } From 32494fb19db1c80409645677eff6a9f6b924b053 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 27 Jan 2025 22:52:21 -0500 Subject: [PATCH 55/68] change RF power level of hotspots to 95 instead of 100 to prevent a condition where spurious emissions may be generated; update README.md to include methods to calibrate a hotspot if a service monitor is available; --- README.md | 12 ++++++++++++ src/host/Host.Config.cpp | 4 ++-- src/host/setup/HostSetup.cpp | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57081573..c85a2c16 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,18 @@ The following setups assume the host is compiled with the setup TUI mode (if ava - Unusually high BER >10% and other various receive problems may be due to the radio/hotspot being off frequency and requiring some adjustment. Even a slight frequency drift can be catastrophic for proper digital modulation. The recommendation is to ensure the interfaced radio does not have an overall reference frequency drift > +/- 150hz. An unusually high BER can also be explained by DC level offsets in the signal paths, or issues with the FM deviation levels on the interfaced radio being too high or too low. - For hotspot operation, it may be necessary to enable/disable the AFC (automatic frequency correction) or change the gain mode. Both of these options can be altered using the setup TUI or directly in the `config.yml` file. In some cases when operating in trunking mode, for example, it may be necessary to change the orientation of the transmit antenna by using a 90 degree adapter as well as changing the gain mode to "Low" to prevent Rx desense. +### (Hotspot) Calibration Steps (using a capable service monitor) + +1. Zero any frequency offsets, both Rx and Tx. Ensure the Tx Deviation level is nominal (50). +2. Using spectrum analyzer mode, and using "z" or "P25 1200 Hz Tone Mode", and begin transmitting. You want to adjust the transmit deviation (T/t) and zero null the center carrier on the spectra seen on the spectrum analyzer (you should maintain the side lobes to the left and right of the center) as much as possible and maintain a clean 1200hz sine tone. +3. Switch to a mode on your service monitor where you can observe the *analog* FM deviation (if you have low and high pass settings like on an HP monitor, set 50hz Low Pass and 3khz High Pass), use "P" or "P25 1011 Hz Test Pattern" and begin transmitting. Observe the FM deviation, you want to adjust the transmit deviation to get the average deviation as close to 2.83khz as possible (a little high is okay, 2.9khz or so will increase BER but it will still be acceptable). +4. Switch to a mode on your service monitor where you can observe the frequency error, its best to view this error in Hz if possible, like step 3, use "P" or "P25 1011 Hz Test Pattern" and begin transmitting. Note the average frequency error. Use the Tx Frequency Adjustment accordingly to set an adjustment. (For example, if the observed frequency error is +200hz from center, you want to enter a -200hz adjustment in calibration/setup.) + +### (Hotspot) Calibration Notes + +- The Rx Frequency adjustment usually follows the Tx Frequency Adjustment, so if you've set a -200hz adjustment for Tx the Rx Frequency Adjustment should be -200hz or around -200hz. +- After calibration use a digital capable radio, with a front panel BER test or a radio with Tuner software capable of BER test. Evaluate Rx and Tx BER. Make fine adjustments if necessary to dial in BER. + ## dvmfne Configuration This source repository contains configuration example files within the configs folder, please review `fne-config.example.yml` for the `dvmfne` for details on various configurable options. When first setting up a FNE instance, it is important to properly configure a `talkgroup_rules.example.yml` file, this file defines all the various rules for valid talkgroups and other settings. diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 86410d98..cb8cf5c3 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -432,7 +432,7 @@ bool Host::createModem() uint8_t afcRange = (uint8_t)hotspotParams["afcRange"].as(1U); int rxTuning = hotspotParams["rxTuning"].as(0); int txTuning = hotspotParams["txTuning"].as(0); - uint8_t rfPower = (uint8_t)hotspotParams["rfPower"].as(100U); + uint8_t rfPower = (uint8_t)hotspotParams["rfPower"].as(95U); yaml::Node repeaterParams = modemConf["repeater"]; @@ -636,7 +636,7 @@ bool Host::createModem() LogInfo(" RX Coarse: %u, Fine: %u", rxCoarse, rxFine); LogInfo(" TX Coarse: %u, Fine: %u", txCoarse, txFine); LogInfo(" RSSI Coarse: %u, Fine: %u", rssiCoarse, rssiFine); - LogInfo(" RF Power Level: %u", rfPower); + LogInfo(" RF Power Level: %u%", rfPower); LogInfo(" RX Level: %.1f%%", rxLevel); LogInfo(" CW Id TX Level: %.1f%%", cwIdTXLevel); LogInfo(" DMR TX Level: %.1f%%", dmrTXLevel); diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 072f488d..a623103a 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -874,7 +874,7 @@ bool HostSetup::createModem(bool consoleDisplay) m_modem->setLevels(rxLevel, txLevel, txLevel, txLevel, txLevel); m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj, nxdnSymLevel3Adj, nxdnSymLevel1Adj); m_modem->setDCOffsetParams(txDCOffset, rxDCOffset); - m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, 100U, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj, adfGainMode, + m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, 95U, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj, adfGainMode, afcEnable, afcKI, afcKP, afcRange); m_modem->setSoftPot(rxCoarsePot, rxFinePot, txCoarsePot, txFinePot, rssiCoarsePot, rssiFinePot); From 23e015676eda3e1e851a61832227e19bc76dd551 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 27 Jan 2025 22:57:22 -0500 Subject: [PATCH 56/68] update README.md hotspot calibration notes; --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c85a2c16..608fa999 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,9 @@ The following setups assume the host is compiled with the setup TUI mode (if ava - The Rx Frequency adjustment usually follows the Tx Frequency Adjustment, so if you've set a -200hz adjustment for Tx the Rx Frequency Adjustment should be -200hz or around -200hz. - After calibration use a digital capable radio, with a front panel BER test or a radio with Tuner software capable of BER test. Evaluate Rx and Tx BER. Make fine adjustments if necessary to dial in BER. +- Calibrating a hotspot without a service monitor is possible, however a bit more tricky and mostly trial and error. The steps are essentially the same as the steps above, with the cavet being *step 2 should be skipped* and a radio capable of measuring Rx/Tx BER using +either front panel controls or tuning software is mandatory. In steps 3 and 4 while observing the BER via whatever available means, you want to essentially vary transmit deviation and frequency offset to find the lowest possible BER. (A very stable SDR *might* be usable +for step 4 to observe frequency error.) ## dvmfne Configuration From 132bec6eaab8fba7ba93deb2f5447e2011c4bc19 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 28 Jan 2025 15:23:28 -0500 Subject: [PATCH 57/68] document setting update timers for RID and TGID ACL rules to zero, to prevent local updats from erasing FNE pushed rules if the update timings weren't perfectly aligned; --- configs/config.example.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configs/config.example.yml b/configs/config.example.yml index a7495665..7609de15 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -645,6 +645,8 @@ system: # Full path to the RID ACL file. file: rid_acl.dat # Amount of time between updates of RID ACL file. (minutes) + # NOTE: If utilizing purely FNE pushed RID ACL rules, this update time should be set to 0 to prevent + # FNE rules from becoming erased. time: 2 # Flag indicating whether or not RID ACLs are enforced. acl: false @@ -656,6 +658,8 @@ system: # Full path to the talkgroup rules file. file: talkgroup_rules.yml # Amount of time between updates of talkgroup rules file. (minutes) + # NOTE: If utilizing purely FNE pushed talkgroup rules, this update time should be set to 0 to prevent + # FNE rules from becoming erased. time: 2 # Flag indicating whether or not TGID ACLs are enforced. acl: false From 3f75869a92fe52a2f8f9d64eabe20ec7f1d77ac8 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 4 Feb 2025 10:58:22 -0500 Subject: [PATCH 58/68] add some mutex locking to the timestamp map updates to prevent concurrency bugs; --- src/common/network/FrameQueue.cpp | 11 ++++++++++- src/common/network/FrameQueue.h | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index c1daca3c..3c855629 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -23,6 +23,12 @@ using namespace network::frame; #include #include +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::mutex FrameQueue::m_fqTimestampLock; + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -170,6 +176,7 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ void FrameQueue::clearTimestamps() { + std::lock_guard lock(m_fqTimestampLock); m_streamTimestamps.clear(); } @@ -187,6 +194,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui uint32_t timestamp = INVALID_TS; if (streamId != 0U) { + std::lock_guard lock(m_fqTimestampLock); auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { timestamp = entry->second; @@ -221,6 +229,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui } if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) { + std::lock_guard lock(m_fqTimestampLock); auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { if (m_debug) diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 3a23ff43..413c69d9 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -21,6 +21,7 @@ #include "common/network/RTPFNEHeader.h" #include "common/network/RawFrameQueue.h" +#include #include namespace network @@ -116,6 +117,7 @@ namespace network private: uint32_t m_peerId; std::unordered_map m_streamTimestamps; + static std::mutex m_fqTimestampLock; /** * @brief Generate RTP message for the frame queue. From 1e6282c92bd02ff56b5ce718e7b136ee3d310996 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 5 Feb 2025 15:27:01 -0500 Subject: [PATCH 59/68] ensure the group flag is set if the LCO is PRIVATE; --- src/common/p25/dfsi/LC.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/p25/dfsi/LC.cpp b/src/common/p25/dfsi/LC.cpp index da8807f4..4463546d 100644 --- a/src/common/p25/dfsi/LC.cpp +++ b/src/common/p25/dfsi/LC.cpp @@ -220,6 +220,9 @@ bool LC::decodeLDU1(const uint8_t* data, uint8_t* imbe) break; } + if (m_control->getLCO() == LCO::PRIVATE) + m_control->setGroup(false); + // by LDU1_VOICE8 we should have all the pertinant RS bytes if (m_frameType == DFSIFrameType::LDU1_VOICE8) { ulong64_t rsValue = 0U; From b419c56405c8ef89026481167c7ad70c6946bd62 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 6 Feb 2025 16:21:56 -0500 Subject: [PATCH 60/68] reject U2U voice channel grant for P25, DMR and NXDN if the unit being called isn't registered; --- src/host/dmr/packet/ControlSignaling.cpp | 8 ++++++++ src/host/nxdn/packet/ControlSignaling.cpp | 8 ++++++++ src/host/p25/packet/ControlSignaling.cpp | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 2835a18f..304d345f 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -868,6 +868,14 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } + if (!grp && !m_tscc->m_ignoreAffiliationCheck) { + // is this the target registered? + if (!m_tscc->m_affiliations->isUnitReg(dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access, IND_VOICE_CALL (Individual Voice Call) ignored, no unit registration, dstId = %u", m_tscc->m_slotNo, dstId); + return false; + } + } + uint32_t availChNo = m_tscc->m_affiliations->getAvailableChannelForSlot(slot); if (!m_tscc->m_affiliations->rfCh()->isRFChAvailable() || availChNo == 0U) { if (grp) { diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index 8d2bc00f..80fc82bf 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -479,6 +479,14 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } } + if (!grp && !m_nxdn->m_ignoreAffiliationCheck) { + // is this the target registered? + if (!m_nxdn->m_affiliations.isUnitReg(dstId)) { + LogWarning(LOG_RF, "NXDN, %s ignored, no unit registration, dstId = %u", rcch->toString().c_str(), dstId); + return false; + } + } + if (!m_nxdn->m_affiliations.rfCh()->isRFChAvailable()) { if (grp) { if (!net) { diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index e377fbda..58f694d8 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -2200,6 +2200,14 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } + if (!grp && !m_p25->m_ignoreAffiliationCheck) { + // is this the target registered? + if (!m_p25->m_affiliations.isUnitReg(dstId)) { + LogWarning(LOG_NET, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request) ignored, no unit registration, dstId = %u", dstId); + return false; + } + } + if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) { if (grp) { if (!net) { From c1d8186da94118dc8dd1c933ba29d6de1c1c470a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 6 Feb 2025 22:39:48 -0500 Subject: [PATCH 61/68] correct situation where a nullptr crash during shutdown would occur if the update timers for RID or TGID ACL on the host were set to 0 (i.e. no update); --- src/host/Host.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/Host.cpp b/src/host/Host.cpp index f1effa93..6c8edf6f 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -1667,11 +1667,11 @@ void Host::setState(uint8_t state) if (m_tidLookup != nullptr) { m_tidLookup->stop(); - delete m_tidLookup; + //delete m_tidLookup; } if (m_ridLookup != nullptr) { m_ridLookup->stop(); - delete m_ridLookup; + //delete m_ridLookup; } } else { From 333ac5c1648102d647cc6205face7c40762b4085 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 7 Feb 2025 17:09:03 -0500 Subject: [PATCH 62/68] add experimental support to encode audio in G.711 uLaw, optionally including a length header; --- configs/bridge-config.example.yml | 7 +- src/bridge/HostBridge.cpp | 237 +++++++++++++++++++++++++++--- src/bridge/HostBridge.h | 27 ++++ 3 files changed, 253 insertions(+), 18 deletions(-) diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 55217ff7..4b3540b9 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -58,7 +58,7 @@ network: # Enable PCM audio over UDP. udpAudio: false - # Enable meta data such as dstId and srcId in the UDP data + # Enable meta data such as dstId and srcId in the UDP data. udpMetadata: false # PCM over UDP send port. udpSendPort: 34001 @@ -68,6 +68,11 @@ network: udpReceivePort: 32001 # PCM over UDP receive address. udpReceiveAddress: "127.0.0.1" + # Flag indicating UDP audio should be encoded using G.711 uLaw. + udpUseULaw: false + # Flag indicating UDP audio should be transmitted without the length leader. + # NOTE: This flag is only applicable when encoding G.711 uLaw. + udpNoIncludeLength: false # Source "Radio ID" for transmitted audio frames. sourceId: 1234567 diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 6fe4e54c..bd625428 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -55,6 +55,18 @@ const int NUMBER_OF_BUFFERS = 32; #define LOCAL_CALL "Local Traffic" #define UDP_CALL "UDP Traffic" +#define SIGN_BIT (0x80) // sign bit for a A-law byte +#define QUANT_MASK (0xf) // quantization field mask +#define NSEGS (8) // number of A-law segments +#define SEG_SHIFT (4) // left shift for segment number +#define SEG_MASK (0x70) // segment field mask + +static short seg_aend[8] = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF }; +static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF }; + +#define BIAS (0x84) // bias for linear code +#define CLIP 8159 + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -135,6 +147,128 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit } } +/* */ + +static short search(short val, short* table, short size) +{ + for (short i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + + return (size); +} + +/* Helper to convert PCM into G.711 aLaw. */ + +uint8_t pcmToaLaw(short pcm) +{ + short mask; + unsigned char aval; + + pcm = pcm >> 3; + + if (pcm >= 0) { + mask = 0xD5; // sign (7th) bit = 1 + } else { + mask = 0x55; // sign bit = 0 + pcm = -pcm - 1; + } + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_aend, 8); + + /* + ** combine the sign, segment, quantization bits + */ + if (seg >= 8) // out of range, return maximum value + return (uint8_t)(0x7F ^ mask); + else { + aval = (uint8_t) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm >> 1) & QUANT_MASK; + else + aval |= (pcm >> seg) & QUANT_MASK; + + return (aval ^ mask); + } +} + +/* Helper to convert G.711 aLaw into PCM. */ + +short aLawToPCM(uint8_t alaw) +{ + alaw ^= 0x55; + + short t = (alaw & QUANT_MASK) << 4; + short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108; + break; + default: + t += 0x108; + t <<= seg - 1; + } + + return ((alaw & SIGN_BIT) ? t : -t); +} + +/* Helper to convert PCM into G.711 uLaw. */ + +uint8_t pcmTouLaw(short pcm) +{ + short mask; + + // get the sign and the magnitude of the value + pcm = pcm >> 2; + if (pcm < 0) { + pcm = -pcm; + mask = 0x7FU; + } else { + mask = 0xFFU; + } + + // clip the magnitude + if (pcm > CLIP) + pcm = CLIP; + pcm += (BIAS >> 2); + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_uend, 8); + + /* + ** combine the sign, segment, quantization bits; + ** and complement the code word + */ + if (seg >= 8) // out of range, return maximum value. + return (uint8_t)(0x7F ^ mask); + else { + uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF); + return (ulaw ^ mask); + } +} + +/* Helper to convert G.711 uLaw into PCM. */ + +short uLawToPCM(uint8_t ulaw) +{ + // complement to obtain normal u-law value + ulaw = ~ulaw; + + /* + ** extract and bias the quantization bits; then + ** shift up by the segment number and subtract out the bias + */ + short t = ((ulaw & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT; + + return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -152,6 +286,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpSendAddress("127.0.0.1"), m_udpReceivePort(32001), m_udpReceiveAddress("127.0.0.1"), + m_udpNoIncludeLength(false), + m_udpUseULaw(false), m_srcId(p25::defines::WUID_FNE), m_srcIdOverride(0U), m_overrideSrcIdFromMDC(false), @@ -867,6 +1003,13 @@ bool HostBridge::createNetwork() m_udpSendAddress = networkConf["udpSendAddress"].as(); m_udpReceivePort = (uint16_t)networkConf["udpReceivePort"].as(34001); m_udpReceiveAddress = networkConf["udpReceiveAddress"].as(); + m_udpUseULaw = networkConf["udpUseULaw"].as(false); + + if (m_udpUseULaw) + m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as(false); + + if (m_udpUseULaw && m_udpMetadata) + m_udpMetadata = false; // metadata isn't supported when encoding uLaw m_srcId = (uint32_t)networkConf["sourceId"].as(p25::defines::WUID_FNE); m_overrideSrcIdFromMDC = networkConf["overrideSourceIdFromMDC"].as(false); @@ -929,6 +1072,10 @@ bool HostBridge::createNetwork() LogInfo(" UDP Audio Send Port: %u", m_udpSendPort); LogInfo(" UDP Audio Receive Address: %s", m_udpReceiveAddress.c_str()); LogInfo(" UDP Audio Receive Port: %u", m_udpReceivePort); + LogInfo(" UDP Audio Use uLaw Encoding: %u", m_udpUseULaw ? "yes" : "no"); + if (m_udpUseULaw) { + LogInfo(" UDP Audio No Length Header: %u", m_udpNoIncludeLength ? "yes" : "no"); + } } LogInfo(" Source ID: %u", m_srcId); @@ -1004,12 +1151,22 @@ void HostBridge::processUDPAudio() if (m_debug) Utils::dump(1U, "UDP Audio Network Packet", buffer, length); - uint32_t pcmLength = __GET_UINT32(buffer, 0U); + uint32_t pcmLength = 0; + if (m_udpNoIncludeLength) { + pcmLength = length; + } else { + pcmLength = __GET_UINT32(buffer, 0U); + } UInt8Array __pcm = std::make_unique(pcmLength); uint8_t* pcm = __pcm.get(); - ::memcpy(pcm, buffer + 4U, pcmLength); + if (m_udpNoIncludeLength) { + ::memcpy(pcm, buffer, pcmLength); + } + else { + ::memcpy(pcm, buffer + 4U, pcmLength); + } // Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength); @@ -1025,9 +1182,17 @@ void HostBridge::processUDPAudio() int smpIdx = 0; short samples[MBE_SAMPLES_LENGTH]; - for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx += 2) { - samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); - smpIdx++; + if (m_udpUseULaw) { + for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx++) { + samples[smpIdx] = uLawToPCM(pcm[pcmIdx]); + smpIdx++; + } + } + else { + for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx += 2) { + samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); + smpIdx++; + } } m_inputAudio.addData(samples, MBE_SAMPLES_LENGTH); @@ -1307,18 +1472,37 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst if (m_udpAudio) { int pcmIdx = 0; uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); - pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); - pcmIdx += 2; + if (m_udpUseULaw) { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = pcmTouLaw(samples[smpIdx]); + } + } + else { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } } uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; if (!m_udpMetadata) { audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) - __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + if (m_udpUseULaw) { + length = (MBE_SAMPLES_LENGTH) + 4U; + if (m_udpNoIncludeLength) { + length = MBE_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + } else { + __SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); + } + } + else { + __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + } } else { length = (MBE_SAMPLES_LENGTH * 2U) + 12U; @@ -1854,18 +2038,37 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI if (m_udpAudio) { int pcmIdx = 0; uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); - pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); - pcmIdx += 2; + if (m_udpUseULaw) { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = pcmTouLaw(samples[smpIdx]); + } + } + else { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } } uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; if (!m_udpMetadata) { audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) - __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + if (m_udpUseULaw) { + length = (MBE_SAMPLES_LENGTH) + 4U; + if (m_udpNoIncludeLength) { + length = MBE_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + } else { + __SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); + } + } + else { + __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + } } else { length = (MBE_SAMPLES_LENGTH * 2U) + 12U; diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 2533fcfe..675f8cfa 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -91,6 +91,31 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unitID, mdc_u8_t extra0, mdc_u8_t extra1, mdc_u8_t extra2, mdc_u8_t extra3, void* context); +/** + * @brief Helper to convert PCM into G.711 aLaw. + * @param pcm PCM value. + * @return uint8_t aLaw value. + */ +uint8_t pcmToaLaw(short pcm); +/** + * @brief Helper to convert G.711 aLaw into PCM. + * @param alaw aLaw value. + * @return short PCM value. + */ +short aLawToPCM(uint8_t alaw); +/** + * @brief Helper to convert PCM into G.711 uLaw. + * @param pcm PCM value. + * @return uint8_t uLaw value. + */ +uint8_t pcmTouLaw(short pcm); +/** + * @brief Helper to convert G.711 uLaw into PCM. + * @param ulaw uLaw value. + * @return short PCM value. + */ +short uLawToPCM(uint8_t ulaw); + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -134,6 +159,8 @@ private: std::string m_udpSendAddress; uint16_t m_udpReceivePort; std::string m_udpReceiveAddress; + bool m_udpNoIncludeLength; + bool m_udpUseULaw; uint32_t m_srcId; uint32_t m_srcIdOverride; From b593f39d0d4e9e1f2ba9c43b03408b8a3356a694 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 7 Feb 2025 21:21:31 -0500 Subject: [PATCH 63/68] ensure during RPTK failure we cleanup the connection if a NAK occurs; --- src/fne/network/FNENetwork.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index c239df4f..5385b893 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -755,12 +755,16 @@ void* FNENetwork::threadedNetworkRx(void* arg) else { LogWarning(LOG_NET, "PEER %u RPTK NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->connectionState()); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + + delete connection; network->erasePeer(peerId); } } } else { network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + + network->erasePeer(peerId); LogWarning(LOG_NET, "PEER %u RPTK NAK, having no connection", peerId); } } From 67c84cdc0a2bb61fe16e0f0ce9d19b9dee203ab4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 7 Feb 2025 23:51:44 -0500 Subject: [PATCH 64/68] pass external flag to the intial isPeerPermitted() check, this is to fix an issue where if a external peer isn't in the always list for an affiliated group it may attempt to check an affiliation list that doesn't exist (external peers don't typically have affiliation lists); --- src/fne/network/callhandler/TagDMRData.cpp | 4 ++-- src/fne/network/callhandler/TagNXDNData.cpp | 4 ++-- src/fne/network/callhandler/TagP25Data.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 47dc7896..fdd74957 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL * */ #include "fne/Defines.h" @@ -129,7 +129,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is the stream valid? if (validate(peerId, dmrData, streamId)) { // is this peer ignored? - if (!isPeerPermitted(peerId, dmrData, streamId)) { + if (!isPeerPermitted(peerId, dmrData, streamId, external)) { return false; } diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index b579a58f..84ffc12b 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL * */ #include "fne/Defines.h" @@ -95,7 +95,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is the stream valid? if (validate(peerId, lc, messageType, streamId)) { // is this peer ignored? - if (!isPeerPermitted(peerId, lc, messageType, streamId)) { + if (!isPeerPermitted(peerId, lc, messageType, streamId, external)) { return false; } diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index a49fd4b5..5de7f021 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL * */ #include "fne/Defines.h" @@ -153,7 +153,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is the stream valid? if (validate(peerId, control, duid, tsbk.get(), streamId)) { // is this peer ignored? - if (!isPeerPermitted(peerId, control, duid, streamId)) { + if (!isPeerPermitted(peerId, control, duid, streamId, external)) { return false; } From 8e7c3436fcfbb553da9259dffa7ba59b39f57ae4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 8 Feb 2025 13:32:26 -0500 Subject: [PATCH 65/68] add new configuration option to disable all U2U calls from passing; --- configs/fne-config.example.yml | 2 ++ src/fne/network/FNENetwork.cpp | 4 ++++ src/fne/network/FNENetwork.h | 1 + src/fne/network/callhandler/TagDMRData.cpp | 2 ++ src/fne/network/callhandler/TagNXDNData.cpp | 2 ++ src/fne/network/callhandler/TagP25Data.cpp | 2 ++ 6 files changed, 13 insertions(+) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 19fa9d74..7ff4c9e2 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -95,6 +95,8 @@ master: # Flag indicating that terminators will be filtered by destination ID (i.e. valid RID or valid TGID). filterTerminators: true + # Flag indicating the FNE will drop all inbound Unit-to-Unit calls. + disallowAllUnitToUnit: false # List of peers that unit to unit calls are dropped for. dropUnitToUnit: [] diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 5385b893..b0507f24 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -85,6 +85,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_restrictGrantToAffOnly(false), m_filterHeaders(true), m_filterTerminators(true), + m_disallowU2U(false), m_dropU2UPeerTable(), m_enableInfluxDB(false), m_influxServerAddress("127.0.0.1"), @@ -161,6 +162,8 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) ** Drop Unit to Unit Peers */ + m_disallowU2U = conf["disallowAllUnitToUnit"].as(false); + yaml::Node& dropUnitToUnit = conf["dropUnitToUnit"]; if (dropUnitToUnit.size() > 0U) { for (size_t i = 0; i < dropUnitToUnit.size(); i++) { @@ -184,6 +187,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no"); LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no"); + LogInfo(" Disallow Unit-to-Unit: %s", m_disallowU2U ? "yes" : "no"); LogInfo(" InfluxDB Reporting Enabled: %s", m_enableInfluxDB ? "yes" : "no"); if (m_enableInfluxDB) { LogInfo(" InfluxDB Address: %s", m_influxServerAddress.c_str()); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index c9e8238a..2a36a0c6 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -463,6 +463,7 @@ namespace network bool m_forceListUpdate; + bool m_disallowU2U; std::vector m_dropU2UPeerTable; bool m_enableInfluxDB; diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index fdd74957..ab0b9e4f 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -656,6 +656,8 @@ bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::NetDat bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool external) { if (data.getFLCO() == FLCO::PRIVATE) { + if (m_network->m_disallowU2U) + return false; if (!m_network->checkU2UDroppedPeer(peerId)) return true; return false; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 84ffc12b..996b0d18 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -463,6 +463,8 @@ bool TagNXDNData::peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound) bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, uint32_t streamId, bool external) { if (!lc.getGroup()) { + if (m_network->m_disallowU2U) + return false; if (!m_network->checkU2UDroppedPeer(peerId)) return true; return false; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 5de7f021..f0c35540 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -882,6 +882,8 @@ bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, uint32_t streamId, bool external) { if (control.getLCO() == LCO::PRIVATE) { + if (m_network->m_disallowU2U) + return false; if (!m_network->checkU2UDroppedPeer(peerId)) return true; return false; From e88a3a0b2ab3d48ac12bdb65edf13784be4c70cb Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 8 Feb 2025 15:07:34 -0500 Subject: [PATCH 66/68] add support to decode LC_CALL_TERM when DVM is used in some reverse repeater situations; --- src/common/p25/lc/tdulc/LC_CALL_TERM.cpp | 14 ++++++++++++-- src/common/p25/lc/tdulc/TDULCFactory.cpp | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp b/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp index 96377749..84634f4c 100644 --- a/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp +++ b/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022,2024,2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -34,7 +34,17 @@ bool LC_CALL_TERM::decode(const uint8_t* data) { assert(data != nullptr); - /* stub */ + uint8_t rs[P25_TDULC_LENGTH_BYTES + 1U]; + ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); + + bool ret = TDULC::decode(data, rs); + if (!ret) + return false; + + ulong64_t rsValue = TDULC::toValue(rs); + + m_implicit = true; + m_dstId = (uint32_t)(rsValue & 0xFFFFFFU); // Target Address return true; } diff --git a/src/common/p25/lc/tdulc/TDULCFactory.cpp b/src/common/p25/lc/tdulc/TDULCFactory.cpp index 8d3bd6a2..4d5a9890 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.cpp +++ b/src/common/p25/lc/tdulc/TDULCFactory.cpp @@ -79,6 +79,8 @@ std::unique_ptr TDULCFactory::createTDULC(const uint8_t* data) return decode(new LC_PRIVATE(), data); case LCO::TEL_INT_VCH_USER: return decode(new LC_TEL_INT_VCH_USER(), data); + case LCO::CALL_TERM: + return decode(new LC_CALL_TERM(), data); default: LogError(LOG_P25, "TDULCFactory::create(), unknown TDULC LCO value, lco = $%02X", lco); break; From 8c48c9bcbebd1181c8f90cc6cbb30ab131d5f4df Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 8 Feb 2025 20:36:43 -0500 Subject: [PATCH 67/68] fix huge glaring bug where the dvmhost was trying to transmit TDULCs across the network as TSBKs; implement proper support to transit TDULC data across the network; --- src/common/network/BaseNetwork.cpp | 51 +++++++++++++++++++++- src/common/network/BaseNetwork.h | 23 +++++++++- src/fne/network/callhandler/TagP25Data.cpp | 25 +++++++++++ src/host/p25/packet/ControlSignaling.cpp | 2 +- src/host/p25/packet/Voice.cpp | 4 +- 5 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index f5235b39..cdfa4e16 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX - * Copyright (C) 2020-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2020-2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2024 Caleb, KO4UYJ * */ @@ -572,6 +572,26 @@ bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data) return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_p25StreamId); } +/* Writes P25 TDULC frame data to the network. */ + +bool BaseNetwork::writeP25TDULC(const p25::lc::LC& control, const uint8_t* data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (m_p25StreamId == 0U) { + m_p25StreamId = createStreamId(); + } + + uint32_t messageLength = 0U; + UInt8Array message = createP25_TDULCMessage(messageLength, control, data); + if (message == nullptr) { + return false; + } + + return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_p25StreamId); +} + /* Writes P25 PDU frame data to the network. */ bool BaseNetwork::writeP25PDU(const p25::data::DataHeader& header, const uint8_t currentBlock, const uint8_t* data, @@ -1025,6 +1045,35 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L return UInt8Array(buffer); } +/* Creates an P25 TDULC frame message. */ + +UInt8Array BaseNetwork::createP25_TDULCMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data) +{ + using namespace p25::defines; + assert(data != nullptr); + + uint8_t* buffer = new uint8_t[P25_TDULC_PACKET_LENGTH + PACKET_PAD]; + ::memset(buffer, 0x00U, P25_TDULC_PACKET_LENGTH + PACKET_PAD); + + // construct P25 message header + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + createP25_MessageHdr(buffer, DUID::TDULC, control, lsd, FrameType::TERMINATOR); + + // pack raw P25 TSDU bytes + uint32_t count = MSG_HDR_SIZE; + + ::memcpy(buffer + 24U, data, P25_TDULC_FRAME_LENGTH_BYTES); + count += P25_TDULC_FRAME_LENGTH_BYTES; + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Message, P25 TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD)); + + length = (P25_TDULC_PACKET_LENGTH + PACKET_PAD); + return UInt8Array(buffer); +} + /* Writes P25 PDU frame data to the network. */ UInt8Array BaseNetwork::createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header, diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index c3a6b26e..8919b8c5 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX - * Copyright (C) 2020-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2020-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -84,6 +84,7 @@ namespace network const uint32_t P25_LDU1_PACKET_LENGTH = 193U; // 24 byte header + DFSI data + 1 byte frame type + 12 byte enc sync const uint32_t P25_LDU2_PACKET_LENGTH = 181U; // 24 byte header + DFSI data + 1 byte frame type const uint32_t P25_TSDU_PACKET_LENGTH = 69U; // 24 byte header + TSDU data + const uint32_t P25_TDULC_PACKET_LENGTH = 78U; // 24 byte header + TDULC data /** * @brief Network Peer Connection Status @@ -381,6 +382,13 @@ namespace network * @returns bool True, if message was sent, otherwise false. */ virtual bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data); + /** + * @brief Writes P25 TDULC frame data to the network. + * @param[in] control Instance of p25::lc::LC containing link control data. + * @param[in] data Buffer containing P25 TDULC data to send. + * @returns bool True, if message was sent, otherwise false. + */ + virtual bool writeP25TDULC(const p25::lc::LC& control, const uint8_t* data); /** * @brief Writes P25 PDU frame data to the network. * @param[in] dataHeader Instance of p25::data::DataHeader containing PDU header data. @@ -634,6 +642,19 @@ namespace network */ UInt8Array createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data); + /** + * @brief Creates an P25 TDULC frame message. + * + * The data packed into a P25 TDULC frame message is essentially just a message header with the FEC encoded + * raw TDULC data. + * + * @param[out] length Length of network message buffer. + * @param[in] control Instance of p25::lc::LC containing link control data. + * @param[in] data Buffer containing P25 TDULC data to send. + * @returns UInt8Array Buffer containing the built network message. + */ + UInt8Array createP25_TDULCMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data); + /** * @brief Creates an P25 PDU frame message. * \code{.unparsed} diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index f0c35540..bd5c188f 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -9,6 +9,7 @@ */ #include "fne/Defines.h" #include "common/p25/lc/tsbk/TSBKFactory.h" +#include "common/p25/lc/tdulc/TDULCFactory.h" #include "common/p25/Sync.h" #include "common/Clock.h" #include "common/Log.h" @@ -764,6 +765,30 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) } } + // are we receiving a TDULC? + if (duid == DUID::TDULC) { + uint32_t frameLength = buffer[23U]; + + UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer + 24U, frameLength); + + std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data.get()); + if (tdulc != nullptr) { + // handle standard P25 reference opcodes + switch (tdulc->getLCO()) { + case LCO::CALL_TERM: + return false; // discard call terms at the FNE + default: + break; + } + } else { + // bryanb: should these be logged? + //std::string peerIdentity = m_network->resolvePeerIdentity(peerId); + //LogWarning(LOG_NET, "PEER %u (%s), passing TDULC that failed to decode? tdulc == nullptr", peerId, peerIdentity.c_str()); + } + } + return true; } diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 58f694d8..9cd38c29 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -1355,7 +1355,7 @@ void ControlSignaling::writeNetworkRF(lc::TDULC* tduLc, const uint8_t* data, boo lc.setSrcId(tduLc->getSrcId()); lc.setDstId(tduLc->getDstId()); - m_p25->m_network->writeP25TSDU(lc, data); + m_p25->m_network->writeP25TDULC(lc, data); if (autoReset) m_p25->m_network->resetP25(); } diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 5106a8f8..5141d2ec 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1358,7 +1358,6 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L // currently ignored -- this is a TODO break; case DUID::TDU: - case DUID::TDULC: // ignore a TDU that doesn't contain our destination ID if (control.getDstId() != m_p25->m_netLastDstId) { return false; @@ -1385,6 +1384,9 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L resetNet(); } break; + case DUID::TDULC: + // currently ignored + break; default: break; From a4e8138c749279554a5cb9488acfe3423b45d77f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 8 Feb 2025 20:54:13 -0500 Subject: [PATCH 68/68] enhance TDULC CALL_TERM handling in dvmhost; allow dvmfne to optionally drop TDULC CALL_TERMs or pass them; --- configs/fne-config.example.yml | 2 ++ src/fne/network/FNENetwork.cpp | 3 +++ src/fne/network/FNENetwork.h | 1 + src/fne/network/callhandler/TagP25Data.cpp | 3 ++- src/host/p25/packet/Voice.cpp | 17 +++++++++++++---- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 7ff4c9e2..8fdbdb2b 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -89,6 +89,8 @@ master: disallowExtAdjStsBcast: true # Flag indicating whether or not a conventional site can override affiliation rules. allowConvSiteAffOverride: true + # Flag indicating whether or not a TDULC call terminations will pass to any peers. + disallowCallTerm: false # Flag indicating that traffic headers will be filtered by destination ID (i.e. valid RID or valid TGID). filterHeaders: true diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index b0507f24..4c16dd79 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -82,6 +82,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_disallowAdjStsBcast(false), m_disallowExtAdjStsBcast(true), m_allowConvSiteAffOverride(false), + m_disallowCallTerm(false), m_restrictGrantToAffOnly(false), m_filterHeaders(true), m_filterTerminators(true), @@ -126,6 +127,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_disallowAdjStsBcast = conf["disallowAdjStsBcast"].as(false); m_disallowExtAdjStsBcast = conf["disallowExtAdjStsBcast"].as(true); m_allowConvSiteAffOverride = conf["allowConvSiteAffOverride"].as(true); + m_disallowCallTerm = conf["disallowCallTerm"].as(false); m_softConnLimit = conf["connectionLimit"].as(MAX_HARD_CONN_CAP); if (m_softConnLimit > MAX_HARD_CONN_CAP) { @@ -183,6 +185,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Disable Packet Data: %s", m_disablePacketData ? "yes" : "no"); LogInfo(" Dump Packet Data: %s", m_dumpPacketData ? "yes" : "no"); LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowExtAdjStsBcast ? "yes" : "no"); + LogInfo(" Disable P25 TDULC call termination broadcasts to any peers: %s", m_disallowCallTerm ? "yes" : "no"); LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no"); LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no"); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 2a36a0c6..8a2721f5 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -456,6 +456,7 @@ namespace network bool m_disallowAdjStsBcast; bool m_disallowExtAdjStsBcast; bool m_allowConvSiteAffOverride; + bool m_disallowCallTerm; bool m_restrictGrantToAffOnly; bool m_filterHeaders; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index bd5c188f..9f33d9d3 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -778,7 +778,8 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) // handle standard P25 reference opcodes switch (tdulc->getLCO()) { case LCO::CALL_TERM: - return false; // discard call terms at the FNE + if (m_network->m_disallowCallTerm) + return false; default: break; } diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 5141d2ec..56f763ff 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017,2018 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -1358,6 +1358,18 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L // currently ignored -- this is a TODO break; case DUID::TDU: + case DUID::TDULC: + if (duid == DUID::TDULC) { + std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data); + if (tdulc == nullptr) { + LogWarning(LOG_NET, P25_TDULC_STR ", undecodable TDULC"); + } + else { + if (tdulc->getLCO() != LCO::CALL_TERM) + break; + } + } + // ignore a TDU that doesn't contain our destination ID if (control.getDstId() != m_p25->m_netLastDstId) { return false; @@ -1384,9 +1396,6 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L resetNet(); } break; - case DUID::TDULC: - // currently ignored - break; default: break;