You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dvmhost/src/sysview/SysViewApplication.h

761 lines
36 KiB

// 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) 2024 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file SysViewApplication.h
* @ingroup fneSysView
*/
#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 "network/PeerNetwork.h"
#include "SysViewMain.h"
#include "SysViewMainWnd.h"
using namespace system_clock;
using namespace network;
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements a color theme for a finalcut application.
* @ingroup fneSysView
*/
class HOST_SW_API dvmColorTheme final : public FWidgetColors
{
public:
/**
* @brief Initializes a new instance of the dvmColorTheme class.
*/
dvmColorTheme()
{
dvmColorTheme::setColorTheme();
}
/**
* @brief Finalizes a instance of the dvmColorTheme class.
*/
~dvmColorTheme() noexcept override = default;
/**
* @brief Get the Class Name object
* @return FString
*/
auto getClassName() const -> FString override { return "dvmColorTheme"; }
/**
* @brief Set the Color Theme object
*/
void setColorTheme() override
{
term_fg = FColor::Cyan;
term_bg = FColor::Blue;
list_fg = FColor::Black;
list_bg = FColor::LightGray;
selected_list_fg = FColor::Red;
selected_list_bg = FColor::LightGray;
dialog_fg = FColor::Black;
dialog_resize_fg = FColor::LightBlue;
dialog_emphasis_fg = FColor::Blue;
dialog_bg = FColor::LightGray;
error_box_fg = FColor::LightRed;
error_box_emphasis_fg = FColor::Yellow;
error_box_bg = FColor::Black;
tooltip_fg = FColor::White;
tooltip_bg = FColor::Black;
shadow_fg = FColor::Black;
shadow_bg = FColor::LightGray; // only for transparent shadow
current_element_focus_fg = FColor::White;
current_element_focus_bg = FColor::Cyan;
current_element_fg = FColor::LightBlue;
current_element_bg = FColor::Cyan;
current_inc_search_element_fg = FColor::LightRed;
selected_current_element_focus_fg = FColor::LightRed;
selected_current_element_focus_bg = FColor::Cyan;
selected_current_element_fg = FColor::Red;
selected_current_element_bg = FColor::Cyan;
label_fg = FColor::Black;
label_bg = FColor::LightGray;
label_inactive_fg = FColor::DarkGray;
label_inactive_bg = FColor::LightGray;
label_hotkey_fg = FColor::Red;
label_hotkey_bg = FColor::LightGray;
label_emphasis_fg = FColor::Blue;
label_ellipsis_fg = FColor::DarkGray;
inputfield_active_focus_fg = FColor::Yellow;
inputfield_active_focus_bg = FColor::Blue;
inputfield_active_fg = FColor::LightGray;
inputfield_active_bg = FColor::Blue;
inputfield_inactive_fg = FColor::Black;
inputfield_inactive_bg = FColor::DarkGray;
toggle_button_active_focus_fg = FColor::Yellow;
toggle_button_active_focus_bg = FColor::Blue;
toggle_button_active_fg = FColor::LightGray;
toggle_button_active_bg = FColor::Blue;
toggle_button_inactive_fg = FColor::Black;
toggle_button_inactive_bg = FColor::DarkGray;
button_active_focus_fg = FColor::Yellow;
button_active_focus_bg = FColor::Blue;
button_active_fg = FColor::White;
button_active_bg = FColor::Blue;
button_inactive_fg = FColor::Black;
button_inactive_bg = FColor::DarkGray;
button_hotkey_fg = FColor::Yellow;
titlebar_active_fg = FColor::Blue;
titlebar_active_bg = FColor::White;
titlebar_inactive_fg = FColor::Blue;
titlebar_inactive_bg = FColor::LightGray;
titlebar_button_fg = FColor::Yellow;
titlebar_button_bg = FColor::LightBlue;
titlebar_button_focus_fg = FColor::LightGray;
titlebar_button_focus_bg = FColor::Black;
menu_active_focus_fg = FColor::Black;
menu_active_focus_bg = FColor::White;
menu_active_fg = FColor::Black;
menu_active_bg = FColor::LightGray;
menu_inactive_fg = FColor::DarkGray;
menu_inactive_bg = FColor::LightGray;
menu_hotkey_fg = FColor::Blue;
menu_hotkey_bg = FColor::LightGray;
statusbar_fg = FColor::Black;
statusbar_bg = FColor::LightGray;
statusbar_hotkey_fg = FColor::Blue;
statusbar_hotkey_bg = FColor::LightGray;
statusbar_separator_fg = FColor::Black;
statusbar_active_fg = FColor::Black;
statusbar_active_bg = FColor::White;
statusbar_active_hotkey_fg = FColor::Blue;
statusbar_active_hotkey_bg = FColor::White;
scrollbar_fg = FColor::Cyan;
scrollbar_bg = FColor::DarkGray;
scrollbar_button_fg = FColor::Yellow;
scrollbar_button_bg = FColor::DarkGray;
scrollbar_button_inactive_fg = FColor::LightGray;
scrollbar_button_inactive_bg = FColor::Black;
progressbar_fg = FColor::Yellow;
progressbar_bg = FColor::Blue;
}
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the finalcut application.
* @ingroup fneSysView
*/
class HOST_SW_API SysViewApplication final : public finalcut::FApplication {
public:
/**
* @brief Initializes a new instance of the SysViewApplication class.
* @param argc Passed argc.
* @param argv Passed argv.
*/
explicit SysViewApplication(const int& argc, char** argv) : FApplication{argc, argv}
{
m_stopWatch.start();
}
/**
* @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;
}
}
/**
* @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::IOSP_GRP_AFF* iosp = static_cast<lc::tsbk::IOSP_GRP_AFF*>(tsbk.get());
LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(),
iosp->getSysId(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str());
}
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());
}
}
}
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__

Powered by TurnKey Linux.