implement REST API stub for conference bridge FNE;

pull/48/head
Bryan Biedenkapp 2 years ago
parent 21a4a97ec3
commit 6b18089d75

@ -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
#

@ -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()
/// <summary>
/// Reads basic configuration parameters from the YAML configuration file.
/// </summary>
/// <returns></returns>
bool HostFNE::readParams()
{
yaml::Node systemConf = m_conf["system"];
@ -304,9 +314,71 @@ bool HostFNE::readParams()
return true;
}
/// <summary>
/// Initializes REST API serivces.
/// </summary>
/// <returns></returns>
bool HostFNE::initializeRESTAPI()
{
yaml::Node systemConf = m_conf["system"];
bool restApiEnable = systemConf["restEnable"].as<bool>(false);
// dump out if both networking and REST API are disabled
if (!restApiEnable) {
return true;
}
std::string restApiAddress = systemConf["restAddress"].as<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)systemConf["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = systemConf["restPassword"].as<std::string>();
bool restApiDebug = systemConf["restDebug"].as<bool>(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;
}
/// <summary>
/// Initializes master FNE network connectivity.
/// </summary>
/// <returns></returns>
bool HostFNE::createMasterNetwork()
{
yaml::Node masterConf = m_conf["master"];
@ -317,6 +389,11 @@ bool HostFNE::createMasterNetwork()
bool verbose = masterConf["verbose"].as<bool>(false);
bool debug = masterConf["debug"].as<bool>(false);
if (id > 999999999U) {
::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999.");
return false;
}
m_dmrEnabled = masterConf["allowDMRTraffic"].as<bool>(true);
m_p25Enabled = masterConf["allowP25Traffic"].as<bool>(true);
m_nxdnEnabled = masterConf["allowNXDNTraffic"].as<bool>(true);
@ -366,6 +443,7 @@ bool HostFNE::createMasterNetwork()
/// <summary>
/// Initializes peer FNE network connectivity.
/// </summary>
/// <returns></returns>
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);

@ -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 <string>
@ -87,8 +88,13 @@ private:
bool m_allowActivityTransfer;
bool m_allowDiagnosticTransfer;
friend class RESTAPI;
RESTAPI* m_RESTAPI;
/// <summary>Reads basic configuration parameters from the INI.</summary>
bool readParams();
/// <summary>Initializes REST API serivces.</summary>
bool initializeRESTAPI();
/// <summary>Initializes master FNE network connectivity.</summary>
bool createMasterNetwork();
/// <summary>Initializes peer FNE network connectivity.</summary>

@ -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"

@ -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"

@ -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 <cstdio>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <unordered_map>
// ---------------------------------------------------------------------------
// Macros
// ---------------------------------------------------------------------------
#define REST_API_BIND(funcAddr, classInstance) std::bind(&funcAddr, classInstance, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
template<typename ... FormatArgs>
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_t>(size_s);
std::unique_ptr<char[]> buf(new char[ size ]);
std::snprintf(buf.get(), size, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size - 1);
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
void setResponseDefaultStatus(json::object& obj)
{
int s = (int)HTTPPayload::OK;
obj["status"].set<int>(s);
}
/// <summary>
///
/// </summary>
/// <param name="reply"></param>
/// <param name="message"></param>
/// <param name="status"></param>
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<int>(s);
response["message"].set<std::string>(message);
reply.payload(response);
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="reply"></param>
/// <param name="obj"></param>
/// <returns></returns>
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<json::object>()) {
errorPayload(reply, "Request was not a valid JSON object.");
return false;
}
obj = v.get<json::object>();
return true;
}
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the RESTAPI class.
/// </summary>
/// <param name="address">Network Hostname/IP address to connect to.</param>
/// <param name="port">Network port number.</param>
/// <param name="password">Authentication password.</param>
/// <param name="host">Instance of the Host class.</param>
/// <param name="debug"></param>
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;
}
/// <summary>
/// Finalizes a instance of the RESTAPI class.
/// </summary>
RESTAPI::~RESTAPI()
{
/* stub */
}
/// <summary>
/// Sets the instances of the Radio ID and Talkgroup ID lookup tables.
/// </summary>
/// <param name="ridLookup">Radio ID Lookup Table Instance</param>
/// <param name="tidLookup">Talkgroup Rules Lookup Table Instance</param>
void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup)
{
m_ridLookup = ridLookup;
m_tidLookup = tidLookup;
}
/// <summary>
/// Opens connection to the network.
/// </summary>
/// <returns></returns>
bool RESTAPI::open()
{
initializeEndpoints();
m_restServer.setHandler(m_dispatcher);
return run();
}
/// <summary>
/// Closes connection to the network.
/// </summary>
void RESTAPI::close()
{
m_restServer.stop();
wait();
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
void RESTAPI::entry()
{
m_restServer.run();
}
/// <summary>
/// Helper to initialize REST API endpoints.
/// </summary>
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));
}
/// <summary>
///
/// </summary>
/// <param name="host"></param>
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);
}
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
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;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="reply"></param>
/// <param name="match"></param>
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<std::string>()) {
invalidateHostToken(host);
errorPayload(reply, "password was not a valid string");
return;
}
std::string auth = req["auth"].get<std::string>();
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<uint64_t> dist(DVM_RAND_MIN, DVM_REST_RAND_MAX);
uint64_t salt = dist(m_random);
m_authTokens[host] = salt;
response["token"].set<std::string>(std::to_string(salt));
reply.payload(response);
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="reply"></param>
/// <param name="match"></param>
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>(std::string((__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")")));
reply.payload(response);
}

@ -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 <vector>
#include <string>
#include <random>
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
class HOST_SW_API HostFNE;
// ---------------------------------------------------------------------------
// 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, HostFNE* 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::TalkgroupRulesLookup* tidLookup);
/// <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::HTTPPayload, network::rest::http::HTTPPayload> RESTDispatcherType;
typedef network::rest::http::HTTPPayload HTTPPayload;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> 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<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 HTTPPayload& request, HTTPPayload& reply);
/// <summary></summary>
void restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary>
void restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
};
#endif // __REST_API_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__

@ -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"

@ -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"

@ -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"

@ -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"

@ -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"

@ -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"

@ -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
*

Loading…
Cancel
Save

Powered by TurnKey Linux.