REST -> RPC Migration (#84)

* migrate away from REST API for inter-dvmhost operations towards a custom UDP RPC framework;

* replace config parameters for REST API in some places with properly named RPC; swap peer Network, FNE DiagNetwork and FNENetwork over from if-else-if ladders to switch statements (switch statements perform logically better after compilation because the compiler tends to optimize these into jump-tables which execute faster);

* continued work on inter-dvmhost REST to RPC transition;

* update build bumper for R04G20 to R04G21;

* cleanup config file;

* clean up doc/commenting;
pull/85/head
Bryan Biedenkapp 11 months ago committed by GitHub
parent 6b5c61009a
commit 3da4eb2d40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -36,6 +36,9 @@ log:
# Network Configuration
#
network:
#
# FNE Configuration
#
# Flag indicating whether or not host networking is enabled.
enable: true
# Network Peer ID
@ -77,6 +80,21 @@ network:
# Flag indicating whether or not verbose debug logging is enabled.
debug: false
#
# RPC Configuration
#
# IP address of the network interface to listen for RPC calls on (or 0.0.0.0 for all).
rpcAddress: 127.0.0.1
# Port number for RPC calls to listen on.
rpcPort: 9890
# RPC access password.
rpcPassword: "ULTRA-VERY-SECURE-DEFAULT"
# Flag indicating whether or not verbose RPC debug logging is enabled.
rpcDebug: false
#
# REST API Configuration
#
# Flag indicating whether or not REST API is enabled.
restEnable: false
# IP address of the network interface to listen for REST API on (or 0.0.0.0 for all).
@ -392,14 +410,12 @@ system:
# These parameters should be left default for dedicated control channels.
#
controlCh:
# REST API IP Address for control channel. (If blank, notifications are disabled.)
restAddress:
# REST API Port number for control channel. (If 0, notifications are disabled.)
restPort: 0
# REST API access password for control channel.
restPassword: "PASSWORD"
# Flag indicating whether or not REST API is operating in SSL mode.
restSsl: false
# RPC IP Address for control channel. (If blank, notifications are disabled.)
rpcAddress: 127.0.0.1
# RPC Port number for control channel. (If 0, notifications are disabled.)
rpcPort: 0
# RPC access password for control channel.
rpcPassword: "ULTRA-VERY-SECURE-DEFAULT"
# Flag indicating voice channels will notify the control channel of traffic status.
notifyEnable: true
# Amount of time between network presence announcements. (seconds)
@ -414,14 +430,12 @@ system:
- channelId: 2
# Channel Number (used to calculate actual host frequency based on the identity table).
channelNo: 1
# REST API IP Address for voice channel.
restAddress: 127.0.0.1
# REST API Port number for voice channel.
restPort: 9990
# REST API access password for voice channel.
restPassword: "PASSWORD"
# Flag indicating whether or not REST API is operating in SSL mode.
restSsl: false
# RPC IP Address for voice channel.
rpcAddress: 127.0.0.1
# RPC Port number for voice channel.
rpcPort: 9890
# RPC access password for voice channel.
rpcPassword: "ULTRA-VERY-SECURE-DEFAULT"
secure:
# AES-128 16-byte Key (used for LLA)

@ -108,7 +108,7 @@ typedef unsigned long long ulong64_t;
#define __EXE_NAME__ ""
#define VERSION_MAJOR "04"
#define VERSION_MINOR "20"
#define VERSION_MINOR "21"
#define VERSION_REV "G"
#define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR
@ -153,6 +153,7 @@ typedef unsigned long long ulong64_t;
const uint32_t REMOTE_MODEM_PORT = 3334;
const uint32_t TRAFFIC_DEFAULT_PORT = 62031;
const uint32_t REST_API_DEFAULT_PORT = 9990;
const uint32_t RPC_DEFAULT_PORT = 9890;
/**
* @brief Operational Host States

@ -61,12 +61,12 @@ namespace lookups
* @brief Initializes a new instance of the VoiceChData class.
* @param chId Voice Channel Identity.
* @param chNo Voice Channel Number.
* @param address REST API Address.
* @param port REST API Port.
* @param password REST API Password.
* @param address RPC/REST API Address.
* @param port RPC/REST API Port.
* @param password RPC/REST API Password.
* @param ssl Flag indicating REST is using SSL.
*/
VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl) :
VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl = false) :
m_chId(chId),
m_chNo(chNo),
m_address(address),
@ -116,15 +116,15 @@ namespace lookups
*/
__READONLY_PROPERTY_PLAIN(uint32_t, chNo);
/**
* @brief REST API Address.
* @brief RPC/REST API Address.
*/
__PROPERTY_PLAIN(std::string, address);
/**
* @brief REST API Port.
* @brief RPC/REST API Port.
*/
__PROPERTY_PLAIN(uint16_t, port);
/**
* @brief REST API Password.
* @brief RPC/REST API Password.
*/
__READONLY_PROPERTY_PLAIN(std::string, password);
/**

@ -251,9 +251,9 @@ void Network::clock(uint32_t ms)
m_pktLastSeq = 0U;
}
// process incoming message frame opcodes
// process incoming message function opcodes
switch (fneHeader.getFunction()) {
case NET_FUNC::PROTOCOL:
case NET_FUNC::PROTOCOL: // Protocol
{
// are protocol messages being user handled?
if (m_userHandleProtocol) {
@ -262,336 +262,371 @@ void Network::clock(uint32_t ms)
break;
}
if (fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR) { // Encapsulated DMR data frame
if (m_enabled && m_dmrEnabled) {
uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 1U : 0U; // this is the raw index for the stream ID array
// process incomfing message subfunction opcodes
switch (fneHeader.getSubFunction()) {
case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame
{
if (m_enabled && m_dmrEnabled) {
uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 1U : 0U; // this is the raw index for the stream ID array
if (m_debug) {
LogDebug(LOG_NET, "DMR Slot %u, peer = %u, len = %u, pktSeq = %u, streamId = %u",
slotNo + 1U, peerId, length, rtpHeader.getSequence(), streamId);
}
if (m_promiscuousPeer) {
m_rxDMRStreamId[slotNo] = streamId;
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxDMRStreamId[slotNo] == 0U) {
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxDMRStreamId[slotNo] = 0U;
}
else {
m_rxDMRStreamId[slotNo] = streamId;
}
if (m_debug) {
LogDebug(LOG_NET, "DMR Slot %u, peer = %u, len = %u, pktSeq = %u, streamId = %u",
slotNo + 1U, peerId, length, rtpHeader.getSequence(), streamId);
}
if (m_promiscuousPeer) {
m_rxDMRStreamId[slotNo] = streamId;
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxDMRStreamId[slotNo] == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) {
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "DMR Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1);
}
if (m_rxDMRStreamId[slotNo] == 0U) {
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxDMRStreamId[slotNo] = 0U;
}
else {
m_rxDMRStreamId[slotNo] = streamId;
}
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxDMRStreamId[slotNo] == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) {
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "DMR Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1);
}
}
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxDMRStreamId[slotNo] = 0U;
m_pktLastSeq = m_pktSeq;
}
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxDMRStreamId[slotNo] = 0U;
}
}
}
}
if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length);
if (length > 255)
LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length);
if (length > 255)
LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
uint8_t len = length;
m_rxDMRData.addData(&len, 1U);
m_rxDMRData.addData(buffer.get(), len);
}
}
else if (fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_P25) { // Encapsulated P25 data frame
if (m_enabled && m_p25Enabled) {
if (m_debug) {
LogDebug(LOG_NET, "P25, peer = %u, len = %u, pktSeq = %u, streamId = %u",
peerId, length, rtpHeader.getSequence(), streamId);
uint8_t len = length;
m_rxDMRData.addData(&len, 1U);
m_rxDMRData.addData(buffer.get(), len);
}
}
break;
if (m_promiscuousPeer) {
m_rxP25StreamId = streamId;
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxP25StreamId == 0U) {
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxP25StreamId = 0U;
}
else {
m_rxP25StreamId = streamId;
}
case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame
{
if (m_enabled && m_p25Enabled) {
if (m_debug) {
LogDebug(LOG_NET, "P25, peer = %u, len = %u, pktSeq = %u, streamId = %u",
peerId, length, rtpHeader.getSequence(), streamId);
}
if (m_promiscuousPeer) {
m_rxP25StreamId = streamId;
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxP25StreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) {
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "P25 Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1);
}
if (m_rxP25StreamId == 0U) {
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxP25StreamId = 0U;
}
else {
m_rxP25StreamId = streamId;
}
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxP25StreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) {
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "P25 Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1);
}
}
m_pktLastSeq = m_pktSeq;
}
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxP25StreamId = 0U;
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxP25StreamId = 0U;
}
}
}
}
if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length);
if (length > 255)
LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length);
if (length > 255)
LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
uint8_t len = length;
m_rxP25Data.addData(&len, 1U);
m_rxP25Data.addData(buffer.get(), len);
}
}
else if (fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN) { // Encapsulated NXDN data frame
if (m_enabled && m_nxdnEnabled) {
if (m_debug) {
LogDebug(LOG_NET, "NXDN, peer = %u, len = %u, pktSeq = %u, streamId = %u",
peerId, length, rtpHeader.getSequence(), streamId);
uint8_t len = length;
m_rxP25Data.addData(&len, 1U);
m_rxP25Data.addData(buffer.get(), len);
}
}
break;
if (m_promiscuousPeer) {
m_rxNXDNStreamId = streamId;
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxNXDNStreamId == 0U) {
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxNXDNStreamId = 0U;
}
else {
m_rxNXDNStreamId = streamId;
}
case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame
{
if (m_enabled && m_nxdnEnabled) {
if (m_debug) {
LogDebug(LOG_NET, "NXDN, peer = %u, len = %u, pktSeq = %u, streamId = %u",
peerId, length, rtpHeader.getSequence(), streamId);
}
if (m_promiscuousPeer) {
m_rxNXDNStreamId = streamId;
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxNXDNStreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) {
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "NXDN Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1);
}
if (m_rxNXDNStreamId == 0U) {
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxNXDNStreamId = 0U;
}
else {
m_rxNXDNStreamId = streamId;
}
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxNXDNStreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) {
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "NXDN Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1);
}
}
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxNXDNStreamId = 0U;
m_pktLastSeq = m_pktSeq;
}
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxNXDNStreamId = 0U;
}
}
}
}
if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length);
if (length > 255)
LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length);
if (length > 255)
LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
uint8_t len = length;
m_rxNXDNData.addData(&len, 1U);
m_rxNXDNData.addData(buffer.get(), len);
uint8_t len = length;
m_rxNXDNData.addData(&len, 1U);
m_rxNXDNData.addData(buffer.get(), len);
}
}
}
else {
break;
default:
Utils::dump("unknown protocol opcode from the master", buffer.get(), length);
break;
}
}
break;
case NET_FUNC::MASTER:
case NET_FUNC::MASTER: // Master
{
if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_WL_RID) { // Radio ID Whitelist
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, WL RID", buffer.get(), length);
if (m_ridLookup != nullptr) {
// update RID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
m_ridLookup->toggleEntry(id, true);
offs += 4U;
}
// process incomfing message subfunction opcodes
switch (fneHeader.getSubFunction()) {
case NET_SUBFUNC::MASTER_SUBFUNC_WL_RID: // Radio ID Whitelist
{
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, WL RID", buffer.get(), length);
if (m_ridLookup != nullptr) {
// update RID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
m_ridLookup->toggleEntry(id, true);
offs += 4U;
}
LogMessage(LOG_NET, "Network Announced %u whitelisted RIDs", len);
LogMessage(LOG_NET, "Network Announced %u whitelisted RIDs", len);
// save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) {
m_ridLookup->commit();
// save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) {
m_ridLookup->commit();
}
}
}
}
}
else if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_BL_RID) { // Radio ID Blacklist
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, BL RID", buffer.get(), length);
if (m_ridLookup != nullptr) {
// update RID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
m_ridLookup->toggleEntry(id, false);
offs += 4U;
}
break;
case NET_SUBFUNC::MASTER_SUBFUNC_BL_RID: // Radio ID Blacklist
{
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, BL RID", buffer.get(), length);
if (m_ridLookup != nullptr) {
// update RID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
m_ridLookup->toggleEntry(id, false);
offs += 4U;
}
LogMessage(LOG_NET, "Network Announced %u blacklisted RIDs", len);
LogMessage(LOG_NET, "Network Announced %u blacklisted RIDs", len);
// save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) {
m_ridLookup->commit();
// save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) {
m_ridLookup->commit();
}
}
}
}
}
else if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_ACTIVE_TGS) { // Talkgroup Active IDs
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, ACTIVE TGS", buffer.get(), length);
if (m_tidLookup != nullptr) {
// update TGID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
uint8_t slot = (buffer[offs + 3U]) & 0x03U;
bool affiliated = (buffer[offs + 3U] & 0x40U) == 0x40U;
bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U;
lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot);
// if the TG is marked as non-preferred, and the TGID exists in the local entries
// erase the local and overwrite with the FNE data
if (nonPreferred) {
if (!tid.isInvalid()) {
m_tidLookup->eraseEntry(id, slot);
tid = m_tidLookup->find(id, slot);
break;
case NET_SUBFUNC::MASTER_SUBFUNC_ACTIVE_TGS: // Talkgroup Active IDs
{
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, ACTIVE TGS", buffer.get(), length);
if (m_tidLookup != nullptr) {
// update TGID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
uint8_t slot = (buffer[offs + 3U]) & 0x03U;
bool affiliated = (buffer[offs + 3U] & 0x40U) == 0x40U;
bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U;
lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot);
// if the TG is marked as non-preferred, and the TGID exists in the local entries
// erase the local and overwrite with the FNE data
if (nonPreferred) {
if (!tid.isInvalid()) {
m_tidLookup->eraseEntry(id, slot);
tid = m_tidLookup->find(id, slot);
}
}
}
if (tid.isInvalid()) {
if (!tid.config().active()) {
m_tidLookup->eraseEntry(id, slot);
if (tid.isInvalid()) {
if (!tid.config().active()) {
m_tidLookup->eraseEntry(id, slot);
}
LogMessage(LOG_NET, "Activated%s%s TG %u TS %u in TGID table",
(nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot);
m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred);
}
LogMessage(LOG_NET, "Activated%s%s TG %u TS %u in TGID table",
(nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot);
m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred);
}
offs += 5U;
}
offs += 5U;
}
LogMessage(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size());
LogMessage(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size());
// save if saving from network is enabled
if (m_saveLookup && len > 0) {
m_tidLookup->commit();
// save if saving from network is enabled
if (m_saveLookup && len > 0) {
m_tidLookup->commit();
}
}
}
}
}
else if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_DEACTIVE_TGS) { // Talkgroup Deactivated IDs
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, DEACTIVE TGS", buffer.get(), length);
if (m_tidLookup != nullptr) {
// update TGID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
uint8_t slot = (buffer[offs + 3U]);
lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot);
if (!tid.isInvalid()) {
LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot);
m_tidLookup->eraseEntry(id, slot);
}
break;
case NET_SUBFUNC::MASTER_SUBFUNC_DEACTIVE_TGS: // Talkgroup Deactivated IDs
{
if (m_enabled && m_updateLookup) {
if (m_debug)
Utils::dump(1U, "Network Received, DEACTIVE TGS", buffer.get(), length);
if (m_tidLookup != nullptr) {
// update TGID lists
uint32_t len = __GET_UINT32(buffer, 6U);
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
uint8_t slot = (buffer[offs + 3U]);
lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot);
if (!tid.isInvalid()) {
LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot);
m_tidLookup->eraseEntry(id, slot);
}
offs += 5U;
}
offs += 5U;
}
LogMessage(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size());
LogMessage(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size());
// save if saving from network is enabled
if (m_saveLookup && len > 0) {
m_tidLookup->commit();
// save if saving from network is enabled
if (m_saveLookup && len > 0) {
m_tidLookup->commit();
}
}
}
}
}
else {
break;
default:
Utils::dump("unknown master control opcode from the master", buffer.get(), length);
break;
}
}
break;
case NET_FUNC::INCALL_CTRL:
case NET_FUNC::INCALL_CTRL: // In-Call Control
{
if (fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR) { // DMR In-Call Control
if (m_enabled && m_dmrEnabled) {
NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U];
uint32_t dstId = __GET_UINT16(buffer, 11U);
uint8_t slot = buffer[14U];
if (m_dmrInCallCallback != nullptr) {
m_dmrInCallCallback(command, dstId, slot);
// process incomfing message subfunction opcodes
switch (fneHeader.getSubFunction()) {
case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // DMR In-Call Control
{
if (m_enabled && m_dmrEnabled) {
NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U];
uint32_t dstId = __GET_UINT16(buffer, 11U);
uint8_t slot = buffer[14U];
if (m_dmrInCallCallback != nullptr) {
m_dmrInCallCallback(command, dstId, slot);
}
}
}
}
else if (fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_P25) { // P25 In-Call Control
if (m_enabled && m_p25Enabled) {
NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U];
uint32_t dstId = __GET_UINT16(buffer, 11U);
if (m_p25InCallCallback != nullptr) {
m_p25InCallCallback(command, dstId);
}
break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // P25 In-Call Control
{
if (m_enabled && m_p25Enabled) {
NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U];
uint32_t dstId = __GET_UINT16(buffer, 11U);
if (m_p25InCallCallback != nullptr) {
m_p25InCallCallback(command, dstId);
}
}
}
}
else if (fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN) { // NXDN In-Call Control
if (m_enabled && m_nxdnEnabled) {
NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U];
uint32_t dstId = __GET_UINT16(buffer, 11U);
if (m_nxdnInCallCallback != nullptr) {
m_nxdnInCallCallback(command, dstId);
break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // NXDN In-Call Control
{
if (m_enabled && m_nxdnEnabled) {
NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U];
uint32_t dstId = __GET_UINT16(buffer, 11U);
if (m_nxdnInCallCallback != nullptr) {
m_nxdnInCallCallback(command, dstId);
}
}
}
}
else {
Utils::dump("unknown protocol opcode from the master", buffer.get(), length);
break;
default:
Utils::dump("unknown incall control opcode from the master", buffer.get(), length);
break;
}
}
break;
case NET_FUNC::KEY_RSP: // Enc. Key Response
case NET_FUNC::KEY_RSP: // Enc. Key Response
{
if (m_enabled) {
using namespace p25::kmm;
@ -629,7 +664,7 @@ void Network::clock(uint32_t ms)
}
break;
case NET_FUNC::NAK: // Master Negative Ack
case NET_FUNC::NAK: // Master Negative Ack
{
// DVM 3.6 adds support to respond with a NAK reason, as such we just check if the NAK response is greater
// then 10 bytes and process the reason value
@ -687,7 +722,7 @@ void Network::clock(uint32_t ms)
}
}
break;
case NET_FUNC::ACK: // Repeater Ack
case NET_FUNC::ACK: // Repeater Ack
{
switch (m_status) {
case NET_STAT_WAITING_LOGIN:
@ -732,7 +767,7 @@ void Network::clock(uint32_t ms)
}
}
break;
case NET_FUNC::MST_CLOSING: // Master Shutdown
case NET_FUNC::MST_CLOSING: // Master Shutdown
{
LogError(LOG_NET, "PEER %u master is closing down, remotePeerId = %u", m_peerId, m_remotePeerId);
m_status = NET_STAT_WAITING_CONNECT;
@ -740,7 +775,7 @@ void Network::clock(uint32_t ms)
open();
}
break;
case NET_FUNC::PONG: // Master Ping Response
case NET_FUNC::PONG: // Master Ping Response
m_timeoutTimer.start();
if (length >= 14) {
if (m_debug)

@ -74,7 +74,7 @@ namespace network
/**
* @brief Implements the core peer networking logic.
* @ingroup network
* @ingroup network_core
*/
class HOST_SW_API Network : public BaseNetwork {
public:

@ -0,0 +1,303 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "common/edac/CRC.h"
#include "common/edac/SHA256.h"
#include "common/network/RPCHeader.h"
#include "common/network/json/json.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "network/RPC.h"
using namespace network;
using namespace network::frame;
#include <cstdio>
#include <cassert>
#include <cmath>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the RPC class. */
RPC::RPC(const std::string& address, uint16_t port, uint16_t localPort, const std::string& password, bool debug) :
m_address(address),
m_port(port),
m_debug(debug),
m_socket(nullptr),
m_frameQueue(nullptr),
m_password(password),
m_handlers()
{
assert(!address.empty());
assert(port > 0U);
assert(!password.empty());
m_socket = new udp::Socket(address, port);
m_frameQueue = new RawFrameQueue(m_socket, debug);
}
/* Finalizes a instance of the RPC class. */
RPC::~RPC()
{
if (m_frameQueue != nullptr) {
delete m_frameQueue;
}
if (m_socket != nullptr) {
delete m_socket;
}
}
/* Updates the timer by the passed number of milliseconds. */
void RPC::clock(uint32_t ms)
{
sockaddr_storage address;
uint32_t addrLen;
frame::RPCHeader rpcHeader;
int length = 0U;
// read message
UInt8Array buffer = m_frameQueue->read(length, address, addrLen);
uint8_t* raw = buffer.get();
if (length > 0) {
if (length < RPC_HEADER_LENGTH_BYTES) {
LogError(LOG_NET, "RPC::clock(), message received from network is malformed! %u bytes != %u bytes",
RPC_HEADER_LENGTH_BYTES, length);
return;
}
// decode RTP header
if (!rpcHeader.decode(buffer.get())) {
LogError(LOG_NET, "RPC::clock(), invalid RPC packet received from network");
return;
}
if (m_debug) {
LogDebugEx(LOG_NET, "RPC::clock()", "RPC, func = $%04X, messageLength = %u", rpcHeader.getFunction(), rpcHeader.getMessageLength());
}
// copy message
uint32_t messageLength = rpcHeader.getMessageLength();
UInt8Array message = std::unique_ptr<uint8_t[]>(new uint8_t[messageLength]);
::memcpy(message.get(), raw + RPC_HEADER_LENGTH_BYTES, messageLength);
uint16_t calc = edac::CRC::createCRC16(message.get(), messageLength * 8U);
if (m_debug) {
LogDebugEx(LOG_NET, "RPC::clock()", "RPC, calc = $%04X, crc = $%04X", calc, rpcHeader.getCRC());
}
if (calc != rpcHeader.getCRC()) {
LogError(LOG_NET, "RPC::clock(), failed CRC CCITT-162 check");
return;
}
// parse JSON body
std::string content = std::string((char*)message.get());
json::value v;
std::string err = json::parse(v, content);
if (!err.empty()) {
LogError(LOG_NET, "RPC::clock(), invalid RPC JSON payload, %s", err.c_str());
return;
}
// ensure parsed JSON is an object
if (!v.is<json::object>()) {
LogError(LOG_NET, "RPC::clock(), invalid RPC JSON payload, request was not a JSON object");
return;
}
json::object request = v.get<json::object>();
json::object response;
// find RPC function callback
if (m_handlers.find(rpcHeader.getFunction()) != m_handlers.end()) {
m_handlers[rpcHeader.getFunction()](request, response);
// remove the reply handler (these should be temporary)
if ((rpcHeader.getFunction() & RPC_REPLY_FUNC) == RPC_REPLY_FUNC) {
m_handlers.erase(rpcHeader.getFunction());
} else {
reply(rpcHeader.getFunction(), response, address, addrLen);
}
} else {
bool isReply = (rpcHeader.getFunction() & RPC_REPLY_FUNC) == RPC_REPLY_FUNC;
if (isReply) {
if (!request["status"].is<int>()) {
::LogError(LOG_NET, "RPC %s:%u, invalid RPC response", udp::Socket::address(address).c_str(), udp::Socket::port(address));
return;
}
int status = request["status"].get<int>();
if (status != network::RPC::OK) {
if (request["message"].is<std::string>()) {
std::string retMsg = request["message"].get<std::string>();
::LogError(LOG_NET, "RPC %s:%u failed, %s", udp::Socket::address(address).c_str(), udp::Socket::port(address), retMsg.c_str());
}
}
}
else
LogWarning(LOG_NET, "RPC::clock(), ignoring unhandled function, func = $%04X, reply = %u", rpcHeader.getFunction() & 0x3FFFU, isReply);
}
}
}
/* Writes an RPC request to the network. */
bool RPC::req(uint16_t func, const json::object& request, RPCType reply, std::string address, uint16_t port)
{
sockaddr_storage addr;
uint32_t addrLen = 0U;
if (udp::Socket::lookup(address, port, addr, addrLen) == 0) {
return req(func, request, reply, addr, addrLen);
}
return false;
}
/* Writes an RPC request to the network. */
bool RPC::req(uint16_t func, const json::object& request, RPCType reply, sockaddr_storage& address, uint32_t addrLen)
{
json::value v = json::value(request);
std::string json = v.serialize();
// generate RPC header
RPCHeader header = RPCHeader();
header.setFunction(func & 0x3FFFU);
header.setMessageLength(json.length() + 1U);
// generate message
CharArray __message = std::make_unique<char[]>(json.length() + 1U);
char* message = __message.get();
::snprintf(message, json.length() + 1U, "%s", json.c_str());
uint16_t crc = edac::CRC::createCRC16((uint8_t*)message, (json.length() + 1U) * 8U);
if (m_debug) {
LogDebugEx(LOG_NET, "RPC::req()", "RPC, crc = $%04X", crc);
}
header.setCRC(crc);
// generate RPC message
UInt8Array __buffer = std::make_unique<uint8_t[]>(json.length() + 9U);
uint8_t* buffer = __buffer.get();
header.encode(buffer);
::memcpy(buffer + 8U, message, json.length() + 1U);
// install reply handler
if (reply != nullptr)
m_handlers[func | RPC_REPLY_FUNC] = reply;
return m_frameQueue->write(buffer, json.length() + 9U, address, addrLen);
}
/* Helper to generate a default response error payload. */
void RPC::defaultResponse(json::object& reply, std::string message, StatusType status)
{
reply = json::object();
int s = (int)status;
reply["status"].set<int>(s);
reply["message"].set<std::string>(message);
}
/* Opens connection to the network. */
bool RPC::open()
{
if (m_debug)
LogMessage(LOG_NET, "Opening RPC network");
// generate AES256 key
size_t size = m_password.size();
uint8_t* in = new uint8_t[size];
for (size_t i = 0U; i < size; i++)
in[i] = m_password.at(i);
uint8_t passwordHash[32U];
::memset(passwordHash, 0x00U, 32U);
edac::SHA256 sha256;
sha256.buffer(in, (uint32_t)(size), passwordHash);
delete[] in;
m_socket->setPresharedKey(passwordHash);
return m_socket->open();
}
/* Closes connection to the network. */
void RPC::close()
{
if (m_debug)
LogMessage(LOG_NET, "Closing RPC network");
m_socket->close();
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Writes an RPC reply to the network. */
bool RPC::reply(uint16_t func, json::object& reply, sockaddr_storage& address, uint32_t addrLen)
{
json::value v = json::value(reply);
std::string json = v.serialize();
// generate RPC header
RPCHeader header = RPCHeader();
header.setFunction(func | RPC_REPLY_FUNC);
header.setMessageLength(json.length() + 1U);
// generate message
CharArray __message = std::make_unique<char[]>(json.length() + 1U);
char* message = __message.get();
::snprintf(message, json.length() + 1U, "%s", json.c_str());
uint16_t crc = edac::CRC::createCRC16((uint8_t*)message, (json.length() + 1U) * 8U);
header.setCRC(crc);
// generate RPC message
UInt8Array __buffer = std::make_unique<uint8_t[]>(json.length() + 9U);
uint8_t* buffer = __buffer.get();
header.encode(buffer);
::memcpy(buffer + 8U, message, json.length() + 1U);
return m_frameQueue->write(buffer, json.length() + 9U, address, addrLen);
}
/* Default status response handler. */
void RPC::defaultHandler(json::object& request, json::object& reply)
{
reply = json::object();
int s = (int)StatusType::UNHANDLED_REQUEST;
reply["status"].set<int>(s);
reply["message"].set<std::string>("unhandled request");
}

@ -0,0 +1,162 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file RPC.h
* @ingroup network_core
* @file RPC.cpp
* @ingroup network_core
*/
#if !defined(__RPC_H__)
#define __RPC_H__
#include "common/Defines.h"
#include "common/network/RawFrameQueue.h"
#include "common/network/json/json.h"
#include "common/network/udp/Socket.h"
#include <string>
#include <cstdint>
#include <functional>
#include <unordered_map>
namespace network
{
// ---------------------------------------------------------------------------
// Macros
// ---------------------------------------------------------------------------
#define RPC_FUNC_BIND(funcAddr, classInstance) std::bind(&funcAddr, classInstance, std::placeholders::_1, std::placeholders::_2)
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements the Remote Procedure Call networking logic.
* @ingroup network_core
*/
class HOST_SW_API RPC {
public:
typedef std::function<void(json::object& request, json::object& reply)> RPCType;
/**
* @brief Status/Response Codes
*/
enum StatusType {
OK = 200, //! OK 200
BAD_REQUEST = 400, //! Bad Request 400
INVALID_ARGS = 401, //! Invalid Arguments 401
UNHANDLED_REQUEST = 402, //! Unhandled Request 402
} status;
/**
* @brief Initializes a new instance of the RPC class.
* @param address Network Hostname/IP address to connect to.
* @param port Network port number.
* @param localPort
* @param peerId Unique ID on the network.
* @param password Network authentication password.
* @param debug Flag indicating whether network debug is enabled.
*/
RPC(const std::string& address, uint16_t port, uint16_t localPort, const std::string& password, bool debug);
/**
* @brief Finalizes a instance of the RPC class.
*/
~RPC();
/**
* @brief Updates the timer by the passed number of milliseconds.
* @param ms Number of milliseconds.
*/
void clock(uint32_t ms);
/**
* @brief Writes an RPC request to the network.
* @param request JSON content body for request.
* @param reply Reply handler.
* @param address IP address to write data to.
* @param port Port number to write data to.
* @returns bool True, if message was written, otherwise false.
*/
bool req(uint16_t func, const json::object& request, RPCType reply, std::string address, uint16_t port);
/**
* @brief Writes an RPC request to the network.
* @param request JSON content body for request.
* @param reply Reply handler.
* @param address IP address to write data to.
* @param addrLen
* @returns bool True, if message was written, otherwise false.
*/
bool req(uint16_t func, const json::object& request, RPCType reply, sockaddr_storage& address, uint32_t addrLen);
/**
* @brief Helper to generate a default response error payload.
* @param reply JSON reply.
* @param message Textual error message to send.
* @param status Status to send.
*/
void defaultResponse(json::object &reply, std::string message, StatusType status = StatusType::BAD_REQUEST);
/**
* @brief Opens connection to the network.
* @returns bool True, if networking has started, otherwise false.
*/
bool open();
/**
* @brief Closes connection to the network.
*/
void close();
/**
* @brief Helper to register an RPC handler.
* @param func Function opcode.
* @param handler Function handler.
*/
void registerHandler(uint16_t func, RPCType handler) { m_handlers[func] = handler; }
/**
* @brief Helper to unregister an RPC handler.
* @param func Function opcode.
*/
void unregisterHandler(uint16_t func) { m_handlers.erase(func); }
private:
std::string m_address;
uint16_t m_port;
bool m_debug;
udp::Socket* m_socket;
RawFrameQueue* m_frameQueue;
std::string m_password;
std::map<uint16_t, RPCType> m_handlers;
/**
* @brief Writes an RPC reply to the network.
* @param request JSON content body for reply.
* @param address IP address to write data to.
* @param addrLen
* @returns bool True, if message was written, otherwise false.
*/
bool reply(uint16_t func, json::object& request, sockaddr_storage& address, uint32_t addrLen);
/**
* @brief Default status response handler.
* @param request JSON request.
* @param reply JSON response.
*/
void defaultHandler(json::object& request, json::object& reply);
};
} // namespace network
#endif // __RPC_H__

@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "network/RPCHeader.h"
#include "Utils.h"
using namespace network::frame;
#include <cassert>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the RPCHeader class. */
RPCHeader::RPCHeader() :
m_crc16(0U),
m_func(0U),
m_messageLength(0U)
{
/* stub */
}
/* Finalizes a instance of the RPCHeader class. */
RPCHeader::~RPCHeader() = default;
/* Decode a RTP header. */
bool RPCHeader::decode(const uint8_t* data)
{
assert(data != nullptr);
m_crc16 = (data[0U] << 8) | (data[1U] << 0); // CRC-16
m_func = __GET_UINT16B(data, 2U); // Function
m_messageLength = __GET_UINT32(data, 4U); // Message Length
return true;
}
/* Encode a RTP header. */
void RPCHeader::encode(uint8_t* data)
{
assert(data != nullptr);
data[0U] = (m_crc16 >> 8) & 0xFFU; // CRC-16 MSB
data[1U] = (m_crc16 >> 0) & 0xFFU; // CRC-16 LSB
__SET_UINT16B(m_func, data, 2U); // Function
__SET_UINT32(m_messageLength, data, 4U); // Message Length
}

@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file RPCHeader.h
* @ingroup network_core
* @file RPCHeader.cpp
* @ingroup network_core
*/
#if !defined(__RPC_HEADER_H__)
#define __RPC_HEADER_H__
#include "common/Defines.h"
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define RPC_HEADER_LENGTH_BYTES 8
#define RPC_REPLY_FUNC 0x8000U
namespace network
{
namespace frame
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents the Remote Procedure Call network frame header.
* \code{.unparsed}
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Payload CRC-16 | Function |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Message Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 8 bytes
* \endcode
*/
class HOST_SW_API RPCHeader {
public:
/**
* @brief Initializes a new instance of the RPCHeader class.
*/
RPCHeader();
/**
* @brief Finalizes a instance of the RPCHeader class.
*/
~RPCHeader();
/**
* @brief Decode a RPC header.
* @param[in] data Buffer containing RPC header to decode.
*/
bool decode(const uint8_t* data);
/**
* @brief Encode a RPC header.
* @param[out] data Buffer to encode an RPC header.
*/
void encode(uint8_t* data);
public:
/**
* @brief Payload packet CRC-16.
*/
__PROPERTY(uint16_t, crc16, CRC);
/**
* @brief Function.
*/
__PROPERTY(uint16_t, func, Function);
/**
* @brief Message Length.
*/
__PROPERTY(uint32_t, messageLength, MessageLength);
};
} // namespace frame
} // namespace network
#endif // __RPC_HEADER_H__

@ -152,8 +152,6 @@ bool Socket::open(const uint32_t af, const std::string& address, const uint16_t
if (!bind(address, port)) {
return false;
}
LogInfoEx(LOG_NET, "Opening UDP port on %u", port);
}
return true;

@ -10,6 +10,7 @@
*/
#include "yaml/Yaml.h"
#include <cstdio>
#include <memory>
#include <fstream>
#include <sstream>
@ -1989,6 +1990,7 @@ namespace yaml
}
catch (Exception const& e)
{
::fprintf(stderr, "YAML Error - %s\n", e.message());
delete pImp;
return false;
}

