Merge branch 'master' into incall_ctrl

pull/86/head
Bryan Biedenkapp 1 year ago
commit 8f74260d5d

@ -440,6 +440,80 @@ if (NOT TARGET tarball)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
endif (NOT TARGET tarball) endif (NOT TARGET tarball)
#
# Custom make target to perform a tarball packaging. This will ultimately contain the same type of pathing
# the non-standard legacy install to "/opt/dvm" does.
#
if (NOT TARGET tarball_notools)
set(CMAKE_INSTALL_PREFIX_TARBALL "tar_build")
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
add_custom_target(tarball_notools
COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL}
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/log
COMMAND touch ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/log/INCLUDE_DIRECTORY
COMMAND cp -v dvmhost ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmmon ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v sysview ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v tged ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v ../configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema
COMMAND cp -v ../configs/schema/*.json ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema
COMMAND cp -v ../configs/*.dat ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw
COMMAND if [ -e dvm-firmware_f4.elf ]\; then cp -v dvm-firmware_f4.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4.bin ]\; then cp -v dvm-firmware_f4.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-pog.elf ]\; then cp -v dvm-firmware_f4-pog.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-pog.bin ]\; then cp -v dvm-firmware_f4-pog.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_eda.elf ]\; then cp -v dvm-firmware_eda.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_eda.bin ]\; then cp -v dvm-firmware_eda.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-dvmv1.elf ]\; then cp -v dvm-firmware_f4-dvmv1.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-dvmv1.bin ]\; then cp -v dvm-firmware_f4-dvmv1.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_due.elf ]\; then cp -v dvm-firmware_due.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_due.bin ]\; then cp -v dvm-firmware_due.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware-hs_f1.elf ]\; then cp -v dvm-firmware-hs_f1.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware-hs_f1.bin ]\; then cp -v dvm-firmware-hs_f1.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e DVM-V24-stm32f103.elf ]\; then cp -v DVM-V24-stm32f103.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e DVM-V24-stm32f103.bin ]\; then cp -v DVM-V24-stm32f103.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND cd ${CMAKE_INSTALL_PREFIX_TARBALL} && tar czvf ../dvmhost_${CPACK_DEBIAN_PACKAGE_VERSION}_${ARCH}.tar.gz *
COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL})
else()
add_custom_target(tarball_notools
COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL}
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/log
COMMAND touch ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/log/INCLUDE_DIRECTORY
COMMAND cp -v dvmhost ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v ../configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema
COMMAND cp -v ../configs/schema/*.json ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema
COMMAND cp -v ../configs/*.dat ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw
COMMAND if [ -e dvm-firmware_f4.elf ]\; then cp -v dvm-firmware_f4.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4.bin ]\; then cp -v dvm-firmware_f4.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-pog.elf ]\; then cp -v dvm-firmware_f4-pog.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-pog.bin ]\; then cp -v dvm-firmware_f4-pog.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_eda.elf ]\; then cp -v dvm-firmware_eda.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_eda.bin ]\; then cp -v dvm-firmware_eda.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-dvmv1.elf ]\; then cp -v dvm-firmware_f4-dvmv1.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_f4-dvmv1.bin ]\; then cp -v dvm-firmware_f4-dvmv1.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_due.elf ]\; then cp -v dvm-firmware_due.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware_due.bin ]\; then cp -v dvm-firmware_due.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware-hs_f1.elf ]\; then cp -v dvm-firmware-hs_f1.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e dvm-firmware-hs_f1.bin ]\; then cp -v dvm-firmware-hs_f1.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e DVM-V24-stm32f103.elf ]\; then cp -v DVM-V24-stm32f103.elf ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND if [ -e DVM-V24-stm32f103.bin ]\; then cp -v DVM-V24-stm32f103.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND cd ${CMAKE_INSTALL_PREFIX_TARBALL} && tar czvf ../dvmhost_${CPACK_DEBIAN_PACKAGE_VERSION}_${ARCH}.tar.gz *
COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL})
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
endif (NOT TARGET tarball_notools)
# #
# Custom make target to perform non-standard legacy install to "/opt/dvm". This is meant # Custom make target to perform non-standard legacy install to "/opt/dvm". This is meant
# to retain backward compatibility with deployment scripts and other tools that work with "/opt/dvm" # to retain backward compatibility with deployment scripts and other tools that work with "/opt/dvm"

@ -69,7 +69,7 @@ master:
# Flag indicating whether packet data will be passed. # Flag indicating whether packet data will be passed.
disablePacketData: false disablePacketData: false
# Flag indicating whether verbose dumping of data packets is enabled. # Flag indicating whether verbose dumping of data packets is enabled.
dumpDataPacket: false dumpPacketData: false
# Delay from when a call on a parrot TG ends to when the playback starts (in milliseconds). # Delay from when a call on a parrot TG ends to when the playback starts (in milliseconds).
parrotDelay: 2000 parrotDelay: 2000

@ -1041,8 +1041,7 @@ void HostBridge::processUDPAudio()
m_txStreamId = 1U; // prevent further false starts -- this isn't the right way to handle this... m_txStreamId = 1U; // prevent further false starts -- this isn't the right way to handle this...
LogMessage(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", UDP_CALL, m_udpSrcId, m_udpDstId); LogMessage(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", UDP_CALL, m_udpSrcId, m_udpDstId);
if (m_grantDemand) { if (m_grantDemand) {
switch (m_txMode) switch (m_txMode) {
{
case TX_MODE_P25: case TX_MODE_P25:
{ {
p25::lc::LC lc = p25::lc::LC(); p25::lc::LC lc = p25::lc::LC();
@ -2101,6 +2100,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId)
data.setSrcId(srcId); data.setSrcId(srcId);
m_network->writeDMRTerminator(data, &m_dmrSeqNo, &m_dmrN, m_dmrEmbeddedData); m_network->writeDMRTerminator(data, &m_dmrSeqNo, &m_dmrN, m_dmrEmbeddedData);
m_network->resetDMR(data.getSlotNo());
} }
break; break;
case TX_MODE_P25: case TX_MODE_P25:
@ -2114,6 +2114,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId)
uint8_t controlByte = 0x00U; uint8_t controlByte = 0x00U;
m_network->writeP25TDU(lc, lsd, controlByte); m_network->writeP25TDU(lc, lsd, controlByte);
m_network->resetP25();
} }
break; break;
} }

@ -35,6 +35,7 @@ TalkgroupRulesLookup::TalkgroupRulesLookup(const std::string& filename, uint32_t
m_reloadTime(reloadTime), m_reloadTime(reloadTime),
m_rules(), m_rules(),
m_acl(acl), m_acl(acl),
m_stop(false),
m_groupHangTime(5U), m_groupHangTime(5U),
m_sendTalkgroups(false), m_sendTalkgroups(false),
m_groupVoice() m_groupVoice()

@ -634,9 +634,9 @@ namespace lookups
yaml::Node m_rules; yaml::Node m_rules;
bool m_acl; bool m_acl;
bool m_stop;
static std::mutex m_mutex; static std::mutex m_mutex;
bool m_stop;
/** /**
* @brief Loads the table from the passed lookup table file. * @brief Loads the table from the passed lookup table file.

@ -80,7 +80,7 @@ namespace network
int status = -1; int status = -1;
struct timeval tv, tvRestore; struct timeval tv, tvRestore;
tv.tv_sec = 2; tv.tv_sec = 5;
tv.tv_usec = 0; tv.tv_usec = 0;
tvRestore = tv; tvRestore = tv;

@ -206,6 +206,8 @@ int HostFNE::run()
#if !defined(_WIN32) #if !defined(_WIN32)
if (!Thread::runAsThread(this, threadVirtualNetworking)) if (!Thread::runAsThread(this, threadVirtualNetworking))
return EXIT_FAILURE; return EXIT_FAILURE;
if (!Thread::runAsThread(this, threadVirtualNetworkingClock))
return EXIT_FAILURE;
#endif // !defined(_WIN32) #endif // !defined(_WIN32)
/* /*
** Main execution loop ** Main execution loop
@ -602,7 +604,7 @@ void* HostFNE::threadMasterNetwork(void* arg)
::pthread_detach(th->thread); ::pthread_detach(th->thread);
#endif // defined(_WIN32) #endif // defined(_WIN32)
std::string threadName("fne:network-loop"); std::string threadName("fne:net");
HostFNE* fne = static_cast<HostFNE*>(th->obj); HostFNE* fne = static_cast<HostFNE*>(th->obj);
if (fne == nullptr) { if (fne == nullptr) {
g_killed = true; g_killed = true;
@ -645,7 +647,7 @@ void* HostFNE::threadDiagNetwork(void* arg)
::pthread_detach(th->thread); ::pthread_detach(th->thread);
#endif // defined(_WIN32) #endif // defined(_WIN32)
std::string threadName("fne:diag-network-loop"); std::string threadName("fne:diag-net");
HostFNE* fne = static_cast<HostFNE*>(th->obj); HostFNE* fne = static_cast<HostFNE*>(th->obj);
if (fne == nullptr) { if (fne == nullptr) {
g_killed = true; g_killed = true;
@ -836,7 +838,7 @@ void* HostFNE::threadVirtualNetworking(void* arg)
if (th != nullptr) { if (th != nullptr) {
::pthread_detach(th->thread); ::pthread_detach(th->thread);
std::string threadName("fne:vtun-loop"); std::string threadName("fne:vt-net-rx");
HostFNE* fne = static_cast<HostFNE*>(th->obj); HostFNE* fne = static_cast<HostFNE*>(th->obj);
if (fne == nullptr) { if (fne == nullptr) {
g_killed = true; g_killed = true;
@ -863,7 +865,6 @@ void* HostFNE::threadVirtualNetworking(void* arg)
stopWatch.start(); stopWatch.start();
while (!g_killed) { while (!g_killed) {
uint32_t ms = stopWatch.elapsed();
stopWatch.start(); stopWatch.start();
uint8_t packet[DEFAULT_MTU_SIZE]; uint8_t packet[DEFAULT_MTU_SIZE];
@ -882,6 +883,55 @@ void* HostFNE::threadVirtualNetworking(void* arg)
} }
} }
Thread::sleep(2U);
}
}
LogDebug(LOG_HOST, "[STOP] %s", threadName.c_str());
delete th;
}
return nullptr;
}
/* Entry point to virtual networking clocking thread. */
void* HostFNE::threadVirtualNetworkingClock(void* arg)
{
thread_t* th = (thread_t*)arg;
if (th != nullptr) {
::pthread_detach(th->thread);
std::string threadName("fne:vt-clock");
HostFNE* fne = static_cast<HostFNE*>(th->obj);
if (fne == nullptr) {
g_killed = true;
LogDebug(LOG_HOST, "[FAIL] %s", threadName.c_str());
}
if (g_killed) {
delete th;
return nullptr;
}
if (!fne->m_vtunEnabled) {
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
if (fne->m_tun != nullptr) {
StopWatch stopWatch;
stopWatch.start();
while (!g_killed) {
uint32_t ms = stopWatch.elapsed();
stopWatch.start();
// clock traffic handler // clock traffic handler
switch (fne->m_packetDataMode) { switch (fne->m_packetDataMode) {
case PacketDataMode::DMR: case PacketDataMode::DMR:
@ -893,7 +943,7 @@ void* HostFNE::threadVirtualNetworking(void* arg)
break; break;
} }
Thread::sleep(5U); Thread::sleep(2U);
} }
} }

@ -163,6 +163,12 @@ private:
* @returns void* (Ignore) * @returns void* (Ignore)
*/ */
static void* threadVirtualNetworking(void* arg); static void* threadVirtualNetworking(void* arg);
/**
* @brief Entry point to virtual networking clocking thread.
* @param arg Instance of the thread_t structure.
* @returns void* (Ignore)
*/
static void* threadVirtualNetworkingClock(void* arg);
#endif // !defined(_WIN32) #endif // !defined(_WIN32)
/** /**
* @brief Processes any peer network traffic. * @brief Processes any peer network traffic.

@ -94,7 +94,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port,
m_influxBucket("dvm"), m_influxBucket("dvm"),
m_influxLogRawData(false), m_influxLogRawData(false),
m_disablePacketData(false), m_disablePacketData(false),
m_dumpDataPacket(false), m_dumpPacketData(false),
m_reportPeerPing(reportPeerPing), m_reportPeerPing(reportPeerPing),
m_verbose(verbose) m_verbose(verbose)
{ {
@ -154,7 +154,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions)
m_filterTerminators = conf["filterTerminators"].as<bool>(true); m_filterTerminators = conf["filterTerminators"].as<bool>(true);
m_disablePacketData = conf["disablePacketData"].as<bool>(false); m_disablePacketData = conf["disablePacketData"].as<bool>(false);
m_dumpDataPacket = conf["dumpDataPacket"].as<bool>(false); m_dumpPacketData = conf["dumpPacketData"].as<bool>(false);
/* /*
** Drop Unit to Unit Peers ** Drop Unit to Unit Peers
@ -177,7 +177,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions)
LogWarning(LOG_NET, "NOTICE: All P25 ADJ_STS_BCAST messages will be blocked and dropped!"); LogWarning(LOG_NET, "NOTICE: All P25 ADJ_STS_BCAST messages will be blocked and dropped!");
} }
LogInfo(" Disable Packet Data: %s", m_disablePacketData ? "yes" : "no"); LogInfo(" Disable Packet Data: %s", m_disablePacketData ? "yes" : "no");
LogInfo(" Dump Packet Data: %s", m_dumpDataPacket ? "yes" : "no"); LogInfo(" Dump Packet Data: %s", m_dumpPacketData ? "yes" : "no");
LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowExtAdjStsBcast ? "yes" : "no"); LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowExtAdjStsBcast ? "yes" : "no");
LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no"); LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no");
LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no"); LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no");
@ -2425,6 +2425,7 @@ bool FNENetwork::writePeerNAK(uint32_t peerId, const char* tag, NET_CONN_NAK_REA
__SET_UINT16B((uint16_t)reason, buffer, 10U); // Reason __SET_UINT16B((uint16_t)reason, buffer, 10U); // Reason
logPeerNAKReason(peerId, tag, reason); logPeerNAKReason(peerId, tag, reason);
LogWarning(LOG_NET, "PEER %u NAK %s -> %s:%u", peerId, tag, udp::Socket::address(addr).c_str(), udp::Socket::port(addr));
return m_frameQueue->write(buffer, 12U, createStreamId(), peerId, m_peerId, return m_frameQueue->write(buffer, 12U, createStreamId(), peerId, m_peerId,
{ NET_FUNC::NAK, NET_SUBFUNC::NOP }, 0U, addr, addrLen); { NET_FUNC::NAK, NET_SUBFUNC::NOP }, 0U, addr, addrLen);
} }

@ -477,7 +477,7 @@ namespace network
influxdb::ServerInfo m_influxServer; influxdb::ServerInfo m_influxServer;
bool m_disablePacketData; bool m_disablePacketData;
bool m_dumpDataPacket; bool m_dumpPacketData;
bool m_reportPeerPing; bool m_reportPeerPing;
bool m_verbose; bool m_verbose;

@ -199,7 +199,14 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
::memcpy(str, decompressed, decompressedLen); ::memcpy(str, decompressed, decompressedLen);
str[decompressedLen] = 0; // null termination str[decompressedLen] = 0; // null termination
std::string filename = "/tmp/talkgroup_rules.yml"; // randomize filename
std::ostringstream s;
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<uint32_t> dist(0x00U, 0xFFFFFFFFU);
s << "/tmp/talkgroup_rules.yml." << dist(mt);
std::string filename = s.str();
std::ofstream file(filename, std::ofstream::out); std::ofstream file(filename, std::ofstream::out);
if (file.fail()) { if (file.fail()) {
LogError(LOG_NET, "Cannot open the talkgroup ID lookup file - %s", filename.c_str()); LogError(LOG_NET, "Cannot open the talkgroup ID lookup file - %s", filename.c_str());
@ -224,7 +231,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
} }
} }
tid_lookup_cleanup: tid_lookup_cleanup:
m_tgidSize = 0U; m_tgidSize = 0U;
m_tgidCompressedSize = 0U; m_tgidCompressedSize = 0U;
if (m_tgidBuffer != nullptr) if (m_tgidBuffer != nullptr)
@ -324,7 +331,14 @@ tid_lookup_cleanup:
::memcpy(str, decompressed, decompressedLen); ::memcpy(str, decompressed, decompressedLen);
str[decompressedLen] = 0; // null termination str[decompressedLen] = 0; // null termination
std::string filename = "/tmp/rid_acl.dat"; // randomize filename
std::ostringstream s;
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<uint32_t> dist(0x00U, 0xFFFFFFFFU);
s << "/tmp/rid_acl.dat." << dist(mt);
std::string filename = s.str();
std::ofstream file(filename, std::ofstream::out); std::ofstream file(filename, std::ofstream::out);
if (file.fail()) { if (file.fail()) {
LogError(LOG_NET, "Cannot open the radio ID lookup file - %s", filename.c_str()); LogError(LOG_NET, "Cannot open the radio ID lookup file - %s", filename.c_str());
@ -349,7 +363,7 @@ tid_lookup_cleanup:
} }
} }
rid_lookup_cleanup: rid_lookup_cleanup:
m_ridSize = 0U; m_ridSize = 0U;
m_ridCompressedSize = 0U; m_ridCompressedSize = 0U;
if (m_ridBuffer != nullptr) if (m_ridBuffer != nullptr)
@ -449,7 +463,14 @@ rid_lookup_cleanup:
::memcpy(str, decompressed, decompressedLen); ::memcpy(str, decompressed, decompressedLen);
str[decompressedLen] = 0; // null termination str[decompressedLen] = 0; // null termination
std::string filename = "/tmp/peer_list.dat"; // randomize filename
std::ostringstream s;
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<uint32_t> dist(0x00U, 0xFFFFFFFFU);
s << "/tmp/peer_list.dat." << dist(mt);
std::string filename = s.str();
std::ofstream file(filename, std::ofstream::out); std::ofstream file(filename, std::ofstream::out);
if (file.fail()) { if (file.fail()) {
LogError(LOG_NET, "Cannot open the peer ID lookup file - %s", filename.c_str()); LogError(LOG_NET, "Cannot open the peer ID lookup file - %s", filename.c_str());
@ -474,7 +495,7 @@ rid_lookup_cleanup:
} }
} }
pid_lookup_cleanup: pid_lookup_cleanup:
m_pidSize = 0U; m_pidSize = 0U;
m_pidCompressedSize = 0U; m_pidCompressedSize = 0U;
if (m_pidBuffer != nullptr) if (m_pidBuffer != nullptr)

@ -1419,12 +1419,6 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply,
return; return;
} }
// validate peer ID is a integer within the JSON blob
if (!req["peerId"].is<uint32_t>()) {
errorPayload(reply, "peer ID was not valid");
return;
}
// validate destination ID is a integer within the JSON blob // validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<uint32_t>()) { if (!req["dstId"].is<uint32_t>()) {
errorPayload(reply, "destination ID was not valid"); errorPayload(reply, "destination ID was not valid");
@ -1437,15 +1431,10 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply,
return; return;
} }
uint32_t peerId = req["peerId"].get<uint32_t>(); uint32_t peerId = req["peerId"].getDefault<uint32_t>(0U);
uint32_t dstId = req["dstId"].get<uint32_t>(); uint32_t dstId = req["dstId"].get<uint32_t>();
uint8_t slot = req["slot"].get<uint8_t>(); uint8_t slot = req["slot"].get<uint8_t>();
if (peerId == 0U) {
errorPayload(reply, "peer ID was not valid");
return;
}
if (dstId == 0U) { if (dstId == 0U) {
errorPayload(reply, "destination ID was not valid"); errorPayload(reply, "destination ID was not valid");
return; return;
@ -1500,26 +1489,15 @@ void RESTAPI::restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply,
return; return;
} }
// validate peer ID is a integer within the JSON blob
if (!req["peerId"].is<uint32_t>()) {
errorPayload(reply, "peer ID was not valid");
return;
}
// validate destination ID is a integer within the JSON blob // validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<uint32_t>()) { if (!req["dstId"].is<uint32_t>()) {
errorPayload(reply, "destination ID was not valid"); errorPayload(reply, "destination ID was not valid");
return; return;
} }
uint32_t peerId = req["peerId"].get<uint32_t>(); uint32_t peerId = req["peerId"].getDefault<uint32_t>(0U);
uint32_t dstId = req["dstId"].get<uint32_t>(); uint32_t dstId = req["dstId"].get<uint32_t>();
if (peerId == 0U) {
errorPayload(reply, "peer ID was not valid");
return;
}
if (dstId == 0U) { if (dstId == 0U) {
errorPayload(reply, "destination ID was not valid"); errorPayload(reply, "destination ID was not valid");
return; return;

@ -985,5 +985,39 @@ void TagDMRData::write_CSBK(uint32_t peerId, uint8_t slot, lc::CSBK* csbk)
return; return;
} }
m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false, true); if (peerId > 0U) {
m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false, true);
} else {
// repeat traffic to the connected peers
if (m_network->m_peers.size() > 0U) {
uint32_t i = 0U;
for (auto peer : m_network->m_peers) {
// every 5 peers flush the queue
if (i % 5U == 0U) {
m_network->m_frameQueue->flushQueue();
}
m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true);
if (m_network->m_debug) {
LogDebug(LOG_NET, "DMR, peer = %u, slotNo = %u, len = %u, stream = %u",
peer.first, slot, messageLength, streamId);
}
i++;
}
m_network->m_frameQueue->flushQueue();
}
// repeat traffic to external peers
if (m_network->m_host->m_peerNetworks.size() > 0U) {
for (auto peer : m_network->m_host->m_peerNetworks) {
uint32_t dstPeerId = peer.second->getPeerId();
peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId);
if (m_network->m_debug) {
LogDebug(LOG_NET, "DMR, peer = %u, slotNo = %u, len = %u, stream = %u",
dstPeerId, slot, messageLength, streamId);
}
}
}
}
} }

@ -1316,5 +1316,38 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk)
} }
uint32_t streamId = m_network->createStreamId(); uint32_t streamId = m_network->createStreamId();
m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false, true); if (peerId > 0U) {
m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false, true);
} else {
// repeat traffic to the connected peers
if (m_network->m_peers.size() > 0U) {
uint32_t i = 0U;
for (auto peer : m_network->m_peers) {
// every 5 peers flush the queue
if (i % 5U == 0U) {
m_network->m_frameQueue->flushQueue();
}
m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true);
if (m_network->m_debug) {
LogDebug(LOG_NET, "P25, peer = %u, len = %u, streamId = %u",
peer.first, messageLength, streamId);
}
i++;
}
m_network->m_frameQueue->flushQueue();
}
// repeat traffic to external peers
if (m_network->m_host->m_peerNetworks.size() > 0U) {
for (auto peer : m_network->m_host->m_peerNetworks) {
uint32_t dstPeerId = peer.second->getPeerId();
peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId);
if (m_network->m_debug) {
LogDebug(LOG_NET, "P25, peer = %u, len = %u, streamId = %u",
dstPeerId, messageLength, streamId);
}
}
}
}
} }

@ -253,8 +253,8 @@ void DMRPacketData::dispatch(uint32_t peerId, dmr::data::NetData& dmrData, const
LogWarning(LOG_NET, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduDataOffset); LogWarning(LOG_NET, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduDataOffset);
} }
if (m_network->m_dumpDataPacket) { if (m_network->m_dumpPacketData) {
Utils::dump(1U, "PDU Packet", status->pduUserData, status->pduDataOffset); Utils::dump(1U, "ISP PDU Packet", status->pduUserData, status->pduDataOffset);
} }
} }
} }

@ -40,6 +40,12 @@ using namespace p25::sndcp;
const uint8_t DATA_CALL_COLL_TIMEOUT = 60U; const uint8_t DATA_CALL_COLL_TIMEOUT = 60U;
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::timed_mutex P25PacketData::m_vtunMutex;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -52,8 +58,8 @@ P25PacketData::P25PacketData(FNENetwork* network, TagP25Data* tag, bool debug) :
m_dataFrames(), m_dataFrames(),
m_status(), m_status(),
m_arpTable(), m_arpTable(),
m_readyForPkt(), m_readyForNextPkt(),
m_suNotReadyTimeout(), m_suSendSeq(),
m_debug(debug) m_debug(debug)
{ {
assert(network != nullptr); assert(network != nullptr);
@ -129,6 +135,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee
status->llId = status->header.getLLId(); status->llId = status->header.getLLId();
m_status[peerId] = status; m_status[peerId] = status;
m_readyForNextPkt[status->llId] = true;
// is this a response header? // is this a response header?
if (status->header.getFormat() == PDUFormatType::RSP) { if (status->header.getFormat() == PDUFormatType::RSP) {
@ -141,7 +148,6 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee
if (status->header.getSAP() != PDUSAP::EXT_ADDR && if (status->header.getSAP() != PDUSAP::EXT_ADDR &&
status->header.getFormat() != PDUFormatType::UNCONFIRMED) { status->header.getFormat() != PDUFormatType::UNCONFIRMED) {
m_readyForPkt[status->llId] = true;
m_suSendSeq[status->llId] = 0U; m_suSendSeq[status->llId] = 0U;
} }
@ -218,7 +224,6 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee
status->extendedAddress = true; status->extendedAddress = true;
status->llId = status->header.getSrcLLId(); status->llId = status->header.getSrcLLId();
m_readyForPkt[status->llId] = true;
m_suSendSeq[status->llId] = 0U; m_suSendSeq[status->llId] = 0U;
offset += P25_PDU_FEC_LENGTH_BYTES; offset += P25_PDU_FEC_LENGTH_BYTES;
@ -277,7 +282,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee
else else
LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i);
if (m_network->m_dumpDataPacket) { if (m_network->m_dumpPacketData) {
Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES);
} }
} }
@ -325,6 +330,8 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee
void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool alreadyQueued) void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool alreadyQueued)
{ {
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
#if !defined(_WIN32) #if !defined(_WIN32)
struct ip* ipHeader = (struct ip*)data; struct ip* ipHeader = (struct ip*)data;
@ -337,30 +344,34 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a
uint8_t proto = ipHeader->ip_p; uint8_t proto = ipHeader->ip_p;
uint16_t pktLen = Utils::reverseEndian(ipHeader->ip_len); // bryanb: this could be problematic on different endianness uint16_t pktLen = Utils::reverseEndian(ipHeader->ip_len); // bryanb: this could be problematic on different endianness
LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s, dstIp = %s, pktLen = %u, proto = %02X", srcIp, dstIp, pktLen, proto);
#if DEBUG_P25_PDU_DATA #if DEBUG_P25_PDU_DATA
Utils::dump(1U, "P25PacketData::processPacketFrame() packet", data, pktLen); Utils::dump(1U, "P25PacketData::processPacketFrame() packet", data, pktLen);
#endif #endif
VTUNDataFrame dataFrame; VTUNDataFrame* dataFrame = new VTUNDataFrame();
dataFrame.buffer = new uint8_t[len]; dataFrame->buffer = new uint8_t[len];
::memcpy(dataFrame.buffer, data, len); ::memcpy(dataFrame->buffer, data, len);
dataFrame.bufferLen = len; dataFrame->bufferLen = len;
dataFrame.pktLen = pktLen; dataFrame->pktLen = pktLen;
dataFrame->proto = proto;
uint32_t dstLlId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); uint32_t dstLlId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr));
dataFrame.srcHWAddr = WUID_FNE; dataFrame->srcHWAddr = WUID_FNE;
dataFrame.srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); dataFrame->srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr);
dataFrame.tgtHWAddr = dstLlId; dataFrame->tgtHWAddr = dstLlId;
dataFrame.tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); dataFrame->tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr);
dataFrame->timestamp = now;
if (dstLlId == 0U) { if (dstLlId == 0U) {
LogMessage(LOG_NET, "P25, no ARP entry for, dstIp = %s", dstIp); LogMessage(LOG_NET, "P25, no ARP entry for, dstIp = %s", dstIp);
write_PDU_ARP(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); write_PDU_ARP(Utils::reverseEndian(ipHeader->ip_dst.s_addr));
} }
m_vtunMutex.try_lock_for(std::chrono::milliseconds(60));
m_dataFrames.push_back(dataFrame); m_dataFrames.push_back(dataFrame);
m_vtunMutex.unlock();
#endif // !defined(_WIN32) #endif // !defined(_WIN32)
} }
@ -368,61 +379,92 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a
void P25PacketData::clock(uint32_t ms) void P25PacketData::clock(uint32_t ms)
{ {
// transmit queued data frames uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
if (m_dataFrames.size() > 0) {
auto& dataFrame = m_dataFrames[0];
if (dataFrame.tgtHWAddr == 0U) { if (m_dataFrames.size() == 0U) {
uint32_t dstLlId = getLLIdAddress(dataFrame.tgtProtoAddr); return;
if (dstLlId == 0U) }
return;
dataFrame.tgtHWAddr = dstLlId; // transmit queued data frames
} bool processed = false;
m_vtunMutex.try_lock_for(std::chrono::milliseconds(60));
auto& dataFrame = m_dataFrames[0];
m_vtunMutex.unlock();
if (dataFrame != nullptr) {
if (now > dataFrame->timestamp + 500U) {
processed = true;
// do we have a valid target address?
if (dataFrame->tgtHWAddr == 0U) {
uint32_t dstLlId = getLLIdAddress(dataFrame->tgtProtoAddr);
if (dstLlId == 0U) {
processed = false;
goto pkt_clock_abort;
}
// don't allow another packet to go out if we haven't acked the previous dataFrame->tgtHWAddr = dstLlId;
if (!m_readyForPkt[dataFrame.tgtHWAddr]) {
m_suNotReadyTimeout[dataFrame.tgtHWAddr].clock(ms);
if (m_suNotReadyTimeout[dataFrame.tgtHWAddr].isRunning() && m_suNotReadyTimeout[dataFrame.tgtHWAddr].hasExpired()) {
m_suNotReadyTimeout[dataFrame.tgtHWAddr].stop();
m_readyForPkt[dataFrame.tgtHWAddr] = true;
} }
return; // is the SU ready for the next packet?
} auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == dataFrame->tgtHWAddr; });
if (ready != m_readyForNextPkt.end()) {
if (!ready->second) {
processed = false;
goto pkt_clock_abort;
}
} else {
processed = false;
goto pkt_clock_abort;
}
m_readyForNextPkt[dataFrame->tgtHWAddr] = false;
std::string srcIp = __IP_FROM_UINT(dataFrame->srcProtoAddr);
std::string tgtIp = __IP_FROM_UINT(dataFrame->tgtProtoAddr);
LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X",
srcIp.c_str(), dataFrame->srcHWAddr, tgtIp.c_str(), dataFrame->tgtHWAddr, dataFrame->pktLen, dataFrame->proto);
// assemble a P25 PDU frame header for transport...
data::DataHeader rspHeader = data::DataHeader();
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::EXT_ADDR);
rspHeader.setLLId(dataFrame->tgtHWAddr);
rspHeader.setBlocksToFollow(1U);
m_readyForPkt[dataFrame.tgtHWAddr] = false; rspHeader.setEXSAP(PDUSAP::PACKET_DATA);
m_suNotReadyTimeout[dataFrame.tgtHWAddr] = Timer(1000U, 5U, 0U); rspHeader.setSrcLLId(WUID_FNE);
m_suNotReadyTimeout[dataFrame.tgtHWAddr].start();
rspHeader.calculateLength(dataFrame->pktLen);
// assemble a P25 PDU frame header for transport... uint32_t pduLength = rspHeader.getPDULength();
data::DataHeader rspHeader = data::DataHeader();
rspHeader.setFormat(PDUFormatType::CONFIRMED); UInt8Array __pduUserData = std::make_unique<uint8_t[]>(pduLength);
rspHeader.setMFId(MFG_STANDARD); uint8_t* pduUserData = __pduUserData.get();
rspHeader.setAckNeeded(true); ::memset(pduUserData, 0x00U, pduLength);
rspHeader.setOutbound(true); ::memcpy(pduUserData + 4U, dataFrame->buffer, dataFrame->pktLen);
rspHeader.setSAP(PDUSAP::EXT_ADDR);
rspHeader.setLLId(dataFrame.tgtHWAddr);
rspHeader.setBlocksToFollow(1U);
rspHeader.setEXSAP(PDUSAP::PACKET_DATA);
rspHeader.setSrcLLId(WUID_FNE);
rspHeader.calculateLength(dataFrame.pktLen);
uint32_t pduLength = rspHeader.getPDULength();
UInt8Array __pduUserData = std::make_unique<uint8_t[]>(pduLength);
uint8_t* pduUserData = __pduUserData.get();
::memset(pduUserData, 0x00U, pduLength);
::memcpy(pduUserData + 4U, dataFrame.buffer, dataFrame.pktLen);
#if DEBUG_P25_PDU_DATA #if DEBUG_P25_PDU_DATA
Utils::dump(1U, "P25PacketData::clock() pduUserData", pduUserData, pduLength); Utils::dump(1U, "P25PacketData::clock() pduUserData", pduUserData, pduLength);
#endif #endif
dispatchUserFrameToFNE(rspHeader, true, pduUserData); dispatchUserFrameToFNE(rspHeader, true, pduUserData);
}
}
delete[] dataFrame.buffer; pkt_clock_abort:
m_dataFrames.pop_front(); m_vtunMutex.try_lock_for(std::chrono::milliseconds(60));
m_dataFrames.pop_front();
if (processed) {
if (dataFrame->buffer != nullptr)
delete[] dataFrame->buffer;
delete dataFrame;
} else {
// requeue packet
m_dataFrames.push_back(dataFrame);
} }
m_vtunMutex.unlock();
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -449,19 +491,21 @@ void P25PacketData::dispatch(uint32_t peerId)
} }
} }
if (m_network->m_dumpDataPacket && status->dataBlockCnt > 0U) { if (m_network->m_dumpPacketData && status->dataBlockCnt > 0U) {
Utils::dump(1U, "PDU Packet", status->pduUserData, status->pduUserDataLength); Utils::dump(1U, "ISP PDU Packet", status->pduUserData, status->pduUserDataLength);
} }
if (status->header.getFormat() == PDUFormatType::RSP) { if (status->header.getFormat() == PDUFormatType::RSP) {
LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u",
status->header.getFormat(), status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), status->header.getFormat(), status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(),
status->header.getLLId(), status->header.getSrcLLId()); status->header.getLLId(), status->header.getSrcLLId());
/*
if (status->header.getResponseClass() == PDUAckClass::ACK && status->header.getResponseType() == PDUAckType::ACK) { if (status->header.getResponseClass() == PDUAckClass::ACK && status->header.getResponseType() == PDUAckType::ACK) {
m_readyForPkt[status->header.getSrcLLId()] = true; m_readyForNextPkt[status->header.getSrcLLId()] = true;
} }
*/
write_PDU_Ack_Response(status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(),
status->header.getLLId(), status->header.getSrcLLId());
return; return;
} }
@ -507,9 +551,17 @@ void P25PacketData::dispatch(uint32_t peerId)
LogWarning(LOG_NET, P25_PDU_STR ", ARP reply, %u is trying to masquerade as us...", srcHWAddr); LogWarning(LOG_NET, P25_PDU_STR ", ARP reply, %u is trying to masquerade as us...", srcHWAddr);
} else { } else {
m_arpTable[srcHWAddr] = srcProtoAddr; m_arpTable[srcHWAddr] = srcProtoAddr;
}
m_readyForPkt[srcHWAddr] = true; // is the SU ready for the next packet?
auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == srcHWAddr; });
if (ready != m_readyForNextPkt.end()) {
if (!ready->second) {
m_readyForNextPkt[srcHWAddr] = true;
}
} else {
m_readyForNextPkt[srcHWAddr] = true;
}
}
} }
#else #else
break; break;
@ -540,7 +592,45 @@ void P25PacketData::dispatch(uint32_t peerId)
uint8_t proto = ipHeader->ip_p; uint8_t proto = ipHeader->ip_p;
uint16_t pktLen = Utils::reverseEndian(ipHeader->ip_len); // bryanb: this could be problematic on different endianness uint16_t pktLen = Utils::reverseEndian(ipHeader->ip_len); // bryanb: this could be problematic on different endianness
LogMessage(LOG_NET, "P25, PDU -> VTUN, IP Data, srcIp = %s, dstIp = %s, pktLen = %u, proto = %02X", srcIp, dstIp, pktLen, proto); // reflect broadcast messages back to the CAI network
bool handled = false;
if (status->header.getLLId() == WUID_ALL) {
LogMessage(LOG_NET, "P25, PDU -> VTUN, IP Data, repeated to CAI, broadcast packet, dstIp = %s (%u)",
dstIp, status->header.getLLId());
dispatchUserFrameToFNE(status->header, status->extendedAddress, status->pduUserData);
handled = true;
// is the source SU one we have proper ARP entries for?
auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->header.getSrcLLId(); });
if (arpEntry == m_arpTable.end()) {
uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr);
LogMessage(LOG_NET, P25_PDU_STR ", adding ARP entry, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), status->header.getSrcLLId());
m_arpTable[status->header.getSrcLLId()] = Utils::reverseEndian(ipHeader->ip_src.s_addr);
}
}
// is the target SU one we have proper ARP entries for?
auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->header.getLLId(); });
if (arpEntry != m_arpTable.end()) {
LogMessage(LOG_NET, "P25, PDU -> VTUN, IP Data, repeated to CAI, destination IP has a CAI ARP table entry, dstIp = %s (%u)",
dstIp, status->header.getLLId());
dispatchUserFrameToFNE(status->header, status->extendedAddress, status->pduUserData);
handled = true;
// is the source SU one we have proper ARP entries for?
auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->header.getSrcLLId(); });
if (arpEntry == m_arpTable.end()) {
uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr);
LogMessage(LOG_NET, P25_PDU_STR ", adding ARP entry, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), status->header.getSrcLLId());
m_arpTable[status->header.getSrcLLId()] = Utils::reverseEndian(ipHeader->ip_src.s_addr);
}
}
// transmit packet to IP network
LogMessage(LOG_NET, "P25, PDU -> VTUN, IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X",
srcIp, status->header.getSrcLLId(), dstIp, status->header.getLLId(), pktLen, proto);
UInt8Array __ipFrame = std::make_unique<uint8_t[]>(pktLen); UInt8Array __ipFrame = std::make_unique<uint8_t[]>(pktLen);
uint8_t* ipFrame = __ipFrame.get(); uint8_t* ipFrame = __ipFrame.get();
@ -553,8 +643,10 @@ void P25PacketData::dispatch(uint32_t peerId)
LogError(LOG_NET, P25_PDU_STR ", failed to write IP frame to virtual tunnel, len %u", pktLen); LogError(LOG_NET, P25_PDU_STR ", failed to write IP frame to virtual tunnel, len %u", pktLen);
} }
write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->header.getNs(), (status->extendedAddress) ? status->header.getSrcLLId() : status->header.getLLId()); // if the packet is unhandled and sent off to VTUN; ack the packet so the sender knows we received it
m_readyForPkt[status->header.getSrcLLId()] = true; if (!handled) {
write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->header.getNs(), status->header.getSrcLLId(), status->header.getLLId());
}
#endif // !defined(_WIN32) #endif // !defined(_WIN32)
} }
break; break;
@ -849,11 +941,9 @@ void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, ui
rspHeader.setLLId(llId); rspHeader.setLLId(llId);
if (srcLlId > 0U) { if (srcLlId > 0U) {
rspHeader.setSrcLLId(srcLlId); rspHeader.setSrcLLId(srcLlId);
rspHeader.setFullMessage(false);
}
else {
rspHeader.setFullMessage(true);
} }
rspHeader.setFullMessage(true);
rspHeader.setBlocksToFollow(0U); rspHeader.setBlocksToFollow(0U);
dispatchUserFrameToFNE(rspHeader, srcLlId > 0U, nullptr); dispatchUserFrameToFNE(rspHeader, srcLlId > 0U, nullptr);
@ -921,7 +1011,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe
edac::CRC::addCRC32(pduUserData, packetLength); edac::CRC::addCRC32(pduUserData, packetLength);
} }
if (m_network->m_dumpDataPacket) { if (m_network->m_dumpPacketData) {
Utils::dump("OSP PDU User Data", pduUserData, packetLength); Utils::dump("OSP PDU User Data", pduUserData, packetLength);
} }

