diff --git a/src/common/Defines.h b/src/common/Defines.h index 0304162a..f9cefa13 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -108,8 +108,8 @@ typedef unsigned long long ulong64_t; #define __EXE_NAME__ "" #define VERSION_MAJOR "04" -#define VERSION_MINOR "10" -#define VERSION_REV "E" +#define VERSION_MINOR "11" +#define VERSION_REV "F" #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR #define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")" diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 8e33146a..168196aa 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -478,6 +478,18 @@ uint32_t AffiliationLookup::getGrantedCh(uint32_t dstId) return 0U; } +/* Helper to get the destination ID for the given channel. */ + +uint32_t AffiliationLookup::getGrantedDstByCh(uint32_t chNo) +{ + for (auto entry : m_grantChTable) { + if (entry.second == chNo) + return entry.first; + } + + return 0U; +} + /* Helper to get the destination ID granted to the given source ID. */ uint32_t AffiliationLookup::getGrantedBySrcId(uint32_t srcId) diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index 1de12a3b..1edc4a39 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/lookups/AffiliationLookup.h @@ -215,6 +215,12 @@ namespace lookups * @returns uint32_t Channel Number */ virtual uint32_t getGrantedCh(uint32_t dstId); + /** + * @brief Helper to get the destination ID for the given channel. + * @param chNo Channel Number + * @returns uint32_t Destination Address. + */ + virtual uint32_t getGrantedDstByCh(uint32_t chNo); /** * @brief Helper to get the destination ID granted to the given source ID. * @param srcId Source Radio ID. diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index e184a507..68b65f62 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -227,6 +227,28 @@ void* DiagNetwork::threadedNetworkRx(void* arg) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .request(network->m_influxServer); } + + // repeat traffic to the connected peers + if (network->m_peers.size() > 0U) { + uint32_t i = 0U; + for (auto peer : network->m_peers) { + if (peer.second != nullptr) { + if (peer.second->isSysView()) { + uint32_t peerStreamId = peer.second->currStreamId(); + if (streamId == 0U) { + streamId = peerStreamId; + } + sockaddr_storage addr = peer.second->socketStorage(); + uint32_t addrLen = peer.second->sockStorageLen(); + + network->m_frameQueue->write(req->buffer, req->length, streamId, peerId, network->m_peerId, + { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, RTP_END_OF_CALL_SEQ, addr, addrLen); + } + } else { + continue; + } + } + } } else { network->writePeerNAK(peerId, TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); @@ -296,6 +318,28 @@ void* DiagNetwork::threadedNetworkRx(void* arg) .field("status", payload) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .request(network->m_influxServer); + + // repeat traffic to the connected peers + if (network->m_peers.size() > 0U) { + uint32_t i = 0U; + for (auto peer : network->m_peers) { + if (peer.second != nullptr) { + if (peer.second->isSysView()) { + uint32_t peerStreamId = peer.second->currStreamId(); + if (streamId == 0U) { + streamId = peerStreamId; + } + sockaddr_storage addr = peer.second->socketStorage(); + uint32_t addrLen = peer.second->sockStorageLen(); + + network->m_frameQueue->write(req->buffer, req->length, streamId, peerId, network->m_peerId, + { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, RTP_END_OF_CALL_SEQ, addr, addrLen); + } + } else { + continue; + } + } + } } else { network->writePeerNAK(peerId, TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED); diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 07face3c..e9cabf01 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -777,6 +777,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) LogInfoEx(LOG_NET, "PEER %u reports identity [%8s]", peerId, identity.c_str()); } + // is the peer reporting it is an external peer? if (peerConfig["externalPeer"].is()) { bool external = peerConfig["externalPeer"].get(); connection->isExternalPeer(external); @@ -784,6 +785,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) LogInfoEx(LOG_NET, "PEER %u reports external peer", peerId); } + // is the peer reporting it is a conventional peer? if (peerConfig["conventionalPeer"].is()) { if (network->m_allowConvSiteAffOverride) { bool convPeer = peerConfig["conventionalPeer"].get(); @@ -793,6 +795,14 @@ void* FNENetwork::threadedNetworkRx(void* arg) } } + // is the peer reporting it is a SysView peer? + if (peerConfig["sysView"].is()) { + bool sysView = peerConfig["sysView"].get(); + connection->isSysView(sysView); + if (sysView) + LogInfoEx(LOG_NET, "PEER %u reports SysView peer", peerId); + } + if (peerConfig["software"].is()) { std::string software = peerConfig["software"].get(); LogInfoEx(LOG_NET, "PEER %u reports software %s", peerId, software.c_str()); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 07be1db8..3f70b8d6 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -115,6 +115,8 @@ namespace network m_lastPing(0U), m_lastACLUpdate(0U), m_isExternalPeer(false), + m_isConventionalPeer(false), + m_isSysView(false), m_config(), m_pktLastSeq(RTP_END_OF_CALL_SEQ), m_pktNextSeq(1U) @@ -142,6 +144,8 @@ namespace network m_lastPing(0U), m_lastACLUpdate(0U), m_isExternalPeer(false), + m_isConventionalPeer(false), + m_isSysView(false), m_config(), m_pktLastSeq(RTP_END_OF_CALL_SEQ), m_pktNextSeq(1U) @@ -223,10 +227,12 @@ namespace network __PROPERTY_PLAIN(bool, isExternalPeer); /** * @brief Flag indicating this connection is from an conventional peer. - * - * */ __PROPERTY_PLAIN(bool, isConventionalPeer); + /** + * @brief Flag indicating this connection is from an SysView peer. + */ + __PROPERTY_PLAIN(bool, isSysView); /** * @brief JSON objecting containing peer configuration information. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index a30826d1..315a1170 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -681,6 +681,14 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t } } + // is this peer a SysView peer? + if (connection != nullptr) { + if (connection->isSysView()) { + external = true; // we'll just set the external flag to disable the affiliation check + // for SysView peers + } + } + // is this a TG that requires affiliations to repeat? // NOTE: external peers *always* repeat traffic regardless of affiliation if (tg.config().affiliated() && !external) { diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 617395da..ed0c51a2 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -496,6 +496,14 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message } } + // is this peer a SysView peer? + if (connection != nullptr) { + if (connection->isSysView()) { + external = true; // we'll just set the external flag to disable the affiliation check + // for SysView peers + } + } + // is this a TG that requires affiliations to repeat? // NOTE: external peers *always* repeat traffic regardless of affiliation if (tg.config().affiliated() && !external) { diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index da1b358f..8bc97552 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -972,6 +972,14 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, } } + // is this peer a SysView peer? + if (connection != nullptr) { + if (connection->isSysView()) { + external = true; // we'll just set the external flag to disable the affiliation check + // for SysView peers + } + } + // is this a TG that requires affiliations to repeat? // NOTE: external peers *always* repeat traffic regardless of affiliation if (tg.config().affiliated() && !external) { diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 36cea8a7..f70f9559 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -1319,6 +1319,69 @@ json::object Host::getStatus() response["p25NAC"].set(m_p25NAC); } + json::array vcChannels = json::array(); + bool _true = true; + bool _false = false; + + if (m_channelLookup->rfChDataSize() > 0) { + for (auto entry : m_channelLookup->rfChDataTable()) { + json::object chData = json::object(); + + uint32_t chNo = entry.first; + chData["channelNo"].set(chNo); + + uint8_t chId = entry.second.chId(); + chData["channelId"].set(chId); + + uint32_t dstId = 0U, srcId = 0U; + + // fetch affiliations from DMR if we're a DMR CC + if (m_dmrTSCCData) { + if (m_dmr->affiliations()->isChBusy(chNo)) { + chData["tx"].set(_true); + } else { + chData["tx"].set(_false); + } + + dstId = m_dmr->affiliations()->getGrantedDstByCh(chNo); + if (dstId > 0U) + srcId = m_dmr->affiliations()->getGrantedSrcId(dstId); + } + + // fetch affiliations from P25 if we're a P25 CC + if (m_p25CCData) { + if (m_p25->affiliations().isChBusy(chNo)) { + chData["tx"].set(_true); + } else { + chData["tx"].set(_false); + } + + dstId = m_p25->affiliations().getGrantedDstByCh(chNo); + if (dstId > 0U) + srcId = m_p25->affiliations().getGrantedSrcId(dstId); + } + + // fetch affiliations from NXDN if we're a NXDN CC + if (m_nxdnCCData) { + if (m_nxdn->affiliations().isChBusy(chNo)) { + chData["tx"].set(_true); + } else { + chData["tx"].set(_false); + } + + dstId = m_nxdn->affiliations().getGrantedDstByCh(chNo); + if (dstId > 0U) + srcId = m_nxdn->affiliations().getGrantedSrcId(dstId); + } + + chData["lastDstId"].set(dstId); + chData["lastSrcId"].set(srcId); + + vcChannels.push_back(json::value(chData)); + } + } + response["vcChannels"].set(vcChannels); + yaml::Node modemConfig = m_conf["system"]["modem"]; { json::object modemInfo = json::object(); diff --git a/src/host/network/Network.cpp b/src/host/network/Network.cpp index 6f8be686..48f9707b 100644 --- a/src/host/network/Network.cpp +++ b/src/host/network/Network.cpp @@ -70,7 +70,8 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_restApiPassword(), m_restApiPort(0), m_conventional(false), - m_remotePeerId(0U) + m_remotePeerId(0U), + m_promiscuousPeer(false) { assert(!address.empty()); assert(port > 0U); @@ -237,7 +238,7 @@ void Network::clock(uint32_t ms) // is this RTP packet destined for us? uint32_t peerId = fneHeader.getPeerId(); - if (m_peerId != peerId) { + if ((m_peerId != peerId) && !m_promiscuousPeer) { LogError(LOG_NET, "Packet received was not destined for us? peerId = %u", peerId); return; } @@ -619,7 +620,9 @@ void Network::clock(uint32_t ms) } break; default: - Utils::dump("unknown opcode from the master", buffer.get(), length); + userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, + buffer.get(), length, fneHeader.getStreamId()); + break; } } @@ -708,6 +711,13 @@ void Network::enable(bool enabled) // Protected Class Members // --------------------------------------------------------------------------- +/* User overrideable handler that allows user code to process network packets not handled by this class. */ + +void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +{ + Utils::dump("unknown opcode from the master", data, length); +} + /* Writes login request to the network. */ bool Network::writeLogin() diff --git a/src/host/network/Network.h b/src/host/network/Network.h index 72f2e67c..64dc2704 100644 --- a/src/host/network/Network.h +++ b/src/host/network/Network.h @@ -211,6 +211,19 @@ namespace network uint32_t m_remotePeerId; + bool m_promiscuousPeer; + + /** + * @brief User overrideable handler that allows user code to process network packets not handled by this class. + * @param peerId Peer ID. + * @param opcode FNE network opcode pair. + * @param[in] data Buffer containing message to send to peer. + * @param length Length of buffer. + * @param streamId Stream ID. + */ + virtual void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, + uint32_t streamId = 0U); + /** * @brief Writes login request to the network. * @returns bool True, if login request was sent, otherwise false. diff --git a/src/sysview/NodeStatusWnd.h b/src/sysview/NodeStatusWnd.h new file mode 100644 index 00000000..5ee019c4 --- /dev/null +++ b/src/sysview/NodeStatusWnd.h @@ -0,0 +1,818 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - FNE System View + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file NodeStatusWnd.h + * @ingroup fneSysView + */ +#if !defined(__NODE_STATUS_WND_H__) +#define __NODE_STATUS_WND_H__ + +#include "host/modem/Modem.h" +#include "common/Log.h" +#include "common/Thread.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define NODE_STATUS_WIDTH 28 +#define NODE_STATUS_HEIGHT 8 +#define NODE_UPDATE_FAIL_CNT 4 + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements the node status display window. + * @ingroup fneSysView + */ +class HOST_SW_API NodeStatusWidget final : public finalcut::FWidget { +public: + /** + * @brief Initializes a new instance of the NodeStatusWidget class. + * @param widget + */ + explicit NodeStatusWidget(FWidget* widget = nullptr) : FWidget{widget} + { + /* stub */ + } + + lookups::VoiceChData chData; + uint8_t channelId; + uint32_t channelNo; + uint32_t peerId; + int32_t uniqueId; + + json::object peerStatus; + + /** + * @brief + * @param status + */ + void setFailed(bool status) { m_failed = status; } + + /** + * @brief + */ + void update() + { + IdenTable entry = g_idenTable->find(channelId); + if (entry.baseFrequency() == 0U) { + ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", channelId); + } + + if (entry.txOffsetMhz() == 0U) { + ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", channelId); + } + + m_chanNo.setText(__INT_STR(channelId) + "-" + __INT_STR(channelNo)); + + uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + float calcTxOffset = entry.txOffsetMhz() * 1000000; + + uint32_t rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * channelNo)) + calcTxOffset); + uint32_t txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * channelNo))); + + std::stringstream ss; + ss << std::fixed << std::setprecision(4) << (float)(txFrequency / 1000000.0f); + + m_txFreq.setText(ss.str()); + + ss.str(std::string()); + ss << std::fixed << std::setprecision(4) << (float)(rxFrequency / 1000000.0f); + + m_rxFreq.setText(ss.str()); + + uint8_t mode = peerStatus["state"].get(); + switch (mode) { + case modem::STATE_DMR: + m_modeStr.setText("DMR"); + break; + case modem::STATE_P25: + m_modeStr.setText("P25"); + break; + case modem::STATE_NXDN: + m_modeStr.setText("NXDN"); + break; + default: + m_modeStr.setText(""); + break; + } + + m_peerIdStr.setText(__INT_STR(peerId)); + + if (peerStatus["lastDstId"].is()) { + uint32_t lastDstId = peerStatus["lastDstId"].get(); + m_lastDst.setText(__INT_STR(lastDstId)); + } + + if (peerStatus["lastSrcId"].is()) { + uint32_t lastSrcId = peerStatus["lastSrcId"].get(); + m_lastSrc.setText(__INT_STR(lastSrcId)); + } + + // get remote node state + if (peerStatus["dmrTSCCEnable"].is() && peerStatus["p25CtrlEnable"].is() && + peerStatus["nxdnCtrlEnable"].is()) { + bool dmrTSCCEnable = peerStatus["dmrTSCCEnable"].get(); + bool dmrCC = peerStatus["dmrCC"].get(); + bool p25CtrlEnable = peerStatus["p25CtrlEnable"].get(); + bool p25CC = peerStatus["p25CC"].get(); + bool nxdnCtrlEnable = peerStatus["nxdnCtrlEnable"].get(); + bool nxdnCC = peerStatus["nxdnCC"].get(); + + // are we a dedicated control channel? + if (dmrCC || p25CC || nxdnCC) { + m_control = true; + tbText = "CONTROL"; + } + + // if we aren't a dedicated control channel; set our + // title bar appropriately and set Tx state + if (!m_control) { + if (dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) { + tbText = "ENH. VOICE/CONV"; + } + else { + tbText = "VOICE/CONV"; + } + + // are we transmitting? + if (peerStatus["tx"].is()) { + m_tx = peerStatus["tx"].get(); + } + else { + m_tx = false; + } + } + } + + redraw(); + } + +private: + bool m_failed; + bool m_control; + bool m_tx; + + FString tbText{}; // title bar text + + FLabel m_modeStr{this}; + FLabel m_peerIdStr{this}; + + FLabel m_channelNoLabel{"Ch. No.: ", this}; + FLabel m_chanNo{this}; + + FLabel m_txFreqLabel{"Tx: ", this}; + FLabel m_txFreq{this}; + FLabel m_rxFreqLabel{"Rx: ", this}; + FLabel m_rxFreq{this}; + + FLabel m_lastDstLabel{"Last Dst: ", this}; + FLabel m_lastDst{this}; + FLabel m_lastSrcLabel{"Last Src: ", this}; + FLabel m_lastSrc{this}; + + /** + * @brief Initializes the window layout. + */ + void initLayout() override + { + setSize(FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT}); + + tbText = "UNKNOWN"; + + initControls(); + } + + /** + * @brief Draws the window. + */ + void draw() override + { + setColor(); + + const auto& wc = getColorTheme(); + setForegroundColor(wc->dialog_fg); + setBackgroundColor(wc->dialog_bg); + + if (FVTerm::getFOutput()->isMonochron()) + setReverse(true); + + drawTitleBar(); + setCursorPos({2, int(getHeight()) - 1}); + + if (FVTerm::getFOutput()->isMonochron()) + setReverse(false); + + if (m_failed) { + setColor(FColor::LightGray, FColor::LightRed); + } + else if (m_control) { + setColor(FColor::LightGray, FColor::Purple1); + } + else if (m_tx) { + setColor(FColor::LightGray, FColor::LightGreen); + } + else { + setColor(FColor::LightGray, FColor::Black); + } + + finalcut::drawBorder(this, FRect(FPoint{1, 2}, FPoint{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT})); + } + + /** + * @brief + */ + void drawTitleBar() + { + print() << FPoint{1, 1}; + + // Fill with spaces (left of the title) + if (FVTerm::getFOutput()->getMaxColor() < 16) + setBold(); + + const auto& wc = getColorTheme(); + + setColor(wc->titlebar_active_fg, wc->titlebar_active_bg); + + const auto width = getWidth(); + auto textWidth = getColumnWidth(tbText); + std::size_t leadingSpace{0}; + + if (width > textWidth) + leadingSpace = (width - textWidth) / 2; + + // Print leading whitespace + print(FString(leadingSpace, L' ')); + + // Print title bar text + if (!tbText.isEmpty()) { + if (textWidth <= width) + print(tbText); + else { + // Print ellipsis + const auto len = getLengthFromColumnWidth(tbText, width - 2); + print(tbText.left(len)); + print(".."); + textWidth = len + 2; + } + } + + // Print trailing whitespace + std::size_t trailingSpace = width - leadingSpace - textWidth; + print(FString(trailingSpace, L' ')); + + if (FVTerm::getFOutput()->getMaxColor() < 16) + unsetBold(); + } + + /** + * @brief Initializes window controls. + */ + void initControls() + { + m_modeStr.setGeometry(FPoint(24, 3), FSize(4, 1)); + m_modeStr.setAlignment(Align::Right); + m_modeStr.setEmphasis(); + + m_peerIdStr.setGeometry(FPoint(19, 4), FSize(9, 1)); + m_peerIdStr.setAlignment(Align::Right); + + // channel number + { + m_channelNoLabel.setGeometry(FPoint(2, 3), FSize(10, 1)); + + m_chanNo.setGeometry(FPoint(11, 3), FSize(8, 1)); + m_chanNo.setText(""); + } + + // channel frequency + { + m_txFreqLabel.setGeometry(FPoint(2, 4), FSize(4, 1)); + m_txFreq.setGeometry(FPoint(6, 4), FSize(8, 1)); + m_txFreq.setText(""); + + m_rxFreqLabel.setGeometry(FPoint(2, 5), FSize(4, 1)); + m_rxFreq.setGeometry(FPoint(6, 5), FSize(8, 1)); + m_rxFreq.setText(""); + } + + // last TG + { + m_lastDstLabel.setGeometry(FPoint(2, 6), FSize(11, 1)); + + m_lastDst.setGeometry(FPoint(13, 6), FSize(8, 1)); + m_lastDst.setText("None"); + } + + // last source + { + m_lastSrcLabel.setGeometry(FPoint(2, 7), FSize(11, 1)); + + m_lastSrc.setGeometry(FPoint(13, 7), FSize(8, 1)); + m_lastSrc.setText("None"); + } + } +}; + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements the node status window. + * @ingroup fneSysView + */ +class HOST_SW_API ScrollView final : public finalcut::FScrollView { +public: + /** + * @brief Initializes a new instance of the ScrollView class. + * @param widget + */ + explicit ScrollView(finalcut::FWidget* widget = nullptr) : finalcut::FScrollView{widget} + { + /* stub */ + } + + /** + * @brief + */ + void update() + { + for (auto entry : getNetwork()->peerStatus) { + auto it = std::find_if(m_nodes.begin(), m_nodes.end(), [&](NodeStatusWidget* wdgt) { + if (wdgt->peerId == entry.first && wdgt->uniqueId == (int32_t)entry.first) + return true; + return false; + }); + + if (it == m_nodes.end()) { + json::object peerObj = entry.second; + addNode(entry.first, peerObj); + + uint32_t peerId = entry.first; + uint8_t channelId = peerObj["channelId"].get(); + uint32_t channelNo = peerObj["channelNo"].get(); + + bool dmrTSCCEnable = peerObj["dmrTSCCEnable"].get(); + bool dmrCC = peerObj["dmrCC"].get(); + bool p25CtrlEnable = peerObj["p25CtrlEnable"].get(); + bool p25CC = peerObj["p25CC"].get(); + bool nxdnCtrlEnable = peerObj["nxdnCtrlEnable"].get(); + bool nxdnCC = peerObj["nxdnCC"].get(); + + if ((dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) && peerObj["vcChannels"].is()) { + json::array vcChannels = peerObj["vcChannels"].get(); + + struct + { + bool operator()(json::value a, json::value b) const + { + json::object aObj = a.get(); + json::object bObj = b.get(); + + uint32_t chNoA = aObj["channelNo"].get(); + uint32_t chNoB = bObj["channelNo"].get(); + + if (chNoA < chNoB) + return true; + return false; + } + } channelComparator; + + // sort channel list + std::sort(vcChannels.begin(), vcChannels.end(), channelComparator); + + for (int32_t i = 0; i < (int32_t)vcChannels.size(); i++) { + + json::object vcObj = vcChannels[i].get(); + + uint8_t vcChannelId = vcObj["channelId"].get(); + uint32_t vcChannelNo = vcObj["channelNo"].get(); + + // skip adding this entry if it matches the primary peer (this indicates a bad configuration) + if ((vcChannelId == channelId) && (vcChannelNo == channelNo)) { + continue; + } + + uint8_t state = modem::STATE_DMR; + if (dmrTSCCEnable) { + state = modem::STATE_DMR; + } + + if (p25CtrlEnable) { + state = modem::STATE_P25; + } + + if (nxdnCtrlEnable) { + state = modem::STATE_NXDN; + } + + vcObj["state"].set(state); + + bool _false = false; + vcObj["dmrTSCCEnable"].set(_false); + vcObj["dmrCC"].set(_false); + vcObj["p25CtrlEnable"].set(_false); + vcObj["p25CC"].set(_false); + vcObj["nxdnCtrlEnable"].set(_false); + vcObj["nxdnCC"].set(_false); + + addNode(peerId, vcObj, i); + } + } + } else { + NodeStatusWidget* wdgt = *it; + json::object peerObj = entry.second; + updateNode(wdgt, entry.first, peerObj); + + uint32_t peerId = entry.first; + uint8_t channelId = peerObj["channelId"].get(); + uint32_t channelNo = peerObj["channelNo"].get(); + + bool dmrTSCCEnable = peerObj["dmrTSCCEnable"].get(); + bool dmrCC = peerObj["dmrCC"].get(); + bool p25CtrlEnable = peerObj["p25CtrlEnable"].get(); + bool p25CC = peerObj["p25CC"].get(); + bool nxdnCtrlEnable = peerObj["nxdnCtrlEnable"].get(); + bool nxdnCC = peerObj["nxdnCC"].get(); + + if ((dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) && peerObj["vcChannels"].is()) { + json::array vcChannels = peerObj["vcChannels"].get(); + + struct + { + bool operator()(json::value a, json::value b) const + { + json::object aObj = a.get(); + json::object bObj = b.get(); + + uint32_t chNoA = aObj["channelNo"].get(); + uint32_t chNoB = bObj["channelNo"].get(); + + if (chNoA < chNoB) + return true; + return false; + } + } channelComparator; + + // sort channel list + std::sort(vcChannels.begin(), vcChannels.end(), channelComparator); + + for (int32_t i = 0; i < (int32_t)vcChannels.size(); i++) { + auto it2 = std::find_if(m_nodes.begin(), m_nodes.end(), [&](NodeStatusWidget* wdgt) { + if (wdgt->peerId == peerId && wdgt->uniqueId == i) + return true; + return false; + }); + + if (it2 != m_nodes.end()) { + NodeStatusWidget* subWdgt = *it2; + json::object vcObj = vcChannels[i].get(); + + uint8_t vcChannelId = vcObj["channelId"].get(); + uint32_t vcChannelNo = vcObj["channelNo"].get(); + + // skip adding this entry if it matches the primary peer (this indicates a bad configuration) + if ((vcChannelId == channelId) && (vcChannelNo == channelNo)) { + continue; + } + + uint8_t state = modem::STATE_DMR; + if (dmrTSCCEnable) { + state = modem::STATE_DMR; + } + + if (p25CtrlEnable) { + state = modem::STATE_P25; + } + + if (nxdnCtrlEnable) { + state = modem::STATE_NXDN; + } + + vcObj["state"].set(state); + + bool _false = false; + vcObj["dmrTSCCEnable"].set(_false); + vcObj["dmrCC"].set(_false); + vcObj["p25CtrlEnable"].set(_false); + vcObj["p25CC"].set(_false); + vcObj["nxdnCtrlEnable"].set(_false); + vcObj["nxdnCC"].set(_false); + + updateNode(subWdgt, peerId, vcObj, i); + } + } + } + } + } + + redraw(); + } + +private: + std::vector m_nodes; + + const int m_defaultOffsX = 2; + int m_nodeWdgtOffsX = m_defaultOffsX; + int m_nodeWdgtOffsY = 2; + + /** + * @brief Initializes the window layout. + */ + void initLayout() override + { + FScrollView::initLayout(); + } + + /** + * @brief + */ + void draw() override + { + const auto& wc = getColorTheme(); + setColor(wc->label_inactive_fg, wc->dialog_bg); + setPrintPos(FPoint{1, 1}); + clearArea(); + + FScrollView::draw(); + } + + /** + * @brief + * @param peerId + * @param peerObj + * @param uniqueId + */ + void addNode(uint32_t peerId, json::object peerObj, int32_t uniqueId = -1) + { + const auto& rootWidget = getRootWidget(); + + int maxWidth = 77; + if (rootWidget) { + maxWidth = rootWidget->getClientWidth() - 3; + } + + NodeStatusWidget* wdgt = new NodeStatusWidget(this); + + uint8_t channelId = peerObj["channelId"].get(); + uint32_t channelNo = peerObj["channelNo"].get(); + + VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string(), false); + + wdgt->channelId = channelId; + wdgt->channelNo = channelNo; + wdgt->chData = data; + wdgt->peerId = peerId; + wdgt->peerStatus = peerObj; + + if (uniqueId == -1) + wdgt->uniqueId = peerId; + else + wdgt->uniqueId = uniqueId; + + // update widget status + try + { + wdgt->update(); + } + catch (std::exception& e) { + ::LogWarning(LOG_HOST, "PEER %u, failed to update peer status, %s", peerId, e.what()); + } + + // set control position + if (m_nodeWdgtOffsX + NODE_STATUS_WIDTH > maxWidth) { + m_nodeWdgtOffsY += NODE_STATUS_HEIGHT + 2; + m_nodeWdgtOffsX = m_defaultOffsX; + } + + wdgt->setGeometry(FPoint{m_nodeWdgtOffsX, m_nodeWdgtOffsY}, FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT}); + + m_nodeWdgtOffsX += NODE_STATUS_WIDTH + 2; + wdgt->redraw(); + + m_nodes.push_back(wdgt); + } + + /** + * @brief + * + * @param wdgt + * @param peerObj + * @param uniqueId + */ + void updateNode(NodeStatusWidget* wdgt, uint32_t peerId, json::object peerObj, int32_t uniqueId = -1) + { + assert(wdgt != nullptr); + + uint8_t channelId = peerObj["channelId"].get(); + uint32_t channelNo = peerObj["channelNo"].get(); + + VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string(), false); + + wdgt->channelId = channelId; + wdgt->channelNo = channelNo; + wdgt->chData = data; + wdgt->peerId = peerId; + wdgt->peerStatus = peerObj; + + if (uniqueId == -1) + wdgt->uniqueId = peerId; + else + wdgt->uniqueId = uniqueId; + + // update widget status + try + { + wdgt->setFailed(false); + wdgt->update(); + } + catch (std::exception& e) { + wdgt->setFailed(true); + ::LogWarning(LOG_HOST, "PEER %u, failed to update peer status, %s", peerId, e.what()); + } + + wdgt->redraw(); + } +}; + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements the node status window. + * @ingroup fneSysView + */ +class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog { +public: + /** + * @brief Initializes a new instance of the NodeStatusWnd class. + * @param widget + */ + explicit NodeStatusWnd(FWidget* widget = nullptr) : FDialog{widget} + { + m_killed = false; + Thread::runAsThread(this, threadNodeUpdate); + } + +private: + bool m_killed; + + ScrollView m_scroll{this}; + + /** + * @brief Initializes the window layout. + */ + void initLayout() override + { + FDialog::setText("Peer Watch"); + FDialog::setSize(FSize{80, 25}); + + FDialog::setMinimizable(false); + FDialog::setShadow(); + + std::size_t maxWidth, maxHeight; + const auto& rootWidget = getRootWidget(); + + if (rootWidget) { + maxWidth = rootWidget->getClientWidth(); + maxHeight = rootWidget->getClientHeight(); + } + else { + // fallback to xterm default size + maxWidth = 80; + maxHeight = 24; + } + + const int x = 1 + int((maxWidth - getWidth()) / 2); + const int y = 1 + int((maxHeight - getHeight()) / 3); + FWindow::setPos(FPoint{x, y}, false); + FDialog::adjustSize(); + + FDialog::setModal(); + + initControls(); + + FDialog::initLayout(); + + rootWidget->redraw(); // bryanb: wtf? + redraw(); + + FWindow::zoomWindow(); + } + + /** + * @brief Initializes window controls. + */ + void initControls() + { + m_scroll.setGeometry(FPoint{1, 1}, FSize{78, 22}); + m_scroll.setScrollSize(FSize{230, 440}); + + m_scroll.update(); + } + + /** + * @brief Adjusts window size. + */ + void adjustSize() override + { + m_scroll.setGeometry(FPoint{1, 1}, FSize{getWidth() - 2, getHeight() - 3}); + } + + /* Entry point to node update thread. */ + + static void* threadNodeUpdate(void* arg) + { + thread_t* th = (thread_t*)arg; + if (th != nullptr) { + #if defined(_WIN32) + ::CloseHandle(th->thread); + #else + ::pthread_detach(th->thread); + #endif // defined(_WIN32) + + std::string threadName("sysview:node-status-update"); + NodeStatusWnd* wnd = (NodeStatusWnd*)th->obj; + if (wnd == nullptr) { + delete th; + return nullptr; + } + + if (wnd->m_killed) { + delete th; + return nullptr; + } + + LogDebug(LOG_HOST, "[ OK ] %s", threadName.c_str()); + #ifdef _GNU_SOURCE + ::pthread_setname_np(th->thread, threadName.c_str()); + #endif // _GNU_SOURCE + + bool killed = wnd->m_killed; + while (!killed) { + if (wnd != nullptr) { + if (wnd->isShown()) { + wnd->m_scroll.update(); + wnd->redraw(); + + killed = wnd->m_killed; + } + + Thread::sleep(1000U); + } else { + break; + } + } + + LogDebug(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; + } + + /* + ** Event Handlers + */ + + /** + * @brief Event that occurs on keyboard key press. + * @param e Keyboard Event. + */ + void onKeyPress(finalcut::FKeyEvent* e) override + { + const auto key = e->key(); + if (key == FKey::Escape) { + this->close(); + } + } + + /** + * @brief Event that occurs when the window is closed. + * @param e Close event. + */ + void onClose(FCloseEvent* e) override + { + m_killed = true; + Thread::sleep(1250U); // this is a horrible way of making sure the update thread stops... + + hide(); + } +}; + +#endif // __NODE_STATUS_WND_H__ \ No newline at end of file diff --git a/src/sysview/SysViewApplication.h b/src/sysview/SysViewApplication.h index c5618098..6c0352cf 100644 --- a/src/sysview/SysViewApplication.h +++ b/src/sysview/SysViewApplication.h @@ -14,28 +14,9 @@ #if !defined(__SYS_VIEW_APPLICATION_H__) #define __SYS_VIEW_APPLICATION_H__ -#include "common/Clock.h" -#include "common/dmr/DMRDefines.h" -#include "common/dmr/lc/csbk/CSBKFactory.h" -#include "common/dmr/lc/LC.h" -#include "common/dmr/lc/FullLC.h" -#include "common/dmr/SlotType.h" -#include "common/dmr/Sync.h" -#include "common/p25/P25Defines.h" -#include "common/p25/lc/tdulc/TDULCFactory.h" -#include "common/p25/lc/tsbk/TSBKFactory.h" -#include "common/nxdn/NXDNDefines.h" -#include "common/nxdn/lc/RTCH.h" -#include "common/Log.h" -#include "common/StopWatch.h" -#include "p25/tsbk/OSP_GRP_AFF.h" -#include "network/PeerNetwork.h" #include "SysViewMain.h" #include "SysViewMainWnd.h" -using namespace system_clock; -using namespace network; - #include using namespace finalcut; @@ -194,571 +175,24 @@ public: */ explicit SysViewApplication(const int& argc, char** argv) : FApplication{argc, argv} { - m_stopWatch.start(); + /* stub */ } /** * @brief Finalizes an instance of the SysViewApplication class. */ ~SysViewApplication() noexcept override { - closePeerNetwork(); - } - - /** - * @brief Initializes peer network connectivity. - * @returns bool - */ - bool createPeerNetwork() - { - yaml::Node fne = g_conf["fne"]; - - std::string password = fne["password"].as(); - - std::string address = fne["masterAddress"].as(); - uint16_t port = (uint16_t)fne["masterPort"].as(); - uint32_t id = fne["peerId"].as(); - - bool encrypted = fne["encrypted"].as(false); - std::string key = fne["presharedKey"].as(); - uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN]; - if (!key.empty()) { - if (key.size() == 32) { - // bryanb: shhhhhhh....dirty nasty hacks - key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs) - LogWarning(LOG_HOST, "Half-length network preshared encryption key detected, doubling key on itself."); - } - - if (key.size() == 64) { - if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { - const char* keyPtr = key.c_str(); - ::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); - - for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { - char t[4] = {keyPtr[0], keyPtr[1], 0}; - presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); - keyPtr += 2 * sizeof(char); - } - } - else { - LogWarning(LOG_HOST, "Invalid characters in the network preshared encryption key. Encryption disabled."); - encrypted = false; - } - } - else { - LogWarning(LOG_HOST, "Invalid network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); - encrypted = false; - } - } - - std::string identity = fne["identity"].as(); - - LogInfo("Network Parameters"); - LogInfo(" Peer ID: %u", id); - LogInfo(" Address: %s", address.c_str()); - LogInfo(" Port: %u", port); - - LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); - - if (id > 999999999U) { - ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); - return false; - } - - // initialize networking - network = new PeerNetwork(address, port, 0U, id, password, true, g_debug, true, true, true, true, true, true, true, true, false); - network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); - network->setLookups(g_ridLookup, g_tidLookup); - - ::LogSetNetwork(network); - - if (encrypted) { - network->setPresharedKey(presharedKey); - } - - network->enable(true); - bool ret = network->open(); - if (!ret) { - delete network; - network = nullptr; - LogError(LOG_HOST, "failed to initialize traffic networking for PEER %u", id); - return false; - } - - ::LogSetNetwork(network); - - return true; - } - - /** - * @brief Shuts down peer networking. - */ - void closePeerNetwork() - { - if (network != nullptr) { - network->close(); - delete network; - } + /* stub */ } - /** - * @brief Instance of the peer network. - */ - network::PeerNetwork* network; - protected: /** * @brief Process external user events. */ void processExternalUserEvent() override { - uint32_t ms = m_stopWatch.elapsed(); - - ms = m_stopWatch.elapsed(); - m_stopWatch.start(); - - // ------------------------------------------------------ - // -- Network Clocking -- - // ------------------------------------------------------ - - if (network != nullptr) { - network->clock(ms); - - hrc::hrc_t pktTime = hrc::now(); - - uint32_t length = 0U; - bool netReadRet = false; - UInt8Array dmrBuffer = network->readDMR(netReadRet, length); - if (netReadRet) { - using namespace dmr; - - uint8_t seqNo = dmrBuffer[4U]; - - uint32_t srcId = __GET_UINT16(dmrBuffer, 5U); - uint32_t dstId = __GET_UINT16(dmrBuffer, 8U); - - DMRDEF::FLCO::E flco = (dmrBuffer[15U] & 0x40U) == 0x40U ? DMRDEF::FLCO::PRIVATE : DMRDEF::FLCO::GROUP; - - uint32_t slotNo = (dmrBuffer[15U] & 0x80U) == 0x80U ? 2U : 1U; - - DMRDEF::DataType::E dataType = (DMRDEF::DataType::E)(dmrBuffer[15U] & 0x0FU); - - data::NetData dmrData; - dmrData.setSeqNo(seqNo); - dmrData.setSlotNo(slotNo); - dmrData.setSrcId(srcId); - dmrData.setDstId(dstId); - dmrData.setFLCO(flco); - - bool dataSync = (dmrBuffer[15U] & 0x20U) == 0x20U; - bool voiceSync = (dmrBuffer[15U] & 0x10U) == 0x10U; - - if (dataSync) { - dmrData.setData(dmrBuffer.get() + 20U); - dmrData.setDataType(dataType); - dmrData.setN(0U); - } - else if (voiceSync) { - dmrData.setData(dmrBuffer.get() + 20U); - dmrData.setDataType(DMRDEF::DataType::VOICE_SYNC); - dmrData.setN(0U); - } - else { - uint8_t n = dmrBuffer[15U] & 0x0FU; - dmrData.setData(dmrBuffer.get() + 20U); - dmrData.setDataType(DMRDEF::DataType::VOICE); - dmrData.setN(n); - } - - // is this the end of the call stream? - if (dataSync && (dataType == DMRDEF::DataType::TERMINATOR_WITH_LC)) { - if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid TERMINATOR, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - - RxStatus status; - auto it = std::find_if(m_dmrStatus.begin(), m_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); - if (it == m_dmrStatus.end()) { - LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - else { - status = it->second; - } - - uint64_t duration = hrc::diff(pktTime, status.callStartTime); - - if (std::find_if(m_dmrStatus.begin(), m_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != m_dmrStatus.end()) { - m_dmrStatus.erase(dstId); - - LogMessage(LOG_NET, "DMR, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); - } - } - - // is this a new call stream? - if (dataSync && (dataType == DMRDEF::DataType::VOICE_LC_HEADER)) { - if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid call, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - - auto it = std::find_if(m_dmrStatus.begin(), m_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); - if (it == m_dmrStatus.end()) { - // this is a new call stream - RxStatus status = RxStatus(); - status.callStartTime = pktTime; - status.srcId = srcId; - status.dstId = dstId; - status.slotNo = slotNo; - m_dmrStatus[dstId] = status; // this *could* be an issue if a dstId appears on both slots somehow... - - LogMessage(LOG_NET, "DMR, Call Start, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - } - - // are we receiving a CSBK? - if (dmrData.getDataType() == DMRDEF::DataType::CSBK) { - uint8_t data[DMRDEF::DMR_FRAME_LENGTH_BYTES + 2U]; - dmrData.getData(data + 2U); - - std::unique_ptr csbk = lc::csbk::CSBKFactory::createCSBK(data + 2U, DMRDEF::DataType::CSBK); - if (csbk != nullptr) { - switch (csbk->getCSBKO()) { - case DMRDEF::CSBKO::BROADCAST: - { - lc::csbk::CSBK_BROADCAST* osp = static_cast(csbk.get()); - if (osp->getAnncType() == DMRDEF::BroadcastAnncType::ANN_WD_TSCC) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u", dmrData.getSlotNo(), csbk->toString().c_str(), - osp->getSystemId(), osp->getLogicalCh1()); - } - } - break; - default: - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u (%s), dstId = %u (%s)", dmrData.getSlotNo(), csbk->toString().c_str(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - break; - } - } - } - - if (g_debug) - LogMessage(LOG_NET, "DMR, slotNo = %u, seqNo = %u, flco = $%02X, srcId = %u, dstId = %u, len = %u", slotNo, seqNo, flco, srcId, dstId, length); - } - - UInt8Array p25Buffer = network->readP25(netReadRet, length); - if (netReadRet) { - using namespace p25; - - uint8_t duid = p25Buffer[22U]; - uint8_t MFId = p25Buffer[15U]; - - // process raw P25 data bytes - UInt8Array data; - uint8_t frameLength = p25Buffer[23U]; - if (duid == P25DEF::DUID::PDU) { - frameLength = length; - data = std::unique_ptr(new uint8_t[length]); - ::memset(data.get(), 0x00U, length); - ::memcpy(data.get(), p25Buffer.get(), length); - } - else { - if (frameLength <= 24) { - data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - } - else { - data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), p25Buffer.get() + 24U, frameLength); - } - } - - uint8_t lco = p25Buffer[4U]; - - uint32_t srcId = __GET_UINT16(p25Buffer, 5U); - uint32_t dstId = __GET_UINT16(p25Buffer, 8U); - - uint32_t sysId = (p25Buffer[11U] << 8) | (p25Buffer[12U] << 0); - uint32_t netId = __GET_UINT16(p25Buffer, 16U); - - // log call status - if (duid != P25DEF::DUID::TSDU && duid != P25DEF::DUID::PDU) { - // is this the end of the call stream? - if ((duid == P25DEF::DUID::TDU) || (duid == P25DEF::DUID::TDULC)) { - if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid TDU, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - - RxStatus status = m_p25Status[dstId]; - uint64_t duration = hrc::diff(pktTime, status.callStartTime); - - if (std::find_if(m_p25Status.begin(), m_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_p25Status.end()) { - m_p25Status.erase(dstId); - - LogMessage(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); - } - } - - // is this a new call stream? - if ((duid != P25DEF::DUID::TDU) && (duid != P25DEF::DUID::TDULC)) { - if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid call, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - - auto it = std::find_if(m_p25Status.begin(), m_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); - if (it == m_p25Status.end()) { - // this is a new call stream - RxStatus status = RxStatus(); - status.callStartTime = pktTime; - status.srcId = srcId; - status.dstId = dstId; - m_p25Status[dstId] = status; - - LogMessage(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - } - } - - switch (duid) { - case P25DEF::DUID::TDU: - case P25DEF::DUID::TDULC: - if (duid == P25DEF::DUID::TDU) { - LogMessage(LOG_NET, P25_TDU_STR ", srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - else { - std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data.get()); - if (tdulc == nullptr) { - LogWarning(LOG_NET, P25_TDULC_STR ", undecodable TDULC"); - } - else { - LogMessage(LOG_NET, P25_TDULC_STR ", srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - } - break; - - case P25DEF::DUID::TSDU: - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); - if (tsbk == nullptr) { - LogWarning(LOG_NET, P25_TSDU_STR ", undecodable TSBK"); - } - else { - switch (tsbk->getLCO()) { - case P25DEF::TSBKO::IOSP_GRP_VCH: - case P25DEF::TSBKO::IOSP_UU_VCH: - { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u (%s), dstId = %u (%s)", - tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - break; - case P25DEF::TSBKO::IOSP_UU_ANS: - { - lc::tsbk::IOSP_UU_ANS* iosp = static_cast(tsbk.get()); - if (iosp->getResponse() > 0U) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u (%s), dstId = %u (%s)", - tsbk->toString(true).c_str(), iosp->getResponse(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - } - break; - case P25DEF::TSBKO::IOSP_STS_UPDT: - { - lc::tsbk::IOSP_STS_UPDT* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u (%s)", - tsbk->toString(true).c_str(), iosp->getStatus(), srcId, resolveRID(srcId).c_str()); - } - break; - case P25DEF::TSBKO::IOSP_MSG_UPDT: - { - lc::tsbk::IOSP_MSG_UPDT* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u (%s), dstId = %u (%s)", - tsbk->toString(true).c_str(), iosp->getMessage(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - break; - case P25DEF::TSBKO::IOSP_RAD_MON: - { - lc::tsbk::IOSP_RAD_MON* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - break; - case P25DEF::TSBKO::IOSP_CALL_ALRT: - { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - break; - case P25DEF::TSBKO::IOSP_ACK_RSP: - { - lc::tsbk::IOSP_ACK_RSP* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u (%s), dstId = %u (%s)", - tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - break; - case P25DEF::TSBKO::IOSP_EXT_FNCT: - { - lc::tsbk::IOSP_EXT_FNCT* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u", - tsbk->toString(true).c_str(), iosp->getService(), srcId, dstId); - } - break; - case P25DEF::TSBKO::ISP_EMERG_ALRM_REQ: - { - // non-emergency mode is a TSBKO::OSP_DENY_RSP - if (!tsbk->getEmergency()) { - lc::tsbk::OSP_DENY_RSP* osp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u (%s), dstId = %u (%s)", - osp->toString().c_str(), osp->getAIV(), osp->getResponse(), - osp->getSrcId(), resolveRID(osp->getSrcId()).c_str(), osp->getDstId(), resolveTGID(osp->getDstId()).c_str()); - } else { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - } - break; - case P25DEF::TSBKO::IOSP_GRP_AFF: - { - lc::tsbk::OSP_GRP_AFF* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, anncId = %u (%s), srcId = %u (%s), dstId = %u (%s), response = $%02X", tsbk->toString().c_str(), - iosp->getAnnounceGroup(), resolveTGID(iosp->getAnnounceGroup()).c_str(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), - iosp->getResponse()); - } - break; - case P25DEF::TSBKO::OSP_U_DEREG_ACK: - { - lc::tsbk::OSP_U_DEREG_ACK* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s)", - tsbk->toString(true).c_str(), srcId, resolveRID(srcId).c_str()); - } - break; - case P25DEF::TSBKO::OSP_LOC_REG_RSP: - { - lc::tsbk::OSP_LOC_REG_RSP* osp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", osp->toString().c_str(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - break; - case P25DEF::TSBKO::OSP_ADJ_STS_BCAST: - { - lc::tsbk::OSP_ADJ_STS_BCAST* osp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", tsbk->toString().c_str(), - osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); - } - break; - default: - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - break; - } - } - break; - } - - if (g_debug) - LogMessage(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length); - } - - UInt8Array nxdnBuffer = network->readNXDN(netReadRet, length); - if (netReadRet) { - using namespace nxdn; - - uint8_t messageType = nxdnBuffer[4U]; - - uint32_t srcId = __GET_UINT16(nxdnBuffer, 5U); - uint32_t dstId = __GET_UINT16(nxdnBuffer, 8U); - - lc::RTCH lc; - - lc.setMessageType(messageType); - lc.setSrcId((uint16_t)srcId & 0xFFFFU); - lc.setDstId((uint16_t)dstId & 0xFFFFU); - - bool group = (nxdnBuffer[15U] & 0x40U) == 0x40U ? false : true; - lc.setGroup(group); - - // specifically only check the following logic for end of call, voice or data frames - if ((messageType == NXDDEF::MessageType::RTCH_TX_REL || messageType == NXDDEF::MessageType::RTCH_TX_REL_EX) || - (messageType == NXDDEF::MessageType::RTCH_VCALL || messageType == NXDDEF::MessageType::RTCH_DCALL_HDR || - messageType == NXDDEF::MessageType::RTCH_DCALL_DATA)) { - // is this the end of the call stream? - if (messageType == NXDDEF::MessageType::RTCH_TX_REL || messageType == NXDDEF::MessageType::RTCH_TX_REL_EX) { - if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid TX_REL, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - - RxStatus status = m_nxdnStatus[dstId]; - uint64_t duration = hrc::diff(pktTime, status.callStartTime); - - if (std::find_if(m_nxdnStatus.begin(), m_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_nxdnStatus.end()) { - m_nxdnStatus.erase(dstId); - - LogMessage(LOG_NET, "NXDN, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); - } - } - - // is this a new call stream? - if ((messageType != NXDDEF::MessageType::RTCH_TX_REL && messageType != NXDDEF::MessageType::RTCH_TX_REL_EX)) { - if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid call, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - - auto it = std::find_if(m_nxdnStatus.begin(), m_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); - if (it == m_nxdnStatus.end()) { - // this is a new call stream - RxStatus status = RxStatus(); - status.callStartTime = pktTime; - status.srcId = srcId; - status.dstId = dstId; - m_nxdnStatus[dstId] = status; - - LogMessage(LOG_NET, "NXDN, Call Start, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); - } - } - } - - if (g_debug) - LogMessage(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length); - } - } - - if (ms < 2U) - Thread::sleep(1U); + /* stub */ } - -private: - /** - * @brief Represents the receive status of a call. - */ - class RxStatus { - public: - system_clock::hrc::hrc_t callStartTime; - system_clock::hrc::hrc_t lastPacket; - uint32_t srcId; - uint32_t dstId; - uint8_t slotNo; - uint32_t streamId; - }; - typedef std::pair StatusMapPair; - std::unordered_map m_dmrStatus; - std::unordered_map m_p25Status; - std::unordered_map m_nxdnStatus; - - StopWatch m_stopWatch; }; #endif // __SYS_VIEW_APPLICATION_H__ \ No newline at end of file diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index 3b491fdf..8df3df84 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.cpp @@ -8,12 +8,30 @@ * */ #include "Defines.h" +#include "common/Clock.h" +#include "common/dmr/DMRDefines.h" +#include "common/dmr/lc/csbk/CSBKFactory.h" +#include "common/dmr/lc/LC.h" +#include "common/dmr/lc/FullLC.h" +#include "common/dmr/SlotType.h" +#include "common/dmr/Sync.h" +#include "common/p25/P25Defines.h" +#include "common/p25/lc/tdulc/TDULCFactory.h" +#include "common/p25/lc/tsbk/TSBKFactory.h" +#include "common/nxdn/NXDNDefines.h" +#include "common/nxdn/lc/RTCH.h" #include "common/yaml/Yaml.h" #include "common/Log.h" +#include "common/StopWatch.h" +#include "common/Thread.h" +#include "p25/tsbk/OSP_GRP_AFF.h" +#include "network/PeerNetwork.h" #include "SysViewMain.h" #include "SysViewApplication.h" #include "SysViewMainWnd.h" +using namespace system_clock; +using namespace network; using namespace lookups; #include @@ -36,6 +54,7 @@ std::string g_iniFile = std::string(DEFAULT_CONF_FILE); yaml::Node g_conf; bool g_debug = false; +bool g_killed = false; bool g_hideLoggingWnd = false; lookups::RadioIdLookup* g_ridLookup = nullptr; @@ -44,6 +63,25 @@ lookups::IdenTableLookup* g_idenTable = nullptr; SysViewApplication* g_app = nullptr; +network::PeerNetwork* g_network = nullptr; + +/** + * @brief Represents the receive status of a call. + */ +class RxStatus { +public: + ::system_clock::hrc::hrc_t callStartTime; + ::system_clock::hrc::hrc_t lastPacket; + uint32_t srcId; + uint32_t dstId; + uint8_t slotNo; + uint32_t streamId; +}; +typedef std::pair StatusMapPair; +std::unordered_map g_dmrStatus; +std::unordered_map g_p25Status; +std::unordered_map g_nxdnStatus; + // --------------------------------------------------------------------------- // Global Functions // --------------------------------------------------------------------------- @@ -122,18 +160,83 @@ std::string resolveTGID(uint32_t id) */ bool createPeerNetwork() { - if (g_app != nullptr) - return g_app->createPeerNetwork(); - return false; -} + yaml::Node fne = g_conf["fne"]; + + std::string password = fne["password"].as(); + + std::string address = fne["masterAddress"].as(); + uint16_t port = (uint16_t)fne["masterPort"].as(); + uint32_t id = fne["peerId"].as(); + + bool encrypted = fne["encrypted"].as(false); + std::string key = fne["presharedKey"].as(); + uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + if (!key.empty()) { + if (key.size() == 32) { + // bryanb: shhhhhhh....dirty nasty hacks + key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs) + LogWarning(LOG_HOST, "Half-length network preshared encryption key detected, doubling key on itself."); + } -/** - * @brief Shuts down peer networking. - */ -void closePeerNetwork() -{ - if (g_app != nullptr) - g_app->closePeerNetwork(); + if (key.size() == 64) { + if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { + const char* keyPtr = key.c_str(); + ::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = {keyPtr[0], keyPtr[1], 0}; + presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + } + else { + LogWarning(LOG_HOST, "Invalid characters in the network preshared encryption key. Encryption disabled."); + encrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + encrypted = false; + } + } + + std::string identity = fne["identity"].as(); + + LogInfo("Network Parameters"); + LogInfo(" Peer ID: %u", id); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + + LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); + + if (id > 999999999U) { + ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); + return false; + } + + // initialize networking + g_network = new PeerNetwork(address, port, 0U, id, password, true, g_debug, true, true, true, true, true, true, true, true, false); + g_network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); + g_network->setLookups(g_ridLookup, g_tidLookup); + + ::LogSetNetwork(g_network); + + if (encrypted) { + g_network->setPresharedKey(presharedKey); + } + + g_network->enable(true); + bool ret = g_network->open(); + if (!ret) { + delete g_network; + g_network = nullptr; + LogError(LOG_HOST, "failed to initialize traffic networking for PEER %u", id); + return false; + } + + ::LogSetNetwork(g_network); + + return true; } /** @@ -141,8 +244,471 @@ void closePeerNetwork() */ network::PeerNetwork* getNetwork() { - if (g_app != nullptr) - return g_app->network; + return g_network; +} + +/* Entry point to network pump update thread. */ + +void* threadNetworkPump(void* arg) +{ + thread_t* th = (thread_t*)arg; + if (th != nullptr) { +#if defined(_WIN32) + ::CloseHandle(th->thread); +#else + ::pthread_detach(th->thread); +#endif // defined(_WIN32) + + std::string threadName("sysview:net-pump"); + if (g_killed) { + delete th; + return nullptr; + } + + LogDebug(LOG_HOST, "[ OK ] %s", threadName.c_str()); +#ifdef _GNU_SOURCE + ::pthread_setname_np(th->thread, threadName.c_str()); +#endif // _GNU_SOURCE + + Timer networkPeerStatusNotify(1000U, 5U); + networkPeerStatusNotify.start(); + + StopWatch stopWatch; + stopWatch.start(); + + while (!g_killed) { + uint32_t ms = stopWatch.elapsed(); + stopWatch.start(); + + // ------------------------------------------------------ + // -- Network Clocking -- + // ------------------------------------------------------ + + if (g_network != nullptr) { + g_network->clock(ms); + + hrc::hrc_t pktTime = hrc::now(); + + uint32_t length = 0U; + bool netReadRet = false; + UInt8Array dmrBuffer = g_network->readDMR(netReadRet, length); + if (netReadRet) { + using namespace dmr; + + uint8_t seqNo = dmrBuffer[4U]; + + uint32_t srcId = __GET_UINT16(dmrBuffer, 5U); + uint32_t dstId = __GET_UINT16(dmrBuffer, 8U); + + DMRDEF::FLCO::E flco = (dmrBuffer[15U] & 0x40U) == 0x40U ? DMRDEF::FLCO::PRIVATE : DMRDEF::FLCO::GROUP; + + uint32_t slotNo = (dmrBuffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + + DMRDEF::DataType::E dataType = (DMRDEF::DataType::E)(dmrBuffer[15U] & 0x0FU); + + data::NetData dmrData; + dmrData.setSeqNo(seqNo); + dmrData.setSlotNo(slotNo); + dmrData.setSrcId(srcId); + dmrData.setDstId(dstId); + dmrData.setFLCO(flco); + + bool dataSync = (dmrBuffer[15U] & 0x20U) == 0x20U; + bool voiceSync = (dmrBuffer[15U] & 0x10U) == 0x10U; + + if (dataSync) { + dmrData.setData(dmrBuffer.get() + 20U); + dmrData.setDataType(dataType); + dmrData.setN(0U); + } + else if (voiceSync) { + dmrData.setData(dmrBuffer.get() + 20U); + dmrData.setDataType(DMRDEF::DataType::VOICE_SYNC); + dmrData.setN(0U); + } + else { + uint8_t n = dmrBuffer[15U] & 0x0FU; + dmrData.setData(dmrBuffer.get() + 20U); + dmrData.setDataType(DMRDEF::DataType::VOICE); + dmrData.setN(n); + } + + // is this the end of the call stream? + if (dataSync && (dataType == DMRDEF::DataType::TERMINATOR_WITH_LC)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "DMR, invalid TERMINATOR, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + RxStatus status; + auto it = std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); + if (it == g_dmrStatus.end()) { + LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + else { + status = it->second; + } + + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != g_dmrStatus.end()) { + g_dmrStatus.erase(dstId); + + LogMessage(LOG_NET, "DMR, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); + } + } + + // is this a new call stream? + if (dataSync && (dataType == DMRDEF::DataType::VOICE_LC_HEADER)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "DMR, invalid call, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + auto it = std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); + if (it == g_dmrStatus.end()) { + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + status.slotNo = slotNo; + g_dmrStatus[dstId] = status; // this *could* be an issue if a dstId appears on both slots somehow... + + LogMessage(LOG_NET, "DMR, Call Start, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + + // are we receiving a CSBK? + if (dmrData.getDataType() == DMRDEF::DataType::CSBK) { + uint8_t data[DMRDEF::DMR_FRAME_LENGTH_BYTES + 2U]; + dmrData.getData(data + 2U); + + std::unique_ptr csbk = lc::csbk::CSBKFactory::createCSBK(data + 2U, DMRDEF::DataType::CSBK); + if (csbk != nullptr) { + switch (csbk->getCSBKO()) { + case DMRDEF::CSBKO::BROADCAST: + { + lc::csbk::CSBK_BROADCAST* osp = static_cast(csbk.get()); + if (osp->getAnncType() == DMRDEF::BroadcastAnncType::ANN_WD_TSCC) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u", dmrData.getSlotNo(), csbk->toString().c_str(), + osp->getSystemId(), osp->getLogicalCh1()); + } + } + break; + default: + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u (%s), dstId = %u (%s)", dmrData.getSlotNo(), csbk->toString().c_str(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + break; + } + } + } + + if (g_debug) + LogMessage(LOG_NET, "DMR, slotNo = %u, seqNo = %u, flco = $%02X, srcId = %u, dstId = %u, len = %u", slotNo, seqNo, flco, srcId, dstId, length); + } + + UInt8Array p25Buffer = g_network->readP25(netReadRet, length); + if (netReadRet) { + using namespace p25; + + uint8_t duid = p25Buffer[22U]; + uint8_t MFId = p25Buffer[15U]; + + // process raw P25 data bytes + UInt8Array data; + uint8_t frameLength = p25Buffer[23U]; + if (duid == P25DEF::DUID::PDU) { + frameLength = length; + data = std::unique_ptr(new uint8_t[length]); + ::memset(data.get(), 0x00U, length); + ::memcpy(data.get(), p25Buffer.get(), length); + } + else { + if (frameLength <= 24) { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + } + else { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), p25Buffer.get() + 24U, frameLength); + } + } + + uint8_t lco = p25Buffer[4U]; + + uint32_t srcId = __GET_UINT16(p25Buffer, 5U); + uint32_t dstId = __GET_UINT16(p25Buffer, 8U); + + uint32_t sysId = (p25Buffer[11U] << 8) | (p25Buffer[12U] << 0); + uint32_t netId = __GET_UINT16(p25Buffer, 16U); + + // log call status + if (duid != P25DEF::DUID::TSDU && duid != P25DEF::DUID::PDU) { + // is this the end of the call stream? + if ((duid == P25DEF::DUID::TDU) || (duid == P25DEF::DUID::TDULC)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "P25, invalid TDU, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + RxStatus status = g_p25Status[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_p25Status.end()) { + g_p25Status.erase(dstId); + + LogMessage(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); + } + } + + // is this a new call stream? + if ((duid != P25DEF::DUID::TDU) && (duid != P25DEF::DUID::TDULC)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "P25, invalid call, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + auto it = std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + if (it == g_p25Status.end()) { + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + g_p25Status[dstId] = status; + + LogMessage(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + } + + switch (duid) { + case P25DEF::DUID::TDU: + case P25DEF::DUID::TDULC: + if (duid == P25DEF::DUID::TDU) { + LogMessage(LOG_NET, P25_TDU_STR ", srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + else { + std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data.get()); + if (tdulc == nullptr) { + LogWarning(LOG_NET, P25_TDULC_STR ", undecodable TDULC"); + } + else { + LogMessage(LOG_NET, P25_TDULC_STR ", srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + break; + + case P25DEF::DUID::TSDU: + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + if (tsbk == nullptr) { + LogWarning(LOG_NET, P25_TSDU_STR ", undecodable TSBK"); + } + else { + switch (tsbk->getLCO()) { + case P25DEF::TSBKO::IOSP_GRP_VCH: + case P25DEF::TSBKO::IOSP_UU_VCH: + { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u (%s), dstId = %u (%s)", + tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + break; + case P25DEF::TSBKO::IOSP_UU_ANS: + { + lc::tsbk::IOSP_UU_ANS* iosp = static_cast(tsbk.get()); + if (iosp->getResponse() > 0U) { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u (%s), dstId = %u (%s)", + tsbk->toString(true).c_str(), iosp->getResponse(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + break; + case P25DEF::TSBKO::IOSP_STS_UPDT: + { + lc::tsbk::IOSP_STS_UPDT* iosp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u (%s)", + tsbk->toString(true).c_str(), iosp->getStatus(), srcId, resolveRID(srcId).c_str()); + } + break; + case P25DEF::TSBKO::IOSP_MSG_UPDT: + { + lc::tsbk::IOSP_MSG_UPDT* iosp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u (%s), dstId = %u (%s)", + tsbk->toString(true).c_str(), iosp->getMessage(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + break; + case P25DEF::TSBKO::IOSP_RAD_MON: + { + //lc::tsbk::IOSP_RAD_MON* iosp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + break; + case P25DEF::TSBKO::IOSP_CALL_ALRT: + { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + break; + case P25DEF::TSBKO::IOSP_ACK_RSP: + { + lc::tsbk::IOSP_ACK_RSP* iosp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u (%s), dstId = %u (%s)", + tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + break; + case P25DEF::TSBKO::IOSP_EXT_FNCT: + { + lc::tsbk::IOSP_EXT_FNCT* iosp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u", + tsbk->toString(true).c_str(), iosp->getService(), srcId, dstId); + } + break; + case P25DEF::TSBKO::ISP_EMERG_ALRM_REQ: + { + // non-emergency mode is a TSBKO::OSP_DENY_RSP + if (!tsbk->getEmergency()) { + lc::tsbk::OSP_DENY_RSP* osp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u (%s), dstId = %u (%s)", + osp->toString().c_str(), osp->getAIV(), osp->getResponse(), + osp->getSrcId(), resolveRID(osp->getSrcId()).c_str(), osp->getDstId(), resolveTGID(osp->getDstId()).c_str()); + } else { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + break; + case P25DEF::TSBKO::IOSP_GRP_AFF: + { + lc::tsbk::OSP_GRP_AFF* iosp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, anncId = %u (%s), srcId = %u (%s), dstId = %u (%s), response = $%02X", tsbk->toString().c_str(), + iosp->getAnnounceGroup(), resolveTGID(iosp->getAnnounceGroup()).c_str(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), + iosp->getResponse()); + } + break; + case P25DEF::TSBKO::OSP_U_DEREG_ACK: + { + //lc::tsbk::OSP_U_DEREG_ACK* iosp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s)", + tsbk->toString(true).c_str(), srcId, resolveRID(srcId).c_str()); + } + break; + case P25DEF::TSBKO::OSP_LOC_REG_RSP: + { + lc::tsbk::OSP_LOC_REG_RSP* osp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", osp->toString().c_str(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + break; + case P25DEF::TSBKO::OSP_ADJ_STS_BCAST: + { + lc::tsbk::OSP_ADJ_STS_BCAST* osp = static_cast(tsbk.get()); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", tsbk->toString().c_str(), + osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); + } + break; + default: + LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + break; + } + } + break; + } + + if (g_debug) + LogMessage(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length); + } + + UInt8Array nxdnBuffer = g_network->readNXDN(netReadRet, length); + if (netReadRet) { + using namespace nxdn; + + uint8_t messageType = nxdnBuffer[4U]; + + uint32_t srcId = __GET_UINT16(nxdnBuffer, 5U); + uint32_t dstId = __GET_UINT16(nxdnBuffer, 8U); + + lc::RTCH lc; + + lc.setMessageType(messageType); + lc.setSrcId((uint16_t)srcId & 0xFFFFU); + lc.setDstId((uint16_t)dstId & 0xFFFFU); + + bool group = (nxdnBuffer[15U] & 0x40U) == 0x40U ? false : true; + lc.setGroup(group); + + // specifically only check the following logic for end of call, voice or data frames + if ((messageType == NXDDEF::MessageType::RTCH_TX_REL || messageType == NXDDEF::MessageType::RTCH_TX_REL_EX) || + (messageType == NXDDEF::MessageType::RTCH_VCALL || messageType == NXDDEF::MessageType::RTCH_DCALL_HDR || + messageType == NXDDEF::MessageType::RTCH_DCALL_DATA)) { + // is this the end of the call stream? + if (messageType == NXDDEF::MessageType::RTCH_TX_REL || messageType == NXDDEF::MessageType::RTCH_TX_REL_EX) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "NXDN, invalid TX_REL, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + RxStatus status = g_nxdnStatus[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(g_nxdnStatus.begin(), g_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_nxdnStatus.end()) { + g_nxdnStatus.erase(dstId); + + LogMessage(LOG_NET, "NXDN, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); + } + } + + // is this a new call stream? + if ((messageType != NXDDEF::MessageType::RTCH_TX_REL && messageType != NXDDEF::MessageType::RTCH_TX_REL_EX)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "NXDN, invalid call, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + auto it = std::find_if(g_nxdnStatus.begin(), g_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + if (it == g_nxdnStatus.end()) { + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + g_nxdnStatus[dstId] = status; + + LogMessage(LOG_NET, "NXDN, Call Start, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + } + + if (g_debug) + LogMessage(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length); + } + } + + if (ms < 2U) + Thread::sleep(1U); + } + + LogDebug(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; } /* Helper to pring usage the command line arguments. (And optionally an error.) */ @@ -283,6 +849,10 @@ int main(int argc, char** argv) ::fatal("cannot read the configuration file - %s (%s)", g_iniFile.c_str(), e.message()); } + /** Network Thread */ + if (!Thread::runAsThread(nullptr, threadNetworkPump)) + return EXIT_FAILURE; + // setup the finalcut tui SysViewApplication app{argc, argv}; g_app = &app; @@ -341,6 +911,15 @@ int main(int argc, char** argv) app.redraw(); int _errno = app.exec(); + + g_logDisplayLevel = 1U; + + g_killed = true; + if (g_network != nullptr) { + g_network->close(); + delete g_network; + } + ::LogFinalise(); return _errno; } diff --git a/src/sysview/SysViewMain.h b/src/sysview/SysViewMain.h index 7ba580d2..510f2d4d 100644 --- a/src/sysview/SysViewMain.h +++ b/src/sysview/SysViewMain.h @@ -90,11 +90,6 @@ extern HOST_SW_API std::string resolveTGID(uint32_t id); */ extern HOST_SW_API bool createPeerNetwork(); -/** - * @brief Shuts down peer networking. - */ -extern HOST_SW_API void closePeerNetwork(); - /** * @brief * @returns PeerNetwork* diff --git a/src/sysview/SysViewMainWnd.h b/src/sysview/SysViewMainWnd.h index ccdb8573..2b3ac015 100644 --- a/src/sysview/SysViewMainWnd.h +++ b/src/sysview/SysViewMainWnd.h @@ -29,6 +29,8 @@ using namespace finalcut; #include "LogDisplayWnd.h" +#include "NodeStatusWnd.h" + #include "PageSubscriberWnd.h" #include "InhibitSubscriberWnd.h" #include "UninhibitSubscriberWnd.h" @@ -44,6 +46,8 @@ using namespace finalcut; #define MINIMUM_SUPPORTED_SIZE_WIDTH 83 #define MINIMUM_SUPPORTED_SIZE_HEIGHT 30 +#define NETWORK_NOT_READY_STR "Peer network is not ready, please wait and try again." + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -63,54 +67,111 @@ public: __InternalOutputStream(m_logWnd); // file menu + m_statusMenu.addCallback("clicked", this, [&]() { + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + NodeStatusWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } + }); + m_keyF11.addCallback("activate", this, [&]() { + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + NodeStatusWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } + }); + m_fileMenuSeparator1.setSeparator(); m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this); m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this); // command menu m_pageSU.addCallback("clicked", this, [&]() { - PageSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + PageSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_keyF5.addCallback("activate", this, [&]() { - PageSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + PageSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_radioCheckSU.addCallback("clicked", this, [&]() { - RadioCheckSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + RadioCheckSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_cmdMenuSeparator1.setSeparator(); m_inhibitSU.addCallback("clicked", this, [&]() { - InhibitSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + InhibitSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_keyF7.addCallback("activate", this, [&]() { - InhibitSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + InhibitSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_uninhibitSU.addCallback("clicked", this, [&]() { - UninhibitSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + UninhibitSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_keyF8.addCallback("activate", this, [&]() { - UninhibitSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + UninhibitSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_cmdMenuSeparator2.setSeparator(); m_dynRegrp.addCallback("clicked", this, [&]() { - DynRegroupSubscriberWnd wnd{this}; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + DynRegroupSubscriberWnd wnd{this}; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_dynRegrpLck.addCallback("clicked", this, [&]() { - DynRegroupSubscriberWnd wnd{this}; - wnd.lock = true; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + DynRegroupSubscriberWnd wnd{this}; + wnd.lock = true; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); m_dynRegrpUnlock.addCallback("clicked", this, [&]() { - DynRegroupSubscriberWnd wnd{this}; - wnd.unlock = true; - wnd.show(); + if (getNetwork()->getStatus() == network::NET_STAT_RUNNING) { + DynRegroupSubscriberWnd wnd{this}; + wnd.unlock = true; + wnd.show(); + } else { + FMessageBox::error(this, NETWORK_NOT_READY_STR); + } }); // help menu @@ -139,6 +200,8 @@ private: FMenuBar m_menuBar{this}; FMenu m_fileMenu{"&File", &m_menuBar}; + FMenuItem m_statusMenu{"&Peer Status", &m_fileMenu}; + FMenuItem m_fileMenuSeparator1{&m_fileMenu}; FMenuItem m_quitItem{"&Quit", &m_fileMenu}; FMenu m_cmdMenu{"&Commands", &m_menuBar}; @@ -160,6 +223,7 @@ private: FStatusKey m_keyF5{FKey::F5, "Page Subscriber", &m_statusBar}; FStatusKey m_keyF7{FKey::F7, "Inhibit Subscriber", &m_statusBar}; FStatusKey m_keyF8{FKey::F8, "Uninhibit Subscriber", &m_statusBar}; + FStatusKey m_keyF11{FKey::F11, "Peer Status", &m_statusBar}; /* ** Event Handlers diff --git a/src/sysview/TransmitWndBase.h b/src/sysview/TransmitWndBase.h index 666aaea0..62862563 100644 --- a/src/sysview/TransmitWndBase.h +++ b/src/sysview/TransmitWndBase.h @@ -167,7 +167,10 @@ protected: void onKeyPress(finalcut::FKeyEvent* e) override { const auto key = e->key(); - if (key == FKey::F12) { + if (key == FKey::Escape) { + this->close(); + } + else if (key == FKey::F12) { setTransmit(); } } diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index c3dab4ec..5933a5c2 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -26,17 +26,81 @@ using namespace network; PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), + peerStatus() { assert(!address.empty()); assert(port > 0U); assert(!password.empty()); + + // ignore the source peer ID for packets destined to SysView + m_promiscuousPeer = true; } // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- +/* User overrideable handler that allows user code to process network packets not handled by this class. */ + +void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +{ + switch (opcode.first) { + case NET_FUNC::TRANSFER: + { + switch (opcode.second) { + case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY: + { + UInt8Array __rawPayload = std::make_unique(length - 11U); + uint8_t* rawPayload = __rawPayload.get(); + ::memset(rawPayload, 0x00U, length - 11U); + ::memcpy(rawPayload, data + 11U, length - 11U); + std::string payload(rawPayload, rawPayload + (length - 11U)); + + bool currState = g_disableTimeDisplay; + g_disableTimeDisplay = true; + ::Log(9999U, nullptr, "%.9u %s", peerId, payload.c_str()); + g_disableTimeDisplay = currState; + } + break; + + case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS: + { + UInt8Array __rawPayload = std::make_unique(length - 11U); + uint8_t* rawPayload = __rawPayload.get(); + ::memset(rawPayload, 0x00U, length - 11U); + ::memcpy(rawPayload, data + 11U, length - 11U); + std::string payload(rawPayload, rawPayload + (length - 11U)); + + // parse JSON body + json::value v; + std::string err = json::parse(v, payload); + if (!err.empty()) { + break; + } + + // ensure parsed JSON is an object + if (!v.is()) { + break; + } + + json::object obj = v.get(); + peerStatus[peerId] = obj; + } + break; + + default: + break; + } + } + break; + + default: + Utils::dump("unknown opcode from the master", data, length); + break; + } +} + /* Writes configuration to the network. */ bool PeerNetwork::writeConfig() @@ -81,6 +145,8 @@ bool PeerNetwork::writeConfig() bool convPeer = true; config["conventionalPeer"].set(convPeer); // Conventional Peer Marker + bool sysView = true; + config["sysView"].set(sysView); // SysView Peer Marker config["software"].set(std::string(software)); // Software ID json::value v = json::value(config); diff --git a/src/sysview/network/PeerNetwork.h b/src/sysview/network/PeerNetwork.h index 68490da0..8620b19a 100644 --- a/src/sysview/network/PeerNetwork.h +++ b/src/sysview/network/PeerNetwork.h @@ -56,7 +56,23 @@ namespace network PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + /** + * @brief Map of peer status. + */ + std::unordered_map peerStatus; + protected: + /** + * @brief User overrideable handler that allows user code to process network packets not handled by this class. + * @param peerId Peer ID. + * @param opcode FNE network opcode pair. + * @param[in] data Buffer containing message to send to peer. + * @param length Length of buffer. + * @param streamId Stream ID. + */ + void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, + uint32_t streamId = 0U) override; + /** * @brief Writes configuration to the network. * @returns bool True, if configuration was sent, otherwise false.