@ -178,9 +178,9 @@ void* DiagNetwork::threadedNetworkRx(void* arg)
::pthread_setname_np(req->thread, peerName.str().c_str());
#endif // _GNU_SOURCE
// process incoming message frame opcodes
// process incoming message function opcodes
switch (req->fneHeader.getFunction()) {
case NET_FUNC::TRANSFER:
case NET_FUNC::TRANSFER: // Transfer
{
// resolve peer ID (used for Activity Log and Status Transfer)
bool validPeerId = false;
@ -203,8 +203,118 @@ void* DiagNetwork::threadedNetworkRx(void* arg)
}
}
if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY) { // Peer Activity Log Transfer
if (network->m_allowActivityTransfer) {
// process incoming message subfunction opcodes
switch (req->fneHeader.getSubFunction()) {
case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY: // Peer Activity Log Transfer
{
if (network->m_allowActivityTransfer) {
if (pktPeerId > 0 && validPeerId) {
FNEPeerConnection* connection = network->m_peers[pktPeerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identity().c_str(), payload.c_str());
// report activity log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("activity")
.tag("peerId", std::to_string(pktPeerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
}
// repeat traffic to the connected SysView peers
if (network->m_peers.size() > 0U) {
for (auto peer : network->m_peers) {
if (peer.second != nullptr) {
if (peer.second->isSysView()) {
sockaddr_storage addr = peer.second->socketStorage();
uint32_t addrLen = peer.second->sockStorageLen();
network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId,
{ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, RTP_END_OF_CALL_SEQ, addr, addrLen);
}
} else {
continue;
}
}
}
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY },
req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId);
}
}
}
}
}
else {
network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
break;
case NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG: // Peer Diagnostic Log Transfer
{
if (network->m_allowDiagnosticTransfer) {
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
bool currState = g_disableTimeDisplay;
g_disableTimeDisplay = true;
::Log(9999U, nullptr, nullptr, 0U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str());
g_disableTimeDisplay = currState;
// report diagnostic log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("diag")
.tag("peerId", std::to_string(peerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
}
}
else {
network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
break;
case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS: // Peer Status Transfer
{
if (pktPeerId > 0 && validPeerId) {
FNEPeerConnection* connection = network->m_peers[pktPeerId];
if (connection != nullptr) {
@ -212,149 +322,51 @@ void* DiagNetwork::threadedNetworkRx(void* arg)
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identity().c_str(), payload.c_str());
// report activity log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("activity")
.tag("peerId", std::to_string(pktPeerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
}
// repeat traffic to the connected SysView peers
if (network->m_peers.size() > 0U) {
// attempt to repeat status traffic to SysView clients
for (auto peer : network->m_peers) {
if (peer.second != nullptr) {
if (peer.second->isSysView()) {
sockaddr_storage addr = peer.second->socketStorage();
uint32_t addrLen = peer.second->sockStorageLen();
if (network->m_debug) {
LogDebug(LOG_NET, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u",
pktPeerId, peer.first, req->length);
}
network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId,
{ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, RTP_END_OF_CALL_SEQ, addr, addrLen);
{ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, RTP_END_OF_CALL_SEQ, addr, addrLen);
}
} else {
continue;
}
}
}
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY },
req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId);
// attempt to repeat status traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS },
req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId);
}
}
}
}
}
}
else {
network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG) { // Peer Diagnostic Log Transfer
if (network->m_allowDiagnosticTransfer) {
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
bool currState = g_disableTimeDisplay;
g_disableTimeDisplay = true;
::Log(9999U, nullptr, nullptr, 0U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str());
g_disableTimeDisplay = currState;
// report diagnostic log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("diag")
.tag("peerId", std::to_string(peerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
}
}
else {
network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS) { // Peer Status Transfer
if (pktPeerId > 0 && validPeerId) {
FNEPeerConnection* connection = network->m_peers[pktPeerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
if (network->m_peers.size() > 0U) {
// attempt to repeat status traffic to SysView clients
for (auto peer : network->m_peers) {
if (peer.second != nullptr) {
if (peer.second->isSysView()) {
sockaddr_storage addr = peer.second->socketStorage();
uint32_t addrLen = peer.second->sockStorageLen();
if (network->m_debug) {
LogDebug(LOG_NET, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u",
pktPeerId, peer.first, req->length);
}
network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId,
{ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, RTP_END_OF_CALL_SEQ, addr, addrLen);
}
} else {
continue;
}
}
break;
// attempt to repeat status traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS },
req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId);
}
}
}
}
}
}
else {
network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
else {
default:
network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET);
Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length);
break;
}
}
break;
@ -412,4 +424,4 @@ void* DiagNetwork::threadedNetworkRx(void* arg)
}
return nullptr;
}
}

