implement the majority of REST API support (this commit breaks RCON and any RCON-based tools [like dvmcmd]) (NOTE: not *all* RCON commands are implemented as REST API yet);

pull/19/head
Bryan Biedenkapp 3 years ago
parent 069adffd7c
commit 529d5c1e83

@ -36,6 +36,7 @@
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <ios> #include <ios>
#include <algorithm>
#include <string> #include <string>
#if !defined(ENABLE_DMR) && !defined(ENABLE_P25) && !defined(ENABLE_NXDN) #if !defined(ENABLE_DMR) && !defined(ENABLE_P25) && !defined(ENABLE_NXDN)
@ -134,7 +135,7 @@ typedef unsigned long long ulong64_t;
const uint32_t REMOTE_MODEM_PORT = 3334; const uint32_t REMOTE_MODEM_PORT = 3334;
const uint32_t TRAFFIC_DEFAULT_PORT = 62031; const uint32_t TRAFFIC_DEFAULT_PORT = 62031;
const uint32_t RCON_DEFAULT_PORT = 9990; const uint32_t REST_API_DEFAULT_PORT = 9990;
const uint8_t BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; const uint8_t BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U };
@ -224,6 +225,17 @@ inline std::string __IP_FROM_ULONG(const ulong64_t& value) {
return ss.str(); return ss.str();
} }
/// <summary>
/// Helper to lower-case an input string.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
inline std::string strtolower(const std::string value) {
std::string v = value;
std::transform(v.begin(), v.end(), v.begin(), ::tolower);
return v;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Macros // Macros
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

@ -40,7 +40,7 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#define LOG_HOST "HOST" #define LOG_HOST "HOST"
#define LOG_RCON "RCON" #define LOG_REST "RESTAPI"
#define LOG_MODEM "MODEM" #define LOG_MODEM "MODEM"
#define LOG_RF "RF" #define LOG_RF "RF"
#define LOG_NET "NET" #define LOG_NET "NET"

@ -206,7 +206,7 @@ void Control::setCCRunning(bool ccRunning)
m_slot2->setCCRunning(ccRunning); m_slot2->setCCRunning(ccRunning);
break; break;
default: default:
LogError(LOG_NET, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo);
break; break;
} }
} }
@ -226,7 +226,7 @@ void Control::setCCHalted(bool ccHalted)
m_slot2->setCCHalted(ccHalted); m_slot2->setCCHalted(ccHalted);
break; break;
default: default:
LogError(LOG_NET, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo);
break; break;
} }
} }
@ -285,7 +285,7 @@ bool Control::processFrame(uint32_t slotNo, uint8_t *data, uint32_t len)
case 2U: case 2U:
return m_slot2->processFrame(data, len); return m_slot2->processFrame(data, len);
default: default:
LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo);
return false; return false;
} }
} }
@ -305,7 +305,7 @@ uint32_t Control::getFrame(uint32_t slotNo, uint8_t* data)
case 2U: case 2U:
return m_slot2->getFrame(data); return m_slot2->getFrame(data);
default: default:
LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo);
return 0U; return 0U;
} }
} }
@ -328,7 +328,7 @@ void Control::clock()
m_slot2->processNetwork(data); m_slot2->processNetwork(data);
break; break;
default: default:
LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo);
break; break;
} }
} }
@ -353,9 +353,28 @@ void Control::permittedTG(uint32_t dstId, uint8_t slot)
m_slot2->permittedTG(dstId); m_slot2->permittedTG(dstId);
break; break;
default: default:
LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slot); LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot);
break;
}
}
/// <summary>
/// Gets instance of the AffiliationLookup class.
/// </summary>
dmr::lookups::DMRAffiliationLookup Control::affiliations()
{
switch (m_tsccSlotNo) {
case 1U:
return m_slot1->m_affiliations;
case 2U:
return m_slot2->m_affiliations;
break;
default:
LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", m_tsccSlotNo);
break; break;
} }
return 0; // ??
} }
/// <summary> /// <summary>
@ -372,7 +391,7 @@ Slot* Control::getTSCCSlot() const
return m_slot2; return m_slot2;
break; break;
default: default:
LogError(LOG_NET, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo);
return nullptr; return nullptr;
} }
} }
@ -394,7 +413,7 @@ void Control::writeRF_Ext_Func(uint32_t slotNo, uint32_t func, uint32_t arg, uin
m_slot2->control()->writeRF_Ext_Func(func, arg, dstId); m_slot2->control()->writeRF_Ext_Func(func, arg, dstId);
break; break;
default: default:
LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo);
break; break;
} }
} }
@ -415,7 +434,7 @@ void Control::writeRF_Call_Alrt(uint32_t slotNo, uint32_t srcId, uint32_t dstId)
m_slot2->control()->writeRF_Call_Alrt(srcId, dstId); m_slot2->control()->writeRF_Call_Alrt(srcId, dstId);
break; break;
default: default:
LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo);
break; break;
} }
} }

@ -33,15 +33,14 @@
#include "Defines.h" #include "Defines.h"
#include "dmr/data/Data.h" #include "dmr/data/Data.h"
#include "dmr/lookups/DMRAffiliationLookup.h"
#include "dmr/Slot.h" #include "dmr/Slot.h"
#include "modem/Modem.h" #include "modem/Modem.h"
#include "network/BaseNetwork.h" #include "network/BaseNetwork.h"
#include "network/RemoteControl.h"
#include "lookups/RSSIInterpolator.h" #include "lookups/RSSIInterpolator.h"
#include "lookups/IdenTableLookup.h" #include "lookups/IdenTableLookup.h"
#include "lookups/RadioIdLookup.h" #include "lookups/RadioIdLookup.h"
#include "lookups/TalkgroupIdLookup.h" #include "lookups/TalkgroupIdLookup.h"
#include "lookups/AffiliationLookup.h"
#include "yaml/Yaml.h" #include "yaml/Yaml.h"
namespace dmr namespace dmr
@ -95,6 +94,9 @@ namespace dmr
/// <summary>Permits a TGID on a non-authoritative host.</summary> /// <summary>Permits a TGID on a non-authoritative host.</summary>
void permittedTG(uint32_t dstId, uint8_t slot); void permittedTG(uint32_t dstId, uint8_t slot);
/// <summary>Gets instance of the DMRAffiliationLookup class.</summary>
lookups::DMRAffiliationLookup affiliations();
/// <summary>Helper to return the slot carrying the TSCC.</summary> /// <summary>Helper to return the slot carrying the TSCC.</summary>
Slot* getTSCCSlot() const; Slot* getTSCCSlot() const;