@ -90,17 +90,20 @@ namespace network
*/ */
class VTUNDataFrame { class VTUNDataFrame {
public: public:
uint32_t srcHWAddr; uint32_t srcHWAddr; //! Source Hardware Address
uint32_t srcProtoAddr; uint32_t srcProtoAddr; //! Source Protocol Address
uint32_t tgtHWAddr; uint32_t tgtHWAddr; //! Target Hardware Address
uint32_t tgtProtoAddr; uint32_t tgtProtoAddr; //! Target Protocol Address
uint8_t* buffer; uint8_t* buffer; //! Raw data buffer
uint32_t bufferLen; uint32_t bufferLen; //! Length of raw data buffer
uint16_t pktLen; uint16_t pktLen; //! Packet Length
uint8_t proto; //! Packet Protocol
uint64_t timestamp; //! Timestamp in milliseconds
}; };
std::deque<VTUNDataFrame> m_dataFrames; std::deque<VTUNDataFrame*> m_dataFrames;
/** /**
* @brief Represents the receive status of a call. * @brief Represents the receive status of a call.
@ -161,13 +164,16 @@ namespace network
typedef std::pair<const uint32_t, RxStatus*> StatusMapPair; typedef std::pair<const uint32_t, RxStatus*> StatusMapPair;
std::unordered_map<uint32_t, RxStatus*> m_status; std::unordered_map<uint32_t, RxStatus*> m_status;
typedef std::pair<const uint32_t, uint32_t> ArpTablePair;
std::unordered_map<uint32_t, uint32_t> m_arpTable; std::unordered_map<uint32_t, uint32_t> m_arpTable;
std::unordered_map<uint32_t, bool> m_readyForPkt; typedef std::pair<const uint32_t, bool> ReadyForNextPktPair;
std::unordered_map<uint32_t, Timer> m_suNotReadyTimeout; std::unordered_map<uint32_t, bool> m_readyForNextPkt;
std::unordered_map<uint32_t, uint8_t> m_suSendSeq; std::unordered_map<uint32_t, uint8_t> m_suSendSeq;
bool m_debug; bool m_debug;
static std::timed_mutex m_vtunMutex;
/** /**
* @brief Helper to dispatch PDU user data. * @brief Helper to dispatch PDU user data.
* @param peerId Peer ID. * @param peerId Peer ID.

@ -191,12 +191,19 @@ namespace network
// set SO_REUSEADDR option // set SO_REUSEADDR option
const int sockOptVal = 1; const int sockOptVal = 1;
#if defined(_WIN32)
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&sockOptVal, sizeof(int)) < 0) {
LogError(LOG_NET, "Failed to connect to InfluxDB server, err: %d", errno);
closesocket(fd);
return 1;
}
#else
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sockOptVal, sizeof(int)) < 0) { if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sockOptVal, sizeof(int)) < 0) {
LogError(LOG_NET, "Failed to connect to InfluxDB server, err: %d", errno); LogError(LOG_NET, "Failed to connect to InfluxDB server, err: %d", errno);
closesocket(fd); closesocket(fd);
return 1; return 1;
} }
#endif
// connect to the server // connect to the server
ret = connect(fd, addr->ai_addr, addr->ai_addrlen); ret = connect(fd, addr->ai_addr, addr->ai_addrlen);
if (ret < 0) { if (ret < 0) {
@ -297,9 +304,11 @@ namespace network
struct linger sl; struct linger sl;
sl.l_onoff = 1; /* non-zero value enables linger option in kernel */ sl.l_onoff = 1; /* non-zero value enables linger option in kernel */
sl.l_linger = 0; /* timeout interval in seconds */ sl.l_linger = 0; /* timeout interval in seconds */
#if defined(_WIN32)
setsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&sl, sizeof(sl));
#else
setsockopt(fd, SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)); setsockopt(fd, SOL_SOCKET, SO_LINGER, &sl, sizeof(sl));
#endif
// close socket // close socket
closesocket(fd); closesocket(fd);
return ret / 100 == 2 ? 0 : ret; return ret / 100 == 2 ? 0 : ret;