@ -548,86 +548,98 @@ void* FNENetwork::threadedNetworkRx(void* arg)
return nullptr;
}
// process incoming message frame opcodes
// process incoming message function opcodes
switch (req->fneHeader.getFunction()) {
case NET_FUNC::PROTOCOL:
case NET_FUNC::PROTOCOL: // Protocol
{
if (req->fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR) { // Encapsulated DMR data frame
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
connection->lastPing(now);
// process incoming message subfunction opcodes
switch (req->fneHeader.getSubFunction()) {
case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
connection->lastPing(now);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
if (network->m_dmrEnabled) {
if (network->m_tagDMR != nullptr) {
network->m_tagDMR->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
if (network->m_dmrEnabled) {
if (network->m_tagDMR != nullptr) {
network->m_tagDMR->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId);
}
} else {
network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
} else {
network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
}
}
else {
network->writePeerNAK(peerId, TAG_DMR_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen);
}
}
else {
network->writePeerNAK(peerId, TAG_DMR_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen);
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_P25) { // Encapsulated P25 data frame
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
connection->lastPing(now);
break;
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
if (network->m_p25Enabled) {
if (network->m_tagP25 != nullptr) {
network->m_tagP25->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId);
case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
connection->lastPing(now);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
if (network->m_p25Enabled) {
if (network->m_tagP25 != nullptr) {
network->m_tagP25->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId);
}
} else {
network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
} else {
network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
}
}
else {
network->writePeerNAK(peerId, TAG_P25_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen);
}
}
else {
network->writePeerNAK(peerId, TAG_P25_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen);
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN) { // Encapsulated NXDN data frame
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
connection->lastPing(now);
break;
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
if (network->m_nxdnEnabled) {
if (network->m_tagNXDN != nullptr) {
network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId);
case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
connection->lastPing(now);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
if (network->m_nxdnEnabled) {
if (network->m_tagNXDN != nullptr) {
network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId);
}
} else {
network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
} else {
network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
}
}
else {
network->writePeerNAK(peerId, TAG_NXDN_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen);
}
}
else {
network->writePeerNAK(peerId, TAG_NXDN_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen);
}
}
else {
Utils::dump("Unknown protocol opcode from peer", req->buffer, req->length);
break;
default:
Utils::dump("unknown protocol opcode from peer", req->buffer, req->length);
break;
}
}
break;
case NET_FUNC::RPTL: // Repeater Login
case NET_FUNC::RPTL: // Repeater Login
{
if (peerId > 0 && (network->m_peers.find(peerId) == network->m_peers.end())) {
if (network->m_peers.size() >= MAX_HARD_CONN_CAP) {
@ -722,7 +734,7 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
}
break;
case NET_FUNC::RPTK: // Repeater Authentication
case NET_FUNC::RPTK: // Repeater Authentication
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
@ -830,7 +842,7 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
}
break;
case NET_FUNC::RPTC: // Repeater Configuration
case NET_FUNC::RPTC: // Repeater Configuration
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
@ -957,7 +969,7 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
break;
case NET_FUNC::RPT_CLOSING: // Repeater Closing (Disconnect)
case NET_FUNC::RPT_CLOSING: // Repeater Closing (Disconnect)
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
@ -976,7 +988,7 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
}
break;
case NET_FUNC::PING: // Repeater Ping
case NET_FUNC::PING: // Repeater Ping
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
@ -1037,7 +1049,7 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
break;
case NET_FUNC::GRANT_REQ: // Repeater Grant Request
case NET_FUNC::GRANT_REQ: // Repeater Grant Request
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
@ -1096,14 +1108,14 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
break;
case NET_FUNC::INCALL_CTRL: // In-Call Control
case NET_FUNC::INCALL_CTRL: // In-Call Control
{
// FNEs are god-like entities, and we don't recognize the authority of foreign FNEs telling us what
// to do...
}
break;
case NET_FUNC::KEY_REQ: // Enc. Key Request
case NET_FUNC::KEY_REQ: // Enc. Key Request
{
using namespace p25::defines;
using namespace p25::kmm;
@ -1220,7 +1232,7 @@ void* FNENetwork::threadedNetworkRx(void* arg)
}
break;
case NET_FUNC::TRANSFER:
case NET_FUNC::TRANSFER: // Transfer
{
// are activity/diagnostic transfers occurring from the alternate port?
if (network->m_host->m_useAlternatePortForDiagnostics) {
@ -1228,323 +1240,351 @@ void* FNENetwork::threadedNetworkRx(void* arg)
// since they should be coming from the alternate port anyway
}
if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY) { // Peer Activity Log Transfer
if (network->m_allowActivityTransfer) {
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
::ActivityLog("%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str());
// report activity log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("activity")
.tag("peerId", std::to_string(peerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
// process incoming message subfunction opcodes
switch (req->fneHeader.getSubFunction()) {
case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY: // Peer Activity Log Transfer
{
if (network->m_allowActivityTransfer) {
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
::ActivityLog("%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str());
// report activity log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("activity")
.tag("peerId", std::to_string(peerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG) { // Peer Diagnostic Log Transfer
if (network->m_allowDiagnosticTransfer) {
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
bool currState = g_disableTimeDisplay;
g_disableTimeDisplay = true;
::Log(9999U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str());
g_disableTimeDisplay = currState;
// report diagnostic log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("diag")
.tag("peerId", std::to_string(peerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
break;
case NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG: // Peer Diagnostic Log Transfer
{
if (network->m_allowDiagnosticTransfer) {
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
UInt8Array __rawPayload = std::make_unique<uint8_t[]>(req->length - 11U);
uint8_t* rawPayload = __rawPayload.get();
::memset(rawPayload, 0x00U, req->length - 11U);
::memcpy(rawPayload, req->buffer + 11U, req->length - 11U);
std::string payload(rawPayload, rawPayload + (req->length - 11U));
bool currState = g_disableTimeDisplay;
g_disableTimeDisplay = true;
::Log(9999U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str());
g_disableTimeDisplay = currState;
// report diagnostic log to InfluxDB
if (network->m_enableInfluxDB) {
influxdb::QueryBuilder()
.meas("diag")
.tag("peerId", std::to_string(peerId))
.field("identity", connection->identity())
.field("msg", payload)
.timestamp(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
.request(network->m_influxServer);
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS) { // Peer Status Transfer
break;
case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS: // Peer Status Transfer
// main traffic port status transfers aren't supported for performance reasons
}
else {
break;
default:
network->writePeerNAK(peerId, streamId, TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET);
Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length);
break;
}
}
break;
case NET_FUNC::ANNOUNCE:
case NET_FUNC::ANNOUNCE: // Announce
{
if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL) { // Announce Group Affiliation
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
// process incoming message subfunction opcodes
switch (req->fneHeader.getSubFunction()) {
case NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL: // Announce Group Affiliation
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
uint32_t dstId = __GET_UINT16(req->buffer, 3U); // Destination Address
aff->groupUnaff(srcId);
aff->groupAff(srcId, dstId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
uint32_t dstId = __GET_UINT16(req->buffer, 3U); // Destination Address
aff->groupUnaff(srcId);
aff->groupAff(srcId, dstId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
}
}
}
}
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG) { // Announce Unit Registration
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
break;
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
aff->unitReg(srcId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
}
}
}
case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG: // Announce Unit Registration
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG) { // Announce Unit Deregistration
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
aff->unitDereg(srcId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
aff->unitReg(srcId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
}
}
}
}
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL) { // Announce Group Affiliation Removal
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
break;
case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG: // Announce Unit Deregistration
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
aff->groupUnaff(srcId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
aff->unitDereg(srcId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
}
}
}
}
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_AFFILS) { // Announce Update All Affiliations
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
break;
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
case NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL: // Announce Group Affiliation Removal
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
if (aff != nullptr) {
aff->clearGroupAff(0U, true);
// update TGID lists
uint32_t len = __GET_UINT32(req->buffer, 0U);
uint32_t offs = 4U;
for (uint32_t i = 0; i < len; i++) {
uint32_t srcId = __GET_UINT16(req->buffer, offs);
uint32_t dstId = __GET_UINT16(req->buffer, offs + 4U);
aff->groupAff(srcId, dstId);
offs += 8U;
}
LogMessage(LOG_NET, "PEER %u (%s) announced %u affiliations", peerId, connection->identity().c_str(), len);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip && aff != nullptr) {
uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address
aff->groupUnaff(srcId);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS },
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
}
}
}
}
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC) { // Announce Site VCs
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
std::vector<uint32_t> vcPeers;
// update peer association
uint32_t len = __GET_UINT32(req->buffer, 0U);
uint32_t offs = 4U;
for (uint32_t i = 0; i < len; i++) {
uint32_t vcPeerId = __GET_UINT32(req->buffer, offs);
if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) {
FNEPeerConnection* vcConnection = network->m_peers[vcPeerId];
if (vcConnection != nullptr) {
vcConnection->ccPeerId(peerId);
vcPeers.push_back(vcPeerId);
break;
case NET_SUBFUNC::ANNC_SUBFUNC_AFFILS: // Announce Update All Affiliations
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId];
if (aff == nullptr) {
LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str());
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID);
}
if (aff != nullptr) {
aff->clearGroupAff(0U, true);
// update TGID lists
uint32_t len = __GET_UINT32(req->buffer, 0U);
uint32_t offs = 4U;
for (uint32_t i = 0; i < len; i++) {
uint32_t srcId = __GET_UINT16(req->buffer, offs);
uint32_t dstId = __GET_UINT16(req->buffer, offs + 4U);
aff->groupAff(srcId, dstId);
offs += 8U;
}
LogMessage(LOG_NET, "PEER %u (%s) announced %u affiliations", peerId, connection->identity().c_str(), len);
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
}
}
}
}
}
offs += 4U;
}
LogMessage(LOG_NET, "PEER %u (%s) announced %u VCs", peerId, connection->identity().c_str(), len);
network->m_ccPeerMap[peerId] = vcPeers;
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
break;
case NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC: // Announce Site VCs
{
if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) {
FNEPeerConnection* connection = network->m_peers[peerId];
if (connection != nullptr) {
std::string ip = udp::Socket::address(req->address);
// validate peer (simple validation really)
if (connection->connected() && connection->address() == ip) {
std::vector<uint32_t> vcPeers;
// update peer association
uint32_t len = __GET_UINT32(req->buffer, 0U);
uint32_t offs = 4U;
for (uint32_t i = 0; i < len; i++) {
uint32_t vcPeerId = __GET_UINT32(req->buffer, offs);
if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) {
FNEPeerConnection* vcConnection = network->m_peers[vcPeerId];
if (vcConnection != nullptr) {
vcConnection->ccPeerId(peerId);
vcPeers.push_back(vcPeerId);
}
}
offs += 4U;
}
LogMessage(LOG_NET, "PEER %u (%s) announced %u VCs", peerId, connection->identity().c_str(), len);
network->m_ccPeerMap[peerId] = vcPeers;
// attempt to repeat traffic to Peer-Link masters
if (network->m_host->m_peerNetworks.size() > 0) {
for (auto peer : network->m_host->m_peerNetworks) {
if (peer.second != nullptr) {
if (peer.second->isEnabled() && peer.second->isPeerLink()) {
peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC },
req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false);
}
}
}
}
}
}
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
else {
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED);
}
}
}
}
}
else {
break;
default:
network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_ILLEGAL_PACKET);
Utils::dump("unknown announcement opcode from the peer", req->buffer, req->length);
}