@ -143,7 +143,7 @@ Host::Host(const std::string& confFile) :
m_controlPermitTG(false), m_controlPermitTG(false),
m_activeTickDelay(5U), m_activeTickDelay(5U),
m_idleTickDelay(5U), m_idleTickDelay(5U),
m_remoteControl(nullptr) m_RESTAPI(nullptr)
{ {
UDPSocket::startup(); UDPSocket::startup();
} }
@ -722,8 +722,8 @@ int Host::run()
setState(STATE_IDLE); setState(STATE_IDLE);
} }
if (m_remoteControl != nullptr) { if (m_RESTAPI != nullptr) {
m_remoteControl->setProtocols(dmr.get(), p25.get(), nxdn.get()); m_RESTAPI->setProtocols(dmr.get(), p25.get(), nxdn.get());
} }
::LogInfoEx(LOG_HOST, "Host is performing late initialization and warmup"); ::LogInfoEx(LOG_HOST, "Host is performing late initialization and warmup");
@ -1335,14 +1335,6 @@ int Host::run()
nxdn->clock(ms); nxdn->clock(ms);
#endif // defined(ENABLE_NXDN) #endif // defined(ENABLE_NXDN)
// ------------------------------------------------------
// -- Remote Control Processing --
// ------------------------------------------------------
if (m_remoteControl != nullptr) {
m_remoteControl->process(this, dmr.get(), p25.get(), nxdn.get());
}
// ------------------------------------------------------ // ------------------------------------------------------
// -- Timer Clocking -- // -- Timer Clocking --
// ------------------------------------------------------ // ------------------------------------------------------
@ -1848,13 +1840,13 @@ bool Host::readParams()
chNo = 4095U; chNo = 4095U;
} }
std::string rconAddress = channel["rconAddress"].as<std::string>("127.0.0.1"); std::string restApiAddress = channel["rconAddress"].as<std::string>("127.0.0.1");
uint16_t rconPort = (uint16_t)channel["rconPort"].as<uint32_t>(RCON_DEFAULT_PORT); uint16_t restApiPort = (uint16_t)channel["rconPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string rconPassword = channel["rconPassword"].as<std::string>(); std::string restApiPassword = channel["rconPassword"].as<std::string>();
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X RCON Adddress %s:%u", m_channelId, chNo, rconAddress.c_str(), rconPort); ::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Adddress %s:%u", m_channelId, chNo, restApiAddress.c_str(), restApiPort);
VoiceChData data = VoiceChData(chNo, rconAddress, rconPort, rconPassword); VoiceChData data = VoiceChData(chNo, restApiAddress, restApiPort, restApiPassword);
m_voiceChData[chNo] = data; m_voiceChData[chNo] = data;
m_voiceChNo.push_back(chNo); m_voiceChNo.push_back(chNo);
} }
@ -2260,20 +2252,20 @@ bool Host::createNetwork()
{ {
yaml::Node networkConf = m_conf["network"]; yaml::Node networkConf = m_conf["network"];
bool netEnable = networkConf["enable"].as<bool>(false); bool netEnable = networkConf["enable"].as<bool>(false);
bool rconEnable = networkConf["rconEnable"].as<bool>(false); bool restApiEnable = networkConf["rconEnable"].as<bool>(false);
// dump out if both networking and RCON are disabled // dump out if both networking and REST API are disabled
if (!netEnable && !rconEnable) { if (!netEnable && !restApiEnable) {
return true; return true;
} }
std::string address = networkConf["address"].as<std::string>(); std::string address = networkConf["address"].as<std::string>();
uint16_t port = (uint16_t)networkConf["port"].as<uint32_t>(TRAFFIC_DEFAULT_PORT); uint16_t port = (uint16_t)networkConf["port"].as<uint32_t>(TRAFFIC_DEFAULT_PORT);
uint16_t local = (uint16_t)networkConf["local"].as<uint32_t>(0U); uint16_t local = (uint16_t)networkConf["local"].as<uint32_t>(0U);
std::string rconAddress = networkConf["rconAddress"].as<std::string>("127.0.0.1"); std::string restApiAddress = networkConf["rconAddress"].as<std::string>("127.0.0.1");
uint16_t rconPort = (uint16_t)networkConf["rconPort"].as<uint32_t>(RCON_DEFAULT_PORT); uint16_t restApiPort = (uint16_t)networkConf["rconPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string rconPassword = networkConf["rconPassword"].as<std::string>(); std::string restApiPassword = networkConf["rconPassword"].as<std::string>();
bool rconDebug = networkConf["rconDebug"].as<bool>(false); bool restApiDebug = networkConf["rconDebug"].as<bool>(false);
uint32_t id = networkConf["id"].as<uint32_t>(0U); uint32_t id = networkConf["id"].as<uint32_t>(0U);
uint32_t jitter = networkConf["talkgroupHang"].as<uint32_t>(360U); uint32_t jitter = networkConf["talkgroupHang"].as<uint32_t>(360U);
std::string password = networkConf["password"].as<std::string>(); std::string password = networkConf["password"].as<std::string>();
@ -2284,11 +2276,16 @@ bool Host::createNetwork()
bool updateLookup = networkConf["updateLookups"].as<bool>(false); bool updateLookup = networkConf["updateLookups"].as<bool>(false);
bool debug = networkConf["debug"].as<bool>(false); bool debug = networkConf["debug"].as<bool>(false);
if (rconPassword.length() > 64) { if (restApiPassword.length() > 64) {
std::string password = rconPassword; std::string password = restApiPassword;
rconPassword = password.substr(0, 64); restApiPassword = password.substr(0, 64);
::LogWarning(LOG_HOST, "REST API password is too long; truncating to the first 64 characters.");
}
::LogWarning(LOG_HOST, "RCON password is too long; truncating to the first 64 characters."); if (restApiPassword.empty() && restApiEnable) {
::LogWarning(LOG_HOST, "REST API password not provided; REST API disabled.");
restApiEnable = false;
} }
IdenTable entry = m_idenTable->find(m_channelId); IdenTable entry = m_idenTable->find(m_channelId);
@ -2314,13 +2311,13 @@ bool Host::createNetwork()
LogInfo(" Debug: yes"); LogInfo(" Debug: yes");
} }
} }
LogInfo(" RCON Enabled: %s", rconEnable ? "yes" : "no"); LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no");
if (rconEnable) { if (restApiEnable) {
LogInfo(" RCON Address: %s", rconAddress.c_str()); LogInfo(" REST API Address: %s", restApiAddress.c_str());
LogInfo(" RCON Port: %u", rconPort); LogInfo(" REST API Port: %u", restApiPort);
if (rconDebug) { if (restApiDebug) {
LogInfo(" RCON Debug: yes"); LogInfo(" REST API Debug: yes");
} }
} }
@ -2331,8 +2328,8 @@ bool Host::createNetwork()
m_network->setLookups(m_ridLookup, m_tidLookup); m_network->setLookups(m_ridLookup, m_tidLookup);
m_network->setMetadata(m_identity, m_rxFrequency, m_txFrequency, entry.txOffsetMhz(), entry.chBandwidthKhz(), m_channelId, m_channelNo, m_network->setMetadata(m_identity, m_rxFrequency, m_txFrequency, entry.txOffsetMhz(), entry.chBandwidthKhz(), m_channelId, m_channelNo,
m_power, m_latitude, m_longitude, m_height, m_location); m_power, m_latitude, m_longitude, m_height, m_location);
if (rconEnable) { if (restApiEnable) {
m_network->setRconData(rconPassword, rconPort); m_network->setRESTAPIData(restApiPassword, restApiPort);
} }
bool ret = m_network->open(); bool ret = m_network->open();
@ -2348,19 +2345,19 @@ bool Host::createNetwork()
} }
// initialize network remote command // initialize network remote command
if (rconEnable) { if (restApiEnable) {
m_remoteControl = new RemoteControl(rconAddress, rconPort, rconPassword, this, rconDebug); m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, this, restApiDebug);
m_remoteControl->setLookups(m_ridLookup, m_tidLookup); m_RESTAPI->setLookups(m_ridLookup, m_tidLookup);
bool ret = m_remoteControl->open(); bool ret = m_RESTAPI->open();
if (!ret) { if (!ret) {
delete m_remoteControl; delete m_RESTAPI;
m_remoteControl = nullptr; m_RESTAPI = nullptr;
LogError(LOG_HOST, "failed to initialize remote command networking! remote command control will be unavailable!"); LogError(LOG_HOST, "failed to initialize REST API networking! REST API will be unavailable!");
// remote command control failing isn't fatal -- we'll allow this to return normally // REST API failing isn't fatal -- we'll allow this to return normally
} }
} }
else { else {
m_remoteControl = nullptr; m_RESTAPI = nullptr;
} }
return true; return true;
@ -2564,9 +2561,9 @@ void Host::setState(uint8_t state)
delete m_network; delete m_network;
} }
if (m_remoteControl != nullptr) { if (m_RESTAPI != nullptr) {
m_remoteControl->close(); m_RESTAPI->close();
delete m_remoteControl; delete m_RESTAPI;
} }
} }
else { else {

@ -33,7 +33,7 @@
#include "Defines.h" #include "Defines.h"
#include "network/Network.h" #include "network/Network.h"
#include "network/RemoteControl.h" #include "network/RESTAPI.h"
#include "modem/Modem.h" #include "modem/Modem.h"
#include "Timer.h" #include "Timer.h"
#include "lookups/AffiliationLookup.h" #include "lookups/AffiliationLookup.h"
@ -49,7 +49,7 @@
// Class Prototypes // Class Prototypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
class HOST_SW_API RemoteControl; class HOST_SW_API RESTAPI;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
@ -152,8 +152,8 @@ private:
uint8_t m_activeTickDelay; uint8_t m_activeTickDelay;
uint8_t m_idleTickDelay; uint8_t m_idleTickDelay;
friend class RemoteControl; friend class RESTAPI;
RemoteControl* m_remoteControl; RESTAPI* m_RESTAPI;
/// <summary>Reads basic configuration parameters from the INI.</summary> /// <summary>Reads basic configuration parameters from the INI.</summary>
bool readParams(); bool readParams();

@ -54,9 +54,9 @@ namespace lookups
} }
/// <summary>Initializes a new instance of the VoiceChData class.</summary> /// <summary>Initializes a new instance of the VoiceChData class.</summary>
/// <param name="chNo">Voice Channel Number.</param> /// <param name="chNo">Voice Channel Number.</param>
/// <param name="address">RCON Address.</param> /// <param name="address">REST API Address.</param>
/// <param name="port">RCON Port.</param> /// <param name="port">REST API Port.</param>
/// <param name="password">RCON Password.</param> /// <param name="password">REST API Password.</param>
VoiceChData(uint32_t chNo, std::string address, uint16_t port, std::string password) : VoiceChData(uint32_t chNo, std::string address, uint16_t port, std::string password) :
m_chNo(chNo), m_chNo(chNo),
m_address(address), m_address(address),
@ -87,11 +87,11 @@ namespace lookups
public: public:
/// <summary>Voice Channel Number.</summary> /// <summary>Voice Channel Number.</summary>
__READONLY_PROPERTY_PLAIN(uint32_t, chNo, chNo); __READONLY_PROPERTY_PLAIN(uint32_t, chNo, chNo);
/// <summary>RCON Address.</summary> /// <summary>REST API Address.</summary>
__READONLY_PROPERTY_PLAIN(std::string, address, address); __READONLY_PROPERTY_PLAIN(std::string, address, address);
/// <summary>RCON Port.</summary> /// <summary>REST API Port.</summary>
__READONLY_PROPERTY_PLAIN(uint16_t, port, port); __READONLY_PROPERTY_PLAIN(uint16_t, port, port);
/// <summary>RCON Password.</summary> /// <summary>REST API Password.</summary>
__READONLY_PROPERTY_PLAIN(std::string, password, password); __READONLY_PROPERTY_PLAIN(std::string, password, password);
}; };

