diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml
index 36bb572f..0b7e7220 100644
--- a/configs/fne-config.example.yml
+++ b/configs/fne-config.example.yml
@@ -123,6 +123,17 @@ system:
# Flag indicating whether or not the host diagnostic log will be sent to the network.
allowDiagnosticTransfer: true
+ # 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).
+ restAddress: 127.0.0.1
+ # Port number for REST API to listen on.
+ restPort: 9990
+ # REST API authentication password.
+ restPassword: "PASSWORD"
+ # Flag indicating whether or not verbose REST API debug logging is enabled.
+ restDebug: false
+
#
# Radio ID ACL Configuration
#
diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp
index 891000bc..663c73a8 100644
--- a/src/fne/HostFNE.cpp
+++ b/src/fne/HostFNE.cpp
@@ -78,7 +78,8 @@ HostFNE::HostFNE(const std::string& confFile) :
m_maxMissedPings(5U),
m_updateLookupTime(10U),
m_allowActivityTransfer(false),
- m_allowDiagnosticTransfer(false)
+ m_allowDiagnosticTransfer(false),
+ m_RESTAPI(nullptr)
{
/* stub */
}
@@ -177,6 +178,9 @@ int HostFNE::run()
m_ridLookup = new RadioIdLookup(ridLookupFile, ridReloadTime, true);
m_ridLookup->read();
+ // initialize REST API
+ initializeRESTAPI();
+
// initialize master networking
ret = createMasterNetwork();
if (!ret)
@@ -234,6 +238,11 @@ int HostFNE::run()
}
m_peerNetworks.clear();
+ if (m_RESTAPI != nullptr) {
+ m_RESTAPI->close();
+ delete m_RESTAPI;
+ }
+
if (m_tidLookup != nullptr) {
m_tidLookup->stop();
delete m_tidLookup;
@@ -253,6 +262,7 @@ int HostFNE::run()
///
/// Reads basic configuration parameters from the YAML configuration file.
///
+///
bool HostFNE::readParams()
{
yaml::Node systemConf = m_conf["system"];
@@ -304,9 +314,71 @@ bool HostFNE::readParams()
return true;
}
+///
+/// Initializes REST API serivces.
+///
+///
+bool HostFNE::initializeRESTAPI()
+{
+ yaml::Node systemConf = m_conf["system"];
+ bool restApiEnable = systemConf["restEnable"].as(false);
+
+ // dump out if both networking and REST API are disabled
+ if (!restApiEnable) {
+ return true;
+ }
+
+ std::string restApiAddress = systemConf["restAddress"].as("127.0.0.1");
+ uint16_t restApiPort = (uint16_t)systemConf["restPort"].as(REST_API_DEFAULT_PORT);
+ std::string restApiPassword = systemConf["restPassword"].as();
+ bool restApiDebug = systemConf["restDebug"].as(false);
+
+ if (restApiPassword.length() > 64) {
+ std::string password = restApiPassword;
+ restApiPassword = password.substr(0, 64);
+
+ ::LogWarning(LOG_HOST, "REST API 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;
+ }
+
+ LogInfo("REST API Parameters");
+ LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no");
+ if (restApiEnable) {
+ LogInfo(" REST API Address: %s", restApiAddress.c_str());
+ LogInfo(" REST API Port: %u", restApiPort);
+
+ if (restApiDebug) {
+ LogInfo(" REST API Debug: yes");
+ }
+ }
+
+ // initialize network remote command
+ if (restApiEnable) {
+ m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, this, restApiDebug);
+ m_RESTAPI->setLookups(m_ridLookup, m_tidLookup);
+ bool ret = m_RESTAPI->open();
+ if (!ret) {
+ delete m_RESTAPI;
+ m_RESTAPI = nullptr;
+ LogError(LOG_HOST, "failed to initialize REST API networking! REST API will be unavailable!");
+ // REST API failing isn't fatal -- we'll allow this to return normally
+ }
+ }
+ else {
+ m_RESTAPI = nullptr;
+ }
+
+ return true;
+}
+
///
/// Initializes master FNE network connectivity.
///
+///
bool HostFNE::createMasterNetwork()
{
yaml::Node masterConf = m_conf["master"];
@@ -317,6 +389,11 @@ bool HostFNE::createMasterNetwork()
bool verbose = masterConf["verbose"].as(false);
bool debug = masterConf["debug"].as(false);
+ if (id > 999999999U) {
+ ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999.");
+ return false;
+ }
+
m_dmrEnabled = masterConf["allowDMRTraffic"].as(true);
m_p25Enabled = masterConf["allowP25Traffic"].as(true);
m_nxdnEnabled = masterConf["allowNXDNTraffic"].as(true);
@@ -366,6 +443,7 @@ bool HostFNE::createMasterNetwork()
///
/// Initializes peer FNE network connectivity.
///
+///
bool HostFNE::createPeerNetworks()
{
yaml::Node& peerList = m_conf["peers"];
@@ -389,6 +467,11 @@ bool HostFNE::createPeerNetworks()
::LogInfoEx(LOG_HOST, "Peer ID %u Master Address %s Master Port %u Identity %s Enabled %u", id, masterAddress.c_str(), masterPort, identity.c_str(), enabled);
+ if (id > 999999999U) {
+ ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999.");
+ continue;
+ }
+
// initialize networking
network::Network* network = new Network(masterAddress, masterPort, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, true, true, m_allowActivityTransfer, m_allowDiagnosticTransfer, false);
network->setMetadata(identity, rxFrequency, txFrequency, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location);
diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h
index 0a6e86f8..755623dd 100644
--- a/src/fne/HostFNE.h
+++ b/src/fne/HostFNE.h
@@ -32,6 +32,7 @@
#include "common/yaml/Yaml.h"
#include "common/Timer.h"
#include "network/FNENetwork.h"
+#include "network/RESTAPI.h"
#include "host/network/Network.h"
#include
@@ -87,8 +88,13 @@ private:
bool m_allowActivityTransfer;
bool m_allowDiagnosticTransfer;
+ friend class RESTAPI;
+ RESTAPI* m_RESTAPI;
+
/// Reads basic configuration parameters from the INI.
bool readParams();
+ /// Initializes REST API serivces.
+ bool initializeRESTAPI();
/// Initializes master FNE network connectivity.
bool createMasterNetwork();
/// Initializes peer FNE network connectivity.
diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp
index 1092fc84..3f02dfad 100644
--- a/src/fne/network/FNENetwork.cpp
+++ b/src/fne/network/FNENetwork.cpp
@@ -23,7 +23,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/edac/SHA256.h"
#include "common/network/json/json.h"
#include "common/Log.h"
diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h
index 24628d4d..056f9a37 100644
--- a/src/fne/network/FNENetwork.h
+++ b/src/fne/network/FNENetwork.h
@@ -26,7 +26,7 @@
#if !defined(__FNE_NETWORK_H__)
#define __FNE_NETWORK_H__
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/network/BaseNetwork.h"
#include "common/network/json/json.h"
#include "common/lookups/RadioIdLookup.h"
diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp
new file mode 100644
index 00000000..e3da6fb8
--- /dev/null
+++ b/src/fne/network/RESTAPI.cpp
@@ -0,0 +1,396 @@
+/**
+* Digital Voice Modem - Conference FNE Software
+* GPLv2 Open Source. Use is subject to license terms.
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+*
+* @package DVM / Conference FNE Software
+*
+*/
+/*
+* Copyright (C) 2024 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.
+*/
+#include "fne/Defines.h"
+#include "common/edac/SHA256.h"
+#include "common/lookups/AffiliationLookup.h"
+#include "common/network/json/json.h"
+#include "common/Log.h"
+#include "common/Thread.h"
+#include "common/Utils.h"
+#include "fne/network/RESTAPI.h"
+#include "HostFNE.h"
+#include "FNEMain.h"
+
+using namespace network;
+using namespace network::rest;
+using namespace network::rest::http;
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+// ---------------------------------------------------------------------------
+// Macros
+// ---------------------------------------------------------------------------
+
+#define REST_API_BIND(funcAddr, classInstance) std::bind(&funcAddr, classInstance, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
+
+// ---------------------------------------------------------------------------
+// Global Functions
+// ---------------------------------------------------------------------------
+
+template
+std::string string_format(const std::string& format, FormatArgs ... args)
+{
+ int size_s = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; // extra space for '\0'
+ if (size_s <= 0)
+ throw std::runtime_error("Error during string formatting.");
+
+ auto size = static_cast(size_s);
+ std::unique_ptr buf(new char[ size ]);
+ std::snprintf(buf.get(), size, format.c_str(), args ...);
+
+ return std::string(buf.get(), buf.get() + size - 1);
+}
+
+///
+///
+///
+///
+void setResponseDefaultStatus(json::object& obj)
+{
+ int s = (int)HTTPPayload::OK;
+ obj["status"].set(s);
+}
+
+///
+///
+///
+///
+///
+///
+void errorPayload(HTTPPayload& reply, std::string message, HTTPPayload::StatusType status = HTTPPayload::BAD_REQUEST)
+{
+ HTTPPayload rep;
+ rep.status = status;
+
+ json::object response = json::object();
+
+ int s = (int)rep.status;
+ response["status"].set(s);
+ response["message"].set(message);
+
+ reply.payload(response);
+}
+
+///
+///
+///
+///
+///
+///
+///
+bool parseRequestBody(const HTTPPayload& request, HTTPPayload& reply, json::object& obj)
+{
+ std::string contentType = request.headers.find("Content-Type");
+ if (contentType != "application/json") {
+ reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST, "application/json");
+ return false;
+ }
+
+ // parse JSON body
+ json::value v;
+ std::string err = json::parse(v, request.content);
+ if (!err.empty()) {
+ errorPayload(reply, err);
+ return false;
+ }
+
+ // ensure parsed JSON is an object
+ if (!v.is()) {
+ errorPayload(reply, "Request was not a valid JSON object.");
+ return false;
+ }
+
+ obj = v.get();
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+// Public Class Members
+// ---------------------------------------------------------------------------
+
+///
+/// Initializes a new instance of the RESTAPI class.
+///
+/// Network Hostname/IP address to connect to.
+/// Network port number.
+/// Authentication password.
+/// Instance of the Host class.
+///
+RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& password, HostFNE* host, bool debug) :
+ m_dispatcher(debug),
+ m_restServer(address, port),
+ m_random(),
+ m_password(password),
+ m_passwordHash(nullptr),
+ m_debug(debug),
+ m_host(host),
+ m_ridLookup(nullptr),
+ m_tidLookup(nullptr),
+ m_authTokens()
+{
+ assert(!address.empty());
+ assert(port > 0U);
+ assert(!password.empty());
+
+ size_t size = password.size();
+
+ uint8_t* in = new uint8_t[size];
+ for (size_t i = 0U; i < size; i++)
+ in[i] = password.at(i);
+
+ m_passwordHash = new uint8_t[32U];
+ ::memset(m_passwordHash, 0x00U, 32U);
+
+ edac::SHA256 sha256;
+ sha256.buffer(in, (uint32_t)(size), m_passwordHash);
+
+ delete[] in;
+
+ if (m_debug) {
+ Utils::dump("REST Password Hash", m_passwordHash, 32U);
+ }
+
+ std::random_device rd;
+ std::mt19937 mt(rd());
+ m_random = mt;
+}
+
+///
+/// Finalizes a instance of the RESTAPI class.
+///
+RESTAPI::~RESTAPI()
+{
+ /* stub */
+}
+
+///
+/// Sets the instances of the Radio ID and Talkgroup ID lookup tables.
+///
+/// Radio ID Lookup Table Instance
+/// Talkgroup Rules Lookup Table Instance
+void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup)
+{
+ m_ridLookup = ridLookup;
+ m_tidLookup = tidLookup;
+}
+
+///
+/// Opens connection to the network.
+///
+///
+bool RESTAPI::open()
+{
+ initializeEndpoints();
+ m_restServer.setHandler(m_dispatcher);
+
+ return run();
+}
+
+///
+/// Closes connection to the network.
+///
+void RESTAPI::close()
+{
+ m_restServer.stop();
+ wait();
+}
+
+// ---------------------------------------------------------------------------
+// Private Class Members
+// ---------------------------------------------------------------------------
+
+///
+///
+///
+void RESTAPI::entry()
+{
+ m_restServer.run();
+}
+
+///
+/// Helper to initialize REST API endpoints.
+///
+void RESTAPI::initializeEndpoints()
+{
+ m_dispatcher.match(PUT_AUTHENTICATE).put(REST_API_BIND(RESTAPI::restAPI_PutAuth, this));
+
+ m_dispatcher.match(GET_VERSION).get(REST_API_BIND(RESTAPI::restAPI_GetVersion, this));
+}
+
+///
+///
+///
+///
+void RESTAPI::invalidateHostToken(const std::string host)
+{
+ auto token = std::find_if(m_authTokens.begin(), m_authTokens.end(), [&](const AuthTokenValueType& tok) { return tok.first == host; });
+ if (token != m_authTokens.end()) {
+ m_authTokens.erase(host);
+ }
+}
+
+///
+///
+///
+///
+bool RESTAPI::validateAuth(const HTTPPayload& request, HTTPPayload& reply)
+{
+ std::string host = request.headers.find("RemoteHost");
+ std::string headerToken = request.headers.find("X-DVM-Auth-Token");
+#if DEBUG_HTTP_PAYLOAD
+ ::LogDebug(LOG_REST, "RESTAPI::validateAuth() token, host = %s, token = %s", host.c_str(), headerToken.c_str());
+#endif
+ if (headerToken == "") {
+ errorPayload(reply, "no authentication token", HTTPPayload::UNAUTHORIZED);
+ return false;
+ }
+
+ for (auto& token : m_authTokens) {
+#if DEBUG_HTTP_PAYLOAD
+ ::LogDebug(LOG_REST, "RESTAPI::validateAuth() valid list, host = %s, token = %s", token.first.c_str(), std::to_string(token.second).c_str());
+#endif
+ if (token.first.compare(host) == 0) {
+#if DEBUG_HTTP_PAYLOAD
+ ::LogDebug(LOG_REST, "RESTAPI::validateAuth() storedToken = %s, passedToken = %s", std::to_string(token.second).c_str(), headerToken.c_str());
+#endif
+ if (std::to_string(token.second).compare(headerToken) == 0) {
+ return true;
+ } else {
+ m_authTokens.erase(host); // devalidate host
+ errorPayload(reply, "invalid authentication token", HTTPPayload::UNAUTHORIZED);
+ return false;
+ }
+ }
+ }
+
+ errorPayload(reply, "illegal authentication token", HTTPPayload::UNAUTHORIZED);
+ return false;
+}
+
+///
+///
+///
+///
+///
+///
+void RESTAPI::restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
+{
+ std::string host = request.headers.find("RemoteHost");
+ json::object response = json::object();
+ setResponseDefaultStatus(response);
+
+ json::object req = json::object();
+ if (!parseRequestBody(request, reply, req)) {
+ return;
+ }
+
+ // validate auth is a string within the JSON blob
+ if (!req["auth"].is()) {
+ invalidateHostToken(host);
+ errorPayload(reply, "password was not a valid string");
+ return;
+ }
+
+ std::string auth = req["auth"].get();
+ if (auth.empty()) {
+ invalidateHostToken(host);
+ errorPayload(reply, "auth cannot be empty");
+ return;
+ }
+
+ if (auth.size() > 64) {
+ invalidateHostToken(host);
+ errorPayload(reply, "auth cannot be longer than 64 characters");
+ return;
+ }
+
+ if (!(auth.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) {
+ invalidateHostToken(host);
+ errorPayload(reply, "auth contains invalid characters");
+ return;
+ }
+
+ if (m_debug) {
+ ::LogDebug(LOG_REST, "/auth auth = %s", auth.c_str());
+ }
+
+ const char* authPtr = auth.c_str();
+ uint8_t* passwordHash = new uint8_t[32U];
+ ::memset(passwordHash, 0x00U, 32U);
+
+ for (uint8_t i = 0; i < 32U; i++) {
+ char t[4] = {authPtr[0], authPtr[1], 0};
+ passwordHash[i] = (uint8_t)::strtoul(t, NULL, 16);
+ authPtr += 2 * sizeof(char);
+ }
+
+ if (m_debug) {
+ Utils::dump("Password Hash", passwordHash, 32U);
+ }
+
+ // compare hashes
+ if (::memcmp(m_passwordHash, passwordHash, 32U) != 0) {
+ invalidateHostToken(host);
+ errorPayload(reply, "invalid password");
+ return;
+ }
+
+ delete[] passwordHash;
+
+ invalidateHostToken(host);
+ std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_REST_RAND_MAX);
+ uint64_t salt = dist(m_random);
+
+ m_authTokens[host] = salt;
+ response["token"].set(std::to_string(salt));
+ reply.payload(response);
+}
+
+///
+///
+///
+///
+///
+///
+void RESTAPI::restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
+{
+ if (!validateAuth(request, reply)) {
+ return;
+ }
+
+ json::object response = json::object();
+ setResponseDefaultStatus(response);
+ response["version"].set(std::string((__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")")));
+
+ reply.payload(response);
+}
diff --git a/src/fne/network/RESTAPI.h b/src/fne/network/RESTAPI.h
new file mode 100644
index 00000000..3bedfad2
--- /dev/null
+++ b/src/fne/network/RESTAPI.h
@@ -0,0 +1,107 @@
+/**
+* Digital Voice Modem - Conference FNE Software
+* GPLv2 Open Source. Use is subject to license terms.
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+*
+* @package DVM / Conference FNE Software
+*
+*/
+/*
+* Copyright (C) 2024 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 "fne/Defines.h"
+#include "common/network/UDPSocket.h"
+#include "common/network/rest/RequestDispatcher.h"
+#include "common/network/rest/http/HTTPServer.h"
+#include "common/lookups/RadioIdLookup.h"
+#include "common/lookups/TalkgroupRulesLookup.h"
+#include "common/Thread.h"
+#include "fne/network/RESTDefines.h"
+
+#include
+#include
+#include
+
+// ---------------------------------------------------------------------------
+// Class Prototypes
+// ---------------------------------------------------------------------------
+
+class HOST_SW_API HostFNE;
+
+// ---------------------------------------------------------------------------
+// Class Declaration
+// Implements the REST API server logic.
+// ---------------------------------------------------------------------------
+
+class HOST_SW_API RESTAPI : private Thread {
+public:
+ /// Initializes a new instance of the RESTAPI class.
+ RESTAPI(const std::string& address, uint16_t port, const std::string& password, HostFNE* host, bool debug);
+ /// Finalizes a instance of the RESTAPI class.
+ ~RESTAPI();
+
+ /// Sets the instances of the Radio ID and Talkgroup ID lookup tables.
+ void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup);
+
+ /// Opens connection to the network.
+ bool open();
+
+ /// Closes connection to the network.
+ void close();
+
+private:
+ typedef network::rest::RequestDispatcher RESTDispatcherType;
+ typedef network::rest::http::HTTPPayload HTTPPayload;
+ RESTDispatcherType m_dispatcher;
+ network::rest::http::HTTPServer m_restServer;
+
+ std::mt19937 m_random;
+
+ std::string m_password;
+ uint8_t* m_passwordHash;
+ bool m_debug;
+
+ HostFNE* m_host;
+
+ ::lookups::RadioIdLookup* m_ridLookup;
+ ::lookups::TalkgroupRulesLookup* m_tidLookup;
+
+ typedef std::unordered_map::value_type AuthTokenValueType;
+ std::unordered_map m_authTokens;
+
+ ///
+ virtual void entry();
+
+ /// Helper to initialize REST API endpoints.
+ void initializeEndpoints();
+
+ ///
+ void invalidateHostToken(const std::string host);
+ ///
+ bool validateAuth(const HTTPPayload& request, HTTPPayload& reply);
+
+ ///
+ void restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
+
+ ///
+ void restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
+};
+
+#endif // __REST_API_H__
diff --git a/src/fne/network/RESTDefines.h b/src/fne/network/RESTDefines.h
new file mode 100644
index 00000000..def01b94
--- /dev/null
+++ b/src/fne/network/RESTDefines.h
@@ -0,0 +1,41 @@
+/**
+* Digital Voice Modem - Conference FNE Software
+* GPLv2 Open Source. Use is subject to license terms.
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+*
+* @package DVM / Conference FNE Software
+*
+*/
+/*
+* Copyright (C) 2024 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_DEFINES_H__)
+#define __REST_DEFINES_H__
+
+#include "fne/Defines.h"
+
+// ---------------------------------------------------------------------------
+// Constants
+// ---------------------------------------------------------------------------
+
+#define DVM_REST_RAND_MAX 0xfffffffffffffffe
+
+#define PUT_AUTHENTICATE "/auth"
+
+#define GET_VERSION "/version"
+
+#endif // __REST_API_H__
diff --git a/src/fne/network/fne/TagDMRData.cpp b/src/fne/network/fne/TagDMRData.cpp
index 6eb7eb0c..e36ed882 100644
--- a/src/fne/network/fne/TagDMRData.cpp
+++ b/src/fne/network/fne/TagDMRData.cpp
@@ -23,7 +23,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/dmr/lc/LC.h"
#include "common/dmr/lc/FullLC.h"
#include "common/Clock.h"
diff --git a/src/fne/network/fne/TagDMRData.h b/src/fne/network/fne/TagDMRData.h
index ccf9444e..b2f46625 100644
--- a/src/fne/network/fne/TagDMRData.h
+++ b/src/fne/network/fne/TagDMRData.h
@@ -26,7 +26,7 @@
#if !defined(__FNE__TAG_DMR_DATA_H__)
#define __FNE__TAG_DMR_DATA_H__
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/dmr/DMRDefines.h"
#include "common/dmr/data/Data.h"
#include "common/Clock.h"
diff --git a/src/fne/network/fne/TagNXDNData.cpp b/src/fne/network/fne/TagNXDNData.cpp
index b5326a73..26c7d658 100644
--- a/src/fne/network/fne/TagNXDNData.cpp
+++ b/src/fne/network/fne/TagNXDNData.cpp
@@ -23,7 +23,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/nxdn/NXDNDefines.h"
#include "common/Clock.h"
#include "common/Log.h"
diff --git a/src/fne/network/fne/TagNXDNData.h b/src/fne/network/fne/TagNXDNData.h
index 53145497..d49723ed 100644
--- a/src/fne/network/fne/TagNXDNData.h
+++ b/src/fne/network/fne/TagNXDNData.h
@@ -26,7 +26,7 @@
#if !defined(__FNE__TAG_NXDN_DATA_H__)
#define __FNE__TAG_NXDN_DATA_H__
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/Clock.h"
#include "common/nxdn/NXDNDefines.h"
#include "common/nxdn/lc/RTCH.h"
diff --git a/src/fne/network/fne/TagP25Data.cpp b/src/fne/network/fne/TagP25Data.cpp
index 4e9c5d90..82320da9 100644
--- a/src/fne/network/fne/TagP25Data.cpp
+++ b/src/fne/network/fne/TagP25Data.cpp
@@ -23,7 +23,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/p25/lc/tsbk/TSBKFactory.h"
#include "common/p25/Sync.h"
#include "common/Clock.h"
diff --git a/src/fne/network/fne/TagP25Data.h b/src/fne/network/fne/TagP25Data.h
index a7380a29..f6299625 100644
--- a/src/fne/network/fne/TagP25Data.h
+++ b/src/fne/network/fne/TagP25Data.h
@@ -26,7 +26,7 @@
#if !defined(__FNE__TAG_P25_DATA_H__)
#define __FNE__TAG_P25_DATA_H__
-#include "Defines.h"
+#include "fne/Defines.h"
#include "common/Clock.h"
#include "common/p25/P25Defines.h"
#include "common/p25/data/DataHeader.h"
diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp
index a26c7657..2fb030e5 100644
--- a/src/host/network/RESTAPI.cpp
+++ b/src/host/network/RESTAPI.cpp
@@ -6,10 +6,6 @@
* @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) 2023 by Bryan Biedenkapp N2PLL
*