@ -50,6 +50,4 @@ file(GLOB dvmhost_SRC
"src/host/modem/port/specialized/*.cpp"
"src/host/network/*.h"
"src/host/network/*.cpp"
"src/remote/RESTClient.cpp"
"src/remote/RESTClient.h"
)

@ -187,19 +187,18 @@ bool Host::readParams()
{
yaml::Node controlCh = rfssConfig["controlCh"];
std::string restApiAddress = controlCh["restAddress"].as<std::string>("");
uint16_t restApiPort = (uint16_t)controlCh["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = controlCh["restPassword"].as<std::string>();
bool restSsl = controlCh["restSsl"].as<bool>(false);
std::string rpcApiAddress = controlCh["rpcAddress"].as<std::string>("");
uint16_t rpcApiPort = (uint16_t)controlCh["rpcPort"].as<uint32_t>(RPC_DEFAULT_PORT);
std::string rpcApiPassword = controlCh["rpcPassword"].as<std::string>();
m_presenceTime = controlCh["presence"].as<uint32_t>(120U);
VoiceChData data = VoiceChData(m_channelId, m_channelNo, restApiAddress, restApiPort, restApiPassword, restSsl);
VoiceChData data = VoiceChData(m_channelId, m_channelNo, rpcApiAddress, rpcApiPort, rpcApiPassword);
m_controlChData = data;
if (!m_controlChData.address().empty() && m_controlChData.port() > 0) {
::LogInfoEx(LOG_HOST, "Control Channel REST API Address %s:%u SSL %u", m_controlChData.address().c_str(), m_controlChData.port(), restSsl);
::LogInfoEx(LOG_HOST, "Control Channel RPC Address %s:%u", m_controlChData.address().c_str(), m_controlChData.port());
} else {
::LogInfoEx(LOG_HOST, "No Control Channel REST API Configured, CC notify disabled");
::LogInfoEx(LOG_HOST, "No Control Channel RPC Configured, CC notify disabled");
}
}
@ -236,14 +235,13 @@ bool Host::readParams()
chNo = 4095U;
}
std::string restApiAddress = channel["restAddress"].as<std::string>("0.0.0.0");
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as<std::string>();
bool restSsl = channel["restSsl"].as<bool>(false);
std::string rpcApiAddress = channel["rpcAddress"].as<std::string>("0.0.0.0");
uint16_t rpcApiPort = (uint16_t)channel["rpcPort"].as<uint32_t>(RPC_DEFAULT_PORT);
std::string rpcApiPassword = channel["rpcPassword"].as<std::string>();
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", chId, chNo, restApiAddress.c_str(), restApiPort, restSsl);
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X RPC Address %s:%u", chId, chNo, rpcApiAddress.c_str(), rpcApiPort);
VoiceChData data = VoiceChData(chId, chNo, restApiAddress, restApiPort, restApiPassword, restSsl);
VoiceChData data = VoiceChData(chId, chNo, rpcApiAddress, rpcApiPort, rpcApiPassword);
m_channelLookup->setRFChData(chNo, data);
m_channelLookup->addRFCh(chNo);
}
@ -330,7 +328,8 @@ bool Host::readParams()
if (!m_authoritative) {
m_supervisor = false;
LogWarning(LOG_HOST, "Host is non-authoritative! This requires REST API to handle permit TG for VCs and grant TG for CCs!");
LogWarning(LOG_HOST, "Host is non-authoritative! This requires a second instance configured in site controller mode.");
LogWarning(LOG_HOST, "RPC is required to handle permit TG for VCs!");
}
}
else {
@ -728,9 +727,37 @@ bool Host::createModem()
bool Host::createNetwork()
{
yaml::Node networkConf = m_conf["network"];
std::string rpcAddress = networkConf["rpcAddress"].as<std::string>("127.0.0.1");
uint16_t rpcPort = (uint16_t)networkConf["rpcPort"].as<uint32_t>(RPC_DEFAULT_PORT);
std::string rpcPassword = networkConf["rpcPassword"].as<std::string>("ULTRA-VERY-SECURE-DEFAULT");
bool rpcDebug = networkConf["rpcDebug"].as<bool>(false);
// initialize RPC
m_rpcAddress = rpcAddress;
m_rpcPort = rpcPort;
g_RPC = new RPC(rpcAddress, rpcPort, 0U, rpcPassword, rpcDebug);
bool ret = g_RPC->open();
if (!ret) {
delete g_RPC;
g_RPC = nullptr;
LogError(LOG_HOST, "failed to initialize RPC networking!");
return false;
}
bool netEnable = networkConf["enable"].as<bool>(false);
bool restApiEnable = networkConf["restEnable"].as<bool>(false);
LogInfo("Network Parameters");
LogInfo(" Enabled: %s", netEnable ? "yes" : "no");
LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no");
LogInfo(" RPC Address: %s", rpcAddress.c_str());
LogInfo(" RPC Port: %u", rpcPort);
if (rpcDebug) {
LogInfo(" RPC Debug: yes");
}
// dump out if both networking and REST API are disabled
if (!netEnable && !restApiEnable) {
return true;
@ -826,12 +853,10 @@ bool Host::createNetwork()
IdenTable entry = m_idenTable->find(m_channelId);
LogInfo("Network Parameters");
LogInfo(" Enabled: %s", netEnable ? "yes" : "no");
if (netEnable) {
LogInfo(" Peer ID: %u", id);
LogInfo(" Address: %s", address.c_str());
LogInfo(" Port: %u", port);
LogInfo(" Master Address: %s", address.c_str());
LogInfo(" Master Port: %u", port);
if (local > 0U)
LogInfo(" Local: %u", local);
else
@ -851,7 +876,7 @@ bool Host::createNetwork()
LogInfo(" Debug: yes");
}
}
LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no");
if (restApiEnable) {
LogInfo(" REST API Address: %s", restApiAddress.c_str());
LogInfo(" REST API Port: %u", restApiPort);
@ -900,7 +925,7 @@ bool Host::createNetwork()
::LogSetNetwork(m_network);
}
// initialize network remote command
// initialize REST API
if (restApiEnable) {
m_restAddress = restApiAddress;
m_restPort = restApiPort;

@ -17,7 +17,6 @@
#include "common/Thread.h"
#include "common/Utils.h"
#include "modem/port/specialized/V24UDPPort.h"
#include "remote/RESTClient.h"
#include "host/Host.h"
#include "ActivityLog.h"
#include "HostMain.h"
@ -35,6 +34,12 @@ using namespace lookups;
#include <unistd.h>
#endif // !defined(_WIN32)
// ---------------------------------------------------------------------------
// Global Variables
// ---------------------------------------------------------------------------
network::RPC* g_RPC = nullptr;
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
@ -160,6 +165,8 @@ Host::Host(const std::string& confFile) :
m_restAddress("0.0.0.0"),
m_restPort(REST_API_DEFAULT_PORT),
m_RESTAPI(nullptr),
m_rpcAddress("0.0.0.0"),
m_rpcPort(RPC_DEFAULT_PORT),
m_dmr(nullptr),
m_p25(nullptr),
m_nxdn(nullptr)
@ -799,6 +806,10 @@ int Host::run()
** Initialize Threads
*/
/** RPC */
if (!Thread::runAsThread(this, threadRPC))
return EXIT_FAILURE;
/** Watchdog */
if (!Thread::runAsThread(this, threadWatchdog))
return EXIT_FAILURE;
@ -1676,6 +1687,11 @@ void Host::setState(uint8_t state)
delete m_modem;
}
if (g_RPC != nullptr) {
g_RPC->close();
delete g_RPC;
}
::LogSetNetwork(nullptr);
if (m_network != nullptr) {
m_network->close();
@ -1707,6 +1723,64 @@ void Host::setState(uint8_t state)
}
}
/* Entry point to RPC clock thread. */
void* Host::threadRPC(void* arg)
{
thread_t* th = (thread_t*)arg;
if (th != nullptr) {
#if defined(_WIN32)
::CloseHandle(th->thread);
#else
::pthread_detach(th->thread);
#endif // defined(_WIN32)
std::string threadName("host:rpc");
Host* host = static_cast<Host*>(th->obj);
if (host == nullptr) {
g_killed = true;
LogError(LOG_HOST, "[FAIL] %s", threadName.c_str());
}
if (g_killed) {
delete th;
return nullptr;
}
LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str());
#ifdef _GNU_SOURCE
::pthread_setname_np(th->thread, threadName.c_str());
#endif // _GNU_SOURCE
StopWatch stopWatch;
stopWatch.start();
while (!g_killed) {
// scope is intentional
{
// ------------------------------------------------------
// -- RPC Clocking --
// ------------------------------------------------------
uint32_t ms = stopWatch.elapsed();
stopWatch.start();
g_RPC->clock(ms);
}
if (host->m_state != STATE_IDLE)
Thread::sleep(m_activeTickDelay);
if (host->m_state == STATE_IDLE)
Thread::sleep(m_idleTickDelay);
}
LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str());
delete th;
}
return nullptr;
}
/* Entry point to modem clock thread. */
void* Host::threadModem(void* arg)
@ -2080,6 +2154,72 @@ void* Host::threadPresence(void* arg)
::pthread_setname_np(th->thread, threadName.c_str());
#endif // _GNU_SOURCE
// register VC -> CC notification RPC handler
g_RPC->registerHandler(RPC_REGISTER_CC_VC, [=](json::object &req, json::object &reply) {
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
if (!host->m_dmrTSCCData && !host->m_p25CCData && !host->m_nxdnCCData) {
g_RPC->defaultResponse(reply, "Host is not a control channel, cannot register voice channel");
return;
}
// validate channelNo is a string within the JSON blob
if (!req["channelNo"].is<int>()) {
g_RPC->defaultResponse(reply, "channelNo was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t channelNo = req["channelNo"].get<uint32_t>();
// validate peerId is a string within the JSON blob
if (!req["peerId"].is<int>()) {
g_RPC->defaultResponse(reply, "peerId was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t peerId = req["peerId"].get<uint32_t>();
// LogDebug(LOG_REST, "RPC_REGISTER_CC_VC callback, channelNo = %u, peerId = %u", channelNo, peerId);
// validate restAddress is a string within the JSON blob
if (!req["rpcAddress"].is<std::string>()) {
g_RPC->defaultResponse(reply, "rpcAddress was not a valid string", network::RPC::INVALID_ARGS);
return;
}
if (!req["rpcPort"].is<int>()) {
g_RPC->defaultResponse(reply, "rpcPort was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
std::string rpcAddress = req["rpcAddress"].get<std::string>();
uint16_t rpcPort = (uint16_t)req["rpcPort"].get<int>();
auto voiceChData = host->rfCh()->rfChDataTable();
if (voiceChData.find(channelNo) != voiceChData.end()) {
::lookups::VoiceChData voiceCh = host->rfCh()->getRFChData(channelNo);
if (voiceCh.address() == "0.0.0.0") {
voiceCh.address(rpcAddress);
}
if (voiceCh.port() == 0U || voiceCh.port() == RPC_DEFAULT_PORT) {
voiceCh.port(rpcPort);
}
host->rfCh()->setRFChData(channelNo, voiceCh);
host->m_voiceChPeerId[channelNo] = peerId;
LogMessage(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo);
LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u", voiceCh.chId(), channelNo, voiceCh.address().c_str(), voiceCh.port());
g_fireCCVCNotification = true; // announce this registration immediately to the FNE
} else {
LogMessage(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo);
g_RPC->defaultResponse(reply, "registration rejected", network::RPC::BAD_REQUEST);
}
});
Timer presenceNotifyTimer(1000U, host->m_presenceTime);
presenceNotifyTimer.start();
bool hasInitialRegistered = false;
@ -2096,15 +2236,15 @@ void* Host::threadPresence(void* arg)
presenceNotifyTimer.clock(ms);
// VC -> CC presence registration
if (!host->m_controlChData.address().empty() && host->m_controlChData.port() != 0 && host->m_network != nullptr && host->m_RESTAPI != nullptr &&
if (!host->m_controlChData.address().empty() && host->m_controlChData.port() != 0 && host->m_network != nullptr &&
!host->m_dmrCtrlChannel && !host->m_p25CtrlChannel && !host->m_nxdnCtrlChannel) {
if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || !hasInitialRegistered) {
LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId());
hasInitialRegistered = true;
std::string localAddress = network::udp::Socket::getLocalAddress();
if (host->m_restAddress == "0.0.0.0") {
host->m_restAddress = localAddress;
if (host->m_rpcAddress == "0.0.0.0") {
host->m_rpcAddress = localAddress;
}
// callback REST API to release the granted TG on the specified control channel
@ -2112,14 +2252,26 @@ void* Host::threadPresence(void* arg)
req["channelNo"].set<uint32_t>(host->m_channelNo);
uint32_t peerId = host->m_network->getPeerId();
req["peerId"].set<uint32_t>(peerId);
req["restAddress"].set<std::string>(host->m_restAddress);
req["restPort"].set<uint16_t>(host->m_restPort);
req["rpcAddress"].set<std::string>(host->m_rpcAddress);
req["rpcPort"].set<uint16_t>(host->m_rpcPort);
int ret = RESTClient::send(host->m_controlChData.address(), host->m_controlChData.port(), host->m_controlChData.password(),
HTTP_PUT, PUT_REGISTER_CC_VC, req, host->m_controlChData.ssl(), REST_QUICK_WAIT, false);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", host->m_controlChData.address().c_str(), host->m_controlChData.port());
}
g_RPC->req(RPC_REGISTER_CC_VC, req, [=](json::object &req, json::object &reply) {
if (!req["status"].is<int>()) {
::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration, invalid RPC response", host->m_controlChData.address().c_str(), host->m_controlChData.port());
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", host->m_controlChData.address().c_str(), host->m_controlChData.port());
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_HOST, "RPC failed, %s", retMsg.c_str());
}
}
else
::LogMessage(LOG_HOST, "CC %s:%u, VC registered, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId());
}, host->m_controlChData.address(), host->m_controlChData.port());
presenceNotifyTimer.start();
}

@ -34,11 +34,13 @@
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/network/json/json.h"
#include "common/network/Network.h"
#include "common/network/RPC.h"
#include "common/yaml/Yaml.h"
#include "dmr/Control.h"
#include "p25/Control.h"
#include "nxdn/Control.h"
#include "network/RESTAPI.h"
#include "network/RPCDefines.h"
#include "modem/Modem.h"
#include "modem/ModemV24.h"
@ -54,6 +56,13 @@
#include <pthread.h>
#endif // defined(_WIN32)
// ---------------------------------------------------------------------------
// Externs
// ---------------------------------------------------------------------------
/** @brief */
extern network::RPC* g_RPC;
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
@ -226,7 +235,10 @@ private:
friend class RESTAPI;
std::string m_restAddress;
uint16_t m_restPort;
RESTAPI *m_RESTAPI;
RESTAPI* m_RESTAPI;
std::string m_rpcAddress;
uint16_t m_rpcPort;
std::unique_ptr<dmr::Control> m_dmr;
std::unique_ptr<p25::Control> m_p25;
@ -268,6 +280,13 @@ private:
*/
void setState(uint8_t state);
/**
* @brief Entry point to RPC clocking thread.
* @param arg Instance of the thread_t structure.
* @returns void* (Ignore)
*/
static void* threadRPC(void* arg);
/**
* @brief Entry point to modem clocking thread.
* @param arg Instance of the thread_t structure.

@ -14,6 +14,7 @@
#include "common/dmr/lc/csbk/CSBKFactory.h"
#include "common/Log.h"
#include "dmr/Control.h"
#include "Host.h"
using namespace dmr;
using namespace dmr::defines;
@ -67,6 +68,12 @@ Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint
m_slot2 = new Slot(2U, timeout, tgHang, queueSize, dumpDataPacket, repeatDataPacket, dumpCSBKData, debug, verbose);
m_tsccCntInterval.start();
// register RPC handlers
g_RPC->registerHandler(RPC_PERMIT_DMR_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this));
g_RPC->registerHandler(RPC_RELEASE_DMR_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this));
g_RPC->registerHandler(RPC_TOUCH_DMR_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this));
g_RPC->registerHandler(RPC_DMR_TSCC_PAYLOAD_ACT, RPC_FUNC_BIND(Control::RPC_tsccPayloadActivate, this));
}
/* Finalizes a instance of the Control class. */
@ -201,6 +208,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa
if (disableGrantSourceIdCheck) {
LogInfo(" TSCC Disable Grant Source ID Check: yes");
}
if (m_supervisor)
LogMessage(LOG_DMR, "Host is configured to operate as a DMR TSCC, site controller mode.");
}
if (disableNetworkGrant) {
LogInfo(" Disable Network Grants: yes");
@ -436,40 +445,6 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, uint8_t slot, bool grp)
}
}
/* Releases a granted TG. */
void Control::releaseGrantTG(uint32_t dstId, uint8_t slot)
{
switch (slot) {
case 1U:
m_slot1->releaseGrantTG(dstId);
break;
case 2U:
m_slot2->releaseGrantTG(dstId);
break;
default:
LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot);
break;
}
}
/* Touchs a granted TG to keep a channel grant alive. */
void Control::touchGrantTG(uint32_t dstId, uint8_t slot)
{
switch (slot) {
case 1U:
m_slot1->touchGrantTG(dstId);
break;
case 2U:
m_slot2->touchGrantTG(dstId);
break;
default:
LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot);
break;
}
}
/* Gets instance of the AffiliationLookup class. */
dmr::lookups::DMRAffiliationLookup* Control::affiliations()
@ -809,3 +784,197 @@ void Control::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId,
break;
}
}
/* (RPC Handler) Permits a TGID on a non-authoritative host. */
void Control::RPC_permittedTG(json::object& req, json::object& reply)
{
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
// validate slot is a integer within the JSON blob
if (!req["slot"].is<int>()) {
g_RPC->defaultResponse(reply, "slot was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint8_t slot = (uint8_t)req["slot"].get<int>();
if (slot == 0U || slot > 2U) {
g_RPC->defaultResponse(reply, "illegal DMR slot");
return;
}
// LogDebugEx(LOG_DMR, "Control::RPC_permittedTG()", "callback, dstId = %u, slot = %u", dstId, slot);
permittedTG(dstId, slot);
}
/* (RPC Handler) Releases a granted TG. */
void Control::RPC_releaseGrantTG(json::object& req, json::object& reply)
{
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
g_RPC->defaultResponse(reply, "destination ID is an illegal TGID");
return;
}
// validate slot is a integer within the JSON blob
if (!req["slot"].is<int>()) {
g_RPC->defaultResponse(reply, "slot was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint8_t slot = (uint8_t)req["slot"].get<int>();
if (slot == 0U || slot > 2U) {
g_RPC->defaultResponse(reply, "illegal DMR slot");
return;
}
// LogDebugEx(LOG_DMR, "Control::RPC_releaseGrantTG()", "callback, dstId = %u, slot = %u", dstId, slot);
switch (slot) {
case 1U:
m_slot1->releaseGrantTG(dstId);
break;
case 2U:
m_slot2->releaseGrantTG(dstId);
break;
default:
LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot);
break;
}
}
/* (RPC Handler) Touchs a granted TG to keep a channel grant alive. */
void Control::RPC_touchGrantTG(json::object& req, json::object& reply)
{
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
g_RPC->defaultResponse(reply, "destination ID is an illegal TGID");
return;
}
// validate slot is a integer within the JSON blob
if (!req["slot"].is<int>()) {
g_RPC->defaultResponse(reply, "slot was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint8_t slot = (uint8_t)req["slot"].get<int>();
if (slot == 0U || slot > 2U) {
g_RPC->defaultResponse(reply, "illegal DMR slot");
return;
}
// LogDebugEx(LOG_DMR, "Control::RPC_touchGrantTG()", "callback, dstId = %u, slot = %u", dstId, slot);
switch (slot) {
case 1U:
m_slot1->touchGrantTG(dstId);
break;
case 2U:
m_slot2->touchGrantTG(dstId);
break;
default:
LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot);
break;
}
}
/* (RPC Handler) Helper to payload activate the slot carrying granted payload traffic. */
void Control::RPC_tsccPayloadActivate(json::object& req, json::object& reply)
{
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["slot"].is<uint8_t>()) {
g_RPC->defaultResponse(reply, "slot was not valid", network::RPC::INVALID_ARGS);
return;
}
// validate clear flag is a boolean within the JSON blob
if (!req["clear"].is<bool>()) {
g_RPC->defaultResponse(reply, "clear flag was not valid", network::RPC::INVALID_ARGS);
return;
}
uint8_t slot = req["slot"].get<uint8_t>();
bool clear = req["clear"].get<bool>();
if (slot == 0U || slot >= 3U) {
g_RPC->defaultResponse(reply, "invalid DMR slot number (slot == 0 or slot > 3)");
return;
}
if (clear) {
tsccClearActivatedSlot(slot);
}
else {
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<uint32_t>()) {
g_RPC->defaultResponse(reply, "destination ID was not valid", network::RPC::INVALID_ARGS);
return;
}
// validate destination ID is a integer within the JSON blob
if (!req["srcId"].is<uint32_t>()) {
g_RPC->defaultResponse(reply, "source ID was not valid", network::RPC::INVALID_ARGS);
return;
}
// validate group flag is a boolean within the JSON blob
if (!req["group"].is<bool>()) {
g_RPC->defaultResponse(reply, "group flag was not valid", network::RPC::INVALID_ARGS);
return;
}
// validate voice flag is a boolean within the JSON blob
if (!req["voice"].is<bool>()) {
g_RPC->defaultResponse(reply, "voice flag was not valid", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
uint32_t srcId = req["srcId"].get<uint32_t>();
bool group = req["group"].get<bool>();
bool voice = req["voice"].get<bool>();
if (dstId == 0U) {
g_RPC->defaultResponse(reply, "destination ID was not valid");
return;
}
tsccActivateSlot(slot, dstId, srcId, group, voice);
}
}

@ -31,6 +31,7 @@
#include "common/lookups/IdenTableLookup.h"
#include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/network/RPC.h"
#include "common/network/RTPFNEHeader.h"
#include "common/network/Network.h"
#include "common/yaml/Yaml.h"
@ -195,18 +196,6 @@ namespace dmr
* @param grp Flag indicating group grant.
*/
void grantTG(uint32_t srcId, uint32_t dstId, uint8_t slot, bool grp);
/**
* @brief Releases a granted TG.
* @param dstId Destination ID.
* @param slot DMR slot number.
*/
void releaseGrantTG(uint32_t dstId, uint8_t slot);
/**
* @brief Touches a granted TG to keep a channel grant alive.
* @param dstId Destination ID.
* @param slot DMR slot number.
*/
void touchGrantTG(uint32_t dstId, uint8_t slot);
/** @} */
/**
@ -350,6 +339,34 @@ namespace dmr
* @param slotNo DMR slot.
*/
void processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo);
/** @name Supervisory Control */
/**
* @brief (RPC Handler) Permits a TGID on a non-authoritative host.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_permittedTG(json::object& req, json::object& reply);
/**
* @brief (RPC Handler) Releases a granted TG.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_releaseGrantTG(json::object& req, json::object& reply);
/**
* @brief (RPC Handler) Touches a granted TG to keep a channel grant alive.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_touchGrantTG(json::object& req, json::object& reply);
/** @} */
/**
* @brief (RPC Handler) Helper to payload activate the slot carrying granted payload traffic.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_tsccPayloadActivate(json::object& req, json::object& reply);
};
} // namespace dmr

@ -19,9 +19,9 @@
#include "common/Utils.h"
#include "dmr/Slot.h"
#include "common/dmr/acl/AccessControl.h"
#include "remote/RESTClient.h"
#include "ActivityLog.h"
#include "HostMain.h"
#include "Host.h"
using namespace dmr;
using namespace dmr::defines;
@ -825,7 +825,10 @@ void Slot::permittedTG(uint32_t dstId)
}
if (m_verbose) {
LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId);
if (dstId == 0U)
LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG unpermit", m_slotNo);
else
LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId);
}
m_permittedDstId = dstId;
@ -1035,8 +1038,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
bool clear = true;
req["clear"].set<bool>(clear);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug);
g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_DMR, "DMR Slot %u, CSBK, RAND (Random Access), failed to clear payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1046,14 +1048,11 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
if (m_authoritative && m_dmr->m_supervisor) {
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_DMR;
req["state"].set<int>(state);
dstId = 0U; // clear TG value
req["dstId"].set<uint32_t>(dstId);
req["slot"].set<uint8_t>(slot);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_dmr->m_debug);
g_RPC->req(RPC_PERMIT_DMR_TG, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_DMR, "DMR Slot %u, CSBK, RAND (Random Access), failed to clear TG permit, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1258,17 +1257,27 @@ void Slot::notifyCC_ReleaseGrant(uint32_t dstId)
// callback REST API to release the granted TG on the specified control channel
json::object req = json::object();
int state = modem::DVM_STATE::STATE_DMR;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
uint8_t slot = m_slotNo;
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
g_RPC->req(RPC_RELEASE_DMR_TG, req, [=](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_DMR, "DMR Slot %u, RPC failed, %s", m_slotNo, retMsg.c_str());
}
}
else
::LogMessage(LOG_DMR, "DMR Slot %u, CC %s:%u, released grant, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}, m_controlChData.address(), m_controlChData.port());
m_rfLastDstId = 0U;
m_rfLastSrcId = 0U;
@ -1294,17 +1303,28 @@ void Slot::notifyCC_TouchGrant(uint32_t dstId)
// callback REST API to touch the granted TG on the specified control channel
json::object req = json::object();
int state = modem::DVM_STATE::STATE_DMR;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
uint8_t slot = m_slotNo;
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
g_RPC->req(RPC_TOUCH_DMR_TG, req, [=](json::object& req, json::object& reply) {
// validate channelNo is a string within the JSON blob
if (!req["status"].is<int>()) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_DMR, "DMR Slot %u, RPC failed, %s", m_slotNo, retMsg.c_str());
}
}
else
::LogMessage(LOG_DMR, "DMR Slot %u, CC %s:%u, touched grant, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}, m_controlChData.address(), m_controlChData.port());
}
/* Write data frame to the network. */

@ -19,8 +19,8 @@
#include "dmr/lc/csbk/CSBK_DVM_GIT_HASH.h"
#include "dmr/packet/ControlSignaling.h"
#include "dmr/Slot.h"
#include "remote/RESTClient.h"
#include "ActivityLog.h"
#include "Host.h"
using namespace dmr;
using namespace dmr::defines;
@ -950,29 +950,40 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
::ActivityLog("DMR", true, "Slot %u group grant request from %u to TG %u", tscc->m_slotNo, srcId, dstId);
}
// callback REST API to permit the granted TG on the specified voice channel
// callback RPC to permit the granted TG on the specified voice channel
if (tscc->m_authoritative && tscc->m_supervisor &&
tscc->m_channelNo != chNo) {
::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_DMR;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
tscc->m_affiliations->releaseGrant(dstId, false);
if (!net) {
writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U);
m_slot->m_rfState = RS_RF_REJECTED;
bool requestFailed = false;
g_RPC->req(RPC_PERMIT_DMR_TG, req, [=, &requestFailed](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, RPC failed, %s", tscc->m_slotNo, retMsg.c_str());
}
tscc->m_affiliations->releaseGrant(dstId, false);
if (!net) {
writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U);
m_slot->m_rfState = RS_RF_REJECTED;
}
requestFailed = true;
}
}, voiceChData.address(), voiceChData.port());
if (requestFailed)
return false;
}
}
else {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1012,8 +1023,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
bool voice = true;
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug);
g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1028,29 +1038,40 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
::ActivityLog("DMR", true, "Slot %u individual grant request from %u to TG %u", tscc->m_slotNo, srcId, dstId);
}
// callback REST API to permit the granted TG on the specified voice channel
// callback RPC to permit the granted TG on the specified voice channel
if (tscc->m_authoritative && tscc->m_supervisor &&
tscc->m_channelNo != chNo) {
::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_DMR;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
tscc->m_affiliations->releaseGrant(dstId, false);
if (!net) {
writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U);
m_slot->m_rfState = RS_RF_REJECTED;
bool requestFailed = false;
g_RPC->req(RPC_PERMIT_DMR_TG, req, [=, &requestFailed](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, RPC failed, %s", tscc->m_slotNo, retMsg.c_str());
}
tscc->m_affiliations->releaseGrant(dstId, false);
if (!net) {
writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U);
m_slot->m_rfState = RS_RF_REJECTED;
}
requestFailed = true;
}
}, voiceChData.address(), voiceChData.port());
if (requestFailed)
return false;
}
}
else {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1088,8 +1109,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
bool voice = true;
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug);
g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1241,8 +1261,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
bool voice = false;
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug);
g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1288,8 +1307,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
bool voice = false;
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug);
g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);

