LCOV - code coverage report
Current view: top level - common - monero_c_rpc_connection.cpp (source / functions) Hit Total Coverage
Test: monero-c coverage @ 3fdc3a1 Lines: 142 142 100.0 %
Date: 2026-10-18 16:42:22 Functions: 38 38 100.0 %

          Line data    Source code
       1             : /**
       2             :  * Copyright (c) Libmonero
       3             :  *
       4             :  * Permission is hereby granted, free of charge, to any person obtaining a copy
       5             :  * of this software and associated documentation files (the "Software"), to deal
       6             :  * in the Software without restriction, including without limitation the rights
       7             :  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       8             :  * copies of the Software, and to permit persons to whom the Software is
       9             :  * furnished to do so, subject to the following conditions:
      10             :  *
      11             :  * The above copyright notice and this permission notice shall be included in all
      12             :  * copies or substantial portions of the Software.
      13             :  *
      14             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      15             :  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      16             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      17             :  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      18             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      19             :  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      20             :  * SOFTWARE.
      21             :  */
      22             : 
      23             : #include "common/monero_c_rpc_connection.h"
      24             : #include "common/monero_c_common.h"
      25             : #include "common/monero_rpc_connection.h"
      26             : 
      27             : #include "rapidjson/document.h"
      28             : 
      29             : #include <boost/property_tree/ptree.hpp>
      30             : #include <cerrno>
      31             : #include <cstdlib>
      32             : #include <memory>
      33             : #include <stdexcept>
      34             : #include <string>
      35             : 
      36             : // monero-cpp has `using namespace monero`, so the C connection name is written with ::
      37             : struct monero_rpc_connection {
      38             :   std::shared_ptr<monero::monero_rpc_connection> connection;
      39             : };
      40             : 
      41             : namespace {
      42             : 
      43             : using monero_c::check_json_depth;
      44             : using monero_c::dup_buffer;
      45             : using monero_c::dup_string;
      46             : using monero_c::guard;
      47             : using monero_c::optional_of;
      48             : using monero_c::require;
      49             : using monero_c::reset_out;
      50             : using monero_c::safe_str;
      51             : 
      52             : // request parameters given as a JSON object, parsed once
      53             : struct json_params : public monero::monero_request_params {
      54             :   rapidjson::Document m_doc;
      55             : 
      56         474 :   explicit json_params(const char* json) {
      57         498 :     check_json_depth(json);
      58         462 :     if (m_doc.Parse(json).HasParseError() || !m_doc.IsObject()) throw std::runtime_error("params must be a JSON object");
      59         860 :   }
      60             : 
      61          88 :   rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override {
      62          88 :     return rapidjson::Value(m_doc, allocator);
      63             :   }
      64             : };
      65             : 
      66         485 : std::shared_ptr<monero::monero_request_params> params_of(const char* params_json) {
      67         485 :   if (params_json == nullptr) return nullptr;
      68         474 :   return std::make_shared<json_params>(params_json);
      69             : }
      70             : 
      71         288 : monero_optional_bool optional_bool_of(const boost::optional<bool>& value) {
      72         288 :   if (value == boost::none) return MONERO_OPTIONAL_BOOL_UNSET;
      73         250 :   return *value ? MONERO_OPTIONAL_BOOL_TRUE : MONERO_OPTIONAL_BOOL_FALSE;
      74             : }
      75             : 
      76             : // true if text follows the JSON number grammar: -?(0|[1-9][0-9]*)(.[0-9]+)?([eE][+-]?[0-9]+)?
      77          56 : bool is_json_number(const std::string& text) {
      78          56 :   size_t i = 0;
      79          56 :   size_t n = text.size();
      80          56 :   if (i < n && text[i] == '-') i++;
      81          56 :   if (i >= n || text[i] < '0' || text[i] > '9') return false;
      82          43 :   if (text[i] == '0') i++;
      83         164 :   else while (i < n && text[i] >= '0' && text[i] <= '9') i++;
      84          43 :   if (i < n && text[i] == '.') {
      85           6 :     i++;
      86           6 :     if (i >= n || text[i] < '0' || text[i] > '9') return false;
      87           9 :     while (i < n && text[i] >= '0' && text[i] <= '9') i++;
      88             :   }
      89          41 :   if (i < n && (text[i] == 'e' || text[i] == 'E')) {
      90           4 :     i++;
      91           4 :     if (i < n && (text[i] == '+' || text[i] == '-')) i++;
      92           4 :     if (i >= n || text[i] < '0' || text[i] > '9') return false;
      93           8 :     while (i < n && text[i] >= '0' && text[i] <= '9') i++;
      94             :   }
      95          40 :   return i == n;
      96             : }
      97             : 
      98             : // writes a property tree value with the type its text suggests. Integers keep their 64 bits,
      99             : // and a number too large for 64 bits stays a string
     100          63 : void write_value(const std::string& text, rapidjson::Writer<rapidjson::StringBuffer>& writer) {
     101          63 :   if (text == "true") { writer.Bool(true); return; }
     102          62 :   if (text == "false") { writer.Bool(false); return; }
     103          57 :   if (text == "null") { writer.Null(); return; }
     104          56 :   if (is_json_number(text)) {
     105          35 :     bool integer = text.find_first_of(".eE") == std::string::npos;
     106          35 :     char* end = nullptr;
     107          35 :     errno = 0;
     108          35 :     if (integer && text[0] == '-') {
     109           3 :       long long value = std::strtoll(text.c_str(), &end, 10);
     110          33 :       if (errno == 0) { writer.Int64(static_cast<int64_t>(value)); return; }
     111          32 :     } else if (integer) {
     112          26 :       unsigned long long value = std::strtoull(text.c_str(), &end, 10);
     113          26 :       if (errno == 0) { writer.Uint64(static_cast<uint64_t>(value)); return; }
     114             :     } else {
     115           6 :       double value = std::strtod(text.c_str(), &end);
     116           6 :       if (errno == 0) { writer.Double(value); return; }
     117             :     }
     118             :   }
     119          24 :   writer.String(text.c_str(), static_cast<rapidjson::SizeType>(text.size()));
     120             : }
     121             : 
     122             : // a property tree has no types and no empty arrays, so a node whose children have no keys is an array
     123          76 : void write_tree(const boost::property_tree::ptree& tree, rapidjson::Writer<rapidjson::StringBuffer>& writer) {
     124          76 :   if (tree.empty()) {
     125          63 :     write_value(tree.data(), writer);
     126          63 :     return;
     127             :   }
     128          13 :   bool is_array = true;
     129          20 :   for (const auto& child : tree) {
     130          16 :     if (!child.first.empty()) {
     131           9 :       is_array = false;
     132           9 :       break;
     133             :     }
     134             :   }
     135          13 :   if (is_array) {
     136           4 :     writer.StartArray();
     137          11 :     for (const auto& child : tree) write_tree(child.second, writer);
     138           4 :     writer.EndArray();
     139             :   } else {
     140           9 :     writer.StartObject();
     141          72 :     for (const auto& child : tree) {
     142          63 :       writer.Key(child.first.c_str(), static_cast<rapidjson::SizeType>(child.first.size()));
     143          63 :       write_tree(child.second, writer);
     144             :     }
     145           9 :     writer.EndObject();
     146             :   }
     147             : }
     148             : 
     149           7 : std::string json_of_tree(const boost::optional<boost::property_tree::ptree>& tree) {
     150           7 :   if (tree == boost::none) throw std::runtime_error("Invalid Monero RPC response");
     151           6 :   rapidjson::StringBuffer buffer;
     152           6 :   rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
     153           6 :   write_tree(*tree, writer);
     154          12 :   return buffer.GetString();
     155           6 : }
     156             : 
     157             : } // namespace
     158             : 
     159             : namespace monero_c {
     160             : 
     161          16 : std::shared_ptr<monero::monero_rpc_connection> connection_of(::monero_rpc_connection* connection) {
     162          16 :   if (connection == nullptr) throw std::runtime_error("connection must not be null");
     163          16 :   return connection->connection;
     164             : }
     165             : 
     166             : } // namespace monero_c
     167             : 
     168             : extern "C" {
     169             : 
     170         182 : monero_result monero_rpc_connection_create(const char* connection_json, ::monero_rpc_connection** out_connection) {
     171         182 :   reset_out(out_connection);
     172         182 :   if (!require(connection_json, "connection_json") || !require(out_connection, "out_connection")) return MONERO_ERROR;
     173         352 :   return guard([&] {
     174         176 :     std::unique_ptr<::monero_rpc_connection> handle(new ::monero_rpc_connection());
     175         176 :     std::string json(connection_json);
     176         176 :     check_json_depth(json);
     177         172 :     handle->connection = monero::monero_rpc_connection::deserialize(json);
     178          53 :     *out_connection = handle.release();
     179         475 :   });
     180             : }
     181             : 
     182          56 : void monero_rpc_connection_free(::monero_rpc_connection* connection) {
     183          56 :   delete connection;
     184          56 : }
     185             : 
     186         285 : monero_result monero_rpc_connection_serialize(::monero_rpc_connection* connection, char** out_json) {
     187         285 :   reset_out(out_json);
     188         285 :   if (!require(connection, "connection") || !require(out_json, "out_json")) return MONERO_ERROR;
     189         558 :   return guard([&] { *out_json = dup_string(connection->connection->serialize()); });
     190             : }
     191             : 
     192         399 : monero_result monero_rpc_connection_set_credentials(::monero_rpc_connection* connection, const char* username, const char* password) {
     193         399 :   if (!require(connection, "connection")) return MONERO_ERROR;
     194         794 :   return guard([&] { connection->connection->set_credentials(safe_str(username), safe_str(password)); });
     195             : }
     196             : 
     197         400 : monero_result monero_rpc_connection_set_attribute(::monero_rpc_connection* connection, const char* key, const char* value) {
     198         400 :   if (!require(connection, "connection") || !require(key, "key") || !require(value, "value")) return MONERO_ERROR;
     199         788 :   return guard([&] { connection->connection->set_attribute(std::string(key), std::string(value)); });
     200             : }
     201             : 
     202         401 : monero_result monero_rpc_connection_get_attribute(::monero_rpc_connection* connection, const char* key, char** out_value) {
     203         401 :   reset_out(out_value);
     204         401 :   if (!require(connection, "connection") || !require(key, "key") || !require(out_value, "out_value")) return MONERO_ERROR;
     205         790 :   return guard([&] { *out_value = dup_string(connection->connection->get_attribute(std::string(key))); });
     206             : }
     207             : 
     208         280 : monero_result monero_rpc_connection_is_onion(::monero_rpc_connection* connection, bool* out_is_onion) {
     209         280 :   if (!require(connection, "connection") || !require(out_is_onion, "out_is_onion")) return MONERO_ERROR;
     210         552 :   return guard([&] { *out_is_onion = connection->connection->is_onion(); });
     211             : }
     212             : 
     213          41 : monero_result monero_rpc_connection_is_i2p(::monero_rpc_connection* connection, bool* out_is_i2p) {
     214          41 :   if (!require(connection, "connection") || !require(out_is_i2p, "out_is_i2p")) return MONERO_ERROR;
     215          74 :   return guard([&] { *out_is_i2p = connection->connection->is_i2p(); });
     216             : }
     217             : 
     218         281 : monero_result monero_rpc_connection_is_online(::monero_rpc_connection* connection, monero_optional_bool* out_is_online) {
     219         281 :   if (!require(connection, "connection") || !require(out_is_online, "out_is_online")) return MONERO_ERROR;
     220         554 :   return guard([&] { *out_is_online = optional_bool_of(connection->connection->is_online()); });
     221             : }
     222             : 
     223           9 : monero_result monero_rpc_connection_is_authenticated(::monero_rpc_connection* connection, monero_optional_bool* out_is_authenticated) {
     224           9 :   if (!require(connection, "connection") || !require(out_is_authenticated, "out_is_authenticated")) return MONERO_ERROR;
     225          10 :   return guard([&] { *out_is_authenticated = optional_bool_of(connection->connection->is_authenticated()); });
     226             : }
     227             : 
     228          10 : monero_result monero_rpc_connection_is_connected(::monero_rpc_connection* connection, monero_optional_bool* out_is_connected) {
     229          10 :   if (!require(connection, "connection") || !require(out_is_connected, "out_is_connected")) return MONERO_ERROR;
     230          12 :   return guard([&] { *out_is_connected = optional_bool_of(connection->connection->is_connected()); });
     231             : }
     232             : 
     233          35 : monero_result monero_rpc_connection_check_connection(::monero_rpc_connection* connection, const uint32_t* timeout_ms, bool* out_changed) {
     234          35 :   if (!require(connection, "connection") || !require(out_changed, "out_changed")) return MONERO_ERROR;
     235          62 :   return guard([&] { *out_changed = connection->connection->check_connection(optional_of(timeout_ms)); });
     236             : }
     237             : 
     238         171 : monero_result monero_rpc_connection_send_json_request(::monero_rpc_connection* connection, const char* method, const char* params_json, const uint32_t* timeout_ms, char** out_json) {
     239         171 :   reset_out(out_json);
     240         171 :   if (!require(connection, "connection") || !require(method, "method") || !require(out_json, "out_json")) return MONERO_ERROR;
     241         330 :   return guard([&] {
     242         165 :     monero::monero_rpc_request request(std::string(method), params_of(params_json));
     243          32 :     *out_json = dup_string(json_of_tree(connection->connection->send_json_request(request, optional_of(timeout_ms)).m_result));
     244         197 :   });
     245             : }
     246             : 
     247         168 : monero_result monero_rpc_connection_send_path_request(::monero_rpc_connection* connection, const char* path, const char* params_json, const uint32_t* timeout_ms, char** out_json) {
     248         168 :   reset_out(out_json);
     249         168 :   if (!require(connection, "connection") || !require(path, "path") || !require(out_json, "out_json")) return MONERO_ERROR;
     250         324 :   return guard([&] {
     251         162 :     monero::monero_rpc_request request(std::string(path), params_of(params_json), false);
     252          34 :     *out_json = dup_string(json_of_tree(connection->connection->send_path_request(request, optional_of(timeout_ms)).m_response));
     253         195 :   });
     254             : }
     255             : 
     256         167 : monero_result monero_rpc_connection_send_binary_request(::monero_rpc_connection* connection, const char* path, const char* params_json, const uint32_t* timeout_ms, uint8_t** out_data, size_t* out_len) {
     257         167 :   reset_out(out_data);
     258         167 :   reset_out(out_len);
     259         167 :   if (!require(connection, "connection") || !require(path, "path") || !require(out_data, "out_data") || !require(out_len, "out_len")) return MONERO_ERROR;
     260         316 :   return guard([&] {
     261         158 :     monero::monero_rpc_request request(std::string(path), params_of(params_json), false);
     262          34 :     monero::monero_rpc_response response = connection->connection->send_binary_request(request, optional_of(timeout_ms));
     263           2 :     *out_data = dup_buffer(response.m_binary.value_or(std::string()), out_len);
     264         192 :   });
     265             : }
     266             : 
     267             : } // extern "C"

Generated by: LCOV version 1.16