dncurrency/nano/node/ipc/flatbuffers_handler.cpp
theohax 9bb89ab323
Improvements in const correctness and 'const' positioning (#3510)
* Improve const correctness and adhere to 'const' specifier positioning style

Co-authored-by: Mario Ortiz Manero <marioortizmanero@gmail.com>
2021-10-21 16:20:09 +03:00

188 lines
5.9 KiB
C++

#include <nano/lib/errors.hpp>
#include <nano/node/ipc/action_handler.hpp>
#include <nano/node/ipc/flatbuffers_handler.hpp>
#include <nano/node/ipc/ipc_config.hpp>
#include <nano/node/ipc/ipc_server.hpp>
#include <boost/dll.hpp>
#include <boost/optional.hpp>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include <flatbuffers/flatbuffers.h>
#include <flatbuffers/idl.h>
#include <flatbuffers/minireflect.h>
#include <flatbuffers/registry.h>
#include <flatbuffers/util.h>
namespace
{
auto handler_map = nano::ipc::action_handler::handler_map ();
/**
* A helper for when it's necessary to create a JSON error response manually
*/
std::string make_error_response (std::string const & error_message)
{
std::ostringstream json;
json << R"json({"message_type": "Error", "message": {"code": 1, "message": ")json"
<< error_message
<< R"json("}})json";
return json.str ();
}
/**
* Returns the 'api/flatbuffers' directory, boost::none if not found.
*/
boost::optional<boost::filesystem::path> get_api_path ()
{
boost::filesystem::path const fb_path = "api/flatbuffers";
if (!boost::filesystem::exists (fb_path))
{
return boost::none;
}
return fb_path;
}
}
nano::ipc::flatbuffers_handler::flatbuffers_handler (nano::node & node_a, nano::ipc::ipc_server & ipc_server_a, std::shared_ptr<nano::ipc::subscriber> const & subscriber_a, nano::ipc::ipc_config const & ipc_config_a) :
node (node_a),
ipc_server (ipc_server_a),
subscriber (subscriber_a),
ipc_config (ipc_config_a)
{
}
std::shared_ptr<flatbuffers::Parser> nano::ipc::flatbuffers_handler::make_flatbuffers_parser (nano::ipc::ipc_config const & ipc_config_a)
{
auto parser (std::make_shared<flatbuffers::Parser> ());
parser->opts.strict_json = true;
parser->opts.skip_unexpected_fields_in_json = ipc_config_a.flatbuffers.skip_unexpected_fields_in_json;
auto api_path = get_api_path ();
if (!api_path)
{
throw nano::error ("Internal IPC error: unable to find api path");
}
char const * include_directories[] = { api_path->string ().c_str (), nullptr };
std::string schemafile;
if (!flatbuffers::LoadFile ((*api_path / "nanoapi.fbs").string ().c_str (), false, &schemafile))
{
throw nano::error ("Internal IPC error: unable to load schema file");
}
auto parse_success = parser->Parse (schemafile.c_str (), include_directories);
if (!parse_success)
{
std::string parser_error = "Internal IPC error: unable to parse schema file: ";
parser_error += parser->error_.c_str ();
throw nano::error (parser_error);
}
return parser;
}
void nano::ipc::flatbuffers_handler::process_json (uint8_t const * message_buffer_a, std::size_t buffer_size_a,
std::function<void (std::shared_ptr<std::string> const &)> const & response_handler)
{
try
{
if (!parser)
{
parser = make_flatbuffers_parser (ipc_config);
}
// Convert request from JSON
auto body (std::string (reinterpret_cast<char *> (const_cast<uint8_t *> (message_buffer_a)), buffer_size_a));
body += '\0';
if (parser->Parse (reinterpret_cast<char const *> (body.data ())))
{
process (parser->builder_.GetBufferPointer (), parser->builder_.GetSize (), [parser = parser, response_handler] (std::shared_ptr<flatbuffers::FlatBufferBuilder> const & fbb) {
// Convert response to JSON
auto json (std::make_shared<std::string> ());
if (!flatbuffers::GenerateText (*parser, fbb->GetBufferPointer (), json.get ()))
{
throw nano::error ("Couldn't serialize response to JSON");
}
response_handler (json);
});
}
else
{
std::string parser_error = "Invalid message format: ";
parser_error += parser->error_.c_str ();
throw nano::error (parser_error);
}
}
catch (nano::error const & err)
{
// Forces the parser construction to be retried as certain errors are
// recoverable (such path errors getting fixed by the user without a node restart)
parser = nullptr;
// Convert error response to JSON. We must construct this manually since the exception
// may be parser related (such as not being able to load the schema)
response_handler (std::make_shared<std::string> (make_error_response (err.get_message ())));
}
catch (...)
{
std::cerr << "Unknown exception in " << __FUNCTION__ << std::endl;
response_handler (std::make_shared<std::string> (make_error_response ("Unknown exception")));
}
}
void nano::ipc::flatbuffers_handler::process (uint8_t const * message_buffer_a, std::size_t buffer_size_a,
std::function<void (std::shared_ptr<flatbuffers::FlatBufferBuilder> const &)> const & response_handler)
{
auto buffer_l (std::make_shared<flatbuffers::FlatBufferBuilder> ());
auto actionhandler (std::make_shared<action_handler> (node, ipc_server, subscriber, buffer_l));
std::string correlationId = "";
// Find and call the action handler
try
{
// By default we verify the buffers, to make sure offsets reside inside the buffer.
// This brings the buffer into cache, making the overall verify+parse overhead low.
if (ipc_config.flatbuffers.verify_buffers)
{
auto verifier (flatbuffers::Verifier (message_buffer_a, buffer_size_a));
if (!nanoapi::VerifyEnvelopeBuffer (verifier))
{
throw nano::error ("Envelope buffer did not pass verifier");
}
}
auto incoming = nanoapi::GetEnvelope (message_buffer_a);
if (incoming == nullptr)
{
nano::error err ("Invalid message");
actionhandler->make_error (err.error_code_as_int (), err.get_message ());
response_handler (buffer_l);
return;
}
auto handler_method = handler_map.find (incoming->message_type ());
if (handler_method != handler_map.end ())
{
if (incoming->correlation_id ())
{
actionhandler->set_correlation_id (incoming->correlation_id ()->str ());
}
handler_method->second (actionhandler.get (), *incoming);
}
else
{
nano::error err ("Unknown message type");
actionhandler->make_error (err.error_code_as_int (), err.get_message ());
}
}
catch (nano::error const & err)
{
actionhandler->make_error (err.error_code_as_int (), err.get_message ());
}
response_handler (buffer_l);
}