@ -288,10 +288,6 @@ void RESTAPI::initializeEndpoints()
m_dispatcher.match(GET_RELEASE_GRNTS).get(REST_API_BIND(RESTAPI::restAPI_GetReleaseGrants, this));
m_dispatcher.match(GET_RELEASE_AFFS).get(REST_API_BIND(RESTAPI::restAPI_GetReleaseAffs, this));
m_dispatcher.match(PUT_REGISTER_CC_VC).put(REST_API_BIND(RESTAPI::restAPI_PutRegisterCCVC, this));
m_dispatcher.match(PUT_RELEASE_TG).put(REST_API_BIND(RESTAPI::restAPI_PutReleaseGrant, this));
m_dispatcher.match(PUT_TOUCH_TG).put(REST_API_BIND(RESTAPI::restAPI_PutTouchGrant, this));
m_dispatcher.match(GET_RID_WHITELIST, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDWhitelist, this));
m_dispatcher.match(GET_RID_BLACKLIST, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDBlacklist, this));
@ -305,7 +301,6 @@ void RESTAPI::initializeEndpoints()
m_dispatcher.match(PUT_DMR_RID).put(REST_API_BIND(RESTAPI::restAPI_PutDMRRID, this));
m_dispatcher.match(GET_DMR_CC_DEDICATED).get(REST_API_BIND(RESTAPI::restAPI_GetDMRCCEnable, this));
m_dispatcher.match(GET_DMR_CC_BCAST).get(REST_API_BIND(RESTAPI::restAPI_GetDMRCCBroadcast, this));
m_dispatcher.match(PUT_DMR_TSCC_PAYLOAD_ACT).put(REST_API_BIND(RESTAPI::restAPI_PutTSCCPayloadActivate, this));
m_dispatcher.match(GET_DMR_AFFILIATIONS).get(REST_API_BIND(RESTAPI::restAPI_GetDMRAffList, this));
/*
@ -970,266 +965,6 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& re
}
}
/* REST API endpoint; implements register CC/VC request. */
void RESTAPI::restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{
if (!validateAuth(request, reply)) {
return;
}
json::object req = json::object();
if (!parseRequestBody(request, reply, req)) {
return;
}
errorPayload(reply, "OK", HTTPPayload::OK);
if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) {
errorPayload(reply, "Host is not a control channel, cannot register voice channel");
return;
}
// validate channelNo is a string within the JSON blob
if (!req["channelNo"].is<int>()) {
errorPayload(reply, "channelNo was not a valid integer");
return;
}
uint32_t channelNo = req["channelNo"].get<uint32_t>();
// validate peerId is a string within the JSON blob
if (!req["peerId"].is<int>()) {
errorPayload(reply, "peerId was not a valid integer");
return;
}
uint32_t peerId = req["peerId"].get<uint32_t>();
// LogDebugEx(LOG_REST, "RESTAPI::restAPI_PutRegisterCCVC()", "callback, channelNo = %u, peerId = %u", channelNo, peerId);
// validate restAddress is a string within the JSON blob
if (!req["restAddress"].is<std::string>()) {
errorPayload(reply, "restAddress was not a valid string");
return;
}
if (!req["restPort"].is<int>()) {
errorPayload(reply, "restPort was not a valid integer");
return;
}
std::string restAddress = req["restAddress"].get<std::string>();
uint16_t restPort = (uint16_t)req["restPort"].get<int>();
auto voiceChData = m_host->rfCh()->rfChDataTable();
if (voiceChData.find(channelNo) != voiceChData.end()) {
::lookups::VoiceChData voiceCh = m_host->rfCh()->getRFChData(channelNo);
if (voiceCh.address() == "0.0.0.0") {
voiceCh.address(restAddress);
}
if (voiceCh.port() == 0U || voiceCh.port() == REST_API_DEFAULT_PORT) {
voiceCh.port(restPort);
}
m_host->rfCh()->setRFChData(channelNo, voiceCh);
m_host->m_voiceChPeerId[channelNo] = peerId;
LogMessage(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo);
LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", voiceCh.chId(), channelNo, voiceCh.address().c_str(), voiceCh.port(), voiceCh.ssl());
g_fireCCVCNotification = true; // announce this registration immediately to the FNE
} else {
LogMessage(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo);
}
}
/* REST API endpoint; implements release grant request. */
void RESTAPI::restAPI_PutReleaseGrant(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{
if (!validateAuth(request, reply)) {
return;
}
json::object req = json::object();
if (!parseRequestBody(request, reply, req)) {
return;
}
errorPayload(reply, "OK", HTTPPayload::OK);
if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) {
errorPayload(reply, "Host is not a control channel, cannot release TG grant");
return;
}
// validate state is a string within the JSON blob
if (!req["state"].is<int>()) {
errorPayload(reply, "state was not a valid integer");
return;
}
DVM_STATE state = (DVM_STATE)req["state"].get<int>();
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
errorPayload(reply, "destination ID was not a valid integer");
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
errorPayload(reply, "destination ID is an illegal TGID");
return;
}
// LogDebugEx(LOG_REST, "RESTAPI::restAPI_PutReleaseGrant()", "callback, state = %u, dstId = %u", state, dstId);
switch (state) {
case STATE_DMR:
{
// validate slot is a integer within the JSON blob
if (!req["slot"].is<int>()) {
errorPayload(reply, "slot was not a valid integer");
return;
}
uint8_t slot = (uint8_t)req["slot"].get<int>();
if (slot == 0U || slot > 2U) {
errorPayload(reply, "illegal DMR slot");
return;
}
if (m_dmr != nullptr) {
m_dmr->releaseGrantTG(dstId, slot);
}
else {
errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
}
}
break;
case STATE_P25:
{
if (m_p25 != nullptr) {
m_p25->releaseGrantTG(dstId);
}
else {
errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
}
}
break;
case STATE_NXDN:
{
if (m_nxdn != nullptr) {
m_nxdn->releaseGrantTG(dstId);
}
else {
errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
}
}
break;
default:
errorPayload(reply, "invalid mode");
}
}
/* REST API endpoint; implements touch grant request. */
void RESTAPI::restAPI_PutTouchGrant(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{
if (!validateAuth(request, reply)) {
return;
}
json::object req = json::object();
if (!parseRequestBody(request, reply, req)) {
return;
}
errorPayload(reply, "OK", HTTPPayload::OK);
if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) {
errorPayload(reply, "Host is not a control channel, cannot touch TG grant");
return;
}
// validate state is a string within the JSON blob
if (!req["state"].is<int>()) {
errorPayload(reply, "state was not a valid integer");
return;
}
DVM_STATE state = (DVM_STATE)req["state"].get<int>();
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
errorPayload(reply, "destination ID was not a valid integer");
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
errorPayload(reply, "destination ID is an illegal TGID");
return;
}
// LogDebugEx(LOG_REST, "RESTAPI::restAPI_PutTouchGrant()", "callback, state = %u, dstId = %u", state, dstId);
switch (state) {
case STATE_DMR:
{
// validate slot is a integer within the JSON blob
if (!req["slot"].is<int>()) {
errorPayload(reply, "slot was not a valid integer");
return;
}
uint8_t slot = (uint8_t)req["slot"].get<int>();
if (slot == 0U || slot > 2U) {
errorPayload(reply, "illegal DMR slot");
return;
}
if (m_dmr != nullptr) {
m_dmr->touchGrantTG(dstId, slot);
}
else {
errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
}
}
break;
case STATE_P25:
{
if (m_p25 != nullptr) {
m_p25->touchGrantTG(dstId);
}
else {
errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
}
}
break;
case STATE_NXDN:
{
if (m_nxdn != nullptr) {
m_nxdn->touchGrantTG(dstId);
}
else {
errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
}
}
break;
default:
errorPayload(reply, "invalid mode");
}
}
/* REST API endpoint; implements get RID whitelist request. */
void RESTAPI::restAPI_GetRIDWhitelist(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
@ -1499,87 +1234,6 @@ void RESTAPI::restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload&
}
}
/* REST API endpoint; implements trigger TSCC payload channel activation request. */
void RESTAPI::restAPI_PutTSCCPayloadActivate(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{
if (!validateAuth(request, reply)) {
return;
}
json::object req = json::object();
if (!parseRequestBody(request, reply, req)) {
return;
}
if (m_dmr == nullptr) {
errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return;
}
// validate destination ID is a integer within the JSON blob
if (!req["slot"].is<uint8_t>()) {
errorPayload(reply, "slot was not valid");
return;
}
// validate clear flag is a boolean within the JSON blob
if (!req["clear"].is<bool>()) {
errorPayload(reply, "clear flag was not valid");
return;
}
uint8_t slot = req["slot"].get<uint8_t>();
bool clear = req["clear"].get<bool>();
if (slot == 0U || slot >= 3U) {
errorPayload(reply, "invalid DMR slot number (slot == 0 or slot > 3)");
return;
}
errorPayload(reply, "OK", HTTPPayload::OK);
if (clear) {
m_dmr->tsccClearActivatedSlot(slot);
}
else {
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<uint32_t>()) {
errorPayload(reply, "destination ID was not valid");
return;
}
// validate destination ID is a integer within the JSON blob
if (!req["srcId"].is<uint32_t>()) {
errorPayload(reply, "source ID was not valid");
return;
}
// validate group flag is a boolean within the JSON blob
if (!req["group"].is<bool>()) {
errorPayload(reply, "group flag was not valid");
return;
}
// validate voice flag is a boolean within the JSON blob
if (!req["voice"].is<bool>()) {
errorPayload(reply, "voice flag was not valid");
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
uint32_t srcId = req["srcId"].get<uint32_t>();
bool group = req["group"].get<bool>();
bool voice = req["voice"].get<bool>();
if (dstId == 0U) {
errorPayload(reply, "destination ID was not valid");
return;
}
m_dmr->tsccActivateSlot(slot, dstId, srcId, group, voice);
}
}
/* REST API endpoint; implements get DMR affiliations request. */
void RESTAPI::restAPI_GetDMRAffList(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)

@ -225,28 +225,6 @@ private:
*/
void restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/**
* @brief REST API endpoint; implements register CC/VC request.
* @param request HTTP request.
* @param reply HTTP reply.
* @param match HTTP request matcher.
*/
void restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/**
* @brief REST API endpoint; implements release grant request.
* @param request HTTP request.
* @param reply HTTP reply.
* @param match HTTP request matcher.
*/
void restAPI_PutReleaseGrant(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/**
* @brief REST API endpoint; implements touch grant request.
* @param request HTTP request.
* @param reply HTTP reply.
* @param match HTTP request matcher.
*/
void restAPI_PutTouchGrant(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/**
* @brief REST API endpoint; implements get RID whitelist request.
* @param request HTTP request.
@ -308,13 +286,6 @@ private:
* @param match HTTP request matcher.
*/
void restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/**
* @brief REST API endpoint; implements trigger TSCC payload channel activation request.
* @param request HTTP request.
* @param reply HTTP reply.
* @param match HTTP request matcher.
*/
void restAPI_PutTSCCPayloadActivate(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/**
* @brief REST API endpoint; implements get DMR affiliations request.
* @param request HTTP request.

@ -65,10 +65,6 @@
#define GET_RELEASE_GRNTS "/release-grants"
#define GET_RELEASE_AFFS "/release-affs"
#define PUT_REGISTER_CC_VC "/register-cc-vc"
#define PUT_RELEASE_TG "/release-tg-grant"
#define PUT_TOUCH_TG "/touch-tg-grant"
#define GET_RID_WHITELIST_BASE "/rid-whitelist/"
#define GET_RID_WHITELIST GET_RID_WHITELIST_BASE"(\\d+)"
#define GET_RID_BLACKLIST_BASE "/rid-blacklist/"
@ -82,7 +78,6 @@
#define PUT_DMR_RID "/dmr/rid"
#define GET_DMR_CC_DEDICATED "/dmr/cc-enable"
#define GET_DMR_CC_BCAST "/dmr/cc-broadcast"
#define PUT_DMR_TSCC_PAYLOAD_ACT "/dmr/payload-activate"
#define GET_DMR_AFFILIATIONS "/dmr/report-affiliations"
#define GET_P25_CC "/p25/cc"

@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - 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) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @defgroup host_rpc Host RPC
* @brief Implementation for the host RPC.
* @ingroup host
*
* @file RPCDefines.h
* @ingroup host_prc
*/
#if !defined(__RPC_DEFINES_H__)
#define __RPC_DEFINES_H__
#include "Defines.h"
/**
* @addtogroup host_rpc
* @{
*/
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define RPC_REGISTER_CC_VC 0x1000U
#define RPC_RELEASE_P25_TG 0x0101U
#define RPC_RELEASE_DMR_TG 0x0102U
#define RPC_RELEASE_NXDN_TG 0x0103U
#define RPC_TOUCH_P25_TG 0x0201U
#define RPC_TOUCH_DMR_TG 0x0202U
#define RPC_TOUCH_NXDN_TG 0x0203U
#define RPC_PERMIT_P25_TG 0x0001U
#define RPC_PERMIT_DMR_TG 0x0002U
#define RPC_PERMIT_NXDN_TG 0x0003U
#define RPC_DMR_TSCC_PAYLOAD_ACT 0x0010U
/** @} */
#endif // __RPC_DEFINES_H__

@ -20,8 +20,8 @@
#include "common/Log.h"
#include "common/Utils.h"
#include "nxdn/Control.h"
#include "remote/RESTClient.h"
#include "ActivityLog.h"
#include "Host.h"
using namespace nxdn;
using namespace nxdn::defines;
@ -134,6 +134,11 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q
lc::RCCH::setVerbose(dumpRCCHData);
lc::RTCH::setVerbose(dumpRCCHData);
// register RPC handlers
g_RPC->registerHandler(RPC_PERMIT_NXDN_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this));
g_RPC->registerHandler(RPC_RELEASE_NXDN_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this));
g_RPC->registerHandler(RPC_TOUCH_NXDN_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this));
}
/* Finalizes a instance of the Control class. */
@ -265,13 +270,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_siteData.channelNo()) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_NXDN;
req["state"].set<int>(state);
dstId = 0U; // clear TG value
req["dstId"].set<uint32_t>(dstId);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_debug);
g_RPC->req(RPC_PERMIT_NXDN_TG, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_NXDN, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_RESP ", failed to clear TG permit, chNo = %u", chNo);
@ -309,6 +311,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
if (m_control->m_disableGrantSrcIdCheck) {
LogInfo(" Disable Grant Source ID Check: yes");
}
if (m_supervisor)
LogMessage(LOG_DMR, "Host is configured to operate as a NXDN control channel, site controller mode.");
}
LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no");
@ -728,7 +732,10 @@ void Control::permittedTG(uint32_t dstId)
}
if (m_verbose) {
LogMessage(LOG_NXDN, "non-authoritative TG permit, dstId = %u", dstId);
if (dstId == 0U)
LogMessage(LOG_NXDN, "non-authoritative TG unpermit");
else
LogMessage(LOG_NXDN, "non-authoritative TG permit, dstId = %u", dstId);
}
m_permittedDstId = dstId;
@ -749,52 +756,6 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, bool grp)
m_control->writeRF_Message_Grant(srcId, dstId, 4U, grp);
}
/* Releases a granted TG. */
void Control::releaseGrantTG(uint32_t dstId)
{
if (!m_enableControl) {
return;
}
if (m_verbose) {
LogMessage(LOG_NXDN, "VC request, release TG grant, dstId = %u", dstId);
}
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_NXDN, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.releaseGrant(dstId, false);
}
}
/* Touches a granted TG to keep a channel grant alive. */
void Control::touchGrantTG(uint32_t dstId)
{
if (!m_enableControl) {
return;
}
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_NXDN, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.touchGrant(dstId);
}
}
/* Clears the current operating RF state back to idle. */
void Control::clearRFReject()
@ -1103,15 +1064,25 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId)
// callback REST API to release the granted TG on the specified control channel
json::object req = json::object();
int state = modem::DVM_STATE::STATE_NXDN;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
g_RPC->req(RPC_RELEASE_NXDN_TG, req, [=](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_NXDN, "RPC failed, %s", retMsg.c_str());
}
}
else
::LogMessage(LOG_NXDN, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}, m_controlChData.address(), m_controlChData.port());
m_rfLastDstId = 0U;
m_rfLastSrcId = 0U;
@ -1137,14 +1108,130 @@ void Control::notifyCC_TouchGrant(uint32_t dstId)
// callback REST API to touch the granted TG on the specified control channel
json::object req = json::object();
int state = modem::DVM_STATE::STATE_NXDN;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
g_RPC->req(RPC_TOUCH_NXDN_TG, req, [=](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_NXDN, "RPC failed, %s", retMsg.c_str());
}
}
else
::LogMessage(LOG_NXDN, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}, m_controlChData.address(), m_controlChData.port());
}
/* (RPC Handler) Permits a TGID on a non-authoritative host. */
void Control::RPC_permittedTG(json::object& req, json::object& reply)
{
if (!m_enableControl) {
g_RPC->defaultResponse(reply, "not NXDN control channel", network::RPC::BAD_REQUEST);
return;
}
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
// LogDebugEx(LOG_NXDN, "Control::RPC_permittedTG()", "callback, dstId = %u, dataPermit = %u", dstId, dataPermit);
permittedTG(dstId);
}
/* (RPC Handler) Releases a granted TG. */
void Control::RPC_releaseGrantTG(json::object& req, json::object& reply)
{
if (!m_enableControl) {
g_RPC->defaultResponse(reply, "not NXDN control channel", network::RPC::BAD_REQUEST);
return;
}
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
g_RPC->defaultResponse(reply, "destination ID is an illegal TGID");
return;
}
// LogDebugEx(LOG_NXDN, "Control::RPC_releaseGrantTG()", "callback, dstId = %u", dstId);
if (m_verbose) {
LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId);
}
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.releaseGrant(dstId, false);
}
}
/* (RPC Handler) Touches a granted TG to keep a channel grant alive. */
void Control::RPC_touchGrantTG(json::object& req, json::object& reply)
{
if (!m_enableControl) {
g_RPC->defaultResponse(reply, "not NXDN control channel");
return;
}
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
g_RPC->defaultResponse(reply, "destination ID is an illegal TGID");
return;
}
// LogDebugEx(LOG_NXDN, "Control::RPC_touchGrantTG()", "callback, dstId = %u", dstId);
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.touchGrant(dstId);
}
}

