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