33 #include <google/protobuf/descriptor.h>
34 #include <google/protobuf/descriptor.pb.h>
35 #include <google/protobuf/text_format.h>
37 #include "../../binary.h"
38 #include "../../cli_option.h"
39 #include "../../codec.h"
41 #include "dccl/version.h"
42 #include "dccl_tool.pb.h"
49 void trim_if(std::string& s,
bool (*predicate)(
char))
53 std::find_if(s.begin(), s.end(), [predicate](
char ch) { return !predicate(ch); }));
57 std::find_if(s.rbegin(), s.rend(), [predicate](
char ch) { return !predicate(ch); }).base(),
60 void trim(std::string& s)
62 trim_if(s, [](
char ch) ->
bool {
return std::isspace(ch); });
64 std::string trim_copy(
const std::string& s)
94 Config() : id_codec(dccl::Codec::default_id_codec_name()) {}
96 Action action{NO_ACTION};
97 std::set<std::string> include;
98 std::vector<std::string> dlopen;
99 std::set<std::string> message;
100 std::map<std::string, std::size_t> hash;
101 std::set<std::string> proto_file;
102 Format format{BINARY};
103 std::string id_codec;
105 bool omit_prefix{
false};
106 bool hash_only{
false};
117 std::pair<bool, std::size_t> load_desc(
dccl::Codec*
dccl,
const google::protobuf::Descriptor* desc,
118 const std::string& name);
119 void parse_options(
int argc,
char* argv[],
dccl::tool::Config* cfg,
int& console_width_);
121 int main(
int argc,
char* argv[])
125 int console_width = -1;
126 parse_options(argc, argv, &cfg, console_width);
129 dccl::dlog.
connect(dccl::logger::WARN_PLUS, &std::cerr);
131 dccl::dlog.
connect(dccl::logger::DEBUG1_PLUS, &std::cerr);
137 std::string first_dl;
138 if (cfg.dlopen.size())
139 first_dl = cfg.dlopen[0];
143 if (console_width >= 0)
145 dccl.set_console_width(console_width);
148 if (cfg.dlopen.size() > 1)
150 for (
auto it = cfg.dlopen.begin() + 1, n = cfg.dlopen.end(); it != n; ++it)
151 dccl.load_library(*it);
153 bool no_messages_specified = cfg.message.empty();
154 for (
auto it = cfg.proto_file.begin(), end = cfg.proto_file.end(); it != end; ++it)
156 const google::protobuf::FileDescriptor* file_desc =
161 std::cerr <<
"failed to read in: " << *it << std::endl;
166 if (no_messages_specified)
168 for (
int i = 0, n = file_desc->message_type_count(); i < n; ++i)
170 cfg.message.insert(file_desc->message_type(i)->full_name());
171 if (i == 0 && cfg.action == ENCODE)
173 std::cerr <<
"Encoding assuming message: "
174 << file_desc->message_type(i)->full_name() << std::endl;
182 for (
auto it = cfg.message.begin(); it != cfg.message.end();)
184 const google::protobuf::Descriptor* desc =
189 std::tie(success, hash) = load_desc(&
dccl, desc, *it);
192 it = cfg.message.erase(it);
196 cfg.hash.insert(std::make_pair(*it, hash));
203 case ENCODE: encode(
dccl, cfg);
break;
204 case DECODE: decode(
dccl, cfg);
break;
205 case ANALYZE: analyze(
dccl, cfg);
break;
206 case DISP_PROTO: disp_proto(
dccl, cfg);
break;
208 std::cerr <<
"No action specified (e.g. analyze, decode, encode). Try --help."
217 for (
const auto& name : cfg.message)
221 const google::protobuf::Descriptor* desc =
223 codec.info(desc, &std::cout);
228 if (cfg.message.size() > 1)
229 std::cout << name <<
": ";
230 std::cout << dccl::hash_as_string(cfg.hash.at(name)) << std::endl;
237 if (cfg.message.size() > 1)
239 std::cerr <<
"No more than one DCCL message can be specified with -m or --message for "
244 else if (cfg.message.size() == 0)
246 std::cerr <<
"You must specify a DCCL message to encode with -m" << std::endl;
250 std::string command_line_name = *cfg.message.begin();
252 while (!std::cin.eof())
255 std::getline(std::cin, input);
264 std::string::size_type close_bracket_pos = input.find(
'|', 1);
265 if (close_bracket_pos == std::string::npos)
267 std::cerr <<
"Incorrectly formatted input: expected '|'" << std::endl;
271 name = input.substr(1, close_bracket_pos - 1);
272 if (cfg.message.find(name) == cfg.message.end())
274 const google::protobuf::Descriptor* desc =
276 if (!load_desc(&
dccl, desc, name).first)
278 std::cerr <<
"Could not load descriptor for message " << name << std::endl;
282 cfg.message.insert(name);
285 if (input.size() > close_bracket_pos + 1)
286 input = input.substr(close_bracket_pos + 1);
292 if (cfg.message.size() == 0)
294 std::cerr <<
"Message name not given with -m or in the input (i.e. '[Name] field1: "
295 "value field2: value')."
300 name = command_line_name;
303 const google::protobuf::Descriptor* desc =
307 std::cerr <<
"No descriptor with name " << name
308 <<
" found! Make sure you have loaded all the necessary .proto files and/or "
309 "shared libraries. Also make sure you specified the fully qualified name "
310 "including the package, if any (e.g. 'goby.acomms.protobuf.NetworkAck', "
311 "not just 'NetworkAck')."
316 std::shared_ptr<google::protobuf::Message> msg =
318 google::protobuf::TextFormat::ParseFromString(input, msg.get());
320 if (msg->IsInitialized())
323 dccl.encode(&encoded, *msg);
329 std::ofstream fout(
"/dev/stdout", std::ios::binary | std::ios::app);
330 fout.write(encoded.data(), encoded.size());
336 dccl::tool::protobuf::ByteString s;
339 google::protobuf::TextFormat::PrintFieldValueToString(
340 s, s.GetDescriptor()->FindFieldByNumber(1), -1, &output);
342 std::cout << output << std::endl;
349 std::stringstream instream(encoded);
350 std::stringstream outstream;
352 D.encode(instream, outstream);
353 std::cout << outstream.str();
355 std::cerr <<
"dccl was not compiled with libb64-dev, so no Base64 "
356 "functionality is available."
369 if (cfg.format == BINARY)
371 std::ifstream fin(
"/dev/stdin", std::ios::binary);
372 std::ostringstream ostrm;
373 ostrm << fin.rdbuf();
378 while (!std::cin.eof())
381 std::getline(std::cin, line);
383 if (trim_copy(line).empty())
393 trim_if(line, [](
char ch) ->
bool {
return ch ==
'"'; });
395 dccl::tool::protobuf::ByteString s;
396 google::protobuf::TextFormat::ParseFieldValueFromString(
397 "\"" + line +
"\"", s.GetDescriptor()->FindFieldByNumber(1), &s);
404 std::stringstream instream(line);
405 std::stringstream outstream;
407 D.decode(instream, outstream);
408 input += outstream.str();
411 std::cerr <<
"dccl was not compiled with libb64-dev, so no Base64 "
412 "functionality is available."
420 while (!input.empty())
422 std::shared_ptr<google::protobuf::Message> msg =
423 dccl.decode<std::shared_ptr<google::protobuf::Message>>(&input);
424 if (!cfg.omit_prefix)
425 std::cout <<
"|" << msg->GetDescriptor()->full_name() <<
"| ";
426 std::cout << msg->ShortDebugString() << std::endl;
432 std::cout <<
"Please note that for Google Protobuf versions < 2.5.0, the dccl extensions will "
433 "not be show below, so you'll need to refer to the original .proto file."
435 for (
const auto& it : cfg.message)
437 const google::protobuf::Descriptor* desc =
440 std::cout << desc->DebugString();
444 std::pair<bool, std::size_t> load_desc(
dccl::Codec*
dccl,
const google::protobuf::Descriptor* desc,
445 const std::string& name)
451 std::size_t hash =
dccl->load(desc);
454 catch (std::exception& e)
456 std::cerr <<
"Not a valid DCCL message: " << desc->full_name()
457 <<
"\n\tWhy: " << e.what() << std::endl;
462 std::cerr <<
"No descriptor with name " << name
463 <<
" found! Make sure you have loaded all the necessary .proto files and/or "
464 "shared libraries. Try --help."
470 void parse_options(
int argc,
char* argv[],
dccl::tool::Config* cfg,
int& console_width_)
472 std::vector<dccl::Option> options;
473 options.emplace_back(
'e',
"encode", no_argument,
"Encode a DCCL message to STDOUT from STDIN");
474 options.emplace_back(
'd',
"decode", no_argument,
"Decode a DCCL message to STDOUT from STDIN");
475 options.emplace_back(
476 'a',
"analyze", no_argument,
477 "Provides information on a given DCCL message definition (e.g. field sizes)");
478 options.emplace_back(
'p',
"display_proto", no_argument,
479 "Display the .proto definition of this message.");
480 options.emplace_back(
'h',
"help", no_argument,
"Gives help on the usage of 'dccl'");
481 options.emplace_back(
'I',
"proto_path", required_argument,
482 "Add another search directory for .proto files");
483 options.emplace_back(
'l',
"dlopen", required_argument,
484 "Open this shared library containing compiled DCCL messages.");
485 options.emplace_back(
'm',
"message", required_argument,
486 "Message name to encode, decode or analyze.");
487 options.emplace_back(
'f',
"proto_file", required_argument,
".proto file to load.");
488 options.emplace_back(0,
"format", required_argument,
489 "Format for encode output or decode input: 'bin' (default) is raw binary, "
490 "'hex' is ascii-encoded hexadecimal, 'textformat' is a Google Protobuf "
491 "TextFormat byte string, 'base64' is ascii-encoded base 64.");
492 options.emplace_back(
'v',
"verbose", no_argument,
"Display extra debugging information.");
493 options.emplace_back(
'o',
"omit_prefix", no_argument,
494 "Omit the DCCL type name prefix from the output of decode.");
495 options.emplace_back(
'i',
"id_codec", required_argument,
496 "(Advanced) name for a nonstandard DCCL ID codec to use");
497 options.emplace_back(
'V',
"version", no_argument,
"DCCL Version");
498 options.emplace_back(
'w',
"console_width", required_argument,
499 "Maximum number of characters used for prettifying console outputs.");
500 options.emplace_back(
'H',
"hash_only", no_argument,
"Only display hash for --analyze action.");
502 std::vector<option> long_options;
503 std::string opt_string;
508 int option_index = 0;
510 int c = getopt_long(argc, argv, opt_string.c_str(), &long_options[0], &option_index);
518 if (long_options[option_index].flag !=
nullptr)
521 if (!strcmp(long_options[option_index].name,
"format"))
523 if (!strcmp(optarg,
"textformat"))
524 cfg->format = TEXTFORMAT;
525 else if (!strcmp(optarg,
"hex"))
527 else if (!strcmp(optarg,
"base64"))
528 cfg->format = BASE64;
529 else if (!strcmp(optarg,
"bin"))
530 cfg->format = BINARY;
533 std::cerr <<
"Invalid format '" << optarg <<
"'" << std::endl;
539 std::cerr <<
"Try --help for valid options." << std::endl;
545 case 'e': cfg->action = ENCODE;
break;
546 case 'd': cfg->action = DECODE;
break;
547 case 'a': cfg->action = ANALYZE;
break;
548 case 'p': cfg->action = DISP_PROTO;
break;
549 case 'I': cfg->include.insert(optarg);
break;
550 case 'l': cfg->dlopen.emplace_back(optarg);
break;
551 case 'm': cfg->message.insert(optarg);
break;
554 char* proto_file_canonical_path = realpath(optarg,
nullptr);
555 if (proto_file_canonical_path)
557 cfg->proto_file.insert(proto_file_canonical_path);
558 free(proto_file_canonical_path);
562 std::cerr <<
"Invalid proto file path: '" << optarg <<
"'" << std::endl;
567 case 'i': cfg->id_codec = optarg;
break;
568 case 'v': cfg->verbose =
true;
break;
569 case 'o': cfg->omit_prefix =
true;
break;
570 case 'H': cfg->hash_only =
true;
break;
573 std::cout <<
"Usage of the Dynamic Compact Control Language (DCCL) tool ('dccl'): "
575 for (
auto& option : options) std::cout <<
" " << option.usage() << std::endl;
580 std::cout << dccl::VERSION_STRING << std::endl;
587 char* end_ptr =
nullptr;
589 auto number = strtol(optarg, &end_ptr, 10);
591 if (optarg == end_ptr)
593 std::cerr <<
"Option -w value \'" << optarg
594 <<
"\' was invalid (no digits found, 0 returned)." << std::endl;
597 else if ((errno == ERANGE) && (number == LONG_MIN))
599 std::cerr <<
"Option -w value \'" << optarg
600 <<
"\' was invalid (underflow occurred)." << std::endl;
603 else if ((errno == ERANGE) && (number == LONG_MAX))
605 std::cerr <<
"Option -w value \'" << optarg
606 <<
"\' was invalid (overflow occurred)." << std::endl;
609 else if (errno == EINVAL)
611 std::cerr <<
"Option -w value \'" << optarg
612 <<
"\' was invalid (base contains unsupported value)." << std::endl;
615 else if ((errno != 0) && (number == 0))
617 std::cerr <<
"Option -w value \'" << optarg
618 <<
"\' was invalid (unspecified error occurred)." << std::endl;
621 else if ((errno == 0) && optarg && (*end_ptr != 0))
623 std::cerr <<
"Option -w value \'" << optarg
624 <<
"\' was invalid (contains additional characters)." << std::endl;
627 else if ((errno == 0) && optarg && !*end_ptr)
631 console_width_ = number;
635 std::cerr <<
"Option -w value \'" << optarg
636 <<
"\' was invalid (negative number)." << std::endl;
642 std::cerr <<
"Option -w value \'" << optarg <<
"\' was invalid (unknown error)."
650 case '?': std::cerr <<
"Try --help for valid options." << std::endl; exit(EXIT_FAILURE);
651 default: exit(EXIT_FAILURE);
658 std::cerr <<
"Unknown arguments: \n";
659 while (optind < argc) std::cerr << argv[optind++];
660 std::cerr << std::endl;
661 std::cerr <<
"Try --help for valid options." << std::endl;