@ -31,6 +31,7 @@
#include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/lookups/AffiliationLookup.h"
#include "common/network/RPC.h"
#include "common/network/RTPFNEHeader.h"
#include "common/network/Network.h"
#include "common/RingBuffer.h"
@ -194,16 +195,6 @@ namespace nxdn
* @param grp Flag indicating group grant.
*/
void grantTG(uint32_t srcId, uint32_t dstId, bool grp);
/**
* @brief Releases a granted TG.
* @param dstId Destination ID.
*/
void releaseGrantTG(uint32_t dstId);
/**
* @brief Touches a granted TG to keep a channel grant alive.
* @param dstId Destination ID.
*/
void touchGrantTG(uint32_t dstId);
/** @} */
/**
@ -390,6 +381,27 @@ namespace nxdn
*/
void notifyCC_TouchGrant(uint32_t dstId);
/** @name Supervisory Control */
/**
* @brief (RPC Handler) Permits a TGID on a non-authoritative host.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_permittedTG(json::object& req, json::object& reply);
/**
* @brief (RPC Handler) Releases a granted TG.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_releaseGrantTG(json::object& req, json::object& reply);
/**
* @brief (RPC Handler) Touches a granted TG to keep a channel grant alive.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_touchGrantTG(json::object& req, json::object& reply);
/** @} */
/**
* @brief Helper to write control channel frame data.
* @returns bool True, if control data is written, otherwise false.

@ -17,8 +17,8 @@
#include "common/Log.h"
#include "common/Utils.h"
#include "nxdn/packet/ControlSignaling.h"
#include "remote/RESTClient.h"
#include "ActivityLog.h"
#include "Host.h"
using namespace nxdn;
using namespace nxdn::defines;
@ -560,28 +560,40 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin
}
}
// callback REST API to permit the granted TG on the specified voice channel
// callback RPC to permit the granted TG on the specified voice channel
if (m_nxdn->m_authoritative && m_nxdn->m_supervisor) {
::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_nxdn->m_siteData.channelNo()) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_NXDN;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT / 2, m_nxdn->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo);
m_nxdn->m_affiliations.releaseGrant(dstId, false);
if (!net) {
writeRF_Message_Deny(0U, srcId, CauseResponse::VD_QUE_GRP_BUSY, MessageType::RTCH_VCALL);
m_nxdn->m_rfState = RS_RF_REJECTED;
bool requestFailed = false;
std::string rcchStr = rcch->toString().c_str();
g_RPC->req(RPC_PERMIT_NXDN_TG, req, [=, &requestFailed, &rcchStr](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcchStr.c_str(), chNo);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError((net) ? LOG_NET : LOG_RF, "NXDN, RPC failed, %s", retMsg.c_str());
}
m_nxdn->m_affiliations.releaseGrant(dstId, false);
if (!net) {
writeRF_Message_Deny(0U, srcId, CauseResponse::VD_QUE_GRP_BUSY, MessageType::RTCH_VCALL);
m_nxdn->m_rfState = RS_RF_REJECTED;
}
requestFailed = true;
}
}, voiceChData.address(), voiceChData.port());
if (requestFailed)
return false;
}
}
else {
::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo);

@ -18,9 +18,9 @@
#include "common/Utils.h"
#include "p25/Control.h"
#include "modem/ModemV24.h"
#include "remote/RESTClient.h"
#include "ActivityLog.h"
#include "HostMain.h"
#include "Host.h"
using namespace p25::packet;
using namespace p25::defines;
@ -159,6 +159,11 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
m_llaRS = new uint8_t[AUTH_KEY_LENGTH_BYTES];
m_llaCRS = new uint8_t[AUTH_KEY_LENGTH_BYTES];
m_llaKS = new uint8_t[AUTH_KEY_LENGTH_BYTES];
// register RPC handlers
g_RPC->registerHandler(RPC_PERMIT_P25_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this));
g_RPC->registerHandler(RPC_RELEASE_P25_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this));
g_RPC->registerHandler(RPC_TOUCH_P25_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this));
}
/* Finalizes a instance of the Control class. */
@ -416,13 +421,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_siteData.channelNo()) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_P25;
req["state"].set<int>(state);
dstId = 0U; // clear TG value
req["dstId"].set<uint32_t>(dstId);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_DEFAULT_WAIT, m_debug);
g_RPC->req(RPC_PERMIT_P25_TG, req, nullptr, voiceChData.address(), voiceChData.port());
}
else {
::LogError(LOG_P25, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Grant), failed to clear TG permit, chNo = %u", chNo);
@ -461,6 +463,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
if (m_control->m_disableGrantSrcIdCheck) {
LogInfo(" Disable Grant Source ID Check: yes");
}
if (m_supervisor)
LogMessage(LOG_P25, "Host is configured to operate as a P25 control channel, site controller mode.");
}
if (m_controlOnly) {
@ -1050,7 +1054,10 @@ void Control::permittedTG(uint32_t dstId, bool dataPermit)
}
if (m_verbose) {
LogMessage(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId);
if (dstId == 0U)
LogMessage(LOG_P25, "non-authoritative TG unpermit");
else
LogMessage(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId);
}
m_permittedDstId = dstId;
@ -1076,52 +1083,6 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, bool grp)
m_control->writeRF_TSDU_Grant(srcId, dstId, 4U, grp);
}
/* Releases a granted TG. */
void Control::releaseGrantTG(uint32_t dstId)
{
if (!m_enableControl) {
return;
}
if (m_verbose) {
LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId);
}
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.releaseGrant(dstId, false);
}
}
/* Touches a granted TG to keep a channel grant alive. */
void Control::touchGrantTG(uint32_t dstId)
{
if (!m_enableControl) {
return;
}
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.touchGrant(dstId);
}
}
/* Clears the current operating RF state back to idle. */
void Control::clearRFReject()
@ -1624,15 +1585,25 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId)
// callback REST API to release the granted TG on the specified control channel
json::object req = json::object();
int state = modem::DVM_STATE::STATE_P25;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
g_RPC->req(RPC_RELEASE_P25_TG, req, [=](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str());
}
}
else
::LogMessage(LOG_P25, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}, m_controlChData.address(), m_controlChData.port());
m_rfLastDstId = 0U;
m_rfLastSrcId = 0U;
@ -1658,15 +1629,25 @@ void Control::notifyCC_TouchGrant(uint32_t dstId)
// callback REST API to touch the granted TG on the specified control channel
json::object req = json::object();
int state = modem::DVM_STATE::STATE_P25;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
g_RPC->req(RPC_TOUCH_P25_TG, req, [=](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str());
}
}
else
::LogMessage(LOG_P25, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}, m_controlChData.address(), m_controlChData.port());
}
/* Helper to write control channel frame data. */
@ -1809,6 +1790,118 @@ void Control::writeRF_TDU(bool noNetwork, bool imm)
}
}
/* (RPC Handler) Permits a TGID on a non-authoritative host. */
void Control::RPC_permittedTG(json::object& req, json::object& reply)
{
if (!m_enableControl) {
g_RPC->defaultResponse(reply, "not P25 control channel", network::RPC::BAD_REQUEST);
return;
}
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
bool dataPermit = false;
// validate destination ID is a integer within the JSON blob
if (req["dataPermit"].is<bool>()) {
dataPermit = (bool)req["dataPermit"].get<bool>();
}
// LogDebugEx(LOG_P25, "Control::RPC_permittedTG()", "callback, dstId = %u, dataPermit = %u", dstId, dataPermit);
permittedTG(dstId, dataPermit);
}
/* (RPC Handler) Releases a granted TG. */
void Control::RPC_releaseGrantTG(json::object& req, json::object& reply)
{
if (!m_enableControl) {
g_RPC->defaultResponse(reply, "not P25 control channel", network::RPC::BAD_REQUEST);
return;
}
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
g_RPC->defaultResponse(reply, "destination ID is an illegal TGID");
return;
}
// LogDebugEx(LOG_P25, "Control::RPC_releaseGrantTG()", "callback, dstId = %u", dstId);
if (m_verbose) {
LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId);
}
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.releaseGrant(dstId, false);
}
}
/* (RPC Handler) Touches a granted TG to keep a channel grant alive. */
void Control::RPC_touchGrantTG(json::object& req, json::object& reply)
{
if (!m_enableControl) {
g_RPC->defaultResponse(reply, "not P25 control channel");
return;
}
g_RPC->defaultResponse(reply, "OK", network::RPC::OK);
// validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) {
g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS);
return;
}
uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) {
g_RPC->defaultResponse(reply, "destination ID is an illegal TGID");
return;
}
// LogDebugEx(LOG_P25, "Control::RPC_touchGrantTG()", "callback, dstId = %u", dstId);
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
}
m_affiliations.touchGrant(dstId);
}
}
/* Helper to setup and generate LLA AM1 parameters. */
void Control::generateLLA_AM1_Parameters()