@ -570,7 +570,7 @@ void Network::clock(uint32_t ms)
} }
} }
if (m_status == NET_STAT_RUNNING || (reason == NET_CONN_NAK_FNE_MAX_CONN)) { if (m_status == NET_STAT_RUNNING && (reason == NET_CONN_NAK_FNE_MAX_CONN)) {
LogWarning(LOG_NET, "PEER %u master NAK; attemping to relogin, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); LogWarning(LOG_NET, "PEER %u master NAK; attemping to relogin, remotePeerId = %u", m_peerId, rtpHeader.getSSRC());
m_status = NET_STAT_WAITING_LOGIN; m_status = NET_STAT_WAITING_LOGIN;
m_timeoutTimer.start(); m_timeoutTimer.start();

@ -381,7 +381,7 @@ bool Data::process(uint8_t* data, uint32_t len)
LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable", LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable",
m_rfDataHeader.getLLId()); m_rfDataHeader.getLLId());
writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfDataHeader.getNs(), m_rfDataHeader.getLLId()); writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfDataHeader.getNs(), m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId());
} }
} }
} }
@ -390,11 +390,8 @@ bool Data::process(uint8_t* data, uint32_t len)
// only repeat the PDU locally if the packet isn't for the FNE // only repeat the PDU locally if the packet isn't for the FNE
if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) { if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) {
if (m_verbose) { writeRF_PDU_Ack_Response(m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(),
LogMessage(LOG_RF, P25_PDU_STR ", repeating ACK PDU, llId = %u, srcLlId = %u", m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId());
}
writeRF_PDU_Buffered(); // re-generate buffered PDU and send it on
} }
} }
else { else {
@ -497,9 +494,6 @@ bool Data::process(uint8_t* data, uint32_t len)
bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength)
{ {
if (m_p25->m_rfState != RS_RF_LISTENING && m_p25->m_netState == RS_NET_IDLE)
return false;
if (m_p25->m_netState != RS_NET_DATA) { if (m_p25->m_netState != RS_NET_DATA) {
m_netDataHeader.reset(); m_netDataHeader.reset();
m_netDataOffset = 0U; m_netDataOffset = 0U;
@ -601,13 +595,8 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength)
} }
} }
if (m_repeatPDU) { writeRF_PDU_Ack_Response(m_netDataHeader.getResponseClass(), m_netDataHeader.getResponseType(), m_netDataHeader.getResponseStatus(),
if (m_verbose) { m_netDataHeader.getLLId(), m_netDataHeader.getSrcLLId());
LogMessage(LOG_NET, P25_PDU_STR ", repeating ACK PDU, llId = %u, srcLlId = %u", m_netDataHeader.getLLId(), m_netDataHeader.getSrcLLId());
}
writeNet_PDU_Buffered(); // re-generate buffered PDU and send it on
}
m_netDataHeader.reset(); m_netDataHeader.reset();
m_netExtendedAddress = false; m_netExtendedAddress = false;
@ -1439,7 +1428,7 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, boo
// Add status bits // Add status bits
P25Utils::addStatusBits(data + 2U, newBitLength, false); P25Utils::addStatusBits(data + 2U, newBitLength, false);
P25Utils::addIdleStatusBits(data + 2U, newBitLength); P25Utils::addTrunkSlotStatusBits(data + 2U, newBitLength);
// Set first busy bits to 1,1 // Set first busy bits to 1,1
P25Utils::setStatusBits(data + 2U, P25_SS0_START, true, true); P25Utils::setStatusBits(data + 2U, P25_SS0_START, true, true);
@ -1697,13 +1686,11 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a
rspHeader.setResponseType(ackType); rspHeader.setResponseType(ackType);
rspHeader.setResponseStatus(ackStatus); rspHeader.setResponseStatus(ackStatus);
rspHeader.setLLId(llId); rspHeader.setLLId(llId);
if (m_rfDataHeader.getSAP() == PDUSAP::EXT_ADDR) { if (srcLlId > 0U) {
rspHeader.setSrcLLId(srcLlId); rspHeader.setSrcLLId(srcLlId);
rspHeader.setFullMessage(false);
}
else {
rspHeader.setFullMessage(true);
} }
rspHeader.setFullMessage(true);
rspHeader.setBlocksToFollow(0U); rspHeader.setBlocksToFollow(0U);
// Generate the PDU header and 1/2 rate Trellis // Generate the PDU header and 1/2 rate Trellis
@ -1711,8 +1698,8 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a
Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS);
if (m_verbose) { if (m_verbose) {
LogMessage(LOG_RF, P25_PDU_STR ", OSP, response, ackClass = $%02X, ackType = $%02X, llId = %u, srcLLId = %u", LogMessage(LOG_RF, P25_PDU_STR ", OSP, response, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLLId = %u",
rspHeader.getResponseClass(), rspHeader.getResponseType(), rspHeader.getLLId(), rspHeader.getSrcLLId()); rspHeader.getResponseClass(), rspHeader.getResponseType(), rspHeader.getResponseStatus(), rspHeader.getLLId(), rspHeader.getSrcLLId());
} }
writeRF_PDU(data, bitLength, noNulls); writeRF_PDU(data, bitLength, noNulls);

