Electroneum
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"
34 #include "net/net_parse_helpers.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 
41 namespace mms
42 {
43 
44 namespace bitmessage_rpc
45 {
46 
48  {
57 
68  };
69  typedef epee::misc_utils::struct_init<message_info_t> message_info;
70 
72  {
73  std::vector<message_info> inboxMessages;
74 
76  KV_SERIALIZE(inboxMessages)
78  };
79  typedef epee::misc_utils::struct_init<inbox_messages_response_t> inbox_messages_response;
80 
81 }
82 
84 {
85  m_run = true;
86 }
87 
88 void message_transporter::set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login)
89 {
90  m_bitmessage_url = bitmessage_address;
91  epee::net_utils::http::url_content address_parts{};
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 
102 bool 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
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 
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
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 
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 
242 bool 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"));
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  {
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
283 std::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 
298 void 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 
303 void 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 
308 void 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 
315 void 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 
320 void message_transporter::end_xml_rpc_cmd(std::string &xml)
321 {
322  xml += "</params></methodCall>";
323 }
324 
325 }
std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len)
#define MERROR(x)
Definition: misc_log_ex.h:73
std::list< std::pair< std::string, std::string > > fields_list
Definition: http_base.h:66
::std::string string
Definition: gtest-port.h:1097
epee::misc_utils::struct_init< response_t > response
bool delete_transport_address(const std::string &transport_address)
std::string transport_id
bool store_t_to_json(t_struct &str_in, std::string &json_buff, size_t indent=0, bool insert_newlines=true)
bool delete_message(const std::string &transport_id)
std::string derive_transport_address(const std::string &seed)
bool load_t_from_json(t_struct &out, const std::string &json_buff)
unsigned int uint32_t
Definition: stdint.h:126
std::string derive_and_receive_transport_address(const std::string &seed)
void set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login)
epee::misc_utils::struct_init< message_info_t > message_info
const T & move(const T &t)
Definition: gtest-port.h:1317
bool parse_url(const std::string url_str, http::url_content &content)
#define LOG_ERROR(x)
Definition: misc_log_ex.h:98
bool send_message(const transport_message &message)
#define PYBITMESSAGE_DEFAULT_API_PORT
#define THROW_WALLET_EXCEPTION(err_type,...)
const char * address
Definition: multisig.cpp:37
std::string to_string(t_connection_type type)
#define END_KV_SERIALIZE_MAP()
signed int int32_t
Definition: stdint.h:123
bool receive_messages(const std::vector< std::string > &destination_transport_addresses, std::vector< transport_message > &messages)
std::string base64_decode(std::string const &encoded_string)
#define BEGIN_KV_SERIALIZE_MAP()
rapidjson::Document json
Definition: transport.cpp:49