/** * 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) 2023 by Bryan Biedenkapp * * 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 #include #include #include #include #include // --------------------------------------------------------------------------- // 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 // --------------------------------------------------------------------------- /// /// /// /// /// /// 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()) { return false; } obj = v.get(); return true; } // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the RESTClient class. /// /// Network Hostname/IP address to connect to. /// Network port number. /// Authentication password. /// Flag indicating whether debug is enabled. 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; } /// /// Finalizes a instance of the RESTClient class. /// RESTClient::~RESTClient() { /* stub */ } /// /// Sends remote control command to the specified modem. /// /// REST API method. /// REST API endpoint. /// REST API endpoint payload. /// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE. 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); } /// /// Sends remote control command to the specified modem. /// /// Network Hostname/IP address to connect to. /// Network port number. /// Authentication password. /// REST API method. /// REST API endpoint. /// REST API endpoint payload. /// Flag indicating whether debug is enabled. /// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE. 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 RESTDispatcherType; RESTDispatcherType m_dispatcher(RESTClient::responseHandler); HTTPClient 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(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(); if (status == HTTPPayload::StatusType::OK) { token = rsp["token"].get(); } 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 // --------------------------------------------------------------------------- /// /// /// /// /// void RESTClient::responseHandler(const HTTPPayload& request, HTTPPayload& reply) { m_responseAvailable = true; m_response = request; } /// /// /// 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; }