Electroneum
Loading...
Searching...
No Matches
message_transporter.cpp
Go to the documentation of this file.
1// Copyright (c) 2018, The Monero Project
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification, are
6// permitted provided that the following conditions are met:
7//
8// 1. Redistributions of source code must retain the above copyright notice, this list of
9// conditions and the following disclaimer.
10//
11// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12// of conditions and the following disclaimer in the documentation and/or other
13// materials provided with the distribution.
14//
15// 3. Neither the name of the copyright holder nor the names of its contributors may be
16// used to endorse or promote products derived from this software without specific
17// prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29#include "message_transporter.h"
30#include "string_coding.h"
31#include <boost/format.hpp>
32#include "wallet_errors.h"
33#include "net/http_client.h"
35#include <algorithm>
36
37#undef ELECTRONEUM_DEFAULT_LOG_CATEGORY
38#define ELECTRONEUM_DEFAULT_LOG_CATEGORY "wallet.mms"
39#define PYBITMESSAGE_DEFAULT_API_PORT 8442
40
41namespace mms
42{
43
82
84{
85 m_run = true;
86}
87
88void message_transporter::set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login)
89{
90 m_bitmessage_url = bitmessage_address;
92 epee::net_utils::parse_url(m_bitmessage_url, address_parts);
93 if (address_parts.port == 0)
94 {
95 address_parts.port = PYBITMESSAGE_DEFAULT_API_PORT;
96 }
97 m_bitmessage_login = bitmessage_login;
98
99 m_http_client.set_server(address_parts.host, std::to_string(address_parts.port), boost::none);
100}
101
102bool message_transporter::receive_messages(const std::vector<std::string> &destination_transport_addresses,
103 std::vector<transport_message> &messages)
104{
105 // The message body of the Bitmessage message is basically the transport message, as JSON (and nothing more).
106 // Weeding out other, non-MMS messages is done in a simple way: If it deserializes without error, it's an MMS message
107 // That JSON is Base64-encoded by the MMS because the Electroneum epee JSON serializer does not escape anything and happily
108 // includes even 0 (NUL) in strings, which might confuse Bitmessage or at least display confusingly in the client.
109 // There is yet another Base64-encoding of course as part of the Bitmessage API for the message body parameter
110 // The Bitmessage API call "getAllInboxMessages" gives back a JSON array with all the messages (despite using
111 // XML-RPC for the calls, and not JSON-RPC ...)
112 m_run.store(true, std::memory_order_relaxed);
113 std::string request;
114 start_xml_rpc_cmd(request, "getAllInboxMessages");
115 end_xml_rpc_cmd(request);
116 std::string answer;
117 post_request(request, answer);
118
119 std::string json = get_str_between_tags(answer, "<string>", "</string>");
121 if (!epee::serialization::load_t_from_json(bitmessage_res, json))
122 {
123 MERROR("Failed to deserialize messages");
124 return true;
125 }
126 size_t size = bitmessage_res.inboxMessages.size();
127 messages.clear();
128
129 for (size_t i = 0; i < size; ++i)
130 {
131 if (!m_run.load(std::memory_order_relaxed))
132 {
133 // Stop was called, don't waste time processing any more messages
134 return false;
135 }
136 const bitmessage_rpc::message_info &message_info = bitmessage_res.inboxMessages[i];
137 if (std::find(destination_transport_addresses.begin(), destination_transport_addresses.end(), message_info.toAddress) != destination_transport_addresses.end())
138 {
140 bool is_mms_message = false;
141 try
142 {
143 // First Base64-decoding: The message body is Base64 in the Bitmessage API
144 std::string message_body = epee::string_encoding::base64_decode(message_info.message);
145 // Second Base64-decoding: The MMS uses Base64 to hide non-textual data in its JSON from Bitmessage
148 MERROR("Failed to deserialize message");
149 else
150 is_mms_message = true;
151 }
152 catch(const std::exception& e)
153 {
154 }
155 if (is_mms_message)
156 {
158 messages.push_back(message);
159 }
160 }
161 }
162
163 return true;
164}
165
167{
168 // <toAddress> <fromAddress> <subject> <message> [encodingType [TTL]]
169 std::string request;
170 start_xml_rpc_cmd(request, "sendMessage");
171 add_xml_rpc_string_param(request, message.destination_transport_address);
172 add_xml_rpc_string_param(request, message.source_transport_address);
173 add_xml_rpc_base64_param(request, message.subject);
175 std::string message_body = epee::string_encoding::base64_encode(json); // See comment in "receive_message" about reason for (double-)Base64 encoding
176 add_xml_rpc_base64_param(request, message_body);
177 add_xml_rpc_integer_param(request, 2);
178 end_xml_rpc_cmd(request);
179 std::string answer;
180 post_request(request, answer);
181 return true;
182}
183
184bool message_transporter::delete_message(const std::string &transport_id)
185{
186 std::string request;
187 start_xml_rpc_cmd(request, "trashMessage");
188 add_xml_rpc_string_param(request, transport_id);
189 end_xml_rpc_cmd(request);
190 std::string answer;
191 post_request(request, answer);
192 return true;
193}
194
195// Deterministically derive a transport / Bitmessage address from 'seed' (the 10-hex-digits
196// auto-config token will be used), but do not set it up for receiving in PyBitmessage as
197// well, because it's possible the address will only ever be used to SEND auto-config data
198std::string message_transporter::derive_transport_address(const std::string &seed)
199{
200 std::string request;
201 start_xml_rpc_cmd(request, "getDeterministicAddress");
202 add_xml_rpc_base64_param(request, seed);
203 add_xml_rpc_integer_param(request, 4); // addressVersionNumber
204 add_xml_rpc_integer_param(request, 1); // streamNumber
205 end_xml_rpc_cmd(request);
206 std::string answer;
207 post_request(request, answer);
208 std::string address = get_str_between_tags(answer, "<string>", "</string>");
209 return address;
210}
211
212// Derive a transport address and configure it for receiving in PyBitmessage, typically
213// for receiving auto-config messages by the wallet of the auto-config organizer
215{
216 // We need to call both "get_deterministic_address" AND "createDeterministicAddresses"
217 // because we won't get back the address from the latter call if it exists already
218 std::string address = derive_transport_address(seed);
219
220 std::string request;
221 start_xml_rpc_cmd(request, "createDeterministicAddresses");
222 add_xml_rpc_base64_param(request, seed);
223 add_xml_rpc_integer_param(request, 1); // numberOfAddresses
224 add_xml_rpc_integer_param(request, 4); // addressVersionNumber
225 end_xml_rpc_cmd(request);
226 std::string answer;
227 post_request(request, answer);
228
229 return address;
230}
231
232bool message_transporter::delete_transport_address(const std::string &transport_address)
233{
234 std::string request;
235 start_xml_rpc_cmd(request, "deleteAddress");
236 add_xml_rpc_string_param(request, transport_address);
237 end_xml_rpc_cmd(request);
238 std::string answer;
239 return post_request(request, answer);
240}
241
242bool message_transporter::post_request(const std::string &request, std::string &answer)
243{
244 // Somehow things do not work out if one tries to connect "m_http_client" to Bitmessage
245 // and keep it connected over the course of several calls. But with a new connection per
246 // call and disconnecting after the call there is no problem (despite perhaps a small
247 // slowdown)
248 epee::net_utils::http::fields_list additional_params;
249
250 // Basic access authentication according to RFC 7617 (which the epee HTTP classes do not seem to support?)
251 // "m_bitmessage_login" just contains what is needed here, "user:password"
252 std::string auth_string = epee::string_encoding::base64_encode((const unsigned char*)m_bitmessage_login.data(), m_bitmessage_login.size());
253 auth_string.insert(0, "Basic ");
254 additional_params.push_back(std::make_pair("Authorization", auth_string));
255
256 additional_params.push_back(std::make_pair("Content-Type", "application/xml; charset=utf-8"));
257 const epee::net_utils::http::http_response_info* response = NULL;
258 std::chrono::milliseconds timeout = std::chrono::seconds(15);
259 bool r = m_http_client.invoke("/", "POST", request, timeout, std::addressof(response), std::move(additional_params));
260 if (r)
261 {
262 answer = response->m_body;
263 }
264 else
265 {
266 LOG_ERROR("POST request to Bitmessage failed: " << request.substr(0, 300));
268 }
269 m_http_client.disconnect(); // see comment above
270 std::string string_value = get_str_between_tags(answer, "<string>", "</string>");
271 if ((string_value.find("API Error") == 0) || (string_value.find("RPC ") == 0))
272 {
273 THROW_WALLET_EXCEPTION(tools::error::bitmessage_api_error, string_value);
274 }
275
276 return r;
277}
278
279// Pick some string between two delimiters
280// When parsing the XML returned by PyBitmessage, don't bother to fully parse it but as a little hack rely on the
281// fact that e.g. a single string returned will be, however deeply nested in "<params><param><value>...", delivered
282// between the very first "<string>" and "</string>" tags to be found in the XML
283std::string message_transporter::get_str_between_tags(const std::string &s, const std::string &start_delim, const std::string &stop_delim)
284{
285 size_t first_delim_pos = s.find(start_delim);
286 if (first_delim_pos != std::string::npos)
287 {
288 size_t end_pos_of_first_delim = first_delim_pos + start_delim.length();
289 size_t last_delim_pos = s.find(stop_delim);
290 if (last_delim_pos != std::string::npos)
291 {
292 return s.substr(end_pos_of_first_delim, last_delim_pos - end_pos_of_first_delim);
293 }
294 }
295 return std::string();
296}
297
298void message_transporter::start_xml_rpc_cmd(std::string &xml, const std::string &method_name)
299{
300 xml = (boost::format("<?xml version=\"1.0\"?><methodCall><methodName>%s</methodName><params>") % method_name).str();
301}
302
303void message_transporter::add_xml_rpc_string_param(std::string &xml, const std::string &param)
304{
305 xml += (boost::format("<param><value><string>%s</string></value></param>") % param).str();
306}
307
308void message_transporter::add_xml_rpc_base64_param(std::string &xml, const std::string &param)
309{
310 // Bitmessage expects some arguments Base64-encoded, but it wants them as parameters of type "string", not "base64" that is also part of XML-RPC
311 std::string encoded_param = epee::string_encoding::base64_encode(param);
312 xml += (boost::format("<param><value><string>%s</string></value></param>") % encoded_param).str();
313}
314
315void message_transporter::add_xml_rpc_integer_param(std::string &xml, const int32_t &param)
316{
317 xml += (boost::format("<param><value><int>%i</int></value></param>") % param).str();
318}
319
320void message_transporter::end_xml_rpc_cmd(std::string &xml)
321{
322 xml += "</params></methodCall>";
323}
324
325}
void set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login)
bool delete_message(const std::string &transport_id)
bool send_message(const transport_message &message)
std::string derive_and_receive_transport_address(const std::string &seed)
bool receive_messages(const std::vector< std::string > &destination_transport_addresses, std::vector< transport_message > &messages)
std::string derive_transport_address(const std::string &seed)
bool delete_transport_address(const std::string &transport_address)
#define KV_SERIALIZE(varialble)
#define END_KV_SERIALIZE_MAP()
#define BEGIN_KV_SERIALIZE_MAP()
#define PYBITMESSAGE_DEFAULT_API_PORT
#define MERROR(x)
Definition misc_log_ex.h:73
#define LOG_ERROR(x)
Definition misc_log_ex.h:98
std::list< std::pair< std::string, std::string > > fields_list
Definition http_base.h:66
bool parse_url(const std::string url_str, http::url_content &content)
bool store_t_to_json(t_struct &str_in, std::string &json_buff, size_t indent=0, bool insert_newlines=true)
bool load_t_from_json(t_struct &out, const std::string &json_buff)
std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len)
std::string base64_decode(std::string const &encoded_string)
epee::misc_utils::struct_init< inbox_messages_response_t > inbox_messages_response
epee::misc_utils::struct_init< message_info_t > message_info
epee::misc_utils::struct_init< transport_message_t > transport_message
unsigned int uint32_t
Definition stdint.h:126
signed int int32_t
Definition stdint.h:123
std::string transport_id
const char * address
Definition multisig.cpp:37
rapidjson::Document json
Definition transport.cpp:49
#define THROW_WALLET_EXCEPTION(err_type,...)