implement the majority of working REST API support (this commit deprecates RCON completely, RCON will no longer function with any build beyond this);
parent
df028fd2f8
commit
35298fe94b
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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_DEFINES_H__)
|
||||
#define __REST_DEFINES_H__
|
||||
|
||||
#include "Defines.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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 RID_CMD_PAGE "page"
|
||||
#define RID_CMD_CHECK "check"
|
||||
#define RID_CMD_INHIBIT "inhibit"
|
||||
#define RID_CMD_UNINHIBIT "uninhibit"
|
||||
#define RID_CMD_GAQ "group-aff-req"
|
||||
#define RID_CMD_UREG "unit-reg"
|
||||
|
||||
#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_BASE "/rid-whitelist/"
|
||||
#define GET_RID_WHITELIST GET_RID_WHITELIST_BASE"(\\d+)"
|
||||
#define GET_RID_BLACKLIST_BASE "/rid-blacklist/"
|
||||
#define GET_RID_BLACKLIST GET_RID_BLACKLIST_BASE"(\\d+)"
|
||||
|
||||
#define GET_DMR_BEACON "/dmr/beacon"
|
||||
#define GET_DMR_DEBUG_BASE "/dmr/debug/"
|
||||
#define GET_DMR_DEBUG GET_DMR_DEBUG_BASE"(\\d+)/(\\d+)"
|
||||
#define GET_DMR_DUMP_CSBK_BASE "/dmr/dump-csbk/"
|
||||
#define GET_DMR_DUMP_CSBK GET_DMR_DUMP_CSBK_BASE"(\\d+)"
|
||||
#define PUT_DMR_RID "/dmr/rid"
|
||||
#define GET_DMR_CC_DEDICATED "/dmr/cc-enable"
|
||||
#define GET_DMR_CC_BCAST "/dmr/cc-broadcast"
|
||||
|
||||
#define GET_P25_CC "/p25/cc"
|
||||
//#define GET_P25_CC_FALLBACK_BASE "/p25/cc-fallback/"
|
||||
//#define GET_P25_CC_FALLBACK GET_P25_CC_FALLBACK_BASE"(\\d+)"
|
||||
#define GET_P25_DEBUG_BASE "/p25/debug/(\\d+)/(\\d+)"
|
||||
#define GET_P25_DEBUG GET_P25_DEBUG_BASE"(\\d+)/(\\d+)"
|
||||
#define GET_P25_DUMP_TSBK_BASE "/p25/dump-tsbk/(\\d+)"
|
||||
#define GET_P25_DUMP_TSBK GET_P25_DUMP_TSBK_BASE"(\\d+)"
|
||||
#define PUT_P25_RID "/p25/rid"
|
||||
#define GET_P25_CC_DEDICATED "/p25/cc-enable"
|
||||
#define GET_P25_CC_BCAST "/p25/cc-broadcast"
|
||||
|
||||
#define GET_NXDN_CC "/nxdn/cc"
|
||||
#define GET_NXDN_DEBUG_BASE "/nxdn/debug/(\\d+)/(\\d+)"
|
||||
#define GET_NXDN_DEBUG GET_NXDN_DEBUG_BASE"(\\d+)/(\\d+)"
|
||||
#define GET_NXDN_DUMP_RCCH_BASE "/nxdn/dump-rcch/(\\d+)"
|
||||
#define GET_NXDN_DUMP_RCCH GET_NXDN_DUMP_RCCH_BASE"(\\d+)"
|
||||
#define GET_NXDN_CC_DEDICATED "/nxdn/cc-enable"
|
||||
|
||||
#endif // __REST_API_H__
|
||||
@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 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 <gatekeep@gmail.com>
|
||||
*
|
||||
* 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 "Defines.h"
|
||||
#include "edac/SHA256.h"
|
||||
#include "network/json/json.h"
|
||||
#include "network/rest/http/HTTPClient.h"
|
||||
#include "network/rest/RequestDispatcher.h"
|
||||
#include "remote/RESTClient.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace network;
|
||||
using namespace network::rest::http;
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define ERRNO_SOCK_OPEN 98
|
||||
#define ERRNO_BAD_API_RESPONSE 97
|
||||
#define ERRNO_API_CALL_TIMEOUT 96
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Static Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool RESTClient::m_responseAvailable = false;
|
||||
HTTPPayload RESTClient::m_response;
|
||||
|
||||
bool RESTClient::m_debug = false;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
bool parseResponseBody(const HTTPPayload& response, json::object& obj)
|
||||
{
|
||||
std::string contentType = response.headers.find("Content-Type");
|
||||
if (contentType != "application/json") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse JSON body
|
||||
json::value v;
|
||||
std::string err = json::parse(v, response.content);
|
||||
if (!err.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure parsed JSON is an object
|
||||
if (!v.is<json::object>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
obj = v.get<json::object>();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RESTClient 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="debug">Flag indicating whether debug is enabled.</param>
|
||||
RESTClient::RESTClient(const std::string& address, uint32_t port, const std::string& password, bool debug) :
|
||||
m_address(address),
|
||||
m_port(port),
|
||||
m_password(password)
|
||||
{
|
||||
assert(!address.empty());
|
||||
assert(port > 0U);
|
||||
|
||||
m_debug = debug;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes a instance of the RESTClient class.
|
||||
/// </summary>
|
||||
RESTClient::~RESTClient()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends remote control command to the specified modem.
|
||||
/// </summary>
|
||||
/// <param name="method">REST API method.</param>
|
||||
/// <param name="endpoint">REST API endpoint.</param>
|
||||
/// <param name="payload">REST API endpoint payload.</param>
|
||||
/// <returns>EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.</returns>
|
||||
int RESTClient::send(const std::string method, const std::string endpoint, json::object payload)
|
||||
{
|
||||
assert(!m_address.empty());
|
||||
assert(m_port > 0U);
|
||||
|
||||
return send(m_address, m_port, m_password, method, endpoint, payload, m_debug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends remote control command to the specified modem.
|
||||
/// </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="method">REST API method.</param>
|
||||
/// <param name="endpoint">REST API endpoint.</param>
|
||||
/// <param name="payload">REST API endpoint payload.</param>
|
||||
/// <param name="debug">Flag indicating whether debug is enabled.</param>
|
||||
/// <returns>EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.</returns>
|
||||
int RESTClient::send(const std::string& address, uint32_t port, const std::string& password, const std::string method,
|
||||
const std::string endpoint, json::object payload, bool debug)
|
||||
{
|
||||
assert(!address.empty());
|
||||
assert(port > 0U);
|
||||
assert(password.empty());
|
||||
|
||||
m_debug = debug;
|
||||
|
||||
typedef network::rest::BasicRequestDispatcher<network::rest::http::HTTPPayload, network::rest::http::HTTPPayload> RESTDispatcherType;
|
||||
RESTDispatcherType m_dispatcher(RESTClient::responseHandler);
|
||||
|
||||
HTTPClient<RESTDispatcherType> client(address, port);
|
||||
if (!client.open())
|
||||
return ERRNO_SOCK_OPEN;
|
||||
client.setHandler(m_dispatcher);
|
||||
|
||||
// generate password SHA hash
|
||||
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);
|
||||
|
||||
uint8_t out[32U];
|
||||
::memset(out, 0x00U, 32U);
|
||||
|
||||
edac::SHA256 sha256;
|
||||
sha256.buffer(in, (uint32_t)(size), out);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::hex;
|
||||
|
||||
for (uint8_t i = 0; i < 32U; i++)
|
||||
ss << std::setw(2) << std::setfill('0') << (int)out[i];
|
||||
|
||||
std::string hash = ss.str();
|
||||
|
||||
// send authentication API
|
||||
json::object request = json::object();
|
||||
request["auth"].set<std::string>(hash);
|
||||
|
||||
HTTPPayload httpPayload = HTTPPayload::requestPayload(HTTP_PUT, "/auth");
|
||||
httpPayload.payload(request);
|
||||
client.request(httpPayload);
|
||||
|
||||
// wait for response and parse
|
||||
if (wait()) {
|
||||
client.close();
|
||||
return ERRNO_API_CALL_TIMEOUT;
|
||||
}
|
||||
|
||||
json::object rsp = json::object();
|
||||
if (!parseResponseBody(m_response, rsp)) {
|
||||
return ERRNO_BAD_API_RESPONSE;
|
||||
}
|
||||
|
||||
std::string token = "";
|
||||
int status = rsp["status"].get<int>();
|
||||
if (status == HTTPPayload::StatusType::OK) {
|
||||
token = rsp["token"].get<std::string>();
|
||||
}
|
||||
else {
|
||||
client.close();
|
||||
return ERRNO_BAD_API_RESPONSE;
|
||||
}
|
||||
|
||||
// send actual API request
|
||||
httpPayload = HTTPPayload::requestPayload(method, endpoint);
|
||||
httpPayload.headers.add("X-DVM-Auth-Token", token);
|
||||
httpPayload.payload(payload);
|
||||
client.request(httpPayload);
|
||||
|
||||
// wait for response and parse
|
||||
if (wait()) {
|
||||
client.close();
|
||||
return ERRNO_API_CALL_TIMEOUT;
|
||||
}
|
||||
|
||||
fprintf(stdout, "%s\r\n", m_response.content.c_str());
|
||||
|
||||
client.close();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="reply"></param>
|
||||
void RESTClient::responseHandler(const HTTPPayload& request, HTTPPayload& reply)
|
||||
{
|
||||
m_responseAvailable = true;
|
||||
m_response = request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
bool RESTClient::wait()
|
||||
{
|
||||
m_responseAvailable = false;
|
||||
|
||||
int timeout = 500;
|
||||
while (!m_responseAvailable && timeout > 0) {
|
||||
timeout--;
|
||||
Thread::sleep(1);
|
||||
}
|
||||
|
||||
if (timeout == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -0,0 +1,684 @@
|
||||
/**
|
||||
* Digital Voice Modem - Remote Command Client
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Remote Command Client
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2023 by Bryan Biedenkapp <gatekeep@gmail.com>
|
||||
*
|
||||
* 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 "remote/RESTClient.h"
|
||||
#include "network/RESTDefines.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#undef __PROG_NAME__
|
||||
#define __PROG_NAME__ "Digital Voice Modem (DVM) REST API Tool"
|
||||
#undef __EXE_NAME__
|
||||
#define __EXE_NAME__ "dvmcmd"
|
||||
|
||||
#define ERRNO_REMOTE_CMD 99
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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_RELEASE_GRANTS "rel-grnts"
|
||||
#define RCD_RELEASE_AFFS "rel-affs"
|
||||
|
||||
#define RCD_DMR_BEACON "dmr-beacon"
|
||||
#define RCD_P25_CC "p25-cc"
|
||||
#define RCD_P25_CC_FALLBACK "p25-cc-fallback"
|
||||
#define RCD_NXDN_CC "nxdn-cc"
|
||||
|
||||
#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_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_NXDN_CC_DEDICATED "nxdn-cc-dedicated"
|
||||
|
||||
#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 BAD_CMD_STR "Bad or invalid remote command"
|
||||
#define NO_DATA_CMD_STR "No data"
|
||||
#define INVALID_AUTH_STR "Invalid authentication"
|
||||
#define INVALID_OPT_STR "Invalid command arguments, "
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Macros
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define IS(s) (::strcmp(argv[i], s) == 0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Variables
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static std::string g_progExe = std::string(__EXE_NAME__);
|
||||
static std::string g_remoteAddress = std::string("127.0.0.1");
|
||||
static uint32_t g_remotePort = REST_API_DEFAULT_PORT;
|
||||
static std::string g_remotePassword = std::string();
|
||||
static bool g_debug = false;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void fatal(const char* message)
|
||||
{
|
||||
::fprintf(stderr, "%s: %s\n", g_progExe.c_str(), message);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void usage(const char* message, const char* arg)
|
||||
{
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
|
||||
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
|
||||
if (message != nullptr) {
|
||||
::fprintf(stderr, "%s: ", g_progExe.c_str());
|
||||
::fprintf(stderr, message, arg);
|
||||
::fprintf(stderr, "\n\n");
|
||||
}
|
||||
|
||||
::fprintf(stdout, "usage: %s [-dvh] [-a <address>] [-p <port>] [-P <password>] <command>\n\n"
|
||||
" -a remote modem command address\n"
|
||||
" -p remote modem command port\n"
|
||||
" -P remote modem authentication password\n"
|
||||
"\n"
|
||||
" -d enable debug\n"
|
||||
" -v show version information\n"
|
||||
" -h show this screen\n"
|
||||
" -- stop handling options\n",
|
||||
g_progExe.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int checkArgs(int argc, char* argv[])
|
||||
{
|
||||
int i, p = 0;
|
||||
|
||||
// iterate through arguments
|
||||
for (i = 1; i <= argc; i++)
|
||||
{
|
||||
if (argv[i] == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*argv[i] != '-') {
|
||||
continue;
|
||||
}
|
||||
else if (IS("--")) {
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
else if (IS("-a")) {
|
||||
if ((argc - 1) <= 0)
|
||||
usage("error: %s", "must specify the address to connect to");
|
||||
g_remoteAddress = std::string(argv[++i]);
|
||||
|
||||
if (g_remoteAddress == "")
|
||||
usage("error: %s", "remote address cannot be blank!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("-p")) {
|
||||
if ((argc - 1) <= 0)
|
||||
usage("error: %s", "must specify the port to connect to");
|
||||
g_remotePort = (uint32_t)::atoi(argv[++i]);
|
||||
|
||||
if (g_remotePort == 0)
|
||||
usage("error: %s", "remote port number cannot be blank or 0!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("-P")) {
|
||||
if ((argc - 1) <= 0)
|
||||
usage("error: %s", "must specify the auth password");
|
||||
g_remotePassword = std::string(argv[++i]);
|
||||
|
||||
if (g_remotePassword == "")
|
||||
usage("error: %s", "remote auth password cannot be blank!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("-d")) {
|
||||
++p;
|
||||
g_debug = true;
|
||||
}
|
||||
else if (IS("-v")) {
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2017-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n");
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (IS("-h")) {
|
||||
usage(nullptr, nullptr);
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else {
|
||||
usage("unrecognized option `%s'", argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (p < 0 || p > argc) {
|
||||
p = 0;
|
||||
}
|
||||
|
||||
return ++p;
|
||||
}
|
||||
|
||||
/// <summary></summary>
|
||||
std::string getArgString(std::vector<std::string> args, uint32_t n)
|
||||
{
|
||||
n += 1;
|
||||
if (n >= args.size())
|
||||
return "";
|
||||
|
||||
return args.at(n);
|
||||
}
|
||||
|
||||
/// <summary></summary>
|
||||
uint64_t getArgUInt64(std::vector<std::string> args, uint32_t n) { return (uint64_t)::atol(getArgString(args, n).c_str()); }
|
||||
/// <summary></summary>
|
||||
uint32_t getArgUInt32(std::vector<std::string> args, uint32_t n) { return (uint32_t)::atoi(getArgString(args, n).c_str()); }
|
||||
/// <summary></summary>
|
||||
int32_t getArgInt32(std::vector<std::string> args, uint32_t n) { return ::atoi(getArgString(args, n).c_str()); }
|
||||
/// <summary></summary>
|
||||
uint16_t getArgUInt16(std::vector<std::string> args, uint32_t n) { return (uint16_t)::atoi(getArgString(args, n).c_str()); }
|
||||
/// <summary></summary>
|
||||
int16_t getArgInt16(std::vector<std::string> args, uint32_t n) { return (int16_t)::atoi(getArgString(args, n).c_str()); }
|
||||
/// <summary></summary>
|
||||
uint8_t getArgUInt8(std::vector<std::string> args, uint32_t n) { return (uint8_t)::atoi(getArgString(args, n).c_str()); }
|
||||
/// <summary></summary>
|
||||
int8_t getArgInt8(std::vector<std::string> args, uint32_t n) { return (int8_t)::atoi(getArgString(args, n).c_str()); }
|
||||
|
||||
/// <summary>
|
||||
/// Helper to print the remote control help.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
std::string displayHelp()
|
||||
{
|
||||
std::string reply = "";
|
||||
|
||||
reply += __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n";
|
||||
reply += "Copyright (c) 2017-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n\r\n";
|
||||
|
||||
reply += "RCON Help\r\nGeneral Commands:\r\n";
|
||||
reply += " version Display current version of host\r\n";
|
||||
reply += " help Displays RCON help\r\n";
|
||||
reply += " status Display current settings and operation mode\r\n";
|
||||
reply += " voice-ch Retrieves the list of configured voice channels\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " mdm-mode <mode> Set current mode of host (idle, lockout, dmr, p25, nxdn)\r\n";
|
||||
reply += " mdm-kill Causes the host to quit\r\n";
|
||||
reply += " mdm-force-kill Causes the host to quit immediately\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " permit-tg <state> <dstid> Causes the host to permit the specified destination ID if non-authoritative\r\n";
|
||||
reply += " grant-tg <state> <dstid> <uu> Causes the host to grant the specified destination ID if non-authoritative\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " rid-whitelist <rid> Whitelists the specified RID in the host ACL tables\r\n";
|
||||
reply += " rid-blacklist <rid> Blacklists the specified RID in the host ACL tables\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " rel-grnts Forcibly releases all channel grants\r\n";
|
||||
reply += " rel-affs Forcibly releases all group affiliations\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " dmr-beacon Transmits a DMR beacon burst\r\n";
|
||||
reply += " p25-cc Transmits a non-continous P25 CC burst\r\n";
|
||||
reply += " p25-cc-fallback <0/1> Sets the P25 CC into conventional fallback mode\r\n";
|
||||
reply += " nxdn-cc Transmits a non-continous NXDN CC burst\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " dmr-debug <debug 0/1> <verbose 0/1>\r\n";
|
||||
reply += " dmr-dump-csbk <0/1>\r\n";
|
||||
reply += " p25-debug <debug 0/1> <verbose 0/1>\r\n";
|
||||
reply += " p25-dump-tsbk <0/1>\r\n";
|
||||
reply += " nxdn-debug <debug 0/1> <verbose 0/1>\r\n";
|
||||
reply += " nxdn-dump-rcch <0/1>\r\n";
|
||||
reply += "\r\nDMR Commands:\r\n";
|
||||
reply += " dmr-rid-page <rid> Pages/Calls the specified RID\r\n";
|
||||
reply += " dmr-rid-check <rid> Radio Checks the specified RID\r\n";
|
||||
reply += " dmr-rid-inhibit <rid> Inhibits the specified RID\r\n";
|
||||
reply += " dmr-rid-uninhibit <rid> Uninhibits the specified RID\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " dmr-cc-dedicated Enables or disables dedicated control channel\r\n";
|
||||
reply += " dmr-cc-bcast Enables or disables broadcast of the control channel\r\n";
|
||||
reply += "\r\nP25 Commands:\r\n";
|
||||
reply += " p25-set-mfid <mfid> Sets the P25 MFId for the next sent P25 command\r\n";
|
||||
reply += " p25-rid-page <rid> Pages/Calls the specified RID\r\n";
|
||||
reply += " p25-rid-check <rid> Radio Checks the specified RID\r\n";
|
||||
reply += " p25-rid-inhibit <rid> Inhibits the specified RID\r\n";
|
||||
reply += " p25-rid-uninhibit <rid> Uninhibits the specified RID\r\n";
|
||||
reply += " p25-rid-gaq <rid> Group affiliation queries the specified RID\r\n";
|
||||
reply += " p25-rid-ureg <rid> Demand unit registration for the specified RID\r\n";
|
||||
reply += "\r\n";
|
||||
reply += " p25-cc-dedicated Enables or disables dedicated control channel\r\n";
|
||||
reply += " p25-cc-bcast Enables or disables broadcast of the control channel\r\n";
|
||||
reply += "\r\nNXDN Commands:\r\n";
|
||||
reply += " nxdn-cc-dedicated Enables or disables dedicated control channel\r\n";
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Program Entry Point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argv[0] != nullptr && *argv[0] != 0)
|
||||
g_progExe = std::string(argv[0]);
|
||||
|
||||
if (argc < 2) {
|
||||
usage("error: %s", "must specify the remote command!");
|
||||
return ERRNO_REMOTE_CMD;
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
// check arguments
|
||||
int i = checkArgs(argc, argv);
|
||||
if (i < argc) {
|
||||
argc -= i;
|
||||
argv += i;
|
||||
}
|
||||
else {
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
}
|
||||
|
||||
// process command
|
||||
std::string cmd = std::string(argv[0]);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
cmd += " ";
|
||||
cmd += std::string(argv[i]);
|
||||
}
|
||||
|
||||
// initialize system logging
|
||||
bool ret = ::LogInitialise("", "", 0U, 1U, true);
|
||||
if (!ret) {
|
||||
::fprintf(stderr, "unable to open the log file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (g_remotePassword.empty()) {
|
||||
::fprintf(stderr, "must specify password!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
RESTClient* client = new RESTClient(g_remoteAddress, g_remotePort, g_remotePassword, g_debug);
|
||||
int retCode = EXIT_SUCCESS;
|
||||
|
||||
std::vector<std::string> args = std::vector<std::string>();
|
||||
args.clear();
|
||||
|
||||
// parse the original command into a vector of strings.
|
||||
char* b = const_cast<char*>(cmd.c_str());
|
||||
char* p = NULL;
|
||||
while ((p = ::strtok(b, " ")) != NULL) {
|
||||
b = NULL;
|
||||
args.push_back(std::string(p));
|
||||
}
|
||||
|
||||
if (args.size() < 1 || args.at(0U) == "") {
|
||||
args.clear();
|
||||
LogWarning(LOG_REST, BAD_CMD_STR);
|
||||
}
|
||||
else {
|
||||
std::string rcom = args.at(0);
|
||||
uint32_t argCnt = args.size() - 1;
|
||||
if (g_debug) {
|
||||
LogInfoEx(LOG_REST, "cmd = %s, argCnt = %u", rcom.c_str(), argCnt);
|
||||
}
|
||||
|
||||
// determine command to execute
|
||||
if (rcom == RCD_GET_HELP) {
|
||||
std::string help = displayHelp();
|
||||
fprintf(stdout, "%s", help.c_str());
|
||||
}
|
||||
else if (rcom == RCD_GET_VERSION) {
|
||||
retCode = client->send(HTTP_GET, GET_VERSION, json::object());
|
||||
}
|
||||
else if (rcom == RCD_GET_STATUS) {
|
||||
retCode = client->send(HTTP_GET, GET_STATUS, json::object());
|
||||
}
|
||||
else if (rcom == RCD_GET_VOICE_CH) {
|
||||
retCode = client->send(HTTP_GET, GET_VOICE_CH, json::object());
|
||||
}
|
||||
else if (rcom == RCD_MODE && argCnt >= 1U) {
|
||||
std::string mode = getArgString(args, 0U);
|
||||
|
||||
json::object req = json::object();
|
||||
req["mode"].set<std::string>(mode);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_MDM_MODE, req);
|
||||
}
|
||||
else if (rcom == RCD_KILL) {
|
||||
json::object req = json::object();
|
||||
bool force = false;
|
||||
req["force"].set<bool>(force);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_MDM_KILL, req);
|
||||
}
|
||||
else if (rcom == RCD_FORCE_KILL) {
|
||||
json::object req = json::object();
|
||||
bool force = true;
|
||||
req["force"].set<bool>(force);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_MDM_KILL, req);
|
||||
}
|
||||
else if (rcom == RCD_PERMIT_TG && argCnt >= 1U) {
|
||||
json::object req = json::object();
|
||||
int state = getArgInt32(args, 0U);
|
||||
req["state"].set<int>(state);
|
||||
|
||||
uint32_t dstId = getArgInt32(args, 1U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
if (state == 1) {
|
||||
uint8_t slot = getArgInt8(args, 2U);
|
||||
req["slot"].set<uint8_t>(slot);
|
||||
}
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_PERMIT_TG, req);
|
||||
}
|
||||
else if (rcom == RCD_GRANT_TG && argCnt >= 1U) {
|
||||
json::object req = json::object();
|
||||
int state = getArgInt32(args, 0U);
|
||||
req["state"].set<int>(state);
|
||||
|
||||
uint32_t dstId = getArgInt32(args, 1U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
uint8_t unitToUnit = getArgInt32(args, 2U);
|
||||
if (unitToUnit == 0U) {
|
||||
bool b = false;
|
||||
req["unitToUnit"].set<bool>(b);
|
||||
} else {
|
||||
bool b = true;
|
||||
req["unitToUnit"].set<bool>(b);
|
||||
}
|
||||
|
||||
if (state == 1) {
|
||||
uint8_t slot = getArgInt8(args, 3U);
|
||||
req["slot"].set<uint8_t>(slot);
|
||||
}
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_GRANT_TG, req);
|
||||
}
|
||||
else if (rcom == RCD_RID_WLIST && argCnt >= 1U) {
|
||||
uint32_t srcId = getArgUInt32(args, 0U);
|
||||
retCode = client->send(HTTP_GET, GET_RID_WHITELIST_BASE + std::to_string(srcId), json::object());
|
||||
}
|
||||
else if (rcom == RCD_RID_BLIST && argCnt >= 1U) {
|
||||
uint32_t srcId = getArgUInt32(args, 0U);
|
||||
retCode = client->send(HTTP_GET, GET_RID_BLACKLIST_BASE + std::to_string(srcId), json::object());
|
||||
}
|
||||
else if (rcom == RCD_RELEASE_GRANTS) {
|
||||
retCode = client->send(HTTP_GET, GET_RELEASE_GRNTS, json::object());
|
||||
}
|
||||
else if (rcom == RCD_RELEASE_AFFS) {
|
||||
retCode = client->send(HTTP_GET, GET_RELEASE_AFFS, json::object());
|
||||
}
|
||||
|
||||
/*
|
||||
** Digital Mobile Radio
|
||||
*/
|
||||
|
||||
else if (rcom == RCD_DMR_BEACON) {
|
||||
retCode = client->send(HTTP_GET, GET_DMR_BEACON, json::object());
|
||||
}
|
||||
else if (rcom == RCD_DMR_DEBUG) {
|
||||
if (argCnt < 2U) {
|
||||
retCode = client->send(HTTP_GET, GET_DMR_DEBUG_BASE, json::object());
|
||||
}
|
||||
else {
|
||||
uint8_t debug = getArgUInt8(args, 0U);
|
||||
uint8_t verbose = getArgUInt8(args, 1U);
|
||||
retCode = client->send(HTTP_GET, GET_DMR_DEBUG_BASE + std::to_string(debug) + "/" + std::to_string(verbose), json::object());
|
||||
}
|
||||
}
|
||||
else if (rcom == RCD_DMR_DUMP_CSBK) {
|
||||
if (argCnt < 1U) {
|
||||
retCode = client->send(HTTP_GET, GET_DMR_DUMP_CSBK_BASE, json::object());
|
||||
}
|
||||
else {
|
||||
uint8_t verbose = getArgUInt8(args, 0U);
|
||||
retCode = client->send(HTTP_GET, GET_DMR_DUMP_CSBK_BASE + std::to_string(verbose), json::object());
|
||||
}
|
||||
}
|
||||
else if (rcom == RCD_DMR_RID_PAGE && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_PAGE));
|
||||
uint8_t slotNo = getArgInt8(args, 0U);
|
||||
req["slot"].set<uint8_t>(slotNo);
|
||||
uint32_t dstId = getArgUInt32(args, 1U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_DMR_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_DMR_RID_CHECK && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_CHECK));
|
||||
uint8_t slotNo = getArgInt8(args, 0U);
|
||||
req["slot"].set<uint8_t>(slotNo);
|
||||
uint32_t dstId = getArgUInt32(args, 1U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_DMR_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_DMR_RID_INHIBIT && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_INHIBIT));
|
||||
uint8_t slotNo = getArgInt8(args, 0U);
|
||||
req["slot"].set<uint8_t>(slotNo);
|
||||
uint32_t dstId = getArgUInt32(args, 1U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_DMR_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_DMR_RID_UNINHIBIT && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_UNINHIBIT));
|
||||
uint8_t slotNo = getArgInt8(args, 0U);
|
||||
req["slot"].set<uint8_t>(slotNo);
|
||||
uint32_t dstId = getArgUInt32(args, 1U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_DMR_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_DMR_CC_DEDICATED) {
|
||||
retCode = client->send(HTTP_GET, GET_DMR_CC_DEDICATED, json::object());
|
||||
}
|
||||
else if (rcom == RCD_DMR_CC_BCAST) {
|
||||
retCode = client->send(HTTP_GET, GET_DMR_CC_BCAST, json::object());
|
||||
}
|
||||
|
||||
/*
|
||||
** Project 25
|
||||
*/
|
||||
|
||||
else if (rcom == RCD_P25_CC) {
|
||||
retCode = client->send(HTTP_GET, GET_P25_CC, json::object());
|
||||
}
|
||||
else if (rcom == RCD_P25_DEBUG) {
|
||||
if (argCnt < 2U) {
|
||||
retCode = client->send(HTTP_GET, GET_P25_DEBUG_BASE, json::object());
|
||||
}
|
||||
else {
|
||||
uint8_t debug = getArgUInt8(args, 0U);
|
||||
uint8_t verbose = getArgUInt8(args, 1U);
|
||||
retCode = client->send(HTTP_GET, GET_P25_DEBUG_BASE + std::to_string(debug) + "/" + std::to_string(verbose), json::object());
|
||||
}
|
||||
}
|
||||
else if (rcom == RCD_P25_DUMP_TSBK) {
|
||||
if (argCnt < 1U) {
|
||||
retCode = client->send(HTTP_GET, GET_P25_DUMP_TSBK_BASE, json::object());
|
||||
}
|
||||
else {
|
||||
uint8_t verbose = getArgUInt8(args, 0U);
|
||||
retCode = client->send(HTTP_GET, GET_P25_DUMP_TSBK_BASE + std::to_string(verbose), json::object());
|
||||
}
|
||||
}
|
||||
else if (rcom == RCD_P25_RID_PAGE && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_PAGE));
|
||||
uint32_t dstId = getArgUInt32(args, 0U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_P25_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_P25_RID_CHECK && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_CHECK));
|
||||
uint32_t dstId = getArgUInt32(args, 0U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_P25_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_P25_RID_INHIBIT && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_INHIBIT));
|
||||
uint32_t dstId = getArgUInt32(args, 0U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_P25_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_P25_RID_UNINHIBIT && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_UNINHIBIT));
|
||||
uint32_t dstId = getArgUInt32(args, 0U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_P25_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_P25_RID_GAQ && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_GAQ));
|
||||
uint32_t dstId = getArgUInt32(args, 0U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_P25_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_P25_RID_UREG && argCnt >= 2U) {
|
||||
json::object req = json::object();
|
||||
req["command"].set<std::string>(std::string(RID_CMD_UREG));
|
||||
uint32_t dstId = getArgUInt32(args, 0U);
|
||||
req["dstId"].set<uint32_t>(dstId);
|
||||
|
||||
retCode = client->send(HTTP_PUT, PUT_P25_RID, req);
|
||||
}
|
||||
else if (rcom == RCD_P25_CC_DEDICATED) {
|
||||
retCode = client->send(HTTP_GET, GET_P25_CC_DEDICATED, json::object());
|
||||
}
|
||||
else if (rcom == RCD_P25_CC_BCAST) {
|
||||
retCode = client->send(HTTP_GET, GET_P25_CC_BCAST, json::object());
|
||||
}
|
||||
|
||||
/*
|
||||
** Next Generation Digital Narrowband
|
||||
*/
|
||||
|
||||
else if (rcom == RCD_NXDN_CC) {
|
||||
retCode = client->send(HTTP_GET, GET_NXDN_CC, json::object());
|
||||
}
|
||||
else if (rcom == RCD_NXDN_DEBUG) {
|
||||
if (argCnt < 2U) {
|
||||
retCode = client->send(HTTP_GET, GET_NXDN_DEBUG_BASE, json::object());
|
||||
}
|
||||
else {
|
||||
uint8_t debug = getArgUInt8(args, 0U);
|
||||
uint8_t verbose = getArgUInt8(args, 1U);
|
||||
retCode = client->send(HTTP_GET, GET_NXDN_DEBUG_BASE + std::to_string(debug) + "/" + std::to_string(verbose), json::object());
|
||||
}
|
||||
}
|
||||
else if (rcom == RCD_NXDN_DUMP_RCCH) {
|
||||
if (argCnt < 1U) {
|
||||
retCode = client->send(HTTP_GET, GET_NXDN_DUMP_RCCH_BASE, json::object());
|
||||
}
|
||||
else {
|
||||
uint8_t verbose = getArgUInt8(args, 0U);
|
||||
retCode = client->send(HTTP_GET, GET_NXDN_DUMP_RCCH_BASE + std::to_string(verbose), json::object());
|
||||
}
|
||||
}
|
||||
else if (rcom == RCD_NXDN_CC_DEDICATED) {
|
||||
retCode = client->send(HTTP_GET, GET_NXDN_CC_DEDICATED, json::object());
|
||||
}
|
||||
|
||||
else {
|
||||
args.clear();
|
||||
LogError(LOG_REST, BAD_CMD_STR " (\"%s\")", rcom.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
::LogFinalise();
|
||||
return retCode;
|
||||
}
|
||||
@ -1,219 +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 <gatekeep@gmail.com>
|
||||
*
|
||||
* 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 "Defines.h"
|
||||
#include "edac/SHA256.h"
|
||||
#include "network/UDPSocket.h"
|
||||
#include "RemoteCommand.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace network;
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define ERRNO_SOCK_OPEN 98
|
||||
#define ERRNO_ADDR_LOOKUP 97
|
||||
#define ERRNO_FAILED_TO_SEND 96
|
||||
|
||||
const uint8_t RCON_FRAME_START = 0xFEU;
|
||||
const uint8_t START_OF_TEXT = 0x02U;
|
||||
const uint8_t END_OF_TEXT = 0x03U;
|
||||
const uint8_t END_OF_BLOCK = 0x17U;
|
||||
const uint8_t REC_SEPARATOR = 0x1EU;
|
||||
|
||||
const uint32_t RC_BUFFER_LENGTH = 250U;
|
||||
const uint32_t RESPONSE_BUFFER_LEN = 4095U;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RemoteCommand 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="debug">Flag indicating whether debug is enabled.</param>
|
||||
RemoteCommand::RemoteCommand(const std::string& address, uint32_t port, const std::string& password, bool debug) :
|
||||
m_address(address),
|
||||
m_port(port),
|
||||
m_password(password),
|
||||
m_debug(debug)
|
||||
{
|
||||
assert(!address.empty());
|
||||
assert(port > 0U);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes a instance of the RemoteCommand class.
|
||||
/// </summary>
|
||||
RemoteCommand::~RemoteCommand()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends remote control command to the specified modem.
|
||||
/// </summary>
|
||||
/// <param name="command">Command string to send to remote modem.</param>
|
||||
/// <returns>EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.</returns>
|
||||
int RemoteCommand::send(const std::string& command)
|
||||
{
|
||||
assert(!m_address.empty());
|
||||
assert(m_port > 0U);
|
||||
|
||||
return send(m_address, m_port, m_password, command, m_debug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends remote control command to the specified modem.
|
||||
/// </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="command">Command string to send to remote modem.</param>
|
||||
/// <param name="debug">Flag indicating whether debug is enabled.</param>
|
||||
/// <returns>EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.</returns>
|
||||
int RemoteCommand::send(const std::string& address, uint32_t port, const std::string& password, const std::string& command, bool debug)
|
||||
{
|
||||
assert(!address.empty());
|
||||
assert(port > 0U);
|
||||
UDPSocket socket(0U);
|
||||
|
||||
bool ret = socket.open();
|
||||
if (!ret)
|
||||
return ERRNO_SOCK_OPEN;
|
||||
|
||||
uint8_t buffer[RC_BUFFER_LENGTH];
|
||||
::memset(buffer, 0x00U, RC_BUFFER_LENGTH);
|
||||
|
||||
sockaddr_storage addr;
|
||||
uint32_t addrLen;
|
||||
|
||||
if (UDPSocket::lookup(address, port, addr, addrLen) != 0) {
|
||||
::LogError(LOG_HOST, "Could not lookup the address of remote");
|
||||
return ERRNO_ADDR_LOOKUP;
|
||||
}
|
||||
|
||||
::LogInfoEx(LOG_HOST, "sending RCON command \"%s\" to %s:%u", command.c_str(), address.c_str(), port);
|
||||
|
||||
buffer[0U] = RCON_FRAME_START;
|
||||
buffer[1U] = START_OF_TEXT;
|
||||
|
||||
if (!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);
|
||||
|
||||
uint8_t out[32U];
|
||||
::memset(out, 0x00U, 32U);
|
||||
|
||||
edac::SHA256 sha256;
|
||||
sha256.buffer(in, (uint32_t)(size), out);
|
||||
|
||||
::memcpy(buffer + 2U, out, 32U);
|
||||
}
|
||||
|
||||
buffer[34U] = REC_SEPARATOR;
|
||||
::memcpy(buffer + 35U, command.c_str(), command.size());
|
||||
|
||||
buffer[35U + command.size()] = END_OF_TEXT;
|
||||
|
||||
if (debug)
|
||||
Utils::dump(1U, "RCON Sent", (uint8_t*)buffer, 36U + command.size());
|
||||
|
||||
ret = socket.write((uint8_t *)buffer, 36U + command.size(), addr, addrLen);
|
||||
if (!ret) {
|
||||
socket.close();
|
||||
::LogError(LOG_HOST, "Failed to send command: \"%s\"", command.c_str());
|
||||
return ERRNO_FAILED_TO_SEND;
|
||||
}
|
||||
|
||||
Thread::sleep(100);
|
||||
|
||||
uint8_t response[RESPONSE_BUFFER_LEN];
|
||||
::memset(response, 0x00U, RESPONSE_BUFFER_LEN);
|
||||
|
||||
int len = 1;
|
||||
uint32_t offs = 0U;
|
||||
do
|
||||
{
|
||||
::memset(buffer, 0x00U, RC_BUFFER_LENGTH);
|
||||
int len = socket.read((uint8_t *)buffer, RC_BUFFER_LENGTH, addr, addrLen);
|
||||
if (len <= 1)
|
||||
break;
|
||||
|
||||
if (offs + len > RESPONSE_BUFFER_LEN)
|
||||
break;
|
||||
|
||||
if (debug)
|
||||
::LogDebug(LOG_HOST, "RemoteCommand::send() block len = %u, offs = %u", len - 3, offs);
|
||||
|
||||
buffer[len] = '\0';
|
||||
|
||||
if (debug)
|
||||
Utils::dump(1U, "RCON Received", (uint8_t*)buffer, len);
|
||||
|
||||
// make sure this is an RCON response
|
||||
if (buffer[0U] != RCON_FRAME_START) {
|
||||
::LogError(LOG_HOST, "Invalid response from host %s:%u", address.c_str(), port);
|
||||
socket.close();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (buffer[1U] != START_OF_TEXT) {
|
||||
::LogError(LOG_HOST, "Invalid response from host %s:%u", address.c_str(), port);
|
||||
socket.close();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
::memcpy(response + offs, buffer + 2U, len - 3U);
|
||||
offs += len - 3U;
|
||||
|
||||
Thread::sleep(100);
|
||||
} while (buffer[len - 1U] != END_OF_TEXT);
|
||||
|
||||
::LogInfoEx(LOG_HOST, ">> %s", response);
|
||||
|
||||
socket.close();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@ -1,228 +0,0 @@
|
||||
/**
|
||||
* Digital Voice Modem - Remote Command Client
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Remote Command Client
|
||||
*
|
||||
*/
|
||||
//
|
||||
// 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 <gatekeep@gmail.com>
|
||||
*
|
||||
* 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 "RemoteCommand.h"
|
||||
#include "edac/SHA256.h"
|
||||
#include "network/UDPSocket.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace network;
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#undef __PROG_NAME__
|
||||
#define __PROG_NAME__ "Digital Voice Modem (DVM) RCON Tool"
|
||||
#undef __EXE_NAME__
|
||||
#define __EXE_NAME__ "dvmcmd"
|
||||
|
||||
#define ERRNO_REMOTE_CMD 99
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Macros
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define IS(s) (::strcmp(argv[i], s) == 0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Variables
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static std::string g_progExe = std::string(__EXE_NAME__);
|
||||
static std::string g_remoteAddress = std::string("127.0.0.1");
|
||||
static uint32_t g_remotePort = REST_API_DEFAULT_PORT;
|
||||
static std::string g_remotePassword = std::string();
|
||||
static bool g_debug = false;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void fatal(const char* message)
|
||||
{
|
||||
::fprintf(stderr, "%s: %s\n", g_progExe.c_str(), message);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void usage(const char* message, const char* arg)
|
||||
{
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2017-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
|
||||
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
|
||||
if (message != nullptr) {
|
||||
::fprintf(stderr, "%s: ", g_progExe.c_str());
|
||||
::fprintf(stderr, message, arg);
|
||||
::fprintf(stderr, "\n\n");
|
||||
}
|
||||
|
||||
::fprintf(stdout, "usage: %s [-dvh] [-a <address>] [-p <port>] [-P <password>] <command>\n\n"
|
||||
" -a remote modem command address\n"
|
||||
" -p remote modem command port\n"
|
||||
" -P remote modem authentication password\n"
|
||||
"\n"
|
||||
" -d enable debug\n"
|
||||
" -v show version information\n"
|
||||
" -h show this screen\n"
|
||||
" -- stop handling options\n",
|
||||
g_progExe.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int checkArgs(int argc, char* argv[])
|
||||
{
|
||||
int i, p = 0;
|
||||
|
||||
// iterate through arguments
|
||||
for (i = 1; i <= argc; i++)
|
||||
{
|
||||
if (argv[i] == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*argv[i] != '-') {
|
||||
continue;
|
||||
}
|
||||
else if (IS("--")) {
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
else if (IS("-a")) {
|
||||
if ((argc - 1) <= 0)
|
||||
usage("error: %s", "must specify the address to connect to");
|
||||
g_remoteAddress = std::string(argv[++i]);
|
||||
|
||||
if (g_remoteAddress == "")
|
||||
usage("error: %s", "remote address cannot be blank!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("-p")) {
|
||||
if ((argc - 1) <= 0)
|
||||
usage("error: %s", "must specify the port to connect to");
|
||||
g_remotePort = (uint32_t)::atoi(argv[++i]);
|
||||
|
||||
if (g_remotePort == 0)
|
||||
usage("error: %s", "remote port number cannot be blank or 0!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("-P")) {
|
||||
if ((argc - 1) <= 0)
|
||||
usage("error: %s", "must specify the auth password");
|
||||
g_remotePassword = std::string(argv[++i]);
|
||||
|
||||
if (g_remotePassword == "")
|
||||
usage("error: %s", "remote auth password cannot be blank!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("-d")) {
|
||||
++p;
|
||||
g_debug = true;
|
||||
}
|
||||
else if (IS("-v")) {
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2017-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n");
|
||||
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n");
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (IS("-h")) {
|
||||
usage(nullptr, nullptr);
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else {
|
||||
usage("unrecognized option `%s'", argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (p < 0 || p > argc) {
|
||||
p = 0;
|
||||
}
|
||||
|
||||
return ++p;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Program Entry Point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argv[0] != nullptr && *argv[0] != 0)
|
||||
g_progExe = std::string(argv[0]);
|
||||
|
||||
if (argc < 2) {
|
||||
usage("error: %s", "must specify the remote command!");
|
||||
return ERRNO_REMOTE_CMD;
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
// check arguments
|
||||
int i = checkArgs(argc, argv);
|
||||
if (i < argc) {
|
||||
argc -= i;
|
||||
argv += i;
|
||||
}
|
||||
else {
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
}
|
||||
|
||||
// process command
|
||||
std::string cmd = std::string(argv[0]);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
cmd += " ";
|
||||
cmd += std::string(argv[i]);
|
||||
}
|
||||
|
||||
// initialize system logging
|
||||
bool ret = ::LogInitialise("", "", 0U, 1U, true);
|
||||
if (!ret) {
|
||||
::fprintf(stderr, "unable to open the log file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
RemoteCommand* command = new RemoteCommand(g_remoteAddress, g_remotePort, g_remotePassword, g_debug);
|
||||
int retCode = command->send(cmd);
|
||||
|
||||
::LogFinalise();
|
||||
return retCode;
|
||||
}
|
||||
Loading…
Reference in new issue