@ -31,6 +31,7 @@
#include "common/lookups/IdenTableLookup.h"
#include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/network/RPC.h"
#include "common/network/RTPFNEHeader.h"
#include "common/network/Network.h"
#include "common/p25/SiteData.h"
@ -211,16 +212,6 @@ namespace p25
* @param grp Flag indicating group grant.
*/
void grantTG(uint32_t srcId, uint32_t dstId, bool grp);
/**
* @brief Releases a granted TG.
* @param dstId Destination ID.
*/
void releaseGrantTG(uint32_t dstId);
/**
* @brief Touches a granted TG to keep a channel grant alive.
* @param dstId Destination ID.
*/
void touchGrantTG(uint32_t dstId);
/** @} */
/**
@ -455,6 +446,27 @@ namespace p25
*/
void writeRF_TDU(bool noNetwork, bool imm = false);
/** @name Supervisory Control */
/**
* @brief (RPC Handler) Permits a TGID on a non-authoritative host.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_permittedTG(json::object& req, json::object& reply);
/**
* @brief (RPC Handler) Releases a granted TG.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_releaseGrantTG(json::object& req, json::object& reply);
/**
* @brief (RPC Handler) Touches a granted TG to keep a channel grant alive.
* @param req JSON request.
* @param reply JSON response.
*/
void RPC_touchGrantTG(json::object& req, json::object& reply);
/** @} */
/**
* @brief Helper to setup and generate LLA AM1 parameters.
*/

