33#include <google/protobuf/descriptor.h>
34#include <google/protobuf/text_format.h>
36#include "../../binary.h"
37#include "../../cli_option.h"
38#include "../../codec.h"
40#include "dccl/version.h"
41#include "dccl_tool.pb.h"
48void trim_if(std::string& s,
bool (*predicate)(
char))
52 std::find_if(s.begin(), s.end(), [predicate](
char ch) { return !predicate(ch); }));
56 std::find_if(s.rbegin(), s.rend(), [predicate](
char ch) { return !predicate(ch); }).base(),
59void trim(std::string& s)
61 trim_if(s, [](
char ch) ->
bool {
return std::isspace(ch); });
63std::string trim_copy(
const std::string& s)
93 Config() : id_codec(dccl::Codec::default_id_codec_name()) {}
95 Action action{NO_ACTION};
96 std::set<std::string> include;
97 std::vector<std::string> dlopen;
98 std::set<std::string> message;
99 std::map<std::string, std::size_t> hash;
100 std::set<std::string> proto_file;
101 Format format{BINARY};
102 std::string id_codec;
104 bool omit_prefix{
false};
105 bool hash_only{
false};
116std::pair<bool, std::size_t> load_desc(
dccl::Codec*
dccl,
const google::protobuf::Descriptor* desc,
117 const std::string& name);
118void parse_options(
int argc,
char* argv[],
dccl::tool::Config* cfg,
int& console_width_);
120int main(
int argc,
char* argv[])
124 int console_width = -1;
125 parse_options(argc, argv, &cfg, console_width);
128 dccl::dlog.
connect(dccl::logger::WARN_PLUS, &std::cerr);
130 dccl::dlog.
connect(dccl::logger::DEBUG1_PLUS, &std::cerr);
134 for (
const auto& it : cfg.include)
dccl::DynamicProtobufManager::add_include_path(it);
136 std::string first_dl;
137 if (cfg.dlopen.size())
138 first_dl = cfg.dlopen[0];
142 if (console_width >= 0)
144 dccl.set_console_width(console_width);
147 if (cfg.dlopen.size() > 1)
149 for (
auto it = cfg.dlopen.begin() + 1, n = cfg.dlopen.end(); it != n; ++it)
150 dccl.load_library(*it);
152 bool no_messages_specified = cfg.message.empty();
153 for (
auto it = cfg.proto_file.begin(), end = cfg.proto_file.end(); it != end; ++it)
155 const google::protobuf::FileDescriptor* file_desc =
160 std::cerr <<
"failed to read in: " << *it << std::endl;
165 if (no_messages_specified)
167 for (
int i = 0, n = file_desc->message_type_count(); i < n; ++i)
169 cfg.message.insert(std::string(file_desc->message_type(i)->full_name()));
170 if (i == 0 && cfg.action == ENCODE)
172 std::cerr <<
"Encoding assuming message: "
173 << file_desc->message_type(i)->full_name() << std::endl;
181 for (
auto it = cfg.message.begin(); it != cfg.message.end();)
183 const google::protobuf::Descriptor* desc =
186 auto [success, hash] = load_desc(&
dccl, desc, *it);
189 it = cfg.message.erase(it);
193 cfg.hash.emplace(*it, hash);
200 case ENCODE: encode(
dccl, cfg);
break;
201 case DECODE: decode(
dccl, cfg);
break;
202 case ANALYZE: analyze(
dccl, cfg);
break;
203 case DISP_PROTO: disp_proto(
dccl, cfg);
break;
205 std::cerr <<
"No action specified (e.g. analyze, decode, encode). Try --help."
214 for (
const auto& name : cfg.message)
218 const google::protobuf::Descriptor* desc =
220 codec.info(desc, &std::cout);
225 if (cfg.message.size() > 1)
226 std::cout << name <<
": ";
227 std::cout << dccl::hash_as_string(cfg.hash.at(name)) << std::endl;
234 if (cfg.message.size() > 1)
236 std::cerr <<
"No more than one DCCL message can be specified with -m or --message for "
241 else if (cfg.message.size() == 0)
243 std::cerr <<
"You must specify a DCCL message to encode with -m" << std::endl;
247 std::string command_line_name = *cfg.message.begin();
249 while (!std::cin.eof())
252 std::getline(std::cin, input);
261 std::string::size_type close_bracket_pos = input.find(
'|', 1);
262 if (close_bracket_pos == std::string::npos)
264 std::cerr <<
"Incorrectly formatted input: expected '|'" << std::endl;
268 name = input.substr(1, close_bracket_pos - 1);
269 if (cfg.message.find(name) == cfg.message.end())
271 const google::protobuf::Descriptor* desc =
273 if (!load_desc(&
dccl, desc, name).first)
275 std::cerr <<
"Could not load descriptor for message " << name << std::endl;
279 cfg.message.insert(name);
282 if (input.size() > close_bracket_pos + 1)
283 input = input.substr(close_bracket_pos + 1);
289 if (cfg.message.size() == 0)
291 std::cerr <<
"Message name not given with -m or in the input (i.e. '[Name] field1: "
292 "value field2: value')."
297 name = command_line_name;
300 const google::protobuf::Descriptor* desc =
304 std::cerr <<
"No descriptor with name " << name
305 <<
" found! Make sure you have loaded all the necessary .proto files and/or "
306 "shared libraries. Also make sure you specified the fully qualified name "
307 "including the package, if any (e.g. 'goby.acomms.protobuf.NetworkAck', "
308 "not just 'NetworkAck')."
313 std::shared_ptr<google::protobuf::Message> msg =
315 google::protobuf::TextFormat::ParseFromString(input, msg.get());
317 if (msg->IsInitialized())
320 dccl.encode(&encoded, *msg);
326 std::ofstream fout(
"/dev/stdout", std::ios::binary | std::ios::app);
327 fout.write(encoded.data(), encoded.size());
333 dccl::tool::protobuf::ByteString s;
336 google::protobuf::TextFormat::PrintFieldValueToString(
337 s, s.GetDescriptor()->FindFieldByNumber(1), -1, &output);
339 std::cout << output << std::endl;
345 std::cout << dccl::b64_encode(encoded) << std::endl;
355 if (cfg.format == BINARY)
357 std::ifstream fin(
"/dev/stdin", std::ios::binary);
358 std::ostringstream ostrm;
359 ostrm << fin.rdbuf();
364 while (!std::cin.eof())
367 std::getline(std::cin, line);
369 if (trim_copy(line).empty())
379 trim_if(line, [](
char ch) ->
bool {
return ch ==
'"'; });
381 dccl::tool::protobuf::ByteString s;
382 google::protobuf::TextFormat::ParseFieldValueFromString(
383 "\"" + line +
"\"", s.GetDescriptor()->FindFieldByNumber(1), &s);
389 input += dccl::b64_decode(line);
395 while (!input.empty())
397 std::shared_ptr<google::protobuf::Message> msg =
398 dccl.decode<std::shared_ptr<google::protobuf::Message>>(&input);
399 if (!cfg.omit_prefix)
400 std::cout <<
"|" << msg->GetDescriptor()->full_name() <<
"| ";
401 std::cout << msg->ShortDebugString() << std::endl;
407 std::cout <<
"Please note that for Google Protobuf versions < 2.5.0, the dccl extensions will "
408 "not be show below, so you'll need to refer to the original .proto file."
410 for (
const auto& it : cfg.message)
412 const google::protobuf::Descriptor* desc =
415 std::cout << desc->DebugString();
419std::pair<bool, std::size_t> load_desc(
dccl::Codec*
dccl,
const google::protobuf::Descriptor* desc,
420 const std::string& name)
426 std::size_t hash =
dccl->load(desc);
429 catch (std::exception& e)
431 std::cerr <<
"Not a valid DCCL message: " << desc->full_name()
432 <<
"\n\tWhy: " << e.what() << std::endl;
437 std::cerr <<
"No descriptor with name " << name
438 <<
" found! Make sure you have loaded all the necessary .proto files and/or "
439 "shared libraries. Try --help."
445void parse_options(
int argc,
char* argv[],
dccl::tool::Config* cfg,
int& console_width_)
447 std::vector<dccl::Option> options;
448 options.emplace_back(
'e',
"encode", no_argument,
"Encode a DCCL message to STDOUT from STDIN");
449 options.emplace_back(
'd',
"decode", no_argument,
"Decode a DCCL message to STDOUT from STDIN");
450 options.emplace_back(
451 'a',
"analyze", no_argument,
452 "Provides information on a given DCCL message definition (e.g. field sizes)");
453 options.emplace_back(
'p',
"display_proto", no_argument,
454 "Display the .proto definition of this message.");
455 options.emplace_back(
'h',
"help", no_argument,
"Gives help on the usage of 'dccl'");
456 options.emplace_back(
'I',
"proto_path", required_argument,
457 "Add another search directory for .proto files");
458 options.emplace_back(
'l',
"dlopen", required_argument,
459 "Open this shared library containing compiled DCCL messages.");
460 options.emplace_back(
'm',
"message", required_argument,
461 "Message name to encode, decode or analyze.");
462 options.emplace_back(
'f',
"proto_file", required_argument,
".proto file to load.");
463 options.emplace_back(0,
"format", required_argument,
464 "Format for encode output or decode input: 'bin' (default) is raw binary, "
465 "'hex' is ascii-encoded hexadecimal, 'textformat' is a Google Protobuf "
466 "TextFormat byte string, 'base64' is ascii-encoded base 64.");
467 options.emplace_back(
'v',
"verbose", no_argument,
"Display extra debugging information.");
468 options.emplace_back(
'o',
"omit_prefix", no_argument,
469 "Omit the DCCL type name prefix from the output of decode.");
470 options.emplace_back(
'i',
"id_codec", required_argument,
471 "(Advanced) name for a nonstandard DCCL ID codec to use");
472 options.emplace_back(
'V',
"version", no_argument,
"DCCL Version");
473 options.emplace_back(
'w',
"console_width", required_argument,
474 "Maximum number of characters used for prettifying console outputs.");
475 options.emplace_back(
'H',
"hash_only", no_argument,
"Only display hash for --analyze action.");
477 std::vector<option> long_options;
478 std::string opt_string;
483 int option_index = 0;
485 int c = getopt_long(argc, argv, opt_string.c_str(), &long_options[0], &option_index);
493 if (long_options[option_index].flag !=
nullptr)
496 if (!strcmp(long_options[option_index].name,
"format"))
498 if (!strcmp(optarg,
"textformat"))
499 cfg->format = TEXTFORMAT;
500 else if (!strcmp(optarg,
"hex"))
502 else if (!strcmp(optarg,
"base64"))
503 cfg->format = BASE64;
504 else if (!strcmp(optarg,
"bin"))
505 cfg->format = BINARY;
508 std::cerr <<
"Invalid format '" << optarg <<
"'" << std::endl;
514 std::cerr <<
"Try --help for valid options." << std::endl;
520 case 'e': cfg->action = ENCODE;
break;
521 case 'd': cfg->action = DECODE;
break;
522 case 'a': cfg->action = ANALYZE;
break;
523 case 'p': cfg->action = DISP_PROTO;
break;
524 case 'I': cfg->include.insert(optarg);
break;
525 case 'l': cfg->dlopen.emplace_back(optarg);
break;
526 case 'm': cfg->message.insert(optarg);
break;
529 char* proto_file_canonical_path = realpath(optarg,
nullptr);
530 if (proto_file_canonical_path)
532 cfg->proto_file.insert(proto_file_canonical_path);
533 free(proto_file_canonical_path);
537 std::cerr <<
"Invalid proto file path: '" << optarg <<
"'" << std::endl;
542 case 'i': cfg->id_codec = optarg;
break;
543 case 'v': cfg->verbose =
true;
break;
544 case 'o': cfg->omit_prefix =
true;
break;
545 case 'H': cfg->hash_only =
true;
break;
548 std::cout <<
"Usage of the Dynamic Compact Control Language (DCCL) tool ('dccl'): "
550 for (
auto& option : options) std::cout <<
" " << option.usage() << std::endl;
555 std::cout << dccl::VERSION_STRING << std::endl;
562 char* end_ptr =
nullptr;
564 auto number = strtol(optarg, &end_ptr, 10);
566 if (optarg == end_ptr)
568 std::cerr <<
"Option -w value \'" << optarg
569 <<
"\' was invalid (no digits found, 0 returned)." << std::endl;
572 else if ((errno == ERANGE) && (number == LONG_MIN))
574 std::cerr <<
"Option -w value \'" << optarg
575 <<
"\' was invalid (underflow occurred)." << std::endl;
578 else if ((errno == ERANGE) && (number == LONG_MAX))
580 std::cerr <<
"Option -w value \'" << optarg
581 <<
"\' was invalid (overflow occurred)." << std::endl;
584 else if (errno == EINVAL)
586 std::cerr <<
"Option -w value \'" << optarg
587 <<
"\' was invalid (base contains unsupported value)." << std::endl;
590 else if ((errno != 0) && (number == 0))
592 std::cerr <<
"Option -w value \'" << optarg
593 <<
"\' was invalid (unspecified error occurred)." << std::endl;
596 else if ((errno == 0) && optarg && (*end_ptr != 0))
598 std::cerr <<
"Option -w value \'" << optarg
599 <<
"\' was invalid (contains additional characters)." << std::endl;
602 else if ((errno == 0) && optarg && !*end_ptr)
606 console_width_ = number;
610 std::cerr <<
"Option -w value \'" << optarg
611 <<
"\' was invalid (negative number)." << std::endl;
617 std::cerr <<
"Option -w value \'" << optarg <<
"\' was invalid (unknown error)."
625 case '?': std::cerr <<
"Try --help for valid options." << std::endl; exit(EXIT_FAILURE);
626 default: exit(EXIT_FAILURE);
633 std::cerr <<
"Unknown arguments: \n";
634 while (optind < argc) std::cerr << argv[optind++];
635 std::cerr << std::endl;
636 std::cerr <<
"Try --help for valid options." << std::endl;
The Dynamic CCL enCODer/DECoder. This is the main class you will use to load, encode and decode DCCL ...
static GoogleProtobufMessagePointer new_protobuf_message(const std::string &protobuf_type_name, bool user_pool_first=false)
Create a new (empty) Google Protobuf message of a given type by name.
static void enable_compilation()
Enable on the fly compilation of .proto files on the local disk. Must be called before load_from_prot...
static const google::protobuf::Descriptor * find_descriptor(const std::string &protobuf_type_name, bool user_pool_first=false)
Finds the Google Protobuf Descriptor (essentially a meta-class for a given Message) from a given Mess...
static const google::protobuf::FileDescriptor * load_from_proto_file(const std::string &protofile_absolute_path)
Load a message from a .proto file on the disk. enable_compilation() must be called first.
void connect(int verbosity_mask, Slot slot)
Connect the output of one or more given verbosities to a slot (function pointer or similar)
static void convert_vector(const std::vector< Option > &options, std::vector< option > *c_options, std::string *opt_string)
Convert a vector of Options into a vector of options (from getopt.h) and an opt_string,...
Dynamic Compact Control Language namespace.
void hex_encode(CharIterator begin, CharIterator end, std::string *out, bool upper_case=false)
Encodes a (little-endian) hexadecimal string from a byte string. Index 0 of begin is written to index...
void hex_decode(const std::string &in, std::string *out)
Decodes a (little-endian) hexadecimal string to a byte string. Index 0 and 1 (first byte) of in are w...