@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Host Monitor Software
* 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 FDblDialog.h
* @ingroup monitor
*/
#if !defined(__F_DBL_DIALOG_H__)
#define __F_DBL_DIALOG_H__
#include "common/Defines.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the double-border dialog.
* @ingroup monitor
*/
class HOST_SW_API FDblDialog : public finalcut::FDialog {
public:
/**
* @brief Initializes a new instance of the FDblDialog class.
* @param widget
*/
explicit FDblDialog(FWidget* widget = nullptr) : finalcut::FDialog{widget}
{
/* stub */
}
protected:
/**
* @brief
*/
void drawBorder() override
{
if (!hasBorder())
return;
setColor();
FRect box{{1, 2}, getSize()};
box.scaleBy(0, -1);
FRect rect = box;
if (rect.x1_ref() > rect.x2_ref())
std::swap(rect.x1_ref(), rect.x2_ref());
if (rect.y1_ref() > rect.y2_ref())
std::swap(rect.y1_ref(), rect.y2_ref());
rect.x1_ref() = std::max(rect.x1_ref(), 1);
rect.y1_ref() = std::max(rect.y1_ref(), 1);
rect.x2_ref() = std::min(rect.x2_ref(), rect.x1_ref() + int(getWidth()) - 1);
rect.y2_ref() = std::min(rect.y2_ref(), rect.y1_ref() + int(getHeight()) - 1);
if (box.getWidth() < 3)
return;
// Use box-drawing characters to draw a border
constexpr std::array<wchar_t, 8> box_char
{{
static_cast<wchar_t>(0x2554), // ╔
static_cast<wchar_t>(0x2550), // ═
static_cast<wchar_t>(0x2557), // ╗
static_cast<wchar_t>(0x2551), // ║
static_cast<wchar_t>(0x2551), // ║
static_cast<wchar_t>(0x255A), // ╚
static_cast<wchar_t>(0x2550), // ═
static_cast<wchar_t>(0x255D) // ╝
}};
drawGenericBox(this, box, box_char);
}
};
#endif // __F_DBL_DIALOG_H__

