refactor SysView and more tightly integrate with the DVM stack; add CC voice channel reporting in the peer status message; add support on the FNE to report activity log and peer status to SysView instances;

pull/72/head
Bryan Biedenkapp 1 year ago
parent aab5e208e4
commit 8f6319d15e

@ -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__ ")"

@ -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)

@ -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.

@ -227,6 +227,28 @@ void* DiagNetwork::threadedNetworkRx(void* arg)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(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::nanoseconds>(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);

@ -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>()) {
bool external = peerConfig["externalPeer"].get<bool>();
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<bool>()) {
if (network->m_allowConvSiteAffOverride) {
bool convPeer = peerConfig["conventionalPeer"].get<bool>();
@ -793,6 +795,14 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
}
// is the peer reporting it is a SysView peer?
if (peerConfig["sysView"].is<bool>()) {
bool sysView = peerConfig["sysView"].get<bool>();
connection->isSysView(sysView);
if (sysView)
LogInfoEx(LOG_NET, "PEER %u reports SysView peer", peerId);
}
if (peerConfig["software"].is<std::string>()) {
std::string software = peerConfig["software"].get<std::string>();
LogInfoEx(LOG_NET, "PEER %u reports software %s", peerId, software.c_str());

@ -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.

@ -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) {

@ -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) {

@ -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) {

@ -1319,6 +1319,69 @@ json::object Host::getStatus()
response["p25NAC"].set<uint32_t>(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<uint32_t>(chNo);
uint8_t chId = entry.second.chId();
chData["channelId"].set<uint8_t>(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<bool>(_true);
} else {
chData["tx"].set<bool>(_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<bool>(_true);
} else {
chData["tx"].set<bool>(_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<bool>(_true);
} else {
chData["tx"].set<bool>(_false);
}
dstId = m_nxdn->affiliations().getGrantedDstByCh(chNo);
if (dstId > 0U)
srcId = m_nxdn->affiliations().getGrantedSrcId(dstId);
}
chData["lastDstId"].set<uint32_t>(dstId);
chData["lastSrcId"].set<uint32_t>(srcId);
vcChannels.push_back(json::value(chData));
}
}
response["vcChannels"].set<json::array>(vcChannels);
yaml::Node modemConfig = m_conf["system"]["modem"];
{
json::object modemInfo = json::object();

@ -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()

@ -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.

@ -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 <final/final.h>
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<uint8_t>();
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>()) {
uint32_t lastDstId = peerStatus["lastDstId"].get<uint32_t>();
m_lastDst.setText(__INT_STR(lastDstId));
}
if (peerStatus["lastSrcId"].is<uint32_t>()) {
uint32_t lastSrcId = peerStatus["lastSrcId"].get<uint32_t>();
m_lastSrc.setText(__INT_STR(lastSrcId));
}
// get remote node state
if (peerStatus["dmrTSCCEnable"].is<bool>() && peerStatus["p25CtrlEnable"].is<bool>() &&
peerStatus["nxdnCtrlEnable"].is<bool>()) {
bool dmrTSCCEnable = peerStatus["dmrTSCCEnable"].get<bool>();
bool dmrCC = peerStatus["dmrCC"].get<bool>();
bool p25CtrlEnable = peerStatus["p25CtrlEnable"].get<bool>();
bool p25CC = peerStatus["p25CC"].get<bool>();
bool nxdnCtrlEnable = peerStatus["nxdnCtrlEnable"].get<bool>();
bool nxdnCC = peerStatus["nxdnCC"].get<bool>();
// 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<bool>()) {
m_tx = peerStatus["tx"].get<bool>();
}
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<uint8_t>();
uint32_t channelNo = peerObj["channelNo"].get<uint32_t>();
bool dmrTSCCEnable = peerObj["dmrTSCCEnable"].get<bool>();
bool dmrCC = peerObj["dmrCC"].get<bool>();
bool p25CtrlEnable = peerObj["p25CtrlEnable"].get<bool>();
bool p25CC = peerObj["p25CC"].get<bool>();
bool nxdnCtrlEnable = peerObj["nxdnCtrlEnable"].get<bool>();
bool nxdnCC = peerObj["nxdnCC"].get<bool>();
if ((dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) && peerObj["vcChannels"].is<json::array>()) {
json::array vcChannels = peerObj["vcChannels"].get<json::array>();
struct
{
bool operator()(json::value a, json::value b) const
{
json::object aObj = a.get<json::object>();
json::object bObj = b.get<json::object>();
uint32_t chNoA = aObj["channelNo"].get<uint32_t>();
uint32_t chNoB = bObj["channelNo"].get<uint32_t>();
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<json::object>();
uint8_t vcChannelId = vcObj["channelId"].get<uint8_t>();
uint32_t vcChannelNo = vcObj["channelNo"].get<uint32_t>();
// 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<uint8_t>(state);
bool _false = false;
vcObj["dmrTSCCEnable"].set<bool>(_false);
vcObj["dmrCC"].set<bool>(_false);
vcObj["p25CtrlEnable"].set<bool>(_false);
vcObj["p25CC"].set<bool>(_false);
vcObj["nxdnCtrlEnable"].set<bool>(_false);
vcObj["nxdnCC"].set<bool>(_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<uint8_t>();
uint32_t channelNo = peerObj["channelNo"].get<uint32_t>();
bool dmrTSCCEnable = peerObj["dmrTSCCEnable"].get<bool>();
bool dmrCC = peerObj["dmrCC"].get<bool>();
bool p25CtrlEnable = peerObj["p25CtrlEnable"].get<bool>();
bool p25CC = peerObj["p25CC"].get<bool>();
bool nxdnCtrlEnable = peerObj["nxdnCtrlEnable"].get<bool>();
bool nxdnCC = peerObj["nxdnCC"].get<bool>();
if ((dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) && peerObj["vcChannels"].is<json::array>()) {
json::array vcChannels = peerObj["vcChannels"].get<json::array>();
struct
{
bool operator()(json::value a, json::value b) const
{
json::object aObj = a.get<json::object>();
json::object bObj = b.get<json::object>();
uint32_t chNoA = aObj["channelNo"].get<uint32_t>();
uint32_t chNoB = bObj["channelNo"].get<uint32_t>();
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<json::object>();
uint8_t vcChannelId = vcObj["channelId"].get<uint8_t>();
uint32_t vcChannelNo = vcObj["channelNo"].get<uint32_t>();
// 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<uint8_t>(state);
bool _false = false;
vcObj["dmrTSCCEnable"].set<bool>(_false);
vcObj["dmrCC"].set<bool>(_false);
vcObj["p25CtrlEnable"].set<bool>(_false);
vcObj["p25CC"].set<bool>(_false);
vcObj["nxdnCtrlEnable"].set<bool>(_false);
vcObj["nxdnCC"].set<bool>(_false);
updateNode(subWdgt, peerId, vcObj, i);
}
}
}
}
}
redraw();
}
private:
std::vector<NodeStatusWidget*> 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<uint8_t>();
uint32_t channelNo = peerObj["channelNo"].get<uint32_t>();
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<uint8_t>();
uint32_t channelNo = peerObj["channelNo"].get<uint32_t>();
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__

@ -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 <final/final.h>
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>();
std::string address = fne["masterAddress"].as<std::string>();
uint16_t port = (uint16_t)fne["masterPort"].as<uint32_t>();
uint32_t id = fne["peerId"].as<uint32_t>();
bool encrypted = fne["encrypted"].as<bool>(false);
std::string key = fne["presharedKey"].as<std::string>();
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<std::string>();
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<lc::CSBK> 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<lc::csbk::CSBK_BROADCAST*>(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<uint8_t[]>(new uint8_t[length]);
::memset(data.get(), 0x00U, length);
::memcpy(data.get(), p25Buffer.get(), length);
}
else {
if (frameLength <= 24) {
data = std::unique_ptr<uint8_t[]>(new uint8_t[frameLength]);
::memset(data.get(), 0x00U, frameLength);
}
else {
data = std::unique_ptr<uint8_t[]>(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<lc::TDULC> 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<lc::TSBK> 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<lc::tsbk::IOSP_UU_ANS*>(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<lc::tsbk::IOSP_STS_UPDT*>(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<lc::tsbk::IOSP_MSG_UPDT*>(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<lc::tsbk::IOSP_RAD_MON*>(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<lc::tsbk::IOSP_ACK_RSP*>(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<lc::tsbk::IOSP_EXT_FNCT*>(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<lc::tsbk::OSP_DENY_RSP*>(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<lc::tsbk::OSP_GRP_AFF*>(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<lc::tsbk::OSP_U_DEREG_ACK*>(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<lc::tsbk::OSP_LOC_REG_RSP*>(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<lc::tsbk::OSP_ADJ_STS_BCAST*>(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());
}
}
/* stub */
}
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);
}
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<const uint32_t, RxStatus> StatusMapPair;
std::unordered_map<uint32_t, RxStatus> m_dmrStatus;
std::unordered_map<uint32_t, RxStatus> m_p25Status;
std::unordered_map<uint32_t, RxStatus> m_nxdnStatus;
StopWatch m_stopWatch;
};
#endif // __SYS_VIEW_APPLICATION_H__

@ -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 <cstdio>
@ -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<const uint32_t, RxStatus> StatusMapPair;
std::unordered_map<uint32_t, RxStatus> g_dmrStatus;
std::unordered_map<uint32_t, RxStatus> g_p25Status;
std::unordered_map<uint32_t, RxStatus> g_nxdnStatus;
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
@ -122,18 +160,83 @@ std::string resolveTGID(uint32_t id)
*/
bool createPeerNetwork()
{
if (g_app != nullptr)
return g_app->createPeerNetwork();
yaml::Node fne = g_conf["fne"];
std::string password = fne["password"].as<std::string>();
std::string address = fne["masterAddress"].as<std::string>();
uint16_t port = (uint16_t)fne["masterPort"].as<uint32_t>();
uint32_t id = fne["peerId"].as<uint32_t>();
bool encrypted = fne["encrypted"].as<bool>(false);
std::string key = fne["presharedKey"].as<std::string>();
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<std::string>();
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;
}
/**
* @brief Shuts down peer networking.
*/
void closePeerNetwork()
{
if (g_app != nullptr)
g_app->closePeerNetwork();
// 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<lc::CSBK> 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<lc::csbk::CSBK_BROADCAST*>(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<uint8_t[]>(new uint8_t[length]);
::memset(data.get(), 0x00U, length);
::memcpy(data.get(), p25Buffer.get(), length);
}
else {
if (frameLength <= 24) {
data = std::unique_ptr<uint8_t[]>(new uint8_t[frameLength]);
::memset(data.get(), 0x00U, frameLength);
}
else {
data = std::unique_ptr<uint8_t[]>(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<lc::TDULC> 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<lc::TSBK> 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<lc::tsbk::IOSP_UU_ANS*>(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<lc::tsbk::IOSP_STS_UPDT*>(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<lc::tsbk::IOSP_MSG_UPDT*>(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<lc::tsbk::IOSP_RAD_MON*>(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<lc::tsbk::IOSP_ACK_RSP*>(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<lc::tsbk::IOSP_EXT_FNCT*>(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<lc::tsbk::OSP_DENY_RSP*>(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<lc::tsbk::OSP_GRP_AFF*>(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<lc::tsbk::OSP_U_DEREG_ACK*>(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<lc::tsbk::OSP_LOC_REG_RSP*>(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<lc::tsbk::OSP_ADJ_STS_BCAST*>(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;
}

@ -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*

@ -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, [&]() {
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, [&]() {
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, [&]() {
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, [&]() {
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, [&]() {
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, [&]() {
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, [&]() {
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, [&]() {
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, [&]() {
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, [&]() {
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

@ -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();
}
}

@ -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<uint8_t[]>(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<uint8_t[]>(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<json::object>()) {
break;
}
json::object obj = v.get<json::object>();
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<bool>(convPeer); // Conventional Peer Marker
bool sysView = true;
config["sysView"].set<bool>(sysView); // SysView Peer Marker
config["software"].set<std::string>(std::string(software)); // Software ID
json::value v = json::value(config);

@ -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<uint32_t, json::object> 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.

Loading…
Cancel
Save

Powered by TurnKey Linux.