@ -22,9 +22,9 @@
#include "p25/packet/Voice.h"
#include "p25/packet/ControlSignaling.h"
#include "p25/lookups/P25AffiliationLookup.h"
#include "remote/RESTClient.h"
#include "ActivityLog.h"
#include "HostMain.h"
#include "Host.h"
using namespace p25;
using namespace p25::defines;
@ -2288,27 +2288,38 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
if (grp) {
// callback REST API to permit the granted TG on the specified voice channel
// callback RPC to permit the granted TG on the specified voice channel
if (m_p25->m_authoritative && m_p25->m_supervisor) {
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_p25->m_siteData.channelNo()) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_P25;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_p25->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request), failed to permit TG for use, chNo = %u", chNo);
m_p25->m_affiliations.releaseGrant(dstId, false);
if (!net) {
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true);
m_p25->m_rfState = RS_RF_REJECTED;
bool requestFailed = false;
g_RPC->req(RPC_PERMIT_P25_TG, req, [=, &requestFailed](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request), failed to permit TG for use, chNo = %u", chNo);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError((net) ? LOG_NET : LOG_RF, "P25, RPC failed, %s", retMsg.c_str());
}
m_p25->m_affiliations.releaseGrant(dstId, false);
if (!net) {
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true);
m_p25->m_rfState = RS_RF_REJECTED;
}
requestFailed = true;
}
}, voiceChData.address(), voiceChData.port());
if (requestFailed)
return false;
}
}
else {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request), failed to permit TG for use, chNo = %u", chNo);
@ -2342,27 +2353,38 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
}
else {
// callback REST API to permit the granted TG on the specified voice channel
// callback RPC to permit the granted TG on the specified voice channel
if (m_p25->m_authoritative && m_p25->m_supervisor) {
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_p25->m_siteData.channelNo()) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_P25;
req["state"].set<int>(state);
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_p25->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), failed to permit TG for use, chNo = %u", chNo);
m_p25->m_affiliations.releaseGrant(dstId, false);
if (!net) {
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true);
m_p25->m_rfState = RS_RF_REJECTED;
bool requestFailed = false;
g_RPC->req(RPC_PERMIT_P25_TG, req, [=, &requestFailed](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), failed to permit TG for use, chNo = %u", chNo);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError((net) ? LOG_NET : LOG_RF, "P25, RPC failed, %s", retMsg.c_str());
}
m_p25->m_affiliations.releaseGrant(dstId, false);
if (!net) {
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true);
m_p25->m_rfState = RS_RF_REJECTED;
}
requestFailed = true;
}
}, voiceChData.address(), voiceChData.port());
if (requestFailed)
return false;
}
}
else {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), failed to permit TG for use, chNo = %u", chNo);
@ -2540,7 +2562,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3
if (chNo > 0U) {
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
// callback REST API to permit the granted TG on the specified voice channel
// callback RPC to permit the granted TG on the specified voice channel
if (m_p25->m_authoritative && m_p25->m_supervisor) {
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_p25->m_siteData.channelNo()) {
@ -2551,16 +2573,29 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3
bool dataCh = true;
req["dataPermit"].set<bool>(dataCh);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_p25->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo);
m_p25->m_affiliations.releaseGrant(srcId, false);
writeRF_TSDU_Deny(srcId, srcId, ReasonCode::DENY_PTT_BONK, TSBKO::ISP_SNDCP_CH_REQ, false, true);
m_p25->m_rfState = RS_RF_REJECTED;
bool requestFailed = false;
g_RPC->req(RPC_PERMIT_P25_TG, req, [=, &requestFailed](json::object& req, json::object& reply) {
if (!req["status"].is<int>()) {
return;
}
int status = req["status"].get<int>();
if (status != network::RPC::OK) {
::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo);
if (req["message"].is<std::string>()) {
std::string retMsg = req["message"].get<std::string>();
::LogError(LOG_RF, "P25, RPC failed, %s", retMsg.c_str());
}
m_p25->m_affiliations.releaseGrant(srcId, false);
writeRF_TSDU_Deny(srcId, srcId, ReasonCode::DENY_PTT_BONK, TSBKO::ISP_SNDCP_CH_REQ, false, true);
m_p25->m_rfState = RS_RF_REJECTED;
requestFailed = true;
}
}, voiceChData.address(), voiceChData.port());
if (requestFailed)
return false;
}
}
else {
::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo);

@ -71,7 +71,6 @@
#define RCD_RELEASE_GRANTS "rel-grnts"
#define RCD_RELEASE_AFFS "rel-affs"
#define RCD_RELEASE_AFF "rel-aff"
#define RCD_DMR_BEACON "dmr-beacon"
#define RCD_P25_CC "p25-cc"
@ -233,7 +232,6 @@ void usage(const char* message, const char* arg)
reply += "\r\n";
reply += " rel-grnts Forcibly releases all channel grants\r\n";
reply += " rel-affs Forcibly releases all group affiliations\r\n";
reply += " rel-aff <state> <dstid> Forcibly releases specified group affiliations\r\n";
reply += "\r\n";
reply += " dmr-beacon Transmits a DMR beacon burst\r\n";
reply += " p25-cc Transmits a non-continous P25 CC burst\r\n";
@ -558,21 +556,6 @@ int main(int argc, char** argv)
else if (rcom == RCD_RELEASE_AFFS) {
retCode = client->send(HTTP_GET, GET_RELEASE_AFFS, json::object(), response);
}
else if (rcom == RCD_RELEASE_AFF) {
json::object req = json::object();
int state = getArgInt32(args, 0U);
req["state"].set<int>(state);
uint32_t dstId = getArgInt32(args, 1U);
req["dstId"].set<uint32_t>(dstId);
if (state == 1) {
uint8_t slot = getArgInt8(args, 2U);
req["slot"].set<uint8_t>(slot);
}
retCode = client->send(HTTP_PUT, PUT_RELEASE_TG, req, response);
}
/*
** Digital Mobile Radio

@ -695,7 +695,7 @@ private:
uint8_t channelId = peerObj["channelId"].get<uint8_t>();
uint32_t channelNo = peerObj["channelNo"].get<uint32_t>();
VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string(), false);
VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string());
wdgt->channelId = channelId;
wdgt->channelNo = channelNo;
@ -747,7 +747,7 @@ private:
uint8_t channelId = peerObj["channelId"].get<uint8_t>();
uint32_t channelNo = peerObj["channelNo"].get<uint32_t>();
VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string(), false);
VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string());
wdgt->channelId = channelId;
wdgt->channelNo = channelNo;

Loading…
Cancel
Save

Powered by TurnKey Linux.