@ -215,8 +215,6 @@ private:
if (FVTerm::getFOutput()->getMaxColor() < 16) if (FVTerm::getFOutput()->getMaxColor() < 16)
setBold(); setBold();
const auto& wc = getColorTheme();
if (!m_tx) { if (!m_tx) {
if (m_failed) { if (m_failed) {
setColor(FColor::Black, FColor::LightRed); setColor(FColor::Black, FColor::LightRed);

@ -20,6 +20,8 @@
#include "remote/RESTClient.h" #include "remote/RESTClient.h"
#include "MonitorMain.h" #include "MonitorMain.h"
#include "FDblDialog.h"
#include <final/final.h> #include <final/final.h>
using namespace finalcut; using namespace finalcut;
@ -31,14 +33,14 @@ using namespace finalcut;
* @brief This class implements the base class for transmit windows. * @brief This class implements the base class for transmit windows.
* @ingroup monitor * @ingroup monitor
*/ */
class HOST_SW_API TransmitWndBase : public finalcut::FDialog { class HOST_SW_API TransmitWndBase : public FDblDialog {
public: public:
/** /**
* @brief Initializes a new instance of the TransmitWndBase class. * @brief Initializes a new instance of the TransmitWndBase class.
* @param channel Channel data. * @param channel Channel data.
* @param widget * @param widget
*/ */
explicit TransmitWndBase(lookups::VoiceChData channel, FWidget* widget = nullptr) : FDialog{widget}, explicit TransmitWndBase(lookups::VoiceChData channel, FWidget* widget = nullptr) : FDblDialog{widget},
m_selectedCh(channel) m_selectedCh(channel)
{ {
/* stub */ /* stub */

@ -19,6 +19,7 @@
#include "fne/network/RESTDefines.h" #include "fne/network/RESTDefines.h"
#include "remote/RESTClient.h" #include "remote/RESTClient.h"
#include "FDblDialog.h"
#include "SysViewMainWnd.h" #include "SysViewMainWnd.h"
#include <final/final.h> #include <final/final.h>
@ -39,13 +40,13 @@ using namespace finalcut;
* @brief This class implements the affiliations list window. * @brief This class implements the affiliations list window.
* @ingroup fneSysView * @ingroup fneSysView
*/ */
class HOST_SW_API AffListWnd final : public finalcut::FDialog { class HOST_SW_API AffListWnd final : public FDblDialog {
public: public:
/** /**
* @brief Initializes a new instance of the AffListWnd class. * @brief Initializes a new instance of the AffListWnd class.
* @param widget * @param widget
*/ */
explicit AffListWnd(FWidget* widget = nullptr) : FDialog{widget} explicit AffListWnd(FWidget* widget = nullptr) : FDblDialog{widget}
{ {
m_timerId = addTimer(10000); // starts the timer every 10 seconds m_timerId = addTimer(10000); // starts the timer every 10 seconds
} }
@ -109,6 +110,7 @@ public:
m_listView.clear(); m_listView.clear();
json::array fneAffils = rsp["affiliations"].get<json::array>(); json::array fneAffils = rsp["affiliations"].get<json::array>();
uint32_t cnt = 0U;
for (auto entry : fneAffils) { for (auto entry : fneAffils) {
json::object peerAffils = entry.get<json::object>(); json::object peerAffils = entry.get<json::object>();
uint32_t peerId = peerAffils["peerId"].getDefault<uint32_t>(0U); uint32_t peerId = peerAffils["peerId"].getDefault<uint32_t>(0U);
@ -139,8 +141,14 @@ public:
const finalcut::FStringList line(columns.cbegin(), columns.cend()); const finalcut::FStringList line(columns.cbegin(), columns.cend());
m_listView.insert(line); m_listView.insert(line);
cnt++;
} }
} }
std::ostringstream wndTitle;
wndTitle << "Affiliations View" << " [" << cnt << "] (10s)";
FDialog::setText(wndTitle.str());
} }
catch (std::exception& e) { catch (std::exception& e) {
::LogWarning(LOG_HOST, "[AFFVIEW] %s:%u, failed to properly handle affiliation request, %s", fneRESTAddress.c_str(), fneRESTPort, e.what()); ::LogWarning(LOG_HOST, "[AFFVIEW] %s:%u, failed to properly handle affiliation request, %s", fneRESTAddress.c_str(), fneRESTPort, e.what());

@ -169,7 +169,7 @@ private:
redraw(); redraw();
}); });
m_subscriber.addCallback("changed", [&]() { m_subscriber.addCallback("changed", [&]() {
if (m_subscriber.getText().c_str() == "") { if (m_subscriber.getText().getLength() == 0U) {
m_srcId = 1U; m_srcId = 1U;
return; return;
} }

@ -0,0 +1,87 @@
// 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 FDblDialog.h
* @ingroup fneSysView
*/
#if !defined(__F_DBL_DIALOG_H__)
#define __F_DBL_DIALOG_H__
#include "common/Defines.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the double-border dialog.
* @ingroup fneSysView
*/
class HOST_SW_API FDblDialog : public finalcut::FDialog {
public:
/**
* @brief Initializes a new instance of the FDblDialog class.
* @param widget
*/
explicit FDblDialog(FWidget* widget = nullptr) : finalcut::FDialog{widget}
{
/* stub */
}
protected:
/**
* @brief
*/
void drawBorder() override
{
if (!hasBorder())
return;
setColor();
FRect box{{1, 2}, getSize()};
box.scaleBy(0, -1);
FRect rect = box;
if (rect.x1_ref() > rect.x2_ref())
std::swap(rect.x1_ref(), rect.x2_ref());
if (rect.y1_ref() > rect.y2_ref())
std::swap(rect.y1_ref(), rect.y2_ref());
rect.x1_ref() = std::max(rect.x1_ref(), 1);
rect.y1_ref() = std::max(rect.y1_ref(), 1);
rect.x2_ref() = std::min(rect.x2_ref(), rect.x1_ref() + int(getWidth()) - 1);
rect.y2_ref() = std::min(rect.y2_ref(), rect.y1_ref() + int(getHeight()) - 1);
if (box.getWidth() < 3)
return;
// Use box-drawing characters to draw a border
constexpr std::array<wchar_t, 8> box_char
{{
static_cast<wchar_t>(0x2554), // ╔
static_cast<wchar_t>(0x2550), // ═
static_cast<wchar_t>(0x2557), // ╗
static_cast<wchar_t>(0x2551), // ║
static_cast<wchar_t>(0x2551), // ║
static_cast<wchar_t>(0x255A), // ╚
static_cast<wchar_t>(0x2550), // ═
static_cast<wchar_t>(0x255D) // ╝
}};
drawGenericBox(this, box, box_char);
}
};
#endif // __F_DBL_DIALOG_H__

@ -9,15 +9,19 @@
*/ */
#if !defined(NO_WEBSOCKETS) #if !defined(NO_WEBSOCKETS)
#include "Defines.h" #include "Defines.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/Log.h" #include "common/Log.h"
#include "common/StopWatch.h" #include "common/StopWatch.h"
#include "common/Thread.h" #include "common/Thread.h"
#include "common/Utils.h" #include "common/Utils.h"
#include "fne/network/RESTDefines.h" #include "fne/network/RESTDefines.h"
#include "remote/RESTClient.h" #include "remote/RESTClient.h"
#include "network/PeerNetwork.h"
#include "HostWS.h" #include "HostWS.h"
#include "SysViewMain.h" #include "SysViewMain.h"
using namespace lookups;
#include <unistd.h> #include <unistd.h>
#include <pwd.h> #include <pwd.h>
@ -27,6 +31,110 @@
#define IDLE_WARMUP_MS 5U #define IDLE_WARMUP_MS 5U
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
/**
* @brief Helper to convert a TalkgroupRuleGroupVoice to JSON.
* @param groupVoice Instance of TalkgroupRuleGroupVoice to convert to JSON.
* @returns json::object JSON object.
*/
json::object tgToJson(const TalkgroupRuleGroupVoice& groupVoice)
{
json::object tg = json::object();
std::string tgName = groupVoice.name();
tg["name"].set<std::string>(tgName);
std::string tgAlias = groupVoice.nameAlias();
tg["alias"].set<std::string>(tgAlias);
bool invalid = groupVoice.isInvalid();
tg["invalid"].set<bool>(invalid);
// source stanza
{
json::object source = json::object();
uint32_t tgId = groupVoice.source().tgId();
source["tgid"].set<uint32_t>(tgId);
uint8_t tgSlot = groupVoice.source().tgSlot();
source["slot"].set<uint8_t>(tgSlot);
tg["source"].set<json::object>(source);
}
// config stanza
{
json::object config = json::object();
bool active = groupVoice.config().active();
config["active"].set<bool>(active);
bool affiliated = groupVoice.config().affiliated();
config["affiliated"].set<bool>(affiliated);
bool parrot = groupVoice.config().parrot();
config["parrot"].set<bool>(parrot);
json::array inclusions = json::array();
std::vector<uint32_t> inclusion = groupVoice.config().inclusion();
if (inclusion.size() > 0) {
for (auto inclEntry : inclusion) {
uint32_t peerId = inclEntry;
inclusions.push_back(json::value((double)peerId));
}
}
config["inclusion"].set<json::array>(inclusions);
json::array exclusions = json::array();
std::vector<uint32_t> exclusion = groupVoice.config().exclusion();
if (exclusion.size() > 0) {
for (auto exclEntry : exclusion) {
uint32_t peerId = exclEntry;
exclusions.push_back(json::value((double)peerId));
}
}
config["exclusion"].set<json::array>(exclusions);
json::array rewrites = json::array();
std::vector<lookups::TalkgroupRuleRewrite> rewrite = groupVoice.config().rewrite();
if (rewrite.size() > 0) {
for (auto rewrEntry : rewrite) {
json::object rewrite = json::object();
uint32_t peerId = rewrEntry.peerId();
rewrite["peerid"].set<uint32_t>(peerId);
uint32_t tgId = rewrEntry.tgId();
rewrite["tgid"].set<uint32_t>(tgId);
uint8_t tgSlot = rewrEntry.tgSlot();
rewrite["slot"].set<uint8_t>(tgSlot);
rewrites.push_back(json::value(rewrite));
}
}
config["rewrite"].set<json::array>(rewrites);
json::array always = json::array();
std::vector<uint32_t> alwaysSend = groupVoice.config().alwaysSend();
if (alwaysSend.size() > 0) {
for (auto alwaysEntry : alwaysSend) {
uint32_t peerId = alwaysEntry;
always.push_back(json::value((double)peerId));
}
}
config["always"].set<json::array>(always);
json::array preferreds = json::array();
std::vector<uint32_t> preferred = groupVoice.config().preferred();
if (preferred.size() > 0) {
for (auto prefEntry : preferred) {
uint32_t peerId = prefEntry;
preferreds.push_back(json::value((double)peerId));
}
}
config["preferred"].set<json::array>(preferreds);
tg["config"].set<json::object>(config);
}
return tg;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -161,6 +269,11 @@ int HostWS::run()
Timer peerStatusUpdate(1000U, 0U, 175U); Timer peerStatusUpdate(1000U, 0U, 175U);
peerStatusUpdate.start(); peerStatusUpdate.start();
Timer tgDataUpdate(1000U, 30U);
tgDataUpdate.start();
Timer ridDataUpdate(1000U, 30U);
ridDataUpdate.start();
setNetDataEventCallback([=](json::object obj) { netDataEvent(obj); }); setNetDataEventCallback([=](json::object obj) { netDataEvent(obj); });
// main execution loop // main execution loop
@ -188,7 +301,10 @@ int HostWS::run()
if (peerStatusUpdate.isRunning() && peerStatusUpdate.hasExpired()) { if (peerStatusUpdate.isRunning() && peerStatusUpdate.hasExpired()) {
peerStatusUpdate.start(); peerStatusUpdate.start();
getNetwork()->lockPeerStatus();
std::map<uint32_t, json::object> peerStatus(getNetwork()->peerStatus.begin(), getNetwork()->peerStatus.end()); std::map<uint32_t, json::object> peerStatus(getNetwork()->peerStatus.begin(), getNetwork()->peerStatus.end());
getNetwork()->unlockPeerStatus();
for (auto entry : peerStatus) { for (auto entry : peerStatus) {
json::object wsObj = json::object(); json::object wsObj = json::object();
std::string type = "peer_status"; std::string type = "peer_status";
@ -254,6 +370,58 @@ int HostWS::run()
} }
} }
} }
// send full talkgroup list data
tgDataUpdate.clock(ms);
if (tgDataUpdate.isRunning() && tgDataUpdate.hasExpired()) {
tgDataUpdate.start();
json::object wsObj = json::object();
std::string type = "tg_data";
json::array tgs = json::array();
if (g_tidLookup != nullptr) {
if (g_tidLookup->groupVoice().size() > 0) {
for (auto entry : g_tidLookup->groupVoice()) {
json::object tg = tgToJson(entry);
tgs.push_back(json::value(tg));
}
}
}
wsObj["payload"].set<json::array>(tgs);
send(wsObj);
}
// send full radio ID list data
ridDataUpdate.clock(ms);
if (ridDataUpdate.isRunning() && ridDataUpdate.hasExpired()) {
ridDataUpdate.start();
json::object wsObj = json::object();
std::string type = "rid_data";
json::array rids = json::array();
if (g_ridLookup != nullptr) {
if (g_ridLookup->table().size() > 0) {
for (auto entry : g_ridLookup->table()) {
json::object ridObj = json::object();
uint32_t rid = entry.first;
ridObj["id"].set<uint32_t>(rid);
bool enabled = entry.second.radioEnabled();
ridObj["enabled"].set<bool>(enabled);
std::string alias = entry.second.radioAlias();
ridObj["alias"].set<std::string>(alias);
rids.push_back(json::value(ridObj));
}
}
}
wsObj["payload"].set<json::array>(rids);
send(wsObj);
}
} else { } else {
// clear ostream // clear ostream
logOutput.str(""); logOutput.str("");

@ -19,6 +19,8 @@
#include "common/Thread.h" #include "common/Thread.h"
#include "SysViewMain.h" #include "SysViewMain.h"
#include "FDblDialog.h"
#include <final/final.h> #include <final/final.h>
using namespace finalcut; using namespace finalcut;
@ -169,7 +171,15 @@ public:
m_tbText = std::string("ENH. VOICE/CONV"); m_tbText = std::string("ENH. VOICE/CONV");
} }
else { else {
m_tbText = std::string("VOICE/CONV"); if (peerStatus["vControl"].is<bool>()) {
bool vControl = peerStatus["vControl"].getDefault<bool>(false);
if (vControl)
m_tbText = std::string("CC-VC");
else
m_tbText = std::string("VOICE/CONV");
} else {
m_tbText = std::string("VOICE/CONV");
}
} }
// are we transmitting? // are we transmitting?
@ -283,8 +293,6 @@ private:
if (FVTerm::getFOutput()->getMaxColor() < 16) if (FVTerm::getFOutput()->getMaxColor() < 16)
setBold(); setBold();
const auto& wc = getColorTheme();
if (!m_tx) { if (!m_tx) {
if (m_failed) { if (m_failed) {
setColor(FColor::Black, FColor::LightRed); setColor(FColor::Black, FColor::LightRed);
@ -452,7 +460,10 @@ public:
void update() void update()
{ {
const auto& rootWidget = getRootWidget(); const auto& rootWidget = getRootWidget();
getNetwork()->lockPeerStatus();
std::map<uint32_t, json::object> peerStatus(getNetwork()->peerStatus.begin(), getNetwork()->peerStatus.end()); std::map<uint32_t, json::object> peerStatus(getNetwork()->peerStatus.begin(), getNetwork()->peerStatus.end());
getNetwork()->unlockPeerStatus();
for (auto entry : peerStatus) { for (auto entry : peerStatus) {
uint32_t peerId = entry.first; uint32_t peerId = entry.first;
json::object peerObj = entry.second; json::object peerObj = entry.second;
@ -528,6 +539,9 @@ public:
vcObj["state"].set<uint8_t>(state); vcObj["state"].set<uint8_t>(state);
bool _true = true;
vcObj["vControl"].set<bool>(_true);
bool _false = false; bool _false = false;
vcObj["dmrTSCCEnable"].set<bool>(_false); vcObj["dmrTSCCEnable"].set<bool>(_false);
vcObj["dmrCC"].set<bool>(_false); vcObj["dmrCC"].set<bool>(_false);
@ -610,6 +624,9 @@ public:
vcObj["state"].set<uint8_t>(state); vcObj["state"].set<uint8_t>(state);
bool _true = true;
vcObj["vControl"].set<bool>(_true);
bool _false = false; bool _false = false;
vcObj["dmrTSCCEnable"].set<bool>(_false); vcObj["dmrTSCCEnable"].set<bool>(_false);
vcObj["dmrCC"].set<bool>(_false); vcObj["dmrCC"].set<bool>(_false);
@ -729,8 +746,6 @@ private:
{ {
assert(wdgt != nullptr); assert(wdgt != nullptr);
const auto& rootWidget = getRootWidget();
uint8_t channelId = peerObj["channelId"].get<uint8_t>(); uint8_t channelId = peerObj["channelId"].get<uint8_t>();
uint32_t channelNo = peerObj["channelNo"].get<uint32_t>(); uint32_t channelNo = peerObj["channelNo"].get<uint32_t>();
@ -770,13 +785,13 @@ private:
* @brief This class implements the node status window. * @brief This class implements the node status window.
* @ingroup fneSysView * @ingroup fneSysView
*/ */
class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog { class HOST_SW_API NodeStatusWnd final : public FDblDialog {
public: public:
/** /**
* @brief Initializes a new instance of the NodeStatusWnd class. * @brief Initializes a new instance of the NodeStatusWnd class.
* @param widget * @param widget
*/ */
explicit NodeStatusWnd(FWidget* widget = nullptr) : FDialog{widget}, explicit NodeStatusWnd(FWidget* widget = nullptr) : FDblDialog{widget},
m_killed(false), m_killed(false),
m_threadStopped(false) m_threadStopped(false)
{ {

@ -19,6 +19,7 @@
#include "fne/network/RESTDefines.h" #include "fne/network/RESTDefines.h"
#include "remote/RESTClient.h" #include "remote/RESTClient.h"
#include "FDblDialog.h"
#include "SysViewMainWnd.h" #include "SysViewMainWnd.h"
#include <final/final.h> #include <final/final.h>
@ -39,13 +40,13 @@ using namespace finalcut;
* @brief This class implements the peer list window. * @brief This class implements the peer list window.
* @ingroup fneSysView * @ingroup fneSysView
*/ */
class HOST_SW_API PeerListWnd final : public finalcut::FDialog { class HOST_SW_API PeerListWnd final : public FDblDialog {
public: public:
/** /**
* @brief Initializes a new instance of the PeerListWnd class. * @brief Initializes a new instance of the PeerListWnd class.
* @param widget * @param widget
*/ */
explicit PeerListWnd(FWidget* widget = nullptr) : FDialog{widget} explicit PeerListWnd(FWidget* widget = nullptr) : FDblDialog{widget}
{ {
m_timerId = addTimer(25000); // starts the timer every 25 seconds m_timerId = addTimer(25000); // starts the timer every 25 seconds
} }
@ -109,6 +110,7 @@ public:
m_listView.clear(); m_listView.clear();
json::array fnePeers = rsp["peers"].get<json::array>(); json::array fnePeers = rsp["peers"].get<json::array>();
uint32_t cnt = 0U;
for (auto entry : fnePeers) { for (auto entry : fnePeers) {
json::object peerObj = entry.get<json::object>(); json::object peerObj = entry.get<json::object>();
uint32_t peerId = peerObj["peerId"].getDefault<uint32_t>(0U); uint32_t peerId = peerObj["peerId"].getDefault<uint32_t>(0U);
@ -213,7 +215,13 @@ public:
const finalcut::FStringList line(columns.cbegin(), columns.cend()); const finalcut::FStringList line(columns.cbegin(), columns.cend());
m_listView.insert(line); m_listView.insert(line);
cnt++;
} }
std::ostringstream wndTitle;
wndTitle << "Peers View" << " [" << cnt << "] (10s)";
FDialog::setText(wndTitle.str());
} }
catch (std::exception& e) { catch (std::exception& e) {
::LogWarning(LOG_HOST, "[AFFVIEW] %s:%u, failed to properly handle peer query request, %s", fneRESTAddress.c_str(), fneRESTPort, e.what()); ::LogWarning(LOG_HOST, "[AFFVIEW] %s:%u, failed to properly handle peer query request, %s", fneRESTAddress.c_str(), fneRESTPort, e.what());

@ -153,6 +153,9 @@ std::string resolveRID(uint32_t id)
return std::string("SYS/FNE"); return std::string("SYS/FNE");
case P25DEF::WUID_ALL: case P25DEF::WUID_ALL:
return std::string("ALL CALL"); return std::string("ALL CALL");
case 0:
return std::string("EXTERNAL/PATCH");
} }
auto entry = g_ridLookup->find(id); auto entry = g_ridLookup->find(id);

@ -28,6 +28,8 @@
#include "host/modem/Modem.h" #include "host/modem/Modem.h"
#include "SysViewMain.h" #include "SysViewMain.h"
#include "FDblDialog.h"
#include <final/final.h> #include <final/final.h>
using namespace finalcut; using namespace finalcut;
@ -83,13 +85,13 @@ public:
* @brief This class implements the base class for transmit windows. * @brief This class implements the base class for transmit windows.
* @ingroup fneSysView * @ingroup fneSysView
*/ */
class HOST_SW_API TransmitWndBase : public finalcut::FDialog { class HOST_SW_API TransmitWndBase : public FDblDialog {
public: public:
/** /**
* @brief Initializes a new instance of the TransmitWndBase class. * @brief Initializes a new instance of the TransmitWndBase class.
* @param widget * @param widget
*/ */
explicit TransmitWndBase(FWidget* widget = nullptr) : FDialog{widget} explicit TransmitWndBase(FWidget* widget = nullptr) : FDblDialog{widget}
{ {
/* stub */ /* stub */
} }

@ -11,6 +11,7 @@
#include "common/network/json/json.h" #include "common/network/json/json.h"
#include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/DFSIDefines.h"
#include "common/p25/dfsi/LC.h" #include "common/p25/dfsi/LC.h"
#include "common/zlib/zlib.h"
#include "common/Utils.h" #include "common/Utils.h"
#include "network/PeerNetwork.h" #include "network/PeerNetwork.h"
#include "SysViewMain.h" #include "SysViewMain.h"
@ -18,6 +19,14 @@
using namespace network; using namespace network;
#include <cassert> #include <cassert>
#include <fstream>
#include <streambuf>
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::mutex PeerNetwork::m_peerStatusMutex;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
@ -28,7 +37,14 @@ using namespace network;
PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, 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) : 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() peerStatus(),
m_peerLink(false),
m_tgidCompressedSize(0U),
m_tgidSize(0U),
m_tgidBuffer(nullptr),
m_ridCompressedSize(0U),
m_ridSize(0U),
m_ridBuffer(nullptr)
{ {
assert(!address.empty()); assert(!address.empty());
assert(port > 0U); assert(port > 0U);
@ -60,7 +76,13 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
bool currState = g_disableTimeDisplay; bool currState = g_disableTimeDisplay;
g_disableTimeDisplay = true; g_disableTimeDisplay = true;
::Log(9999U, nullptr, "%.9u %s", peerId, payload.c_str());
std::string identity = std::string();
auto it = std::find_if(g_peerIdentityNameMap.begin(), g_peerIdentityNameMap.end(), [&](PeerIdentityMapPair x) { return x.first == peerId; });
if (it != g_peerIdentityNameMap.end())
identity = g_peerIdentityNameMap[peerId];
::Log(9999U, nullptr, "%.9u (%8s) %s", peerId, identity.c_str(), payload.c_str());
g_disableTimeDisplay = currState; g_disableTimeDisplay = currState;
} }
break; break;
@ -89,6 +111,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
} }
json::object obj = v.get<json::object>(); json::object obj = v.get<json::object>();
std::lock_guard<std::mutex> lock(m_peerStatusMutex);
peerStatus[peerId] = obj; peerStatus[peerId] = obj;
} }
break; break;
@ -99,6 +122,279 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
} }
break; break;
case NET_FUNC::PEER_LINK:
{
switch (opcode.second) {
case NET_SUBFUNC::PL_TALKGROUP_LIST:
{
uint8_t curBlock = data[8U];
uint8_t blockCnt = data[9U];
// if this is the first block store sizes and initialize temp buffer
if (curBlock == 0U) {
m_tgidSize = __GET_UINT32(data, 0U);
m_tgidCompressedSize = __GET_UINT32(data, 4U);
if (m_tgidBuffer != nullptr)
delete[] m_tgidBuffer;
if (m_tgidSize < PEER_LINK_BLOCK_SIZE)
m_tgidBuffer = new uint8_t[PEER_LINK_BLOCK_SIZE + 1U];
else
m_tgidBuffer = new uint8_t[m_tgidSize + 1U];
}
if (m_tgidBuffer != nullptr) {
if (curBlock < blockCnt) {
uint32_t offs = curBlock * PEER_LINK_BLOCK_SIZE;
::memcpy(m_tgidBuffer + offs, data + 10U, PEER_LINK_BLOCK_SIZE);
// Utils::dump(1U, "Block Payload", data, 10U + PEER_LINK_BLOCK_SIZE);
} else {
uint32_t offs = curBlock * PEER_LINK_BLOCK_SIZE;
::memcpy(m_tgidBuffer + offs, data + 10U, PEER_LINK_BLOCK_SIZE);
// Utils::dump(1U, "Block Payload", data, 10U + PEER_LINK_BLOCK_SIZE);
// Utils::dump(1U, "Compressed Payload", m_tgidBuffer, m_tgidCompressedSize);
// handle last block
// compression structures
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// set input data
strm.avail_in = m_tgidCompressedSize;
strm.next_in = m_tgidBuffer;
// initialize decompression
int ret = inflateInit(&strm);
if (ret != Z_OK) {
LogError(LOG_NET, "PEER %u error initializing ZLIB", peerId);
m_tgidSize = 0U;
m_tgidCompressedSize = 0U;
if (m_tgidBuffer != nullptr)
delete[] m_tgidBuffer;
m_tgidBuffer = nullptr;
break;
}
// decompress data
std::vector<uint8_t> decompressedData;
uint8_t outbuffer[1024];
do {
strm.avail_out = sizeof(outbuffer);
strm.next_out = outbuffer;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR) {
LogError(LOG_NET, "PEER %u error decompressing TGID list", peerId);
inflateEnd(&strm);
goto tid_lookup_cleanup; // yes - I hate myself; but this is quick
}
decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out);
} while (ret != Z_STREAM_END);
// cleanup
inflateEnd(&strm);
// scope is intentional
{
uint32_t decompressedLen = strm.total_out;
uint8_t* decompressed = decompressedData.data();
// Utils::dump(1U, "Raw TGID Data", decompressed, decompressedLen);
// check that we got the appropriate data
if (decompressedLen == m_tgidSize) {
// store to file
std::unique_ptr<char[]> __str = std::make_unique<char[]>(decompressedLen + 1U);
char* str = __str.get();
::memcpy(str, decompressed, decompressedLen);
str[decompressedLen] = 0; // null termination
// randomize filename
std::ostringstream s;
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<uint32_t> dist(0x00U, 0xFFFFFFFFU);
s << "/tmp/talkgroup_rules.yml." << dist(mt);
std::string filename = s.str();
std::ofstream file(filename, std::ofstream::out);
if (file.fail()) {
LogError(LOG_NET, "Cannot open the talkgroup ID lookup file - %s", filename.c_str());
goto tid_lookup_cleanup; // yes - I hate myself; but this is quick
}
file << str;
file.close();
m_tidLookup->stop(true);
m_tidLookup->filename(filename);
m_tidLookup->reload();
// flag this peer as Peer-Link enabled
m_peerLink = true;
// cleanup temporary file
::remove(filename.c_str());
}
else {
LogError(LOG_NET, "PEER %u error decompressed TGID list, was not of expected size! %u != %u", peerId, decompressedLen, m_tgidSize);
}
}
tid_lookup_cleanup:
m_tgidSize = 0U;
m_tgidCompressedSize = 0U;
if (m_tgidBuffer != nullptr)
delete[] m_tgidBuffer;
m_tgidBuffer = nullptr;
}
}
}
break;
case NET_SUBFUNC::PL_RID_LIST:
{
uint8_t curBlock = data[8U];
uint8_t blockCnt = data[9U];
// if this is the first block store sizes and initialize temp buffer
if (curBlock == 0U) {
m_ridSize = __GET_UINT32(data, 0U);
m_ridCompressedSize = __GET_UINT32(data, 4U);
if (m_ridBuffer != nullptr)
delete[] m_ridBuffer;
if (m_ridSize < PEER_LINK_BLOCK_SIZE)
m_ridBuffer = new uint8_t[PEER_LINK_BLOCK_SIZE + 1U];
else
m_ridBuffer = new uint8_t[m_ridSize + 1U];
}
if (m_ridBuffer != nullptr) {
if (curBlock < blockCnt) {
uint32_t offs = curBlock * PEER_LINK_BLOCK_SIZE;
::memcpy(m_ridBuffer + offs, data + 10U, PEER_LINK_BLOCK_SIZE);
// Utils::dump(1U, "Block Payload", data, 10U + PEER_LINK_BLOCK_SIZE);
} else {
uint32_t offs = curBlock * PEER_LINK_BLOCK_SIZE;
::memcpy(m_ridBuffer + offs, data + 10U, PEER_LINK_BLOCK_SIZE);
// Utils::dump(1U, "Block Payload", data, 10U + PEER_LINK_BLOCK_SIZE);
// Utils::dump(1U, "Compressed Payload", m_ridBuffer, m_ridCompressedSize);
// handle last block
// compression structures
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// set input data
strm.avail_in = m_ridCompressedSize;
strm.next_in = m_ridBuffer;
// initialize decompression
int ret = inflateInit(&strm);
if (ret != Z_OK) {
LogError(LOG_NET, "PEER %u error initializing ZLIB", peerId);
m_ridSize = 0U;
m_ridCompressedSize = 0U;
if (m_ridBuffer != nullptr)
delete[] m_ridBuffer;
m_ridBuffer = nullptr;
break;
}
// decompress data
std::vector<uint8_t> decompressedData;
uint8_t outbuffer[1024];
do {
strm.avail_out = sizeof(outbuffer);
strm.next_out = outbuffer;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR) {
LogError(LOG_NET, "PEER %u error decompressing RID list", peerId);
inflateEnd(&strm);
goto rid_lookup_cleanup; // yes - I hate myself; but this is quick
}
decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out);
} while (ret != Z_STREAM_END);
// cleanup
inflateEnd(&strm);
// scope is intentional
{
uint32_t decompressedLen = strm.total_out;
uint8_t* decompressed = decompressedData.data();
// Utils::dump(1U, "Raw RID Data", decompressed, decompressedLen);
// check that we got the appropriate data
if (decompressedLen == m_ridSize) {
// store to file
std::unique_ptr<char[]> __str = std::make_unique<char[]>(decompressedLen + 1U);
char* str = __str.get();
::memcpy(str, decompressed, decompressedLen);
str[decompressedLen] = 0; // null termination
// randomize filename
std::ostringstream s;
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<uint32_t> dist(0x00U, 0xFFFFFFFFU);
s << "/tmp/rid_acl.dat." << dist(mt);
std::string filename = s.str();
std::ofstream file(filename, std::ofstream::out);
if (file.fail()) {
LogError(LOG_NET, "Cannot open the radio ID lookup file - %s", filename.c_str());
goto rid_lookup_cleanup; // yes - I hate myself; but this is quick
}
file << str;
file.close();
m_ridLookup->stop(true);
m_ridLookup->filename(filename);
m_ridLookup->reload();
// flag this peer as Peer-Link enabled
m_peerLink = true;
// cleanup temporary file
::remove(filename.c_str());
}
else {
LogError(LOG_NET, "PEER %u error decompressed RID list, was not of expected size! %u != %u", peerId, decompressedLen, m_ridSize);
}
}
rid_lookup_cleanup:
m_ridSize = 0U;
m_ridCompressedSize = 0U;
if (m_ridBuffer != nullptr)
delete[] m_ridBuffer;
m_ridBuffer = nullptr;
}
}
}
break;
default:
break;
}
}
break;
default: default:
Utils::dump("unknown opcode from the master", data, length); Utils::dump("unknown opcode from the master", data, length);
break; break;
@ -147,6 +443,8 @@ bool PeerNetwork::writeConfig()
rcon["port"].set<uint16_t>(m_restApiPort); // REST API Port rcon["port"].set<uint16_t>(m_restApiPort); // REST API Port
config["rcon"].set<json::object>(rcon); config["rcon"].set<json::object>(rcon);
bool external = true;
config["externalPeer"].set<bool>(external); // External Peer Marker
bool convPeer = true; bool convPeer = true;
config["conventionalPeer"].set<bool>(convPeer); // Conventional Peer Marker config["conventionalPeer"].set<bool>(convPeer); // Conventional Peer Marker
bool sysView = true; bool sysView = true;

@ -25,6 +25,7 @@
#include <string> #include <string>
#include <cstdint> #include <cstdint>
#include <mutex>
namespace network namespace network
{ {
@ -56,6 +57,20 @@ namespace network
PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, 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); bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup);
/**
* @brief Flag indicating whether or not SysView has received Peer-Link data transfers.
*/
bool hasPeerLink() const { return m_peerLink; }
/**
* @brief Helper to lock the peer status mutex.
*/
void lockPeerStatus() { m_peerStatusMutex.lock(); }
/**
* @brief Helper to unlock the peer status mutex.
*/
void unlockPeerStatus() { m_peerStatusMutex.unlock(); }
/** /**
* @brief Map of peer status. * @brief Map of peer status.
*/ */
@ -78,6 +93,20 @@ namespace network
* @returns bool True, if configuration was sent, otherwise false. * @returns bool True, if configuration was sent, otherwise false.
*/ */
bool writeConfig() override; bool writeConfig() override;
private:
static std::mutex m_peerStatusMutex;
bool m_peerLink;
uint32_t m_tgidCompressedSize;
uint32_t m_tgidSize;
uint8_t* m_tgidBuffer;
uint32_t m_ridCompressedSize;
uint32_t m_ridSize;
uint8_t* m_ridBuffer;
}; };
} // namespace network } // namespace network

@ -16,6 +16,8 @@
#include "common/Thread.h" #include "common/Thread.h"
#include "FDblDialog.h"
#include <final/final.h> #include <final/final.h>
using namespace finalcut; using namespace finalcut;
@ -27,13 +29,13 @@ using namespace finalcut;
* @brief This class implements the base class for windows with close buttons. * @brief This class implements the base class for windows with close buttons.
* @ingroup tged * @ingroup tged
*/ */
class HOST_SW_API CloseWndBase : public finalcut::FDialog { class HOST_SW_API CloseWndBase : public FDblDialog {
public: public:
/** /**
* @brief Initializes a new instance of the CloseWndBase class. * @brief Initializes a new instance of the CloseWndBase class.
* @param widget * @param widget
*/ */
explicit CloseWndBase(FWidget* widget = nullptr) : FDialog{widget} explicit CloseWndBase(FWidget* widget = nullptr) : FDblDialog{widget}
{ {
/* stub */ /* stub */
} }

@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Talkgroup Editor
* 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 FDblDialog.h
* @ingroup tged
*/
#if !defined(__F_DBL_DIALOG_H__)
#define __F_DBL_DIALOG_H__
#include "common/Defines.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the double-border dialog.
* @ingroup tged
*/
class HOST_SW_API FDblDialog : public finalcut::FDialog {
public:
/**
* @brief Initializes a new instance of the FDblDialog class.
* @param widget
*/
explicit FDblDialog(FWidget* widget = nullptr) : finalcut::FDialog{widget}
{
/* stub */
}
protected:
/**
* @brief
*/
void drawBorder() override
{
if (!hasBorder())
return;
setColor();
FRect box{{1, 2}, getSize()};
box.scaleBy(0, -1);
FRect rect = box;
if (rect.x1_ref() > rect.x2_ref())
std::swap(rect.x1_ref(), rect.x2_ref());
if (rect.y1_ref() > rect.y2_ref())
std::swap(rect.y1_ref(), rect.y2_ref());
rect.x1_ref() = std::max(rect.x1_ref(), 1);
rect.y1_ref() = std::max(rect.y1_ref(), 1);
rect.x2_ref() = std::min(rect.x2_ref(), rect.x1_ref() + int(getWidth()) - 1);
rect.y2_ref() = std::min(rect.y2_ref(), rect.y1_ref() + int(getHeight()) - 1);
if (box.getWidth() < 3)
return;
// Use box-drawing characters to draw a border
constexpr std::array<wchar_t, 8> box_char
{{
static_cast<wchar_t>(0x2554), // ╔
static_cast<wchar_t>(0x2550), // ═
static_cast<wchar_t>(0x2557), // ╗
static_cast<wchar_t>(0x2551), // ║
static_cast<wchar_t>(0x2551), // ║
static_cast<wchar_t>(0x255A), // ╚
static_cast<wchar_t>(0x2550), // ═
static_cast<wchar_t>(0x255D) // ╝
}};
drawGenericBox(this, box, box_char);
}
};
#endif // __F_DBL_DIALOG_H__

@ -210,6 +210,7 @@ int main(int argc, char** argv)
g_tidLookups = new TalkgroupRulesLookup(g_iniFile, 0U, false); g_tidLookups = new TalkgroupRulesLookup(g_iniFile, 0U, false);
g_tidLookups->read(); g_tidLookups->read();
LogMessage(LOG_HOST, "Loaded talkgroup rules file: %s", g_iniFile.c_str());
// show and start the application // show and start the application
wnd.show(); wnd.show();

@ -74,7 +74,7 @@ public:
m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X
m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this); m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this);
m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this); m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this);
m_keyF5.addCallback("activate", this, [&]() { g_tidLookups->reload(); m_wnd->loadListView(); }); m_keyF5.addCallback("activate", this, [&]() { g_tidLookups->reload(); m_wnd->loadListView(); LogMessage(LOG_HOST, "Loaded talkgroup rules file: %s", g_iniFile.c_str()); });
m_backupOnSave.setChecked(); m_backupOnSave.setChecked();

@ -16,6 +16,7 @@
#include "common/Log.h" #include "common/Log.h"
#include "FDblDialog.h"
#include "TGEdMainWnd.h" #include "TGEdMainWnd.h"
#include "TGEditWnd.h" #include "TGEditWnd.h"
@ -37,13 +38,13 @@ using namespace finalcut;
* @brief This class implements the talkgroup list window. * @brief This class implements the talkgroup list window.
* @ingroup tged * @ingroup tged
*/ */
class HOST_SW_API TGListWnd final : public finalcut::FDialog { class HOST_SW_API TGListWnd final : public FDblDialog {
public: public:
/** /**
* @brief Initializes a new instance of the TGListWnd class. * @brief Initializes a new instance of the TGListWnd class.
* @param widget * @param widget
*/ */
explicit TGListWnd(FWidget* widget = nullptr) : FDialog{widget} explicit TGListWnd(FWidget* widget = nullptr) : FDblDialog{widget}
{ {
/* stub */ /* stub */
} }
@ -287,6 +288,50 @@ private:
loadListView(); loadListView();
} }
/**
* @brief
*/
void drawBorder() override
{
if (!hasBorder())
return;
setColor();
FRect box{{1, 2}, getSize()};
box.scaleBy(0, -1);
FRect rect = box;
if (rect.x1_ref() > rect.x2_ref())
std::swap(rect.x1_ref(), rect.x2_ref());
if (rect.y1_ref() > rect.y2_ref())
std::swap(rect.y1_ref(), rect.y2_ref());
rect.x1_ref() = std::max(rect.x1_ref(), 1);
rect.y1_ref() = std::max(rect.y1_ref(), 1);
rect.x2_ref() = std::min(rect.x2_ref(), rect.x1_ref() + int(getWidth()) - 1);
rect.y2_ref() = std::min(rect.y2_ref(), rect.y1_ref() + int(getHeight()) - 1);
if (box.getWidth() < 3)
return;
// Use box-drawing characters to draw a border
constexpr std::array<wchar_t, 8> box_char
{{
static_cast<wchar_t>(0x2554),
static_cast<wchar_t>(0x2550),
static_cast<wchar_t>(0x2557),
static_cast<wchar_t>(0x2551),
static_cast<wchar_t>(0x2551),
static_cast<wchar_t>(0x255A),
static_cast<wchar_t>(0x2550),
static_cast<wchar_t>(0x255D)
}};
drawGenericBox(this, box, box_char);
}
/* /*
** Event Handlers ** Event Handlers
*/ */

@ -0,0 +1,34 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only
#/**
#* Digital Voice Modem - Host Software
#* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#*
#*/
#/*
#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU General Public License as published by
#* the Free Software Foundation; either version 2 of the License, or
#* (at your option) any later version.
#*
#* This program is distributed in the hope that it will be useful,
#* but WITHOUT ANY WARRANTY; without even the implied warranty of
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#* GNU General Public License for more details.
#*
#* You should have received a copy of the GNU General Public License
#* along with this program; if not, write to the Free Software
#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#*/
LOG_COLOR="s#W:#\x1b[0m\x1b[1m\x1b[33m&#; s#E:#\x1b[0m\x1b[1m\x1b[31m&#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&#; s#U:#\x1b[44m\x1b[1m\x1b[33m&#;"
P25_COLOR="s#LDU#\x1b[36m&#; s#TDU#\x1b[0m\x1b[32m&#; s#HDU#\x1b[0m\x1b[32m&#; s#TSDU#\x1b[0m\x1b[35m&#"
AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#;"
RF_HIGHLIGHT="s#(RF)#\x1b[1m\x1b[34m&\x1b[0m#;"
NET_HIGHLIGHT="s#(NET)#\x1b[1m\x1b[36m&\x1b[0m#;"
sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${P25_COLOR}; ${AFF_COLOR}"

@ -5,8 +5,6 @@
#* GPLv2 Open Source. Use is subject to license terms. #* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. #* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#* #*
#* @package DVM / Host Software
#*
#*/ #*/
#/* #/*
#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL #* Copyright (C) 2022 by Bryan Biedenkapp N2PLL

@ -5,8 +5,6 @@
#* GPLv2 Open Source. Use is subject to license terms. #* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. #* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#* #*
#* @package DVM / Host Software
#*
#*/ #*/
#/* #/*
#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL #* Copyright (C) 2022 by Bryan Biedenkapp N2PLL

@ -5,8 +5,6 @@
#* GPLv2 Open Source. Use is subject to license terms. #* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. #* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#* #*
#* @package DVM / Host Software
#*
#*/ #*/
#/* #/*
#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL #* Copyright (C) 2022 by Bryan Biedenkapp N2PLL

@ -5,8 +5,6 @@
#* GPLv2 Open Source. Use is subject to license terms. #* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. #* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#* #*
#* @package DVM / Host Software
#*
#*/ #*/
#/* #/*
#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL #* Copyright (C) 2022 by Bryan Biedenkapp N2PLL

@ -5,8 +5,6 @@
#* GPLv2 Open Source. Use is subject to license terms. #* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. #* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#* #*
#* @package DVM / Host Software
#*
#*/ #*/
#/* #/*
#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL #* Copyright (C) 2022 by Bryan Biedenkapp N2PLL

@ -5,8 +5,6 @@
#* GPLv2 Open Source. Use is subject to license terms. #* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. #* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#* #*
#* @package DVM / Host Software
#*
#*/ #*/
#/* #/*
#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL #* Copyright (C) 2022 by Bryan Biedenkapp N2PLL

Loading…
Cancel
Save

Powered by TurnKey Linux.