@ -34,7 +34,7 @@
#include "Defines.h" #include "Defines.h"
#include "modem/port/IModemPort.h" #include "modem/port/IModemPort.h"
#include "network/RemoteControl.h" #include "network/RESTAPI.h"
#include "RingBuffer.h" #include "RingBuffer.h"
#include "Timer.h" #include "Timer.h"
@ -56,7 +56,7 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
class HOST_SW_API HostCal; class HOST_SW_API HostCal;
class HOST_SW_API RemoteControl; class HOST_SW_API RESTAPI;
namespace modem namespace modem
{ {
@ -349,7 +349,7 @@ namespace modem
private: private:
friend class ::HostCal; friend class ::HostCal;
friend class ::RemoteControl; friend class ::RESTAPI;
port::IModemPort* m_port; port::IModemPort* m_port;

@ -86,8 +86,8 @@ Network::Network(const std::string& address, uint16_t port, uint16_t local, uint
m_longitude(0.0F), m_longitude(0.0F),
m_height(0), m_height(0),
m_location(), m_location(),
m_rconPassword(), m_restApiPassword(),
m_rconPort(0) m_restApiPort(0)
{ {
assert(!address.empty()); assert(!address.empty());
assert(port > 0U); assert(port > 0U);
@ -148,14 +148,14 @@ void Network::setMetadata(const std::string& identity, uint32_t rxFrequency, uin
} }
/// <summary> /// <summary>
/// Sets RCON configuration settings from the modem. /// Sets REST API configuration settings from the modem.
/// </summary> /// </summary>
/// <param name="password"></param> /// <param name="password"></param>
/// <param name="port"></param> /// <param name="port"></param>
void Network::setRconData(const std::string& password, uint16_t port) void Network::setRESTAPIData(const std::string& password, uint16_t port)
{ {
m_rconPassword = password; m_restApiPassword = password;
m_rconPort = port; m_restApiPort = port;
} }
/// <summary> /// <summary>
@ -539,8 +539,8 @@ bool Network::writeConfig()
// RCON // RCON
json::object rcon = json::object(); json::object rcon = json::object();
rcon["password"].set<std::string>(m_rconPassword); // RCON Password rcon["password"].set<std::string>(m_restApiPassword); // REST API Password
rcon["port"].set<uint16_t>(m_rconPort); // RCON Port rcon["port"].set<uint16_t>(m_restApiPort); // REST API Port
config["rcon"].set<json::object>(rcon); config["rcon"].set<json::object>(rcon);
config["software"].set<std::string>(std::string(software)); // Software ID config["software"].set<std::string>(std::string(software)); // Software ID

@ -59,8 +59,8 @@ namespace network
/// <summary>Sets metadata configuration settings from the modem.</summary> /// <summary>Sets metadata configuration settings from the modem.</summary>
void setMetadata(const std::string& callsign, uint32_t rxFrequency, uint32_t txFrequency, float txOffsetMhz, float chBandwidthKhz, void setMetadata(const std::string& callsign, uint32_t rxFrequency, uint32_t txFrequency, float txOffsetMhz, float chBandwidthKhz,
uint8_t channelId, uint32_t channelNo, uint32_t power, float latitude, float longitude, int height, const std::string& location); uint8_t channelId, uint32_t channelNo, uint32_t power, float latitude, float longitude, int height, const std::string& location);
/// <summary>Sets RCON configuration settings from the modem.</summary> /// <summary>Sets REST API configuration settings from the modem.</summary>
void setRconData(const std::string& password, uint16_t port); void setRESTAPIData(const std::string& password, uint16_t port);
/// <summary>Gets the current status of the network.</summary> /// <summary>Gets the current status of the network.</summary>
uint8_t getStatus(); uint8_t getStatus();
@ -109,8 +109,8 @@ namespace network
int m_height; int m_height;
std::string m_location; std::string m_location;
std::string m_rconPassword; std::string m_restApiPassword;
uint16_t m_rconPort; uint16_t m_restApiPort;
/// <summary>Writes login request to the network.</summary> /// <summary>Writes login request to the network.</summary>
bool writeLogin(); bool writeLogin();

File diff suppressed because it is too large Load Diff

@ -0,0 +1,230 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
/*
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(__REST_API_H__)
#define __REST_API_H__
#include "Defines.h"
#include "network/UDPSocket.h"
#include "network/rest/RequestDispatcher.h"
#include "network/rest/http/HTTPServer.h"
#include "lookups/RadioIdLookup.h"
#include "lookups/TalkgroupIdLookup.h"
#include "Thread.h"
#include <vector>
#include <string>
#include <random>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define DVM_REST_RAND_MAX 0xfffffffffffffffe
#define PUT_AUTHENTICATE "/auth"
#define GET_VERSION "/version"
#define GET_STATUS "/status"
#define GET_VOICE_CH "/voice-ch"
#define PUT_MDM_MODE "/mdm/mode"
#define MODE_OPT_IDLE "idle"
#define MODE_OPT_LCKOUT "lockout"
#define MODE_OPT_FDMR "dmr"
#define MODE_OPT_FP25 "p25"
#define MODE_OPT_FNXDN "nxdn"
#define PUT_MDM_KILL "/mdm/kill"
#define PUT_PERMIT_TG "/permit-tg"
#define PUT_GRANT_TG "/grant-tg"
#define GET_RELEASE_GRNTS "/release-grants"
#define GET_RELEASE_AFFS "/release-affs"
#define GET_RID_WHITELIST "/rid-whitelist/(\\d+)"
#define GET_RID_BLACKLIST "/rid-blacklist/(\\d+)"
#define GET_DMR_BEACON "/dmr/beacon"
#define GET_DMR_DEBUG "/dmr/debug/(\\d+)/(\\d+)"
#define GET_DMR_DUMP_CSBK "/dmr/dump-csbk/(\\d+)"
#define PUT_DMR_RID "/dmr/rid"
#define GET_DMR_CC_DEDICATED "/dmr/cc-enable/(\\d+)"
#define GET_DMR_CC_BCAST "/dmr/cc-broadcast/(\\d+)"
#define GET_P25_CC "/p25/cc"
//#define GET_P25_CC_FALLBACK "/p25/cc-fallback/(\\d+)"
#define GET_P25_DEBUG "/p25/debug/(\\d+)/(\\d+)"
#define GET_P25_DUMP_TSBK "/p25/dump-tsbk/(\\d+)"
#define PUT_P25_RID "/p25/rid"
#define GET_P25_CC_DEDICATED "/p25/cc-enable/(\\d+)"
#define GET_P25_CC_BCAST "/p25/cc-broadcast/(\\d+)"
#define GET_NXDN_DEBUG "/nxdn/debug/(\\d+)/(\\d+)"
#define GET_NXDN_DUMP_RCCH "/nxdn/dump-rcch/(\\d+)"
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
class HOST_SW_API Host;
namespace dmr { class HOST_SW_API Control; }
namespace p25 { class HOST_SW_API Control; }
namespace nxdn { class HOST_SW_API Control; }
// ---------------------------------------------------------------------------
// Class Declaration
// Implements the REST API server logic.
// ---------------------------------------------------------------------------
class HOST_SW_API RESTAPI : private Thread {
public:
/// <summary>Initializes a new instance of the RESTAPI class.</summary>
RESTAPI(const std::string& address, uint16_t port, const std::string& password, Host* host, bool debug);
/// <summary>Finalizes a instance of the RESTAPI class.</summary>
~RESTAPI();
/// <summary>Sets the instances of the Radio ID and Talkgroup ID lookup tables.</summary>
void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup);
/// <summary>Sets the instances of the digital radio protocols.</summary>
void setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary>Opens connection to the network.</summary>
bool open();
/// <summary>Closes connection to the network.</summary>
void close();
private:
typedef network::rest::RequestDispatcher<network::rest::http::HTTPRequest, network::rest::http::HTTPReply> RESTDispatcherType;
typedef network::rest::http::HTTPRequest HTTPRequest;
typedef network::rest::http::HTTPReply HTTPReply;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> m_restServer;
std::mt19937 m_random;
uint8_t m_p25MFId;
std::string m_password;
uint8_t* m_passwordHash;
bool m_debug;
Host* m_host;
dmr::Control* m_dmr;
p25::Control* m_p25;
nxdn::Control* m_nxdn;
::lookups::RadioIdLookup* m_ridLookup;
::lookups::TalkgroupIdLookup* m_tidLookup;
typedef std::unordered_map<std::string, uint64_t>::value_type AuthTokenValueType;
std::unordered_map<std::string, uint64_t> m_authTokens;
/// <summary></summary>
virtual void entry();
/// <summary>Helper to initialize REST API endpoints.</summary>
void initializeEndpoints();
/// <summary></summary>
void invalidateHostToken(const std::string host);
/// <summary></summary>
bool validateAuth(const HTTPRequest& request, HTTPReply& reply);
/// <summary></summary>
void restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetReleaseGrants(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetReleaseAffs(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetRIDWhitelist(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetRIDBlacklist(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/*
** Digital Mobile Radio
*/
/// <summary></summary>
void restAPI_GetDMRBeacon(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetDMRCCEnable(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetDMRCCBroadcast(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/*
** Project 25
*/
/// <summary></summary>
void restAPI_GetP25CC(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetP25CCEnable(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetP25CCBroadcast(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/*
** Next Generation Digital Narrowband
*/
/// <summary></summary>
void restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetNXDNDumpRCCH(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match);
};
#endif // __REST_API_H__

File diff suppressed because it is too large Load Diff

@ -1,214 +0,0 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
//
/*
* Copyright (C) 2019 by Jonathan Naylor G4KLX
* Copyright (C) 2019-2023 by Bryan Biedenkapp N2PLL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(__REMOTE_CONTROL_H__)
#define __REMOTE_CONTROL_H__
#include "Defines.h"
#include "network/UDPSocket.h"
#include "network/rest/RequestDispatcher.h"
#include "network/rest/http/HTTPServer.h"
#include "lookups/RadioIdLookup.h"
#include "lookups/TalkgroupIdLookup.h"
#include "Thread.h"
#include <vector>
#include <string>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define RCD_GET_VERSION "version"
#define RCD_GET_HELP "help"
#define RCD_GET_STATUS "status"
#define RCD_GET_VOICE_CH "voice-ch"
#define RCD_MODE "mdm-mode"
#define RCD_MODE_OPT_IDLE "idle"
#define RCD_MODE_OPT_LCKOUT "lockout"
#define RCD_MODE_OPT_FDMR "dmr"
#define RCD_MODE_OPT_FP25 "p25"
#define RCD_MODE_OPT_FNXDN "nxdn"
#define RCD_KILL "mdm-kill"
#define RCD_FORCE_KILL "mdm-force-kill"
#define RCD_PERMIT_TG "permit-tg"
#define RCD_GRANT_TG "grant-tg"
#define RCD_RID_WLIST "rid-whitelist"
#define RCD_RID_BLIST "rid-blacklist"
#define RCD_DMR_BEACON "dmr-beacon"
#define RCD_P25_CC "p25-cc"
#define RCD_P25_CC_FALLBACK "p25-cc-fallback"
#define RCD_DMR_RID_PAGE "dmr-rid-page"
#define RCD_DMR_RID_CHECK "dmr-rid-check"
#define RCD_DMR_RID_INHIBIT "dmr-rid-inhibit"
#define RCD_DMR_RID_UNINHIBIT "dmr-rid-uninhibit"
#define RCD_P25_SET_MFID "p25-set-mfid"
#define RCD_P25_RID_PAGE "p25-rid-page"
#define RCD_P25_RID_CHECK "p25-rid-check"
#define RCD_P25_RID_INHIBIT "p25-rid-inhibit"
#define RCD_P25_RID_UNINHIBIT "p25-rid-uninhibit"
#define RCD_P25_RID_GAQ "p25-rid-gaq"
#define RCD_P25_RID_UREG "p25-rid-ureg"
#define RCD_P25_RELEASE_GRANTS "p25-rel-grnts"
#define RCD_P25_RELEASE_AFFS "p25-rel-affs"
#define RCD_DMR_CC_DEDICATED "dmr-cc-dedicated"
#define RCD_DMR_CC_BCAST "dmr-cc-bcast"
#define RCD_P25_CC_DEDICATED "p25-cc-dedicated"
#define RCD_P25_CC_BCAST "p25-cc-bcast"
#define RCD_DMR_DEBUG "dmr-debug"
#define RCD_DMR_DUMP_CSBK "dmr-dump-csbk"
#define RCD_P25_DEBUG "p25-debug"
#define RCD_P25_DUMP_TSBK "p25-dump-tsbk"
#define RCD_NXDN_DEBUG "nxdn-debug"
#define RCD_NXDN_DUMP_RCCH "nxdn-dump-rcch"
#define RCD_DMRD_MDM_INJ "debug-dmrd-mdm-inj"
#define RCD_P25D_MDM_INJ "debug-p25d-mdm-inj"
#define RCD_NXDD_MDM_INJ "debug-nxdd-mdm-inj"
#define RCD_PING "ping"
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
class HOST_SW_API Host;
namespace dmr { class HOST_SW_API Control; }
namespace p25 { class HOST_SW_API Control; }
namespace nxdn { class HOST_SW_API Control; }
// ---------------------------------------------------------------------------
// Class Declaration
// Implements the remote control networking logic.
// ---------------------------------------------------------------------------
class HOST_SW_API RemoteControl : private Thread {
public:
/// <summary>Initializes a new instance of the RemoteControl class.</summary>
RemoteControl(const std::string& address, uint16_t port, const std::string& password, Host* host, bool debug);
/// <summary>Finalizes a instance of the RemoteControl class.</summary>
~RemoteControl();
/// <summary>Sets the instances of the Radio ID and Talkgroup ID lookup tables.</summary>
void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup);
/// <summary>Sets the instances of the digital radio protocols.</summary>
void setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary>Process remote network command data.</summary>
void process(Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary>Opens connection to the network.</summary>
bool open();
/// <summary>Closes connection to the network.</summary>
void close();
private:
typedef network::rest::RequestDispatcher<network::rest::http::HTTPRequest, network::rest::http::HTTPReply> RESTDispatcherType;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> m_restServer;
network::UDPSocket m_socket;
uint8_t m_p25MFId;
std::string m_password;
uint8_t* m_passwordHash;
bool m_debug;
Host* m_host;
dmr::Control* m_dmr;
p25::Control* m_p25;
nxdn::Control* m_nxdn;
::lookups::RadioIdLookup* m_ridLookup;
::lookups::TalkgroupIdLookup* m_tidLookup;
/// <summary>Helper to initialize REST API endpoints.</summary>
void initializeEndpoints();
/// <summary></summary>
virtual void entry();
/// <summary>Helper to send response to client.</summary>
void writeResponse(std::string reply, sockaddr_storage address, uint32_t addrLen);
/// <summary>Helper to print the remote control help.</summary>
std::string displayHelp();
/// <summary></summary>
std::string rcdGetStatus(Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary></summary>
std::string rcdGetVoiceCh(Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary></summary>
std::string rcdMode(std::vector<std::string> args, Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary></summary>
std::string rcdPermitTG(std::vector<std::string> args, Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary></summary>
std::string rcdGrantTG(std::vector<std::string> args, Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary></summary>
std::string rcdDMRModemInj(std::vector<std::string> args, Host* host, dmr::Control* dmr);
/// <summary></summary>
std::string rcdP25ModemInj(std::vector<std::string> args, Host* host, p25::Control* p25);
/// <summary></summary>
std::string rcdNXDNModemInj(std::vector<std::string> args, Host* host, nxdn::Control* nxdn);
/// <summary></summary>
std::string getArgString(std::vector<std::string> args, uint32_t n) const;
/// <summary></summary>
__forceinline uint64_t getArgUInt64(std::vector<std::string> args, uint32_t n) const { return (uint64_t)::atol(getArgString(args, n).c_str()); }
/// <summary></summary>
__forceinline uint32_t getArgUInt32(std::vector<std::string> args, uint32_t n) const { return (uint32_t)::atoi(getArgString(args, n).c_str()); }
/// <summary></summary>
__forceinline int32_t getArgInt32(std::vector<std::string> args, uint32_t n) const { return ::atoi(getArgString(args, n).c_str()); }
/// <summary></summary>
__forceinline uint16_t getArgUInt16(std::vector<std::string> args, uint32_t n) const { return (uint16_t)::atoi(getArgString(args, n).c_str()); }
/// <summary></summary>
__forceinline int16_t getArgInt16(std::vector<std::string> args, uint32_t n) const { return (int16_t)::atoi(getArgString(args, n).c_str()); }
/// <summary></summary>
__forceinline uint8_t getArgUInt8(std::vector<std::string> args, uint32_t n) const { return (uint8_t)::atoi(getArgString(args, n).c_str()); }
/// <summary></summary>
__forceinline int8_t getArgInt8(std::vector<std::string> args, uint32_t n) const { return (int8_t)::atoi(getArgString(args, n).c_str()); }
};
#endif // __REMOTE_CONTROL_H__

@ -49,9 +49,9 @@ namespace network
struct RequestMatch : std::smatch struct RequestMatch : std::smatch
{ {
/// <summary>Initializes a new instance of the RequestMatch structure.</summary> /// <summary>Initializes a new instance of the RequestMatch structure.</summary>
RequestMatch(const std::smatch& m, const std::string& d) : std::smatch(m), data(d) { /* stub */ } RequestMatch(const std::smatch& m, const std::string& c) : std::smatch(m), content(c) { /* stub */ }
std::string data; std::string content;
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -98,7 +98,7 @@ namespace network
/// <summary></summary> /// <summary></summary>
void handleRequest(const Request& request, Reply& reply, const std::smatch &what) { void handleRequest(const Request& request, Reply& reply, const std::smatch &what) {
// dispatching to matching based on handler // dispatching to matching based on handler
RequestMatch match(what, request.data); RequestMatch match(what, request.content);
auto& handler = m_handlers[request.method]; auto& handler = m_handlers[request.method];
if (handler) { if (handler) {
handler(request, reply, match); handler(request, reply, match);
@ -128,20 +128,21 @@ namespace network
RequestDispatcher(const std::string& basePath, bool debug) : m_basePath(basePath), m_debug(debug) { /* stub */ } RequestDispatcher(const std::string& basePath, bool debug) : m_basePath(basePath), m_debug(debug) { /* stub */ }
/// <summary></summary> /// <summary></summary>
MatcherType& match(const std::string& expression) MatcherType& match(const std::string& expression, bool regex = false)
{ {
MatcherTypePtr& p = m_matchers[expression]; MatcherTypePtr& p = m_matchers[expression];
if (!p) { if (!p) {
if (m_debug) { if (m_debug) {
::LogDebug(LOG_RCON, "creating REST RequestDispatcher, expression = %s", expression.c_str()); ::LogDebug(LOG_REST, "creating RequestDispatcher, expression = %s", expression.c_str());
} }
p = std::make_shared<MatcherType>(expression); p = std::make_shared<MatcherType>(expression);
} else { } else {
if (m_debug) { if (m_debug) {
::LogDebug(LOG_RCON, "fetching REST RequestDispatcher, expression = %s", expression.c_str()); ::LogDebug(LOG_REST, "fetching RequestDispatcher, expression = %s", expression.c_str());
} }
} }
p->setRegEx(regex);
return *p; return *p;
} }
@ -153,7 +154,7 @@ namespace network
if (!matcher.second->regex()) { if (!matcher.second->regex()) {
if (request.uri.find(matcher.first) != std::string::npos) { if (request.uri.find(matcher.first) != std::string::npos) {
if (m_debug) { if (m_debug) {
::LogDebug(LOG_RCON, "non-regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); ::LogDebug(LOG_REST, "non-regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str());
} }
//what = matcher.first; //what = matcher.first;
@ -163,7 +164,7 @@ namespace network
} else { } else {
if (std::regex_match(request.uri, what, std::regex(matcher.first))) { if (std::regex_match(request.uri, what, std::regex(matcher.first))) {
if (m_debug) { if (m_debug) {
::LogDebug(LOG_RCON, "regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); ::LogDebug(LOG_REST, "regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str());
} }
matcher.second->handleRequest(request, reply, what); matcher.second->handleRequest(request, reply, what);
@ -172,7 +173,7 @@ namespace network
} }
} }
::LogError(LOG_RCON, "unknown endpoint, uri = %s", request.uri.c_str()); ::LogError(LOG_REST, "unknown endpoint, uri = %s", request.uri.c_str());
reply = http::HTTPReply::stockReply(http::HTTPReply::BAD_REQUEST, "application/json"); reply = http::HTTPReply::stockReply(http::HTTPReply::BAD_REQUEST, "application/json");
} }

@ -38,9 +38,9 @@
#define __REST_HTTP__CONNECTION_H__ #define __REST_HTTP__CONNECTION_H__
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPReply.h"
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPRequest.h"
#include "network/rest/http/HTTPRequestLexer.h" #include "network/rest/http/HTTPRequestLexer.h"
#include "network/rest/http/HTTPReply.h"
#include <array> #include <array>
#include <memory> #include <memory>
@ -104,13 +104,14 @@ namespace network
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
if (!ec) { if (!ec) {
HTTPRequestLexer::ResultType result; HTTPRequestLexer::ResultType result;
char* data; char* content;
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
std::tie(result, data) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); std::string contentLength = m_request.headers.find("Content-Length");
auto header = std::find_if(m_request.headers.begin(), m_request.headers.end(), [](const HTTPHeader& h) { return h.name == "content-length"; }); if (contentLength != "") {
if (header != m_request.headers.end()) { size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10);
size_t length = (size_t)::strtoul(header->value.c_str(), NULL, 10); m_request.content = std::string(content, length);
m_request.data = std::string(data, length);
} }
if (result == HTTPRequestLexer::GOOD) { if (result == HTTPRequestLexer::GOOD) {
@ -137,13 +138,13 @@ namespace network
if (!m_persistent) { if (!m_persistent) {
auto self(this->shared_from_this()); auto self(this->shared_from_this());
} else { } else {
m_reply.headers.emplace_back("Connection:", "keep-alive"); m_reply.headers.add("Connection", "keep-alive");
} }
asio::async_write(m_socket, m_reply.toBuffers(), [=](asio::error_code ec, std::size_t) { asio::async_write(m_socket, m_reply.toBuffers(), [=](asio::error_code ec, std::size_t) {
if (m_persistent) { if (m_persistent) {
m_lexer.reset(); m_lexer.reset();
m_reply.headers.resize(0); m_reply.headers = HTTPHeaders();
m_reply.status = HTTPReply::OK; m_reply.status = HTTPReply::OK;
m_reply.content = ""; m_reply.content = "";
m_request = HTTPRequest(); m_request = HTTPRequest();

@ -1,71 +0,0 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_HEADER_H__)
#define __REST_HTTP__HTTP_HEADER_H__
#include "Defines.h"
#include <string>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Structure Declaration
// This class implements a model for an HTTP header.
// ---------------------------------------------------------------------------
struct HTTPHeader
{
std::string name;
std::string value;
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader() { /* stub */ }
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader(const std::string& name, const std::string& value) : name(name), value(value) { /* stub */ }
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_HEADER_H__

@ -0,0 +1,132 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_HEADERS_H__)
#define __REST_HTTP__HTTP_HEADERS_H__
#include "Defines.h"
#include "Log.h"
#include <string>
#include <vector>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
struct HTTPReply;
// ---------------------------------------------------------------------------
// Structure Declaration
//
// ---------------------------------------------------------------------------
struct HTTPHeaders
{
struct Header
{
std::string name;
std::string value;
Header() : name{}, value{} { /* stub */}
Header(std::string n, std::string v) : name{n}, value{v} { /* stub */ }
};
/// <summary>Gets the list of HTTP headers.</summary>
std::vector<Header> headers() const { return m_headers; }
/// <summary>Returns true if the headers are empty.</summary>
bool empty() const { return m_headers.empty(); }
/// <summary>Returns the number of headers.</summary>
std::size_t size() const { return m_headers.size(); }
/// <summary>Clears the list of HTTP headers.</summary>
void clearHeaders() { m_headers = std::vector<Header>(); }
/// <summary>Helper to add a HTTP header.</summary>
void add(const std::string& name, const std::string& value)
{
//::LogDebug(LOG_REST, "HTTPHeaders::add(), header = %s, value = %s", name.c_str(), value.c_str());
for (auto& header : m_headers) {
if (::strtolower(header.name) == ::strtolower(name)) {
header.value = value;
return;
}
}
m_headers.push_back(Header(name, value));
//for (auto header : m_headers)
// ::LogDebug(LOG_REST, "HTTPHeaders::add() m_headers.header = %s, m_headers.value = %s", header.name.c_str(), header.value.c_str());
}
/// <summary>Helper to add a HTTP header.</summary>
void remove(const std::string headerName)
{
auto header = std::find_if(m_headers.begin(), m_headers.end(), [&](const Header& h) {
return ::strtolower(h.name) == ::strtolower(headerName);
});
if (header != m_headers.end()) {
m_headers.erase(header);
}
}
/// <summary>Helper to find the named HTTP header.</summary>
std::string find(const std::string headerName) const
{
auto header = std::find_if(m_headers.begin(), m_headers.end(), [&](const Header& h) {
return ::strtolower(h.name) == ::strtolower(headerName);
});
if (header != m_headers.end()) {
return header->value;
}
else {
return "";
}
}
private:
friend struct HTTPReply;
std::vector<Header> m_headers;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_HEADERS_H__

@ -36,6 +36,7 @@
*/ */
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPReply.h" #include "network/rest/http/HTTPReply.h"
#include "Log.h"
using namespace network::rest::http; using namespace network::rest::http;
@ -299,7 +300,9 @@ std::vector<asio::const_buffer> HTTPReply::toBuffers()
buffers.push_back(status_strings::toBuffer(status)); buffers.push_back(status_strings::toBuffer(status));
for (std::size_t i = 0; i < headers.size(); ++i) { for (std::size_t i = 0; i < headers.size(); ++i) {
HTTPHeader& h = headers[i]; HTTPHeaders::Header& h = headers.m_headers[i];
//::LogDebug(LOG_REST, "HTTPReply::toBuffers() header = %s, value = %s", h.name.c_str(), h.value.c_str());
buffers.push_back(asio::buffer(h.name)); buffers.push_back(asio::buffer(h.name));
buffers.push_back(asio::buffer(misc_strings::name_value_separator)); buffers.push_back(asio::buffer(misc_strings::name_value_separator));
buffers.push_back(asio::buffer(h.value)); buffers.push_back(asio::buffer(h.value));
@ -368,7 +371,10 @@ HTTPReply HTTPReply::stockReply(HTTPReply::StatusType status, const std::string
/// <param name="contentType"></param> /// <param name="contentType"></param>
void HTTPReply::ensureDefaultHeaders(std::string contentType) void HTTPReply::ensureDefaultHeaders(std::string contentType)
{ {
headers.push_back(HTTPHeader("Content-Length", std::to_string(content.size()))); headers.add("Content-Type", contentType);
headers.push_back(HTTPHeader("Content-Type", contentType)); headers.add("Content-Length", std::to_string(content.size()));
headers.push_back(HTTPHeader("Server", std::string((__EXE_NAME__ "/" __VER__)))); headers.add("Server", std::string((__EXE_NAME__ "/" __VER__)));
//for (auto header : headers.headers())
// ::LogDebug(LOG_REST, "HTTPReply::ensureDefaultHeaders() header = %s, value = %s", header.name.c_str(), header.value.c_str());
} }

@ -39,7 +39,7 @@
#include "Defines.h" #include "Defines.h"
#include "network/json/json.h" #include "network/json/json.h"
#include "network/rest/http/HTTPHeader.h" #include "network/rest/http/HTTPHeaders.h"
#include <string> #include <string>
#include <vector> #include <vector>
@ -82,7 +82,7 @@ namespace network
SERVICE_UNAVAILABLE = 503 SERVICE_UNAVAILABLE = 503
} status; } status;
std::vector<HTTPHeader> headers; HTTPHeaders headers;
std::string content; std::string content;
/// <summary>Convert the reply into a vector of buffers. The buffers do not own the /// <summary>Convert the reply into a vector of buffers. The buffers do not own the

@ -38,7 +38,8 @@
#define __REST_HTTP__HTTP_REQUEST_H__ #define __REST_HTTP__HTTP_REQUEST_H__
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPHeader.h" #include "network/rest/http/HTTPHeaders.h"
#include "Log.h"
#include <string> #include <string>
#include <vector> #include <vector>
@ -49,7 +50,6 @@ namespace network
{ {
namespace http namespace http
{ {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Structure Declaration // Structure Declaration
// This struct implements a model of a request received from a HTTP client. // This struct implements a model of a request received from a HTTP client.
@ -60,11 +60,12 @@ namespace network
std::string method; std::string method;
std::string uri; std::string uri;
HTTPHeaders headers;
std::string content;
int httpVersionMajor; int httpVersionMajor;
int httpVersionMinor; int httpVersionMinor;
std::vector<HTTPHeader> headers;
std::string data;
}; };
} // namespace http } // namespace http
} // namespace rest } // namespace rest

@ -106,11 +106,9 @@ void HTTPRequestHandler::handleRequest(const HTTPRequest& request, HTTPReply& re
while (is.read(buf, sizeof(buf)).gcount() > 0) while (is.read(buf, sizeof(buf)).gcount() > 0)
reply.content.append(buf, is.gcount()); reply.content.append(buf, is.gcount());
reply.headers.resize(2); reply.headers.clearHeaders();
reply.headers[0].name = "Content-Length"; reply.headers.add("Content-Length", std::to_string(reply.content.size()));
reply.headers[0].value = std::to_string(reply.content.size()); reply.headers.add("Content-Type", "application/octet-stream");
reply.headers[1].name = "Content-Type";
reply.headers[1].value = "application/octet-stream";
} }
/// <summary> /// <summary>

@ -37,6 +37,7 @@
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPRequestLexer.h" #include "network/rest/http/HTTPRequestLexer.h"
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPRequest.h"
#include "Log.h"
using namespace network::rest::http; using namespace network::rest::http;
@ -51,6 +52,7 @@ using namespace network::rest::http;
/// </summary> /// </summary>
HTTPRequestLexer::HTTPRequestLexer() : HTTPRequestLexer::HTTPRequestLexer() :
m_headers(),
m_state(METHOD_START) m_state(METHOD_START)
{ {
/* stub */ /* stub */
@ -60,6 +62,7 @@ HTTPRequestLexer::HTTPRequestLexer() :
void HTTPRequestLexer::reset() void HTTPRequestLexer::reset()
{ {
m_state = METHOD_START; m_state = METHOD_START;
m_headers = std::vector<LexedHeader>();
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -234,8 +237,8 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
return BAD; return BAD;
} }
else { else {
req.headers.push_back(HTTPHeader()); m_headers.push_back(LexedHeader());
req.headers.back().name.push_back(std::tolower(input)); m_headers.back().name.push_back(std::tolower(input));
m_state = HEADER_NAME; m_state = HEADER_NAME;
return INDETERMINATE; return INDETERMINATE;
} }
@ -253,7 +256,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
} }
else { else {
m_state = HEADER_VALUE; m_state = HEADER_VALUE;
req.headers.back().value.push_back(input); m_headers.back().value.push_back(input);
return INDETERMINATE; return INDETERMINATE;
} }
@ -267,7 +270,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
} }
else else
{ {
req.headers.back().name.push_back(std::tolower(input)); m_headers.back().name.push_back(std::tolower(input));
return INDETERMINATE; return INDETERMINATE;
} }
@ -290,7 +293,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
return BAD; return BAD;
} }
else { else {
req.headers.back().value.push_back(input); m_headers.back().value.push_back(input);
return INDETERMINATE; return INDETERMINATE;
} }
@ -304,7 +307,16 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
} }
case EXPECTING_NEWLINE_3: case EXPECTING_NEWLINE_3:
return (input == '\n') ? GOOD : BAD; if (input == '\n') {
for (auto header : m_headers) {
//::LogDebug(LOG_REST, "HTTPRequestLexer::consume(), header = %s, value = %s", header.name.c_str(), header.value.c_str());
req.headers.add(header.name, header.value);
}
return GOOD;
} else {
return BAD;
}
default: default:
return BAD; return BAD;

@ -38,6 +38,7 @@
#define __REST_HTTP__HTTP_REQUEST_PARSER_H__ #define __REST_HTTP__HTTP_REQUEST_PARSER_H__
#include <tuple> #include <tuple>
#include <vector>
namespace network namespace network
{ {
@ -96,6 +97,17 @@ namespace network
/// <summary>Check if a byte is an digit.</summary> /// <summary>Check if a byte is an digit.</summary>
static bool isDigit(int c); static bool isDigit(int c);
struct LexedHeader
{
std::string name;
std::string value;
LexedHeader() { /* stub */ }
LexedHeader(const std::string& name, const std::string& value) : name(name), value(value) {}
};
std::vector<LexedHeader> m_headers;
enum state enum state
{ {
METHOD_START, METHOD_START,

@ -40,7 +40,6 @@
#include "nxdn/packet/Data.h" #include "nxdn/packet/Data.h"
#include "nxdn/SiteData.h" #include "nxdn/SiteData.h"
#include "network/BaseNetwork.h" #include "network/BaseNetwork.h"
#include "network/RemoteControl.h"
#include "lookups/RSSIInterpolator.h" #include "lookups/RSSIInterpolator.h"
#include "lookups/IdenTableLookup.h" #include "lookups/IdenTableLookup.h"
#include "lookups/RadioIdLookup.h" #include "lookups/RadioIdLookup.h"
@ -107,6 +106,9 @@ namespace nxdn
/// <summary>Permits a TGID on a non-authoritative host.</summary> /// <summary>Permits a TGID on a non-authoritative host.</summary>
void permittedTG(uint32_t dstId); void permittedTG(uint32_t dstId);
/// <summary>Gets instance of the AffiliationLookup class.</summary>
lookups::AffiliationLookup affiliations() { return m_affiliations; }
/// <summary>Flag indicating whether the processor or is busy or not.</summary> /// <summary>Flag indicating whether the processor or is busy or not.</summary>
bool isBusy() const; bool isBusy() const;

@ -38,7 +38,6 @@
#include "p25/packet/Voice.h" #include "p25/packet/Voice.h"
#include "p25/packet/Trunk.h" #include "p25/packet/Trunk.h"
#include "network/BaseNetwork.h" #include "network/BaseNetwork.h"
#include "network/RemoteControl.h"
#include "lookups/RSSIInterpolator.h" #include "lookups/RSSIInterpolator.h"
#include "lookups/IdenTableLookup.h" #include "lookups/IdenTableLookup.h"
#include "lookups/RadioIdLookup.h" #include "lookups/RadioIdLookup.h"

@ -34,7 +34,6 @@
#include "p25/lc/TDULC.h" #include "p25/lc/TDULC.h"
#include "p25/Control.h" #include "p25/Control.h"
#include "network/BaseNetwork.h" #include "network/BaseNetwork.h"
#include "network/RemoteControl.h"
#include "Timer.h" #include "Timer.h"
#include <cstdio> #include <cstdio>

@ -186,7 +186,7 @@ int RemoteCommand::send(const std::string& address, uint32_t port, const std::st
break; break;
if (debug) if (debug)
::LogDebug(LOG_RCON, "RemoteCommand::send() block len = %u, offs = %u", len - 3, offs); ::LogDebug(LOG_HOST, "RemoteCommand::send() block len = %u, offs = %u", len - 3, offs);
buffer[len] = '\0'; buffer[len] = '\0';

@ -65,7 +65,7 @@ using namespace network;
static std::string g_progExe = std::string(__EXE_NAME__); static std::string g_progExe = std::string(__EXE_NAME__);
static std::string g_remoteAddress = std::string("127.0.0.1"); static std::string g_remoteAddress = std::string("127.0.0.1");
static uint32_t g_remotePort = RCON_DEFAULT_PORT; static uint32_t g_remotePort = REST_API_DEFAULT_PORT;
static std::string g_remotePassword = std::string(); static std::string g_remotePassword = std::string();
static bool g_debug = false; static bool g_debug = false;

Loading…
Cancel
Save

Powered by